chore: something changed, commit before reorg
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
from cpv3.modules.project_workspaces.schemas import (
|
||||
ActiveJobState,
|
||||
ProjectWorkspaceState,
|
||||
SetSourceFileAction,
|
||||
SilenceSettingsState,
|
||||
SilenceState,
|
||||
StartSilenceDetectAction,
|
||||
StartTranscriptionAction,
|
||||
TranscriptionRequestState,
|
||||
)
|
||||
from cpv3.modules.project_workspaces.service import (
|
||||
ProjectWorkspaceService,
|
||||
WorkspaceRevisionConflictError,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_workspace_returns_default_state_when_workspace_missing() -> None:
|
||||
project = SimpleNamespace(id=uuid.uuid4(), workspace_state=None)
|
||||
|
||||
service = ProjectWorkspaceService(session=AsyncMock())
|
||||
service._repo = SimpleNamespace(
|
||||
get_by_project_id=AsyncMock(return_value=None),
|
||||
create=AsyncMock(
|
||||
return_value=SimpleNamespace(
|
||||
project_id=project.id,
|
||||
revision=0,
|
||||
state=ProjectWorkspaceState().model_dump(mode="json"),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
workspace = await service.get_workspace(project=project)
|
||||
|
||||
assert workspace.revision == 0
|
||||
assert workspace.phase == "INGEST"
|
||||
assert workspace.current_screen == "upload"
|
||||
assert workspace.active_job is None
|
||||
assert workspace.source_file_id is None
|
||||
assert workspace.workspace_view.used_file_ids == []
|
||||
assert workspace.workspace_view.selected_file_id is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_action_set_source_file_moves_workspace_to_verify() -> None:
|
||||
project = SimpleNamespace(id=uuid.uuid4(), workspace_state=None)
|
||||
requester = SimpleNamespace(id=uuid.uuid4(), is_staff=False)
|
||||
file_id = uuid.uuid4()
|
||||
workspace_row = SimpleNamespace(
|
||||
project_id=project.id,
|
||||
revision=0,
|
||||
state=ProjectWorkspaceState().model_dump(mode="json"),
|
||||
)
|
||||
saved_state: dict[str, object] = {}
|
||||
|
||||
async def update_state(*, project_id, expected_revision, state):
|
||||
saved_state.update(state)
|
||||
return SimpleNamespace(
|
||||
project_id=project_id,
|
||||
revision=expected_revision + 1,
|
||||
state=state,
|
||||
)
|
||||
|
||||
service = ProjectWorkspaceService(session=AsyncMock())
|
||||
service._repo = SimpleNamespace(
|
||||
get_by_project_id=AsyncMock(return_value=workspace_row),
|
||||
create=AsyncMock(),
|
||||
update_state=AsyncMock(side_effect=update_state),
|
||||
)
|
||||
service._file_repo = SimpleNamespace(
|
||||
get_by_id=AsyncMock(
|
||||
return_value=SimpleNamespace(
|
||||
id=file_id,
|
||||
owner_id=requester.id,
|
||||
project_id=project.id,
|
||||
path="users/test/source.mp4",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
workspace = await service.apply_action(
|
||||
requester=requester,
|
||||
project=project,
|
||||
action=SetSourceFileAction(
|
||||
type="SET_SOURCE_FILE",
|
||||
revision=0,
|
||||
file_id=file_id,
|
||||
),
|
||||
)
|
||||
|
||||
assert workspace.revision == 1
|
||||
assert workspace.phase == "VERIFY"
|
||||
assert workspace.current_screen == "verify"
|
||||
assert workspace.source_file_id == file_id
|
||||
assert saved_state["source_file_id"] == str(file_id)
|
||||
assert saved_state["workspace_view"] == {
|
||||
"used_file_ids": [str(file_id)],
|
||||
"selected_file_id": str(file_id),
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_action_rejects_stale_revision() -> None:
|
||||
project = SimpleNamespace(id=uuid.uuid4(), workspace_state=None)
|
||||
workspace_row = SimpleNamespace(
|
||||
project_id=project.id,
|
||||
revision=2,
|
||||
state=ProjectWorkspaceState().model_dump(mode="json"),
|
||||
)
|
||||
|
||||
service = ProjectWorkspaceService(session=AsyncMock())
|
||||
service._repo = SimpleNamespace(
|
||||
get_by_project_id=AsyncMock(return_value=workspace_row),
|
||||
create=AsyncMock(),
|
||||
update_state=AsyncMock(),
|
||||
)
|
||||
|
||||
with pytest.raises(WorkspaceRevisionConflictError):
|
||||
await service.apply_action(
|
||||
requester=SimpleNamespace(id=uuid.uuid4(), is_staff=False),
|
||||
project=project,
|
||||
action=SetSourceFileAction(
|
||||
type="SET_SOURCE_FILE",
|
||||
revision=1,
|
||||
file_id=uuid.uuid4(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_silence_detect_submits_task_and_tracks_active_job() -> None:
|
||||
project = SimpleNamespace(id=uuid.uuid4(), workspace_state=None)
|
||||
requester = SimpleNamespace(id=uuid.uuid4(), is_staff=False)
|
||||
source_file_id = uuid.uuid4()
|
||||
workspace_state = ProjectWorkspaceState(
|
||||
phase="SILENCE",
|
||||
source_file_id=source_file_id,
|
||||
silence=SilenceState(
|
||||
status="CONFIGURED",
|
||||
settings=SilenceSettingsState(
|
||||
min_silence_duration_ms=250,
|
||||
silence_threshold_db=18,
|
||||
padding_ms=125,
|
||||
),
|
||||
),
|
||||
)
|
||||
workspace_row = SimpleNamespace(
|
||||
project_id=project.id,
|
||||
revision=0,
|
||||
state=workspace_state.model_dump(mode="json"),
|
||||
)
|
||||
submitted_response = SimpleNamespace(job_id=uuid.uuid4(), status="PENDING")
|
||||
|
||||
async def update_state(*, project_id, expected_revision, state):
|
||||
return SimpleNamespace(
|
||||
project_id=project_id,
|
||||
revision=expected_revision + 1,
|
||||
state=state,
|
||||
)
|
||||
|
||||
task_service = SimpleNamespace(
|
||||
submit_silence_detect=AsyncMock(return_value=submitted_response),
|
||||
)
|
||||
|
||||
service = ProjectWorkspaceService(session=AsyncMock())
|
||||
service._repo = SimpleNamespace(
|
||||
get_by_project_id=AsyncMock(return_value=workspace_row),
|
||||
create=AsyncMock(),
|
||||
update_state=AsyncMock(side_effect=update_state),
|
||||
)
|
||||
service._file_repo = SimpleNamespace(
|
||||
get_by_id=AsyncMock(
|
||||
return_value=SimpleNamespace(
|
||||
id=source_file_id,
|
||||
owner_id=requester.id,
|
||||
project_id=project.id,
|
||||
path="projects/test/video.mp4",
|
||||
)
|
||||
)
|
||||
)
|
||||
service._task_service_factory = lambda: task_service
|
||||
|
||||
workspace = await service.apply_action(
|
||||
requester=requester,
|
||||
project=project,
|
||||
action=StartSilenceDetectAction(
|
||||
type="START_SILENCE_DETECT",
|
||||
revision=0,
|
||||
),
|
||||
)
|
||||
|
||||
task_service.submit_silence_detect.assert_awaited_once()
|
||||
assert workspace.current_screen == "processing"
|
||||
assert workspace.active_job == ActiveJobState(
|
||||
job_id=submitted_response.job_id,
|
||||
job_type="SILENCE_DETECT",
|
||||
)
|
||||
assert workspace.silence.detect_job_id == submitted_response.job_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_transcription_persists_request_and_processing_job() -> None:
|
||||
project = SimpleNamespace(id=uuid.uuid4(), workspace_state=None)
|
||||
requester = SimpleNamespace(id=uuid.uuid4(), is_staff=False)
|
||||
source_file_id = uuid.uuid4()
|
||||
workspace_state = ProjectWorkspaceState(
|
||||
phase="TRANSCRIPTION",
|
||||
source_file_id=source_file_id,
|
||||
)
|
||||
workspace_row = SimpleNamespace(
|
||||
project_id=project.id,
|
||||
revision=3,
|
||||
state=workspace_state.model_dump(mode="json"),
|
||||
)
|
||||
submitted_response = SimpleNamespace(job_id=uuid.uuid4(), status="PENDING")
|
||||
|
||||
async def update_state(*, project_id, expected_revision, state):
|
||||
return SimpleNamespace(
|
||||
project_id=project_id,
|
||||
revision=expected_revision + 1,
|
||||
state=state,
|
||||
)
|
||||
|
||||
task_service = SimpleNamespace(
|
||||
submit_transcription_generate=AsyncMock(return_value=submitted_response),
|
||||
)
|
||||
|
||||
service = ProjectWorkspaceService(session=AsyncMock())
|
||||
service._repo = SimpleNamespace(
|
||||
get_by_project_id=AsyncMock(return_value=workspace_row),
|
||||
create=AsyncMock(),
|
||||
update_state=AsyncMock(side_effect=update_state),
|
||||
)
|
||||
service._file_repo = SimpleNamespace(
|
||||
get_by_id=AsyncMock(
|
||||
return_value=SimpleNamespace(
|
||||
id=source_file_id,
|
||||
owner_id=requester.id,
|
||||
project_id=project.id,
|
||||
path="projects/test/video.mp4",
|
||||
)
|
||||
)
|
||||
)
|
||||
service._task_service_factory = lambda: task_service
|
||||
|
||||
request = TranscriptionRequestState(engine="whisper", language="ru", model="base")
|
||||
workspace = await service.apply_action(
|
||||
requester=requester,
|
||||
project=project,
|
||||
action=StartTranscriptionAction(
|
||||
type="START_TRANSCRIPTION",
|
||||
revision=3,
|
||||
request=request,
|
||||
),
|
||||
)
|
||||
|
||||
assert workspace.current_screen == "transcription-processing"
|
||||
assert workspace.transcription.request == request
|
||||
assert workspace.active_job == ActiveJobState(
|
||||
job_id=submitted_response.job_id,
|
||||
job_type="TRANSCRIPTION_GENERATE",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_job_update_moves_transcription_job_to_review() -> None:
|
||||
project = SimpleNamespace(id=uuid.uuid4(), workspace_state=None)
|
||||
job_id = uuid.uuid4()
|
||||
transcription_id = uuid.uuid4()
|
||||
artifact_id = uuid.uuid4()
|
||||
workspace_state = ProjectWorkspaceState(
|
||||
phase="TRANSCRIPTION",
|
||||
active_job=ActiveJobState(job_id=job_id, job_type="TRANSCRIPTION_GENERATE"),
|
||||
transcription={
|
||||
"status": "PROCESSING",
|
||||
"job_id": job_id,
|
||||
"artifact_id": None,
|
||||
"transcription_id": None,
|
||||
"reviewed": False,
|
||||
},
|
||||
)
|
||||
workspace_row = SimpleNamespace(
|
||||
project_id=project.id,
|
||||
revision=4,
|
||||
state=workspace_state.model_dump(mode="json"),
|
||||
)
|
||||
|
||||
async def update_state(*, project_id, expected_revision, state):
|
||||
return SimpleNamespace(
|
||||
project_id=project_id,
|
||||
revision=expected_revision + 1,
|
||||
state=state,
|
||||
)
|
||||
|
||||
service = ProjectWorkspaceService(session=AsyncMock())
|
||||
service._repo = SimpleNamespace(
|
||||
get_by_project_id=AsyncMock(return_value=workspace_row),
|
||||
create=AsyncMock(),
|
||||
update_state=AsyncMock(side_effect=update_state),
|
||||
)
|
||||
|
||||
workspace = await service.apply_job_update(
|
||||
project=project,
|
||||
job=SimpleNamespace(
|
||||
id=job_id,
|
||||
project_id=project.id,
|
||||
job_type="TRANSCRIPTION_GENERATE",
|
||||
status="DONE",
|
||||
output_data={
|
||||
"transcription_id": str(transcription_id),
|
||||
"artifact_id": str(artifact_id),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert workspace is not None
|
||||
assert workspace.revision == 5
|
||||
assert workspace.phase == "TRANSCRIPTION"
|
||||
assert workspace.current_screen == "subtitle-revision"
|
||||
assert workspace.active_job is None
|
||||
assert workspace.transcription.transcription_id == transcription_id
|
||||
assert workspace.transcription.artifact_id == artifact_id
|
||||
assert workspace.transcription.reviewed is False
|
||||
Reference in New Issue
Block a user