- dashboard.html: each section sits in OPTIONAL:<name> BEGIN/END blocks in the style, markup and script, with sample answers so a page with no bot shows it working. Core change: setUser records the role on <body>, so CSS hides admin-only parts from operators. - templates/sections.py keeps the chosen sections and deletes the rest, markers included; it stops on a broken marker. - templates/sections.md: what each section needs from the host (events, actions, desktop slot snippets, the role re-check, the login hand-off). - SKILL.md step 5 asks one multi-select question and runs sections.py. - Page check: checkpoint states, admin visibility by role and assign/remove, login theme and hand-off to auth or the stub. Cases skip on a generated page without the section; the template must carry all three. The harness signs in when the page has a login screen. - tests/test_sections.py: no sections leave no trace, and that page still passes the page check. - Eval runner: a-empty-sections variant (all three) and per-section checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
56 lines
2.3 KiB
Python
56 lines
2.3 KiB
Python
"""Optional sections: the strip tool keeps the chosen sections and leaves nothing of the rest."""
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
TEMPLATES = ROOT / "rpa-dashboard-no-ai" / "templates"
|
|
spec = importlib.util.spec_from_file_location("sections", TEMPLATES / "sections.py")
|
|
sections = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(sections)
|
|
|
|
TEMPLATE = (TEMPLATES / "dashboard.html").read_text(encoding="utf-8")
|
|
# Text each optional section brings into the page (markup, script or style).
|
|
TRACES = {
|
|
"checkpoints": re.compile(r"checkpoint|cpstate|cp-", re.I),
|
|
"admin": re.compile(r"admin", re.I),
|
|
"login": re.compile(r"login_|sign in|sign out|logout", re.I),
|
|
}
|
|
|
|
|
|
def test_no_sections_leave_nothing_behind():
|
|
html = sections.keep(TEMPLATE, [])
|
|
for name, trace in TRACES.items():
|
|
assert not trace.search(html), f"{name} left behind: {trace.search(html)[0]!r}"
|
|
assert "OPTIONAL" not in html
|
|
|
|
|
|
@pytest.mark.parametrize("chosen", [["checkpoints"], ["admin"], ["login"], ["checkpoints", "admin", "login"]])
|
|
def test_chosen_sections_are_kept_and_the_rest_removed(chosen):
|
|
html = sections.keep(TEMPLATE, chosen)
|
|
for name, trace in TRACES.items():
|
|
assert bool(trace.search(html)) == (name in chosen), name
|
|
assert "OPTIONAL" not in html
|
|
|
|
|
|
def test_unknown_section_is_refused():
|
|
with pytest.raises(SystemExit, match="checkpoints, admin, login"):
|
|
sections.keep(TEMPLATE, ["charts"])
|
|
|
|
|
|
def test_page_without_sections_passes_the_page_check(tmp_path):
|
|
fill = {"APP_NAME": "T", "APP_SUBTITLE": "", "LOGO_SRC": "", "API_BASE": "",
|
|
"STEPS_JSON": json.dumps(["Start", "Work", "Done"]),
|
|
"REPORT_COLUMNS_JSON": json.dumps([{"key": "item", "label": "Item"}, {"key": "status", "label": "Status"}])}
|
|
page_file = tmp_path / "dashboard.html"
|
|
page_file.write_text(re.sub(r"\{\{(\w+)\}\}", lambda m: fill[m[1]], sections.keep(TEMPLATE, [])), encoding="utf-8")
|
|
r = subprocess.run([sys.executable, "-m", "pytest", str(ROOT / "tests" / "page_check"), "-q", "-p", "no:cacheprovider"],
|
|
env={**os.environ, "PAGE_CHECK_HTML": str(page_file)}, capture_output=True, text=True)
|
|
assert r.returncode == 0, r.stdout[-2000:]
|