"""TeamPlan / SubTask 数据模型单元测试 (hub-and-spoke 模式)""" from __future__ import annotations from agentkit.experts.plan import ( MergeStrategy, PlanStatus, SubTask, SubTaskStatus, TeamPlan, ) # ── 辅助函数 ────────────────────────────────────────────── def _make_subtask( id: str = "subtask_1", description: str = "分析数据", assigned_expert: str = "analyst", status: SubTaskStatus = SubTaskStatus.PENDING, result: dict | None = None, ) -> SubTask: """创建测试用 SubTask 实例""" return SubTask( id=id, description=description, assigned_expert=assigned_expert, status=status, result=result, ) def _make_valid_plan() -> TeamPlan: """创建一个有效的 hub-and-spoke 执行计划""" subtasks = [ _make_subtask(id="s1", description="分析需求", assigned_expert="analyst"), _make_subtask(id="s2", description="设计架构", assigned_expert="architect"), _make_subtask(id="s3", description="编写代码", assigned_expert="coder"), ] return TeamPlan( id="plan_001", task="实现用户登录功能", subtasks=subtasks, status=PlanStatus.DRAFT, lead_expert="architect", ) # ── MergeStrategy 测试 ──────────────────────────────────── class TestMergeStrategy: """MergeStrategy 枚举测试""" def test_only_best_strategy_exists(self): """hub-and-spoke 模式仅保留 BEST 策略""" assert MergeStrategy.BEST == "best" # 确保只有 BEST 一个值 assert len(list(MergeStrategy)) == 1 def test_no_vote_or_fusion(self): """VOTE 和 FUSION 已被移除""" assert not hasattr(MergeStrategy, "VOTE") assert not hasattr(MergeStrategy, "FUSION") # ── PlanStatus 测试 ─────────────────────────────────────── class TestPlanStatus: """PlanStatus 枚举测试""" def test_statuses_exist(self): """必要的计划状态都存在""" assert PlanStatus.DRAFT == "draft" assert PlanStatus.EXECUTING == "executing" assert PlanStatus.COMPLETED == "completed" assert PlanStatus.FAILED == "failed" assert PlanStatus.FALLBACK == "fallback" def test_no_confirmed_status(self): """CONFIRMED 状态已被移除(hub-and-spoke 无需确认阶段)""" assert not hasattr(PlanStatus, "CONFIRMED") # ── SubTaskStatus 测试 ──────────────────────────────────── class TestSubTaskStatus: """SubTaskStatus 枚举测试""" def test_statuses_exist(self): """子任务状态都存在""" assert SubTaskStatus.PENDING == "pending" assert SubTaskStatus.RUNNING == "running" assert SubTaskStatus.COMPLETED == "completed" assert SubTaskStatus.FAILED == "failed" # ── SubTask 测试 ────────────────────────────────────────── class TestSubTask: """SubTask 数据模型测试""" def test_creation_with_all_fields(self): """创建 SubTask 并设置所有字段""" subtask = SubTask( id="subtask_a", description="竞品分析", assigned_expert="analyst", status=SubTaskStatus.RUNNING, result={"report": "竞品分析报告"}, ) assert subtask.id == "subtask_a" assert subtask.description == "竞品分析" assert subtask.assigned_expert == "analyst" assert subtask.status == SubTaskStatus.RUNNING assert subtask.result == {"report": "竞品分析报告"} def test_default_values(self): """默认值:自动生成 id,PENDING 状态""" subtask = SubTask(description="测试任务") assert subtask.id is not None assert len(subtask.id) > 0 assert subtask.description == "测试任务" assert subtask.assigned_expert == "" assert subtask.status == SubTaskStatus.PENDING assert subtask.result is None def test_to_dict_from_dict_roundtrip(self): """to_dict / from_dict 往返序列化""" subtask = SubTask( id="roundtrip_subtask", description="往返测试", assigned_expert="tester", status=SubTaskStatus.COMPLETED, result={"key": "value"}, ) d = subtask.to_dict() restored = SubTask.from_dict(d) assert restored.id == subtask.id assert restored.description == subtask.description assert restored.assigned_expert == subtask.assigned_expert assert restored.status == subtask.status assert restored.result == subtask.result def test_to_dict_structure(self): """to_dict 返回正确的字典结构""" subtask = _make_subtask( id="struct_test", description="结构测试", assigned_expert="dev", status=SubTaskStatus.RUNNING, result={"output": "data"}, ) d = subtask.to_dict() assert d["id"] == "struct_test" assert d["description"] == "结构测试" assert d["assigned_expert"] == "dev" assert d["status"] == "running" assert d["result"] == {"output": "data"} # ── TeamPlan 测试 ───────────────────────────────────────── class TestTeamPlan: """TeamPlan 数据模型测试""" def test_creation(self): """创建 TeamPlan""" plan = _make_valid_plan() assert plan.id == "plan_001" assert plan.task == "实现用户登录功能" assert len(plan.subtasks) == 3 assert plan.status == PlanStatus.DRAFT assert plan.lead_expert == "architect" def test_default_values(self): """默认值:自动生成 id,空子任务列表""" plan = TeamPlan(task="测试任务") assert plan.id is not None assert plan.task == "测试任务" assert plan.subtasks == [] assert plan.status == PlanStatus.DRAFT assert plan.lead_expert == "" def test_to_dict_from_dict_roundtrip(self): """to_dict / from_dict 往返序列化""" plan = _make_valid_plan() d = plan.to_dict() restored = TeamPlan.from_dict(d) assert restored.id == plan.id assert restored.task == plan.task assert len(restored.subtasks) == len(plan.subtasks) assert restored.status == plan.status assert restored.lead_expert == plan.lead_expert for original, restored_st in zip(plan.subtasks, restored.subtasks): assert restored_st.id == original.id assert restored_st.description == original.description assert restored_st.assigned_expert == original.assigned_expert assert restored_st.status == original.status def test_get_subtask_by_id(self): """get_subtask 根据 ID 获取子任务""" plan = _make_valid_plan() st = plan.get_subtask("s2") assert st is not None assert st.id == "s2" assert st.description == "设计架构" def test_get_subtask_with_nonexistent_id_returns_none(self): """get_subtask 对不存在的 ID 返回 None""" plan = _make_valid_plan() assert plan.get_subtask("nonexistent") is None def test_update_subtask_status(self): """update_subtask_status 更新子任务状态和结果""" plan = _make_valid_plan() plan.update_subtask_status("s1", SubTaskStatus.COMPLETED, {"output": "分析完成"}) st = plan.get_subtask("s1") assert st is not None assert st.status == SubTaskStatus.COMPLETED assert st.result == {"output": "分析完成"} def test_update_subtask_status_without_result(self): """update_subtask_status 不传 result 时不覆盖已有 result""" plan = _make_valid_plan() plan.update_subtask_status("s1", SubTaskStatus.COMPLETED, {"output": "done"}) plan.update_subtask_status("s1", SubTaskStatus.RUNNING) st = plan.get_subtask("s1") assert st is not None assert st.status == SubTaskStatus.RUNNING assert st.result == {"output": "done"} def test_completed_subtasks_property(self): """completed_subtasks 返回已完成的子任务""" plan = _make_valid_plan() plan.update_subtask_status("s1", SubTaskStatus.COMPLETED) plan.update_subtask_status("s2", SubTaskStatus.COMPLETED) plan.update_subtask_status("s3", SubTaskStatus.FAILED) completed = plan.completed_subtasks assert len(completed) == 2 assert {st.id for st in completed} == {"s1", "s2"} def test_failed_subtasks_property(self): """failed_subtasks 返回失败的子任务""" plan = _make_valid_plan() plan.update_subtask_status("s1", SubTaskStatus.COMPLETED) plan.update_subtask_status("s2", SubTaskStatus.FAILED) plan.update_subtask_status("s3", SubTaskStatus.FAILED) failed = plan.failed_subtasks assert len(failed) == 2 assert {st.id for st in failed} == {"s2", "s3"} def test_all_done_property_when_all_completed(self): """all_done 当所有子任务完成时返回 True""" plan = _make_valid_plan() for st in plan.subtasks: plan.update_subtask_status(st.id, SubTaskStatus.COMPLETED) assert plan.all_done is True def test_all_done_property_when_some_failed(self): """all_done 当所有子任务完成或失败时返回 True""" plan = _make_valid_plan() plan.update_subtask_status("s1", SubTaskStatus.COMPLETED) plan.update_subtask_status("s2", SubTaskStatus.FAILED) plan.update_subtask_status("s3", SubTaskStatus.COMPLETED) assert plan.all_done is True def test_all_done_property_when_pending(self): """all_done 当有子任务未完成时返回 False""" plan = _make_valid_plan() plan.update_subtask_status("s1", SubTaskStatus.COMPLETED) # s2 and s3 still pending assert plan.all_done is False def test_all_done_property_empty_plan(self): """all_done 当没有子任务时返回 True(vacuous truth)""" plan = TeamPlan(task="空计划") assert plan.all_done is True