Files
felhom.eu/scripts/test_status_view_gate.py
T

194 lines
11 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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)