6816d4a4f0
ensure_positioning_framings adds 5 Architect framings to the core positioning variant group alongside Option A/B, so the group holds 7 candidates and choose_variant retires 6. The two thesis tests still asserted the pre-framings count of 2 — the tests were stale, not the seed. Realign them, document the 2+5=7 seed structure in the thesis guide, and refresh AGENTS.md Current state (13/13 tests green).
88 lines
4.4 KiB
Python
88 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Backend test for the Workshop's new thesis actions: edit, retire-subtree,
|
|
choose-variant, and one-click dual-approval -> canonical. Runs init_db (which seeds the
|
|
v5 thesis) against a throwaway DB, then exercises the architect_tools + thesis_review
|
|
functions the new routes call. Offline + synthetic.
|
|
|
|
Run: cd backend && python3 test_thesis_actions.py
|
|
"""
|
|
import os
|
|
import sqlite3
|
|
import sys
|
|
import tempfile
|
|
|
|
_tmp = tempfile.mkdtemp()
|
|
os.environ["CRM_DATA_DIR"] = _tmp
|
|
os.environ["CRM_DB_PATH"] = os.path.join(_tmp, "crm.db")
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
import server # noqa: E402 (the CRM server; it imports architect_tools + thesis_review for us)
|
|
at = server._architect_tools
|
|
thesis_review = server.thesis_review
|
|
|
|
FAILS = []
|
|
|
|
|
|
def check(cond, msg):
|
|
print((" PASS " if cond else " FAIL ") + msg)
|
|
if not cond:
|
|
FAILS.append(msg)
|
|
|
|
|
|
def main():
|
|
server.init_db()
|
|
db = server.DB_PATH
|
|
c = sqlite3.connect(db)
|
|
c.row_factory = sqlite3.Row
|
|
|
|
# choose-variant: pick Option A in the positioning group -> every other option retired.
|
|
# The group holds Option A/B plus the 5 Architect framings (ensure_positioning_framings) = 7.
|
|
opts = c.execute("SELECT id, title FROM thesis_nodes WHERE variant_group='positioning' AND deleted_at IS NULL").fetchall()
|
|
check(len(opts) == 7, f"positioning starts with 7 options: Option A/B + 5 Architect framings (got {len(opts)})")
|
|
a = next(o for o in opts if "Option A" in (o["title"] or ""))
|
|
res = at.choose_variant(a["id"], db=db)
|
|
expected_retired = len(opts) - 1 # 6: Option B + the 5 framings
|
|
check(res.get("retired_siblings") == expected_retired, f"choose_variant retired the {expected_retired} siblings (got {res.get('retired_siblings')})")
|
|
live = c.execute("SELECT COUNT(*) FROM thesis_nodes WHERE variant_group='positioning' AND deleted_at IS NULL").fetchone()[0]
|
|
check(live == 1, f"positioning now has 1 live option (got {live})")
|
|
check(c.execute("SELECT status FROM thesis_nodes WHERE id=?", (a["id"],)).fetchone()[0] == "approved",
|
|
"chosen option marked approved")
|
|
|
|
# edit: manual human edit updates body + writes a revision
|
|
thr = c.execute("SELECT id FROM thesis_nodes WHERE node_type='throughline' AND deleted_at IS NULL").fetchone()
|
|
at.upsert_thesis_node(None, "throughline", "EDITED THROUGHLINE.", node_id=thr["id"], actor_type="human",
|
|
change_reason="manual edit", actor_id="u1", db=db)
|
|
check(c.execute("SELECT body FROM thesis_nodes WHERE id=?", (thr["id"],)).fetchone()[0] == "EDITED THROUGHLINE.",
|
|
"manual edit updated the node body")
|
|
rev = c.execute("SELECT actor_type FROM thesis_node_revisions WHERE node_id=? ORDER BY rev_no DESC LIMIT 1", (thr["id"],)).fetchone()
|
|
check(rev and rev[0] == "human", "edit recorded a 'human' revision")
|
|
|
|
# retire-subtree: retiring the Pillars section drops it + its 3 pillar claims
|
|
sec = c.execute("SELECT id FROM thesis_nodes WHERE node_type='section' AND title='Pillars' AND deleted_at IS NULL").fetchone()
|
|
kids = c.execute("SELECT COUNT(*) FROM thesis_nodes WHERE parent_id=? AND deleted_at IS NULL", (sec["id"],)).fetchone()[0]
|
|
res = at.retire_node(sec["id"], db=db)
|
|
check(res.get("count") == kids + 1, f"retire_node soft-deleted the section + {kids} children (got {res.get('count')})")
|
|
check(c.execute("SELECT deleted_at FROM thesis_nodes WHERE id=?", (sec["id"],)).fetchone()[0] is not None,
|
|
"section is soft-deleted")
|
|
|
|
# one-click dual approval -> canonical (two distinct admins)
|
|
ver = at.create_thesis_version("core", created_by="u1", db=db)
|
|
at.submit_version_for_review(ver["id"], db=db)
|
|
r1 = thesis_review.record_review(db, ver["id"], "u1", "approve")
|
|
check(r1["approvals"] == 1 and not r1["promoted_to_canonical"], "1st approval does not promote")
|
|
r2 = thesis_review.record_review(db, ver["id"], "u2", "approve")
|
|
check(r2["approvals"] == 2 and r2["promoted_to_canonical"], "2nd distinct approval promotes to canonical")
|
|
check(thesis_review.get_canonical(db, "core").get("status") == "ok", "core line now has a canonical version")
|
|
# a 2nd approval from the SAME admin would NOT have promoted (distinct approvers only) — covered by record_review
|
|
|
|
c.close()
|
|
print()
|
|
if FAILS:
|
|
print(f"FAILED ({len(FAILS)})")
|
|
sys.exit(1)
|
|
print("ALL PASS (thesis workshop actions)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|