# -*- coding: utf-8 -*- """Decoy tests for status_view_gate.py (R-1174). Run: python3 scripts/test_status_view_gate.py Exit 0 every case behaved · 1 a case did not. A DECOY IS THE LABEL WITHOUT THE FACT (R-421). Each case below takes a GENUINE page — the fixture, shaped like the real STATUS.md — and breaks ONE check the way a real session would: the „Updated" paragraph with an „Earlier" chain behind it, a decided row left in the decide table, a Hungarian do-line, a page that meets the size by deleting the P1 or the „Later" line. Each must convict, and must convict ON THAT CHECK (its `[check]` tag in the output): a decoy caught by some other check proves nothing about the check it was built for. Both directions: the genuine fixture and the REAL STATUS.md against the REAL register must pass. The date switch is tested on both sides (FELHOM_GATE_TODAY). Fixtures are temp files; nothing here touches the real tree. """ import os import subprocess import sys import tempfile HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.dirname(HERE) GATE = os.path.join(HERE, "status_view_gate.py") BEFORE = "2026-10-26" # the last warn-only day AFTER = "2026-10-27" # SWITCH_DATE: convictions refuse REGISTER = u"""# OPEN-ITEMS — fixture | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| | **R-10** | Security & access | P1 | **Reused logins.** | **NARROWED** | — | rotate | operator | | **R-11** | Backup & restore | P3 | **Immich off-site size.** | **READY** | D21 | decide | CC | | **R-12** | Process & tooling | P4 | **A failed disk.** | **OPEN** | — | pull it | operator | | **R-13** | Business & legal | P4 | **A lawyer.** | WAITING-ON-OPERATOR | — | send | Viktor | | **R-14** | Install & onboarding | P3 | **The minimum machine.** | WAITING-ON-OPERATOR | — | approve | operator | | **R-15** | Box system & updates | P3 | **The scratch box.** | OPEN | — | fix | CC | """ PAGE = u"""# STATUS — what works, what's broken, what's next **Ready for the first real tester? Yes.** The laptop is off. **Updated 2026-10-20 (day).** Hub 0.162.0 is live (your yes). This page is one screen again. A rehearsal runs tonight. ## What works - A box installs from nothing (map A1). ## What is broken - **P1:** reused logins (R-10, yours). - The scratch box (R-15). ## What we work on - The rehearsal. CC. ## Waiting on you — decide | # | Row | Question | Pick (why) | The other option costs | If you do nothing | Blocked | |---|---|---|---|---|---|---| | D21 | R-11 | Leave out the AI model files now? | **B now** | A: a new restore path | 12 times the data | — | | D5 | R-14 | Approve the minimum machine? | **Approve** | Change it: one edit | no hardware page | the hardware page | ## Waiting on you — do **Still needed? (yes/no)** 1. Look at the Calibre-Web page („Adatok és mentés"). Since 2026-10-16. **The rest** - Pull the failed disk (R-12). Since 2026-10-12. - Rotate the reused logins (R-10, P1). Since 2026-10-09. - Later (yours, not urgent): R-13. """ PASSED, FAILED = [], [] def run(page, register=REGISTER, today=BEFORE, raw_page_path=None, raw_reg_path=None): with tempfile.TemporaryDirectory() as d: p = raw_page_path or os.path.join(d, "STATUS.md") r = raw_reg_path or os.path.join(d, "OPEN-ITEMS.md") if raw_page_path is None and page is not None: open(p, "w", encoding="utf-8").write(page) if raw_reg_path is None and register is not None: open(r, "w", encoding="utf-8").write(register) env = dict(os.environ) env.pop("FELHOM_GATE_TODAY", None) if today: env["FELHOM_GATE_TODAY"] = today pr = subprocess.run([sys.executable, GATE, p, r], capture_output=True, text=True, env=env, encoding="utf-8", errors="replace") return pr.returncode, pr.stdout + pr.stderr def mutate(old, new, page=PAGE): assert page.count(old) == 1, "fixture anchor not unique: %r" % old return page.replace(old, new) def case(name, page, expect_rc, must=None, mustnot=None, **kw): rc, out = run(page, **kw) ok = rc == expect_rc and (must is None or must in out) and (mustnot is None or mustnot not in out) (PASSED if ok else FAILED).append(name) print("%s %s (rc=%d, want %d%s)" % ("PASS" if ok else "FAIL", name, rc, expect_rc, ", must %r" % must if must else "")) if not ok: print(" " + out.replace("\n", "\n ")[:1500]) def decoy(name, page, check): """A decoy convicts on its own check: WARNING before the switch (exit 0), refusal from it (exit 1).""" case(name + " / before the switch: warns", page, 0, must="WARNING [%s]" % check, today=BEFORE) case(name + " / from the switch: refuses", page, 1, must="CONVICTED [%s]" % check, today=AFTER) # ── the genuine article, both sides of the switch ──────────────────────────────────────────────── case("genuine fixture passes (before)", PAGE, 0, must="status-view: OK", mustnot="WARNING", today=BEFORE) case("genuine fixture passes (after)", PAGE, 0, must="status-view: OK", today=AFTER) case("clean run says what it looked at", PAGE, 0, must="lines /", today=BEFORE) # ── size ───────────────────────────────────────────────────────────────────────────────────────── decoy("size: 50 more What-works lines", mutate("- A box installs from nothing (map A1).\n", "- A box installs from nothing (map A1).\n" + "- One more capability, in plain words (map B1).\n" * 50), "size") decoy("size: few lines, too many characters", mutate("- A box installs from nothing (map A1).\n", "- A box installs from nothing (map A1). " + "It works well and it is proven live. " * 170 + "\n"), "size") case("size: a long decide table is exempt", mutate( "| D5 | R-14 |", "| D5 | R-14 | Approve? | **Approve** | one edit | no page | — |\n" * 60 + "| D6 | R-14 |"), 0, must="status-view: OK", today=AFTER) # ── updated ────────────────────────────────────────────────────────────────────────────────────── decoy("updated: news prepended, the old paragraph left behind", mutate( "**Updated 2026-10-20 (day).**", "**Updated 2026-10-21 (day).** New news.\n\n**Updated 2026-10-20 (day).**"), "updated") decoy("updated: an Earlier chain inside the paragraph", mutate( "A rehearsal runs tonight.", "A rehearsal runs tonight. **Earlier:** a lot happened."), "updated") decoy("updated: four sentences", mutate( "A rehearsal runs tonight.", "A rehearsal runs tonight. The installer is published."), "updated") decoy("updated: no Updated paragraph at all", mutate( "**Updated 2026-10-20 (day).** ", ""), "updated") case("updated: version numbers are not sentence ends", mutate( "Hub 0.162.0 is live (your yes).", "Hub 0.162.0 and agent 0.161.0 are live (your yes)."), 0, must="status-view: OK", today=AFTER) # ── english ────────────────────────────────────────────────────────────────────────────────────── decoy("english: a Hungarian do-line", mutate( "**The rest**\n", "**The rest**\n- Nézd meg a Calibre-Web oldalát a demo-hp-n.\n"), "english") case("english: the same text inside a quote passes", mutate( "**The rest**\n", "**The rest**\n- Read the line „Nézd meg a Calibre-Web oldalát” on demo-hp.\n"), 0, must="status-view: OK", today=AFTER) # ── open-ids ───────────────────────────────────────────────────────────────────────────────────── decoy("open-ids: a closed row cited in What works", mutate( "(map A1).", "(map A1; R-99 closed it)."), "open-ids") # ── decide ─────────────────────────────────────────────────────────────────────────────────────── decoy("decide: a DECIDED row left in the table", mutate( "| D5 | R-14 |", "| D20 | R-11 | **DECIDED 2026-10-20: A** — built and live. | done | — | — | — |\n| D5 | R-14 |"), "decide") decoy("decide: a row whose Pick cell reads done", mutate( "| D5 | R-14 | Approve the minimum machine? | **Approve** |", "| D5 | R-14 | Approve the minimum machine? | done |"), "decide") # ── p1 ─────────────────────────────────────────────────────────────────────────────────────────── decoy("p1: the page meets its size by deleting the P1 line", mutate( "- **P1:** reused logins (R-10, yours).\n", ""), "p1") decoy("p1: the P1 moved to What works", mutate( "- **P1:** reused logins (R-10, yours).\n", "", page=mutate( "- A box installs from nothing (map A1).\n", "- A box installs from nothing (map A1). Logins: R-10.\n")), "p1") # ── operator ───────────────────────────────────────────────────────────────────────────────────── decoy("operator: the Later line deleted", mutate("- Later (yours, not urgent): R-13.\n", ""), "operator") decoy("operator: a waiting row named only in What is broken", mutate( "- Pull the failed disk (R-12). Since 2026-10-12.\n", "", page=mutate( "- The scratch box (R-15).\n", "- The scratch box (R-15). The failed disk (R-12).\n")), "operator") # ── inconclusive: never a pass, no warn period ─────────────────────────────────────────────────── case("inconclusive: a page with no sections", u"# STATUS\n\nAll good.\n", 2, must="INCONCLUSIVE", today=BEFORE) case("inconclusive: a page missing the do section", PAGE.split("## Waiting on you — do")[0], 2, must="INCONCLUSIVE", today=BEFORE) case("inconclusive: an empty register", PAGE, 2, must="INCONCLUSIVE", register=u"# nothing here\n", today=BEFORE) case("inconclusive: no register file", PAGE, 2, must="INCONCLUSIVE", raw_reg_path=os.path.join(HERE, "no-such-register.md"), today=BEFORE) # ── the real page against the real register ────────────────────────────────────────────────────── case("the real STATUS.md passes against the real register", None, 0, must="status-view: OK", raw_page_path=os.path.join(ROOT, "STATUS.md"), raw_reg_path=os.path.join(ROOT, "documentation", "backlog", "OPEN-ITEMS.md"), today=None) print("\n%d passed, %d failed" % (len(PASSED), len(FAILED))) if FAILED: for f in FAILED: print(" FAILED: " + f) sys.exit(1) sys.exit(0)