Add a Confluence documentation lane to the Plane-2 pipeline, flag-gated behind AGENT_TEAM_CONFLUENCE_ENABLED (default off; daemon behavior unchanged when off). - confluence/client.py: OAuth 2LO + Basic REST client, dry-run-default writes - confluence/mermaid.py: vendored ADF-only Mermaid editor (macro-count + revert-diff guards, dry-run default) - nodes/confluence_writer.py(+_llm): conf_draft -> conf_gate -> conf_write, both direct (task_kind=confluence) and post-build documentation flows - task_model/graph/coordinator: new phases, state channels, route_after_intake, CONFLUENCE_APPROVAL_KIND gate delivery, task_kind forwarding - db schema v5: widen pending_questions kind CHECK (atomic rebuild) - tests for client, mermaid, writer node, ledger v5, coordinator gate, e2e
136 lines
3.7 KiB
Python
136 lines
3.7 KiB
Python
"""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"
|