"""Unit tests for agent_team.task_model (§3.3, §3.3.1).""" from __future__ import annotations from agent_team.task_model import ( Phase, PipelineState, TaskRecord, TaskStatus, new_thread_id, task_from_dict, task_from_json, task_to_dict, task_to_json, ) def test_new_thread_id_unique_hex() -> None: a = new_thread_id() b = new_thread_id() assert a != b assert len(a) == 32 int(a, 16) # must be valid hex def test_phase_members() -> None: assert {p.name for p in Phase} == { "INTAKE", "CLARIFY", "PLAN", "REVIEW", "BUILD", "VERIFY", "CONF_DRAFT", "CONF_GATE", "CONF_WRITE", "PARKED", "DONE", } def test_task_record_defaults() -> None: rec = TaskRecord( thread_id="t1", status=TaskStatus.ACTIVE, current_phase=Phase.INTAKE, ) assert rec.qa_history == [] assert rec.plan is None assert rec.review_verdicts == [] assert rec.candidate_diff is None assert rec.diff_hash is None assert rec.ci_results is None assert rec.transport == "" def test_to_dict_serializes_enums_to_values() -> None: rec = TaskRecord( thread_id="t1", status=TaskStatus.WAITING_HUMAN, current_phase=Phase.CLARIFY, ) data = task_to_dict(rec) assert data["status"] == "waiting_human" assert data["current_phase"] == "clarify" def test_roundtrip_dict() -> None: rec = TaskRecord( thread_id="t1", status=TaskStatus.PARKED, current_phase=Phase.PLAN, qa_history=[{"q": "x", "a": "y"}], plan={"phases": [1, 2]}, review_verdicts=["REQUEST_CHANGES"], candidate_diff="diff --git a b", diff_hash="deadbeef", ci_results={"conclusion": "success"}, run_id="27990718108", ci_correlation_tag="t1-1a2b3c", dispatched_at="2026-06-17T00:30:00Z", build_loops=2, transport="slack", created_at="2026-06-17T00:00:00Z", updated_at="2026-06-17T01:00:00Z", ) restored = task_from_dict(task_to_dict(rec)) assert restored == rec # P3 dispatch->verify plumbing fields survive the dict round-trip. assert restored.run_id == "27990718108" assert restored.ci_correlation_tag == "t1-1a2b3c" assert restored.dispatched_at == "2026-06-17T00:30:00Z" # The durable build<->verify loop count round-trips (LOGIC-RACE-01). assert restored.build_loops == 2 def test_build_loops_defaults_zero_and_roundtrips() -> None: rec = TaskRecord( thread_id="t1", status=TaskStatus.ACTIVE, current_phase=Phase.BUILD, ) assert rec.build_loops == 0 # A dict missing build_loops (older record) defaults to 0, not a crash. legacy = task_to_dict(rec) del legacy["build_loops"] assert task_from_dict(legacy).build_loops == 0 def test_roundtrip_json() -> None: rec = TaskRecord( thread_id="t2", status=TaskStatus.DONE, current_phase=Phase.DONE, diff_hash="abc", ) restored = task_from_json(task_to_json(rec)) assert restored == rec assert restored.status is TaskStatus.DONE assert restored.current_phase is Phase.DONE def test_pipeline_state_keys_mirror_task_record() -> None: # Every PipelineState key should be a TaskRecord field. state_keys = set(PipelineState.__annotations__) record_fields = set(TaskRecord.__dataclass_fields__) assert state_keys == record_fields def test_pipeline_state_usable_as_dict() -> None: state: PipelineState = { "thread_id": "t1", "status": "active", "current_phase": "intake", } assert state["thread_id"] == "t1"