Files
damascus-orchestrator/tests/test_spec_path_persistence.py
kaykayyali 339faf47a0
Some checks failed
test / contract-and-unit (push) Has been cancelled
feat(orchestrator): persist spec_path on spec-phase pass (ADR-004) (#30)
2026-06-27 16:38:24 +00:00

87 lines
3.3 KiB
Python

"""
Unit tests for ADR-004: persist `spec_path` on spec-phase pass.
Story: S1 — Persist spec_path on spec-phase pass
ADR: wiki/decisions/ADR-004-persist-spec-path-on-pass.md
Contract:
- verdict=pass + phase=spec => spec_path from feedback is written to the row.
- verdict != pass + phase=spec => spec_path is unchanged.
These tests drive `cycle.tick()` against the real test Postgres (conftest's
default `db-test` service) and stub `phases.refine_spec` so the LLM is
never called. The other moveable parts (wiki, relay) are also stubbed so
the test is hermetic.
"""
from __future__ import annotations
import pytest
from conftest import get_row, insert_work_item
from damascus import cycle, phases, relay, wiki
def _stub_phase_returning(verdict: str, feedback: dict):
"""Build a fake phases.refine_spec that returns a fixed verdict/feedback."""
def fake_refine_spec(cur, item):
print(f"DEBUG stub: item phase={item.get('phase')!r} id={item.get('id')!r}")
print(f"DEBUG stub: returning verdict={verdict!r} feedback={feedback!r}")
return {"verdict": verdict, "feedback": feedback}
return fake_refine_spec
def _run_tick_with_stub(monkeypatch, verdict: str, feedback: dict) -> None:
"""Run one orchestrator tick with the spec phase stubbed.
wiki.init_wiki and relay.post are no-ops so the test does not touch
the host filesystem or any external service.
"""
monkeypatch.setattr(wiki, "init_wiki", lambda: None)
monkeypatch.setattr(relay, "post", lambda line: None)
monkeypatch.setattr(phases, "refine_spec", _stub_phase_returning(verdict, feedback))
out = cycle.tick()
print(f"DEBUG tick: claimed={out['claimed']!r} transition={out['transition']!r}")
assert out["claimed"] is not None, "tick did not claim a row"
assert out["transition"]["verdict"] == verdict
def test_pass_verdict_persists_spec_path(monkeypatch):
"""AC: On verdict=pass in spec phase, work_items.spec_path equals
the absolute path returned in verdict_feedback."""
rid = insert_work_item(phase="spec", story_id="S1-pass", title="Persist spec path on pass")
expected_path = "/data/specs/wh40k-pc/S1-pass.spec.md"
_run_tick_with_stub(monkeypatch, "pass", {
"spec_path": expected_path,
"preview": "# Goal\n...",
})
row = get_row(rid)
assert row["spec_path"] == expected_path, (
f"spec_path not persisted on pass: row has {row['spec_path']!r}, "
f"expected {expected_path!r}"
)
# The phase should have advanced spec -> build (the contract for pass).
assert row["phase"] == "build"
def test_non_pass_verdict_does_not_persist(monkeypatch):
"""AC: On a non-pass verdict in spec phase, work_items.spec_path is unchanged.
For a freshly-inserted row, spec_path starts NULL and stays NULL."""
rid = insert_work_item(phase="spec", story_id="S1-nopass",
title="Spec ambiguous case")
_run_tick_with_stub(monkeypatch, "spec_ambiguous", {
"issue_id": "test-issue-id",
"preview": "# Goal\n...",
})
row = get_row(rid)
assert row["spec_path"] is None, (
f"spec_path must be unchanged on non-pass; row has {row['spec_path']!r}"
)
# spec_ambiguous rolls back the attempts increment AND routes to
# awaiting_human (contract per ADR-004 + design doc §5).
assert row["phase"] == "awaiting_human"