#!/usr/bin/env python3 """Unit checks for theme validation and the customCss sanitiser.""" import os, sys sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) from market.theme import sanitize_css, sanitize_patch, build_theme_action, PRESETS fails = 0 def check(name, cond): global fails fails += not cond print(("PASS " if cond else "FAIL ") + name) evil = ("@import url(https://evil.example/x.css); .card{color:red!important;position:fixed;z-index:99;--p-ink:transparent;" "background:url(https://evil.example/t.png)} body{display:none} :root{--acc:red} h1{background:url(https://images.unsplash.com/a.jpg);margin:-500px}" " .x{color:expression(alert(1))} .y{behavior:url(x.htc)} .z::before{content:'x'}" " .t{transform:scale(0)} .k{color:blue}") out = sanitize_css(evil) check("css: @import stripped", "@" not in out and "evil.example/x.css" not in out) check("css: !important stripped", "important" not in out) check("css: position/z-index/content/transform dropped", not any(k in out for k in ("position", "z-index", "content", "transform"))) check("css: custom properties dropped", "--" not in out) check("css: non-allowlisted url() neutralised", "evil.example" not in out and "url(https://images.unsplash.com/a.jpg)" in out) check("css: body/:root selectors dropped", "body" not in out and ":root" not in out) check("css: script/expression/behavior gone", not any(k in out.lower() for k in ("Hi there", "chips": ["Bikes", {"label": "Garden", "say": "garden stuff"}]}, "sections": {"hidden": ["bullets", "nope"], "order": ["results", "hero"]}, "evil": "x", "background": {"kind": "image", "url": "https://evil.example/bg.jpg"}}) check("patch: bad colour dropped, good kept", p["palette"] == {"accent": "green"}) check("patch: font alias resolved", p["font"] == "Comic Neue") check("patch: numbers clamped", p["fontScale"] == 1.75 and p["radius"] == 0 and p["columns"] == 6 and p["chat"]["width"] == 600) check("patch: html stripped from copy", "<" not in p["copy"]["headline"]) check("patch: sections validated", p["sections"]["hidden"] == ["bullets"] and p["sections"]["order"][:2] == ["results", "hero"]) check("patch: unknown keys and non-allowlisted image dropped", "evil" not in p and "background" not in p) a = build_theme_action({"preset": "Retro"}) check("preset: retro expands", a["preset"] == "retro" and a["base"]["font"] == "VT323") check("preset: alias", build_theme_action({"preset": "cyberpunk"})["preset"] == "neon") check("reset action", build_theme_action({"reset": True})["reset"] is True) check("all presets validate cleanly", all(sanitize_patch(v) for v in PRESETS.values())) sys.exit(1 if fails else 0)