Files
felhom.eu/documentation/audits/design-build-2026-10-06/tools/wgerstep.py
T

141 lines
6.9 KiB
Python

"""wgerstep.py <new-definition-dir> — R-762 on scratch 9202 (drill catalog), ONE process, through the product.
1 DRILL: wger un-hidden (DRILL only); install fresh at the live definition; seed (the fixture) + read back (C1);
2 a progress photo posted to the gallery (the household's API with its session); its /media URL read: 404 expected
(the R-762 fault — the control that the later 200 is the fix, not the test);
3 the login page's own CSS links read: 404 expected;
4 a DRILL commit replaces the compose with the new definition + one ladder entry; sync; the product's guarded Update;
5 after: the seed, the same photo URL (200 + the same byte count), the CSS links (200), wger-files' memory;
6 box verdict JSON; removed through the product (KEEP=1 keeps it).
Evidence: ../F/box/step.txt + box-verdict-wger.json."""
import json, os, re, struct, subprocess, sys, tempfile, time, zlib
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
import box_walk as w
import upgrade_fixtures_box as fixtures
APP, SUB = "wger", "fitness"
NEWDEF = sys.argv[1]
HERE = os.path.dirname(os.path.abspath(__file__))
EVD = os.path.join(HERE, "..", "F", "box"); os.makedirs(EVD, exist_ok=True)
log = open(f"{EVD}/step.txt", "a", buffering=1)
D = "/mnt/5_hdd/felhom.eu/drill/app-catalog-drill"
REC = {"app": APP}
def say(*a):
w.say(*a); log.write(" ".join(map(str, a)) + "\n")
def git(*a):
return subprocess.run(["git", "-C", D, *a], check=True, capture_output=True, text=True).stdout
def png(path, n=64):
"""A real PNG (n x n, random-ish colour) — the gallery validates the image."""
import secrets
rgb = secrets.token_bytes(3)
raw = b"".join(b"\x00" + rgb * n for _ in range(n))
def chunk(t, d):
return struct.pack(">I", len(d)) + t + d + struct.pack(">I", zlib.crc32(t + d) & 0xffffffff)
data = (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", n, n, 8, 2, 0, 0, 0))
+ chunk(b"IDAT", zlib.compress(raw)) + chunk(b"IEND", b""))
open(path, "wb").write(data)
return len(data)
def css_links():
rc, code, page = w.app_curl(SUB, "/en/user/login")
links = re.findall(r'(/static/[^"]+\.css)', page or "")[:3]
return {l: w.app_curl(SUB, l)[1] for l in links}
fx = fixtures.FIXTURES[APP]
w.login()
if w.stack(APP).get("deployed"):
say("wger already installed — removing it first (scratch box)")
w.remove(APP)
git("pull", "-q", "--rebase", "origin", "main")
fy = f"{D}/templates/{APP}/.felhom.yml"
s = open(fy).read()
if "lifecycle: hidden" in s:
open(fy, "w").write(s.replace("lifecycle: hidden", "lifecycle: available", 1))
git("commit", "-q", "-am", "DRILL wger: un-hidden for the R-762 box step (DRILL only)")
git("push", "-q", "origin", "main")
say("drill:", git("log", "--oneline", "-1").strip())
w.sync_rescan()
time.sleep(5)
w.sync_rescan()
if not w.deploy(APP, SUB):
sys.exit(say("RESULT the install did not complete") or 1)
before = (w.stack(APP).get("app_config") or {}).get("pinned_images")
say(f"[1] installed, pinned={before}")
tok = fx.seed(w, SUB, say)
if tok is None or not fx.verify(w, SUB, tok, say):
sys.exit(say(f"RESULT C1 failed: {getattr(fx, 'tried', '')}") or 1)
jar = tempfile.mktemp(prefix="wger-jar-")
hdr, why = fx._login(w, SUB, tok["pw"], jar)
pic = tempfile.mktemp(prefix="wger-photo-", suffix=".png")
size = png(pic)
rc, code, out = w.app_curl(SUB, "/api/v2/gallery/", *hdr, "-F", f"image=@{pic};type=image/png", "-F", "date=2026-10-06",
"-F", "description=r762", method="POST")
say(f"[2] POST /api/v2/gallery/ (a {size} B PNG) -> {code}")
img = None
try:
img = json.loads(out).get("image")
except Exception:
say(f" body {out[:200]}")
path = re.sub(r"^https?://[^/]+", "", img or "")
REC["photo_path"], REC["photo_bytes"] = path, size
code_b = w.app_curl(SUB, path)[1] if path else None
REC["photo_before"] = code_b
REC["css_before"] = css_links()
say(f"[2] the photo {path} read BEFORE the step -> {code_b}; CSS before {REC['css_before']}")
git("pull", "-q", "--rebase", "origin", "main")
newc = open(os.path.join(NEWDEF, "docker-compose.yml")).read()
open(f"{D}/templates/{APP}/docker-compose.yml", "w").write(newc)
to = dict(re.findall(r"^ ([a-z0-9-]+):\s*\n(?:(?! [a-z0-9-]+:\s*\n).*\n)*?\s+image:\s*(\S+)", newc, re.M))
s = open(fy).read()
entry = {"from": before, "to": to, "verdict": "proven", "tested_at": "DRILL", "harness_version": 5,
"evidence": "DRILL (box proof in progress)", "marks": {"files_may_change": False, "needs_person": None, "memory_tight": False}}
s = s.rstrip("\n") + "\n - " + json.dumps(entry) + "\n"
s = re.sub(r'^ mem_limit: .*$', ' mem_limit: "416M"', s, count=1, flags=re.M)
open(fy, "w").write(s)
git("commit", "-q", "-am", f"DRILL wger: the R-762 definition (wger-files) + its ladder entry (box proof)")
git("push", "-q", "origin", "main")
say("drill:", git("log", "--oneline", "-1").strip(), "| to:", to)
w.sync_rescan(APP, to.get("wger-files"))
REC["badge_before"] = w.badges(APP)
since = w.guest("date -u +%Y-%m-%dT%H:%M:%SZ").strip()
res = w.press_update(APP, poll=1, cap_s=1800)
for p in res.get("phases", []):
log.write(f" phase +{p['t']}s {p['phase']} | err={p['error']}\n")
time.sleep(15)
read = fx.verify(w, SUB, tok, say)
code_a = w.app_curl(SUB, path)[1] if path else None
rc, _, body = w.app_curl(SUB, path, "-o", "/dev/null", "-w", "%{size_download}") if path else (1, None, "")
REC["photo_after"], REC["photo_after_bytes"] = code_a, body.strip()[-12:]
REC["css_after"] = css_links()
REC["memory"] = w.guest("docker stats --no-stream --format '{{.Name}} {{.MemUsage}}' | grep -i wger").strip().splitlines()
lines = w.guest(f"docker logs --since {since} felhom-controller 2>&1 | grep -E 'update {APP}' | grep -v DEBUG | cut -c1-400")
log.write(lines + "\n")
st = w.stack(APP); after = (st.get("app_config") or {}).get("pinned_images")
REC["badge_after"] = w.badges(APP)
say(f"[5] after: phase={res.get('final_phase')} pinned={after} seed={read} photo={code_a} ({REC['photo_after_bytes']}) "
f"css={REC['css_after']} mem={REC['memory']}")
ok = (res.get("final_phase") == "done" and read and after == to and code_a == "200"
and REC["css_after"] and all(c == "200" for c in REC["css_after"].values()))
verdict = {"app": APP, "venue": "box 9202 (drill catalog), the product's guarded Update (R-762 definition step)",
"from": before, "to": after, "verdict": "proven" if ok else "failed",
"seed_read_before": True, "seed_read_after": read, "healthy_after": st.get("state") == "running",
"duration_s": res.get("duration_s"), "final_phase": res.get("final_phase"), "measured_at": since,
"evidence": "felhom.eu/documentation/audits/design-build-2026-10-06/F/box/step.txt", "r762": REC}
json.dump(verdict, open(f"{EVD}/box-verdict-{APP}.json", "w"), indent=2)
say(f"RESULT {verdict['verdict']}")
for f in (jar, pic):
if os.path.exists(f):
os.unlink(f)
if not os.environ.get("KEEP"):
say(f"remove -> {w.remove(APP)}")