From f82527aa82e93348878647e46a752fd39b7317aa Mon Sep 17 00:00:00 2001 From: CI Bot Date: Wed, 22 Jul 2026 17:38:31 +0800 Subject: [PATCH] =?UTF-8?q?test:=20P3-1=20=E7=AC=AC=E5=8D=81=E4=B9=9D?= =?UTF-8?q?=E6=B3=A2=20generation=5Ftasks=E5=BA=94=E7=94=A8=E5=B1=82?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95=2017?= =?UTF-8?q?=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CreateGenerationTaskUseCase 3个 - GetGenerationTaskUseCase 2个 - ListUserTasksFilteredUseCase 4个 - RetryGenerationTaskUseCase 5个 - Command/Filter/Result对象 3个 - 合计 17 个测试全部通过 --- tests/unit/test_generation_tasks.py | 332 ++++++++++++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100755 tests/unit/test_generation_tasks.py diff --git a/tests/unit/test_generation_tasks.py b/tests/unit/test_generation_tasks.py new file mode 100755 index 000000000..a4c23206f --- /dev/null +++ b/tests/unit/test_generation_tasks.py @@ -0,0 +1,332 @@ +""" +生成任务应用层用例单元测试(第十九波) + +覆盖: +- 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