Files
ThothII/harness/tests/test_cte_plan_doc.py
T

120 lines
4.2 KiB
Python

"""L1: `tht cte plan --doc -` — persists the cte_plan_doc.json chain documentation.
cte_plan.json (list[str], load-bearing for tht.phase.next_cte) stays untouched; the
doc (payload-A shape: {"ctes": [{"name", "purpose", "rationale", "depends_on", ...}]})
is validated (names must match --name, same order) and written verbatim to a
separate sessions/<id>/cte_plan_doc.json. --doc is optional; without it, behavior
is unchanged (existing test_cte_next.py covers the no-doc path).
"""
import json
from typer.testing import CliRunner
from tht.cli.cte_cmd import cte_app
from tht.config import DatabaseConfig
from tht.decisions import append_decision
from tht.session.store import create_session
def _db():
return DatabaseConfig(database="testdb", user="u", password="p", schema="public")
def _patch_cfg(monkeypatch, tmp_path):
from tht.cli import cte_cmd
class FakePaths:
sessions = tmp_path
class FakeCfg:
paths = FakePaths()
database = _db()
monkeypatch.setattr(cte_cmd, "_load_config_or_exit", lambda _: FakeCfg())
def _advance_to_phase_6(sdir):
"""plan_cmd requires phase >= 6: approve phases 1..5 in order."""
for n in range(1, 6):
append_decision(sdir, type="phase_approved", subject=f"phase:{n}")
def test_plan_with_matching_doc_writes_cte_plan_doc_json(tmp_path, monkeypatch):
m = create_session("q", _db(), tmp_path)
_advance_to_phase_6(tmp_path / m.id)
_patch_cfg(monkeypatch, tmp_path)
doc = {
"question": "domanda riscritta",
"strategy": "razionale",
"ctes": [
{"name": "a", "purpose": "p_a", "rationale": "r_a", "depends_on": []},
{"name": "b", "purpose": "p_b", "rationale": "r_b", "depends_on": ["a"]},
],
}
res = CliRunner().invoke(
cte_app,
["plan", "--session", m.id, "--name", "a", "--name", "b", "--doc", "-"],
input=json.dumps(doc),
)
assert res.exit_code == 0, res.output
plan_path = tmp_path / m.id / "cte_plan.json"
assert json.loads(plan_path.read_text()) == ["a", "b"]
doc_path = tmp_path / m.id / "cte_plan_doc.json"
assert json.loads(doc_path.read_text()) == doc
def test_plan_without_doc_unchanged(tmp_path, monkeypatch):
m = create_session("q", _db(), tmp_path)
_advance_to_phase_6(tmp_path / m.id)
_patch_cfg(monkeypatch, tmp_path)
res = CliRunner().invoke(cte_app, ["plan", "--session", m.id, "--name", "a"])
assert res.exit_code == 0, res.output
assert json.loads((tmp_path / m.id / "cte_plan.json").read_text()) == ["a"]
assert not (tmp_path / m.id / "cte_plan_doc.json").exists()
def test_plan_doc_name_mismatch_rejected(tmp_path, monkeypatch):
m = create_session("q", _db(), tmp_path)
_advance_to_phase_6(tmp_path / m.id)
_patch_cfg(monkeypatch, tmp_path)
doc = {"ctes": [{"name": "a", "purpose": "p"}, {"name": "zzz", "purpose": "p2"}]}
res = CliRunner().invoke(
cte_app,
["plan", "--session", m.id, "--name", "a", "--name", "b", "--doc", "-"],
input=json.dumps(doc),
)
assert res.exit_code == 1
assert res.stdout == ""
# neither file should be written on a rejected doc
assert not (tmp_path / m.id / "cte_plan.json").exists()
assert not (tmp_path / m.id / "cte_plan_doc.json").exists()
def test_plan_doc_wrong_order_rejected(tmp_path, monkeypatch):
m = create_session("q", _db(), tmp_path)
_advance_to_phase_6(tmp_path / m.id)
_patch_cfg(monkeypatch, tmp_path)
doc = {"ctes": [{"name": "b", "purpose": "p"}, {"name": "a", "purpose": "p2"}]}
res = CliRunner().invoke(
cte_app,
["plan", "--session", m.id, "--name", "a", "--name", "b", "--doc", "-"],
input=json.dumps(doc),
)
assert res.exit_code == 1
assert not (tmp_path / m.id / "cte_plan_doc.json").exists()
def test_plan_doc_invalid_json_rejected(tmp_path, monkeypatch):
m = create_session("q", _db(), tmp_path)
_advance_to_phase_6(tmp_path / m.id)
_patch_cfg(monkeypatch, tmp_path)
res = CliRunner().invoke(
cte_app,
["plan", "--session", m.id, "--name", "a", "--doc", "-"],
input="{not valid json",
)
assert res.exit_code == 1
assert not (tmp_path / m.id / "cte_plan_doc.json").exists()