"""Static checks on the dashboard page. The page is plain HTML+JS with no build step, which is a deliberate choice — but it means nothing catches a typo'd element id or an unescaped interpolation until a user opens the page. These tests are that safety net, and they need no browser and no node. """ import re from pathlib import Path import pytest PAGE = Path(__file__).parent.parent / "behavision" / "static" / "dashboard.html" HTML = PAGE.read_text(encoding="utf-8") SCRIPT = re.search(r"", HTML, re.S).group(1) IDS = set(re.findall(r'id="([^"]+)"', HTML)) def test_every_getelementbyid_target_exists(): """A mistyped id fails silently as `null.innerHTML` at runtime, on a page nobody runs in CI.""" referenced = set(re.findall(r"getElementById\('([^']+)'\)", SCRIPT)) assert referenced <= IDS, f"no such element: {sorted(referenced - IDS)}" def test_every_form_field_in_F_has_an_input(): """Fields are addressed as `f-` built from the F list, so the static check above cannot see them.""" fields = re.search(r"const F = \[(.*?)\];", SCRIPT, re.S).group(1) names = re.findall(r"'([^']+)'", fields) assert names, "F list not found" missing = [n for n in names if f"f-{n}" not in IDS] assert not missing, f"form fields with no input: {missing}" def test_placement_wizard_is_present(): for needed in ("wizard", "wz-body", "wz-close", "wz-again", "wz-loosen"): assert needed in IDS, f"missing {needed}" def test_loosening_the_gate_is_only_offered_for_a_marginal_camera(): """For a poor camera the answer is to move it. Dropping the gate there turns a visible miss into an invisible wrong match, which is worse.""" handler = re.search(r"wz-loosen'\)\.hidden =(.*?);", SCRIPT, re.S).group(1) assert "marginal" in handler def test_camera_settings_ui_is_present(): for needed in ("cam-new", "cam-list", "cam-form", "cam-save", "cam-test-btn", "cam-cancel"): assert needed in IDS, f"missing {needed}" def test_user_supplied_values_are_escaped(): """Identity labels and camera ids are user input and this page has already had one stored-XSS bug. A bare `${x.label}` is that bug coming back. Scoped to template literals that build markup — a literal containing a tag is destined for innerHTML. Interpolating into `textContent` needs no escaping and must not be flagged, or the test trains people to ignore it. """ literals = re.findall(r"`([^`]*)`", SCRIPT, re.S) offenders = [] for lit in literals: if "<" not in lit: continue offenders += re.findall( r"\$\{\s*[A-Za-z_][\w]*(?:\.[\w]+)+\s*\}", lit) assert not offenders, f"unescaped interpolation into markup: {offenders}" def test_live_feeds_are_not_rebuilt_on_every_refresh(): """Re-assigning an MJPEG src restarts the stream. Rebuilding the feeds on the 3s refresh would leave every camera flickering forever, so renderFeeds must bail out when the camera set is unchanged.""" body = re.search(r"function renderFeeds\(cams\) \{(.*?)\n\}", SCRIPT, re.S) assert body, "renderFeeds not found" assert "return" in body.group(1).split("feedKey = key")[0], \ "renderFeeds must return early when the camera set has not changed" def test_feeds_use_id_not_camera_id(): """`camera_id` comes from worker.stats() and only exists while the worker runs; a stored camera that failed to start has only `id`. Using the wrong one put the literal string 'undefined' in the stream URL.""" feeds = re.search(r"function renderFeeds\(cams\) \{(.*?)\n\}", SCRIPT, re.S) assert "camera_id" not in feeds.group(1) def test_script_is_syntactically_valid(): """Caught a real `const cams` redeclaration the first time it ran. Skipped when node is absent - the suite stays dependency-light.""" import shutil import subprocess import tempfile node = shutil.which("node") if not node: pytest.skip("node not installed") tmp = Path(tempfile.mkdtemp()) / "dashboard.js" tmp.write_text(SCRIPT) r = subprocess.run([node, "--check", str(tmp)], capture_output=True, text=True) assert r.returncode == 0, r.stderr def test_hidden_elements_are_actually_hidden(): """An author `display:` rule beats the UA stylesheet's `[hidden] { display: none }` — same specificity, author sheet wins. The placement wizard is a `position:fixed` full-screen modal styled `display:flex`, so it sat open over the dashboard on every page load until a matching `[hidden]` rule was added. Nothing in the suite noticed, because every other check reads the markup and the JS rather than the CSS. Any element that carries the `hidden` attribute or is toggled through the `.hidden` property must not be given an unconditional `display` by id. """ style = re.search(r"", HTML, re.S).group(1) toggled = set(re.findall(r'id="([^"]+)"[^>]*\shidden[\s>]', HTML)) toggled |= set(re.findall(r"getElementById\('([^']+)'\)\.hidden\s*=", SCRIPT)) assert toggled, "no hidden-toggled elements found - has the page changed?" for name in sorted(toggled): # Rules that set display for this id, ignoring any that are themselves # qualified by [hidden] (those are the fix, not the bug). rules = re.findall(r"#%s(\[[^\]]*\])?\s*\{([^}]*)\}" % re.escape(name), style) sets_display = [q for q, body in rules if "display" in body and "[hidden]" not in (q or "")] if not sets_display: continue guard = re.search(r"#%s\[hidden\]\s*\{[^}]*display\s*:\s*none" % re.escape(name), style) assert guard, ( f"#{name} is toggled with the `hidden` attribute but its CSS sets " f"`display` unconditionally, which overrides it. Add " f"`#{name}[hidden] {{ display: none; }}`." )