""" 生成任务应用层用例单元测试(第十九波) 覆盖: - CreateGenerationTaskUseCase - GetGenerationTaskUseCase - ListUserTasksFilteredUseCase - RetryGenerationTaskUseCase - Command / Filter / Result 对象 """ from unittest.mock import MagicMock import pytest from packages.application.generation_tasks import ( CreateGenerationTaskCommand, CreateGenerationTaskUseCase, GetGenerationTaskUseCase, ListGenerationTasksResult, ListTasksFilter, ListUserTasksFilteredUseCase, RetryGenerationTaskUseCase, ) from packages.domain.generation_task import GenerationTask, GenerationTaskStatus @pytest.fixture def mock_repo(): return MagicMock() def make_task(status=GenerationTaskStatus.PENDING, **kwargs): task = GenerationTask( id="task-1", project_id="proj-1", asset_library_id="lib-1", strategy_id="strat-1", template_id="tmpl-1", asset_ids=["asset-1"], title_ids=["title-1"], voice_ids=["voice-1"], created_by_user_id="user-1", video_title="测试标题", ) if status != GenerationTaskStatus.PENDING: object.__setattr__(task, "status", status) # 应用额外 kwargs for k, v in kwargs.items(): object.__setattr__(task, k, v) return task # ============================================================ # CreateGenerationTaskUseCase # ============================================================ class TestCreateGenerationTaskUseCase: """CreateGenerationTaskUseCase 创建生成任务""" def test_create_success(self, mock_repo): """正常创建任务""" mock_repo.create.side_effect = lambda t: t cmd = CreateGenerationTaskCommand( project_id="proj-1", asset_library_id="lib-1", strategy_id="strat-1", voice_library_id="vlib-1", template_id="tmpl-1", asset_ids=["a1", "a2"], title_ids=["t1"], voice_ids=["v1"], created_by_user_id="user-1", source_edit_plan_id="plan-1", asset_select_mode="auto", batch_id="batch-1", video_title="我的视频", auto_retry_enabled=True, auto_retry_max=3, ) uc = CreateGenerationTaskUseCase(mock_repo) task = uc.execute(cmd) assert task.project_id == "proj-1" assert task.asset_library_id == "lib-1" assert task.strategy_id == "strat-1" assert task.voice_library_id == "vlib-1" assert task.template_id == "tmpl-1" assert task.asset_ids == ["a1", "a2"] assert task.title_ids == ["t1"] assert task.voice_ids == ["v1"] assert task.created_by_user_id == "user-1" assert task.source_edit_plan_id == "plan-1" assert task.asset_select_mode == "auto" assert task.batch_id == "batch-1" assert task.video_title == "我的视频" assert task.auto_retry_enabled is True assert task.auto_retry_max == 3 assert task.status == GenerationTaskStatus.PENDING assert task.progress == 0.0 assert task.result_count == 0 mock_repo.create.assert_called_once() def test_create_default_values(self, mock_repo): """默认参数值""" mock_repo.create.side_effect = lambda t: t cmd = CreateGenerationTaskCommand( project_id="proj-1", asset_library_id="lib-1", ) uc = CreateGenerationTaskUseCase(mock_repo) task = uc.execute(cmd) assert task.asset_ids == [] assert task.title_ids == [] assert task.voice_ids == [] assert task.created_by_user_id == "" assert task.video_title == "" assert task.auto_retry_enabled is False assert task.auto_retry_max == 0 def test_create_id_is_generated(self, mock_repo): """ID 会自动生成""" mock_repo.create.side_effect = lambda t: t cmd = CreateGenerationTaskCommand(project_id="proj-1", asset_library_id="lib-1") uc = CreateGenerationTaskUseCase(mock_repo) task = uc.execute(cmd) assert task.id assert isinstance(task.id, str) assert len(task.id) > 10 # uuid hex # ============================================================ # GetGenerationTaskUseCase # ============================================================ class TestGetGenerationTaskUseCase: """GetGenerationTaskUseCase 获取任务""" def test_get_existing(self, mock_repo): """获取存在的任务""" task = make_task() mock_repo.get.return_value = task uc = GetGenerationTaskUseCase(mock_repo) result = uc.execute("task-1") assert result is task mock_repo.get.assert_called_once_with("task-1") def test_get_not_found(self, mock_repo): """获取不存在的任务返回 None""" mock_repo.get.return_value = None uc = GetGenerationTaskUseCase(mock_repo) result = uc.execute("nonexistent") assert result is None # ============================================================ # ListUserTasksFilteredUseCase # ============================================================ class TestListUserTasksFilteredUseCase: """ListUserTasksFilteredUseCase 按用户筛选任务""" def test_list_without_filters(self, mock_repo): """无筛选条件查询""" tasks = [make_task(), make_task()] mock_repo.list_by_user_filtered.return_value = tasks mock_repo.count_by_user_filtered.return_value = 2 uc = ListUserTasksFilteredUseCase(mock_repo) result = uc.execute("user-1") assert isinstance(result, ListGenerationTasksResult) assert len(result.items) == 2 assert result.total == 2 mock_repo.list_by_user_filtered.assert_called_once_with("user-1", status=None, limit=None, offset=0) mock_repo.count_by_user_filtered.assert_called_once_with("user-1", status=None) def test_list_with_status_filter(self, mock_repo): """按状态筛选""" mock_repo.list_by_user_filtered.return_value = [] mock_repo.count_by_user_filtered.return_value = 0 uc = ListUserTasksFilteredUseCase(mock_repo) uc.execute("user-1", status="running") mock_repo.list_by_user_filtered.assert_called_once_with("user-1", status="running", limit=None, offset=0) mock_repo.count_by_user_filtered.assert_called_once_with("user-1", status="running") def test_list_with_pagination(self, mock_repo): """分页查询""" mock_repo.list_by_user_filtered.return_value = [] mock_repo.count_by_user_filtered.return_value = 100 uc = ListUserTasksFilteredUseCase(mock_repo) result = uc.execute("user-1", limit=10, offset=20) assert result.total == 100 mock_repo.list_by_user_filtered.assert_called_once_with("user-1", status=None, limit=10, offset=20) def test_list_empty_result(self, mock_repo): """空结果""" mock_repo.list_by_user_filtered.return_value = [] mock_repo.count_by_user_filtered.return_value = 0 uc = ListUserTasksFilteredUseCase(mock_repo) result = uc.execute("user-1", status="failed") assert result.items == [] assert result.total == 0 # ============================================================ # RetryGenerationTaskUseCase # ============================================================ class TestRetryGenerationTaskUseCase: """RetryGenerationTaskUseCase 重试失败任务""" def test_retry_success(self, mock_repo): """失败任务重试成功""" task = make_task( status=GenerationTaskStatus.FAILED, error_message="网络超时", retry_count=0, ) mock_repo.get.return_value = task mock_repo.update.side_effect = lambda t: t uc = RetryGenerationTaskUseCase(mock_repo) result = uc.execute("task-1") assert result.status == GenerationTaskStatus.PENDING assert result.retry_count == 1 assert result.error_message == "" assert result.error_info == {} assert result.progress == 0.0 assert result.result_count == 0 assert result.started_at is None assert result.completed_at is None mock_repo.update.assert_called_once() def test_retry_not_found(self, mock_repo): """任务不存在""" mock_repo.get.return_value = None uc = RetryGenerationTaskUseCase(mock_repo) with pytest.raises(ValueError, match="任务不存在"): uc.execute("nonexistent") def test_retry_not_failed(self, mock_repo): """非失败状态不能重试""" task = make_task(status=GenerationTaskStatus.RUNNING) mock_repo.get.return_value = task uc = RetryGenerationTaskUseCase(mock_repo) with pytest.raises(ValueError, match="只有失败状态"): uc.execute("task-1") def test_retry_pending_not_allowed(self, mock_repo): """pending 状态不能重试""" task = make_task(status=GenerationTaskStatus.PENDING) mock_repo.get.return_value = task uc = RetryGenerationTaskUseCase(mock_repo) with pytest.raises(ValueError, match="只有失败状态"): uc.execute("task-1") def test_retry_preserves_id(self, mock_repo): """重试复用同一个 task_id""" task = make_task(status=GenerationTaskStatus.FAILED) original_id = task.id mock_repo.get.return_value = task mock_repo.update.side_effect = lambda t: t uc = RetryGenerationTaskUseCase(mock_repo) result = uc.execute("task-1") assert result.id == original_id # ============================================================ # Command / Filter / Result 对象 # ============================================================ class TestCommandAndDataObjects: """命令对象和数据对象""" def test_create_command_defaults(self): cmd = CreateGenerationTaskCommand() assert cmd.project_id == "" assert cmd.asset_library_id == "" assert cmd.asset_ids == [] assert cmd.title_ids == [] assert cmd.voice_ids == [] assert cmd.auto_retry_enabled is False assert cmd.auto_retry_max == 0 def test_list_filter_defaults(self): f = ListTasksFilter() assert f.status is None def test_list_result(self): task = make_task() r = ListGenerationTasksResult(items=[task], total=1) assert len(r.items) == 1 assert r.total == 1