diff --git a/test_lifestrument.py b/test_lifestrument.py new file mode 100644 index 0000000..add4c0b --- /dev/null +++ b/test_lifestrument.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +"""Self-check for viz/life.html (Lifestrument). Stdlib only, no deps. + +Asserts the three things that keep the fun page honest: + 1. life.html parses as balanced HTML (well-formed tags). + 2. every hub source it wires is a real config.json signal (or a local me.* one). + 3. the banned families (money & machinery) appear NOWHERE in the file. + +Run: python3 test_lifestrument.py +""" +import json +import os +import re +import sys +from html.parser import HTMLParser + +HERE = os.path.dirname(os.path.abspath(__file__)) +LIFE = os.path.join(HERE, "viz", "life.html") +CONFIG = os.path.join(HERE, "config.json") + +BANNED_PREFIXES = ("crypto.", "fx.", "market.", "econ.", "debt.", "quake.", "planes.", "iss.") +# void elements never need a close tag +VOID = {"area", "base", "br", "col", "embed", "hr", "img", "input", "link", + "meta", "param", "source", "track", "wbr"} + + +class Balance(HTMLParser): + def __init__(self): + super().__init__() + self.stack = [] + self.errors = [] + + def handle_starttag(self, tag, attrs): + if tag not in VOID: + self.stack.append(tag) + + def handle_startendtag(self, tag, attrs): + pass # self-closing, balanced by definition + + def handle_endtag(self, tag): + if tag in VOID: + return + if not self.stack: + self.errors.append(f"stray {tag}>") + return + if self.stack[-1] == tag: + self.stack.pop() + elif tag in self.stack: + # close intervening (lenient, like a browser) but flag it + while self.stack and self.stack[-1] != tag: + self.stack.pop() + if self.stack: + self.stack.pop() + else: + self.errors.append(f"unexpected {tag}>") + + +def test_balanced_html(html): + p = Balance() + p.feed(html) + assert not p.errors, f"HTML tag errors: {p.errors}" + assert not p.stack, f"unclosed tags: {p.stack}" + print(" ok: HTML is balanced") + + +def test_no_banned(html): + hits = [] + for pref in BANNED_PREFIXES: + # word-ish boundary so 'econ.' doesn't match e.g. 'second.' β require a + # quote/backtick/space before the prefix, as our source keys always are. + for m in re.finditer(r'["\'`\s(]' + re.escape(pref), html): + frag = html[m.start():m.start() + 40].replace("\n", " ") + hits.append(f"{pref!r} near: β¦{frag}β¦") + assert not hits, "banned money/machinery keys present:\n " + "\n ".join(hits) + print(f" ok: none of {BANNED_PREFIXES} appear") + + +def test_whitelist_valid(html): + signals = set(json.load(open(CONFIG))["signals"].keys()) + # pull the WHITELIST Set literal from the source + m = re.search(r"const WHITELIST = new Set\(\[(.*?)\]\)", html, re.S) + assert m, "could not find WHITELIST Set in life.html" + keys = re.findall(r'"([^"]+)"', m.group(1)) + assert len(keys) >= 20, f"whitelist suspiciously small: {len(keys)} keys" + missing = [k for k in keys if k not in signals] + assert not missing, f"whitelist keys not in config.json signals: {missing}" + # none of the whitelisted keys may themselves be banned + bad = [k for k in keys if k.startswith(BANNED_PREFIXES)] + assert not bad, f"whitelist contains banned keys: {bad}" + print(f" ok: all {len(keys)} whitelist keys exist in config.json, none banned") + + +def test_reuses_real_endpoints(html): + for ep in ("/api/login", "/api/signup", "/api/me", "/api/logout"): + assert ep in html, f"missing expected auth endpoint {ep}" + # exact field names the hub expects + assert "identifier:" in html, "login must send {identifier, password}" + assert "invite:" in html, "signup must send an invite code" + print(" ok: reuses the real /api/* auth endpoints with correct fields") + + +def main(): + html = open(LIFE, encoding="utf-8").read() + print("test_lifestrument.py β checking viz/life.html") + test_balanced_html(html) + test_no_banned(html) + test_whitelist_valid(html) + test_reuses_real_endpoints(html) + print("ALL CHECKS PASSED") + + +if __name__ == "__main__": + try: + main() + except AssertionError as e: + print("FAIL:", e) + sys.exit(1) diff --git a/viz/index.html b/viz/index.html index 3291371..f2de019 100644 --- a/viz/index.html +++ b/viz/index.html @@ -6642,6 +6642,7 @@ : "π tab feed cancelled"; eventTicker.unshift({ text: msg, tAdded: now(), src: "sys" }); }); ctxItem("β¨ keys β remap the keyboard", () => openKeys()); + ctxItem("π± lifestrument β the gentle one (new tab)", () => window.open("/life.html", "_blank")); if (commune.room) { const gOwner = commune.alloc["@godtime"]; ctxItem(gOwner === communeMe() ? "πΈ release the godtime" : diff --git a/viz/life.html b/viz/life.html new file mode 100644 index 0000000..1715456 --- /dev/null +++ b/viz/life.html @@ -0,0 +1,1151 @@ + + +
+ + + + +