From 485a4df2b0af4f7b6463a7f6ec40053fa7692def Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Tue, 28 Jul 2026 13:48:03 +0800 Subject: [PATCH] =?UTF-8?q?test(wave158):=20edit=5Fplan=E5=89=AA=E8=BE=91?= =?UTF-8?q?=E8=AE=A1=E5=88=92=E5=8D=95=E6=B5=8B=20+32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为 domain/edit_plan.py 新增 32 个单测: - EditPlanStatus 枚举:6个 - create 工厂/校验:11个(必填/strip/空值/默认值) - 状态流转(draft→editing→rendering→completed/failed):14个 - config 独立性:1个 --- tests/unit/domain/test_edit_plan.py | 296 ++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 tests/unit/domain/test_edit_plan.py diff --git a/tests/unit/domain/test_edit_plan.py b/tests/unit/domain/test_edit_plan.py new file mode 100644 index 000000000..8a19bd6a6 --- /dev/null +++ b/tests/unit/domain/test_edit_plan.py @@ -0,0 +1,296 @@ +"""edit_plan 单测. + +domain 层剪辑计划纯逻辑模块,0 外部依赖。 +覆盖:枚举常量、create工厂/校验、完整状态流转、重置。 +""" + +from __future__ import annotations + +from packages.domain.edit_plan import EditPlan, EditPlanStatus + + +class TestEditPlanStatus: + """EditPlanStatus 枚举测试.""" + + def test_five_statuses(self): + """五种状态.""" + assert len(EditPlanStatus) == 5 + + def test_draft(self): + assert EditPlanStatus.DRAFT == "draft" + + def test_editing(self): + assert EditPlanStatus.EDITING == "editing" + + def test_rendering(self): + assert EditPlanStatus.RENDERING == "rendering" + + def test_completed(self): + assert EditPlanStatus.COMPLETED == "completed" + + def test_failed(self): + assert EditPlanStatus.FAILED == "failed" + + +class TestEditPlanCreate: + """EditPlan.create 工厂测试.""" + + def test_create_minimal(self): + """最简创建.""" + plan = EditPlan.create(template_id="tpl1", name=" 我的计划 ") + assert plan.template_id == "tpl1" + assert plan.name == "我的计划" + assert plan.status == EditPlanStatus.DRAFT + assert plan.total_duration == 0.0 + assert plan.config == {} + assert plan.project_id == "" + assert plan.created_by_user_id == "" + assert isinstance(plan.id, str) + assert len(plan.id) > 0 + + def test_create_full(self): + """带全部字段.""" + plan = EditPlan.create( + template_id="tpl1", + name="口播视频计划", + config={"bgm": "rock"}, + total_duration=120.5, + source_edit_plan_id="src1", + project_id="proj1", + created_by_user_id="user1", + ) + assert plan.name == "口播视频计划" + assert plan.total_duration == 120.5 + assert plan.source_edit_plan_id == "src1" + assert plan.project_id == "proj1" + assert plan.created_by_user_id == "user1" + assert plan.config == {"bgm": "rock"} + + def test_create_empty_name(self): + """空名称无效.""" + try: + EditPlan.create(template_id="t1", name="") + assert False + except ValueError as e: + assert "名称" in str(e) + + def test_create_whitespace_name(self): + """空白名称无效.""" + try: + EditPlan.create(template_id="t1", name=" ") + assert False + except ValueError as e: + assert "名称" in str(e) + + def test_create_empty_template_id(self): + """空 template_id 无效.""" + try: + EditPlan.create(template_id="", name="计划") + assert False + except ValueError as e: + assert "template_id" in str(e) + + def test_create_whitespace_template_id(self): + """空白 template_id 无效.""" + try: + EditPlan.create(template_id=" ", name="计划") + assert False + except ValueError as e: + assert "template_id" in str(e) + + def test_config_none_defaults_empty(self): + """config=None 默认为空 dict.""" + plan = EditPlan.create(template_id="t1", name="p", config=None) + assert plan.config == {} + + def test_unique_id(self): + """不同计划 id 不同.""" + p1 = EditPlan.create("t", "p1") + p2 = EditPlan.create("t", "p2") + assert p1.id != p2.id + + def test_has_timestamps(self): + """有创建和更新时间.""" + plan = EditPlan.create("t", "p") + assert plan.created_at is not None + assert plan.updated_at is not None + + def test_strips_string_fields(self): + """字符串字段 strip.""" + plan = EditPlan.create( + template_id=" t1 ", + name=" n ", + source_edit_plan_id=" s ", + project_id=" p ", + created_by_user_id=" u ", + ) + assert plan.template_id == "t1" + assert plan.name == "n" + assert plan.source_edit_plan_id == "s" + assert plan.project_id == "p" + assert plan.created_by_user_id == "u" + + +class TestEditPlanStatusFlow: + """状态流转测试.""" + + def _make_plan(self): + return EditPlan.create(template_id="t1", name="测试计划") + + def test_initial_status_draft(self): + """初始状态 draft.""" + plan = self._make_plan() + assert plan.status == EditPlanStatus.DRAFT + + def test_draft_to_editing(self): + """draft -> editing.""" + plan = self._make_plan() + old = plan.updated_at + plan.start_editing() + assert plan.status == EditPlanStatus.EDITING + assert plan.updated_at >= old + + def test_editing_to_rendering(self): + """editing -> rendering.""" + plan = self._make_plan() + plan.start_editing() + plan.start_rendering() + assert plan.status == EditPlanStatus.RENDERING + + def test_rendering_to_completed(self): + """rendering -> completed.""" + plan = self._make_plan() + plan.start_editing() + plan.start_rendering() + plan.mark_completed() + assert plan.status == EditPlanStatus.COMPLETED + + def test_rendering_to_failed(self): + """rendering -> failed.""" + plan = self._make_plan() + plan.start_editing() + plan.start_rendering() + plan.mark_failed() + assert plan.status == EditPlanStatus.FAILED + + def test_failed_to_draft_reset(self): + """failed -> draft 重置.""" + plan = self._make_plan() + plan.start_editing() + plan.start_rendering() + plan.mark_failed() + plan.reset_to_draft() + assert plan.status == EditPlanStatus.DRAFT + + def test_cannot_editing_from_completed(self): + """completed 不能 start_editing.""" + plan = self._make_plan() + plan.start_editing() + plan.start_rendering() + plan.mark_completed() + try: + plan.start_editing() + assert False + except ValueError as e: + assert "draft" in str(e).lower() + + def test_cannot_rendering_from_draft(self): + """draft 不能直接 start_rendering.""" + plan = self._make_plan() + try: + plan.start_rendering() + assert False + except ValueError as e: + assert "editing" in str(e).lower() + + def test_cannot_complete_from_editing(self): + """editing 不能直接完成.""" + plan = self._make_plan() + plan.start_editing() + try: + plan.mark_completed() + assert False + except ValueError as e: + assert "rendering" in str(e).lower() + + def test_cannot_fail_from_editing(self): + """editing 不能直接失败.""" + plan = self._make_plan() + plan.start_editing() + try: + plan.mark_failed() + assert False + except ValueError as e: + assert "rendering" in str(e).lower() + + def test_cannot_reset_from_draft(self): + """draft 不能重置.""" + plan = self._make_plan() + try: + plan.reset_to_draft() + assert False + except ValueError as e: + assert "failed" in str(e).lower() + + def test_cannot_reset_from_completed(self): + """completed 不能重置.""" + plan = self._make_plan() + plan.start_editing() + plan.start_rendering() + plan.mark_completed() + try: + plan.reset_to_draft() + assert False + except ValueError as e: + assert "failed" in str(e).lower() + + def test_full_happy_path(self): + """完整成功路径.""" + plan = self._make_plan() + assert plan.status == EditPlanStatus.DRAFT + plan.start_editing() + assert plan.status == EditPlanStatus.EDITING + plan.start_rendering() + assert plan.status == EditPlanStatus.RENDERING + plan.mark_completed() + assert plan.status == EditPlanStatus.COMPLETED + + def test_failed_reset_retry(self): + """失败后重置重试完整路径.""" + plan = self._make_plan() + plan.start_editing() + plan.start_rendering() + plan.mark_failed() + assert plan.status == EditPlanStatus.FAILED + plan.reset_to_draft() + assert plan.status == EditPlanStatus.DRAFT + # 重新走一遍 + plan.start_editing() + plan.start_rendering() + plan.mark_completed() + assert plan.status == EditPlanStatus.COMPLETED + + def test_each_transition_updates_timestamp(self): + """每次状态变更都更新 updated_at.""" + plan = self._make_plan() + timestamps = [plan.updated_at] + plan.start_editing() + timestamps.append(plan.updated_at) + plan.start_rendering() + timestamps.append(plan.updated_at) + plan.mark_completed() + timestamps.append(plan.updated_at) + # 单调递增 + for i in range(1, len(timestamps)): + assert timestamps[i] >= timestamps[i - 1] + + +class TestEditPlanConfig: + """config 独立性测试.""" + + def test_config_independent(self): + """不同计划的 config 独立.""" + p1 = EditPlan.create(template_id="t", name="p1") + p2 = EditPlan.create(template_id="t", name="p2") + p1.config["key"] = "value" + assert "key" not in p2.config