Files
Behavision/tests/test_dashboard.py
Suriyakumarvijayanayagam dad04e8cda Behavision: face recognition for retail, edge to head office
Five components that ship as one product:

- behavision/  the recognition engine. RTSP ingest, YuNet detection, IoU
               tracking, ArcFace embeddings, a FAISS/SQLite gallery, and a
               FastAPI dashboard. Identity is decided once per TRACK from an
               average of at least three embeddings, never per frame.
- agent/       the Go edge agent: supervises the engine, holds a durable
               spool, and drains it to MQTT. Nothing is acked before the
               broker confirms.
- desktop/     the shop PC application (Wails + React + tray).
- server/      the cloud API, MQTT consumer, reports and assistant.
- web/         platform.loyaly.ai, the head-office app, embedded in the
               server binary.

The gallery stores 512-float embeddings and timestamps - no images unless
`app.store_faces` is switched on. Those embeddings are biometric personal
data under GDPR and India's DPDP: template inversion reconstructs a
recognisable face from an ArcFace vector, so data/behavision.db is treated
as a biometric database and DELETE /api/visitors/{id} is a real erasure.

CLAUDE.md carries the reasoning behind every non-obvious decision here,
including the ones that were measured and the ones that were wrong first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HViLj9gYNRtSr7YVZmW5sn
2026-09-04 11:14:18 +05:30

139 lines
5.9 KiB
Python

"""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"<script>(.*?)</script>", 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-<name>` 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 <img> 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"<style>(.*?)</style>", 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; }}`."
)