Files
Behavision/tests/test_installer.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

84 lines
3.6 KiB
Python

"""The installer describes a layout nothing else checks.
`tests/test_paths.py` already asserts that every file `behavision.spec` ships
actually exists, for the reason that a rename otherwise fails only inside the
bundle - the one place nothing is tested. The installer has the same property
and one worse: it runs on a machine none of us has, at a customer's counter,
and its failure mode is a shop PC that installs cleanly and then does nothing.
These are cheap string checks on purpose. They cannot prove the package works
on Windows - only a Windows box can - but they catch the class of mistake that
would otherwise get that far: a path renamed on one side of the build and not
the other.
"""
from __future__ import annotations
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
ISS = (ROOT / "installer" / "behavision.iss").read_text(encoding="utf-8")
PS1 = (ROOT / "installer" / "build.ps1").read_text(encoding="utf-8")
GO_DEFAULTS = (ROOT / "agent" / "pkg" / "config" / "config.go").read_text(encoding="utf-8")
def _sources() -> list[str]:
"""Every `Source:` path the [Files] section installs."""
return re.findall(r'^Source:\s*"([^"]+)"', ISS, re.MULTILINE)
def test_every_installed_source_is_produced_by_the_build():
"""A Source: the build never stages makes ISCC fail at compile time — but
only on the machine that compiles it, which is not this one."""
staged = {
r"..\dist\Behavision\Behavision.exe",
r"..\dist\Behavision\behavision-agent.exe",
r"..\dist\Behavision\engine\*",
r"vendor\MicrosoftEdgeWebview2Setup.exe",
}
assert set(_sources()) == staged, (
"installer [Files] and installer/build.ps1 have drifted apart"
)
for name in ("Behavision.exe", "behavision-agent.exe", "engine",
"MicrosoftEdgeWebview2Setup.exe"):
assert name in PS1, f"build.ps1 does not produce {name}"
def test_the_agent_looks_for_the_engine_where_the_installer_puts_it():
"""The one coupling between the Go side and the installer.
The app resolves `EngineExe` against the directory holding its own
executable. If the installer puts the engine somewhere else, the app starts,
shows a healthy window, and never recognises anybody.
"""
assert 'filepath.Join("engine", "behavision")' in GO_DEFAULTS
assert r'DestDir: "{app}\engine"' in ISS
def test_nothing_writable_is_placed_under_program_files():
"""The install root is read-only for the account the app runs as.
Everything written - the biometric database, logs, the camera list, the
downloaded models - belongs in the state root. An installer that seeds a
writable file under {app} would work for the administrator who installed it
and fail for the shop assistant who uses it.
"""
assert "{commonappdata}\\Behavision" in ISS
for src in _sources():
assert not src.endswith((".db", ".json", ".yaml", ".log")), src
def test_models_are_not_bundled():
"""~200 MB, downloaded resumably on first run. Bundling them quadruples the
package and forces a re-sign for a model change."""
assert not any(s.endswith((".onnx", ".caffemodel")) for s in _sources())
assert "setup-models" in ISS
def test_the_app_is_offered_at_startup_and_not_installed_as_a_service():
"""A service runs in session 0 and cannot draw a tray icon — Windows
session isolation, not a library limitation. Since the product is "the user
starts and stops it from the tray", autostart is a per-user Run entry."""
assert "{userstartup}" in ISS
assert "sc.exe" not in ISS and "nssm" not in ISS.lower()