"""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