Files
vigilar/tests/unit/test_web.py
adlee-was-taken 038ab0af12 fix: address final-review items (status endpoint, docs, tests)
Follow-up to the holistic review of the PIN-unification branch:

- /system/status now reads the real arm state from the arm_state_log
  table via get_current_arm_state, instead of returning a hardcoded
  'DISARMED' stub. Without this, polling after the new async 202
  arm/disarm flow was a UX dead-end — clients never saw the state
  change they just requested. DB read failures degrade gracefully.

- Operator guide: correct the claim that 'vigilar config set-pin'
  populates recovery_passphrase_hash. It doesn't. recovery_passphrase
  _hash has no CLI helper today; it must be set manually.

- Tests: add a fail-closed regression for verify_pin on malformed
  stored hashes, and a companion test confirming the deprecation
  warning stays silent on a fully migrated config.

All address specific review comments on the branch; no scope creep.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 12:26:30 -04:00

138 lines
4.2 KiB
Python

"""Tests for Flask web application."""
from vigilar.config import CameraConfig, VigilarConfig
from vigilar.web.app import create_app
def test_app_creates():
cfg = VigilarConfig()
app = create_app(cfg)
assert app is not None
def test_index_loads():
cfg = VigilarConfig(cameras=[
CameraConfig(id="test", display_name="Test", rtsp_url="rtsp://localhost"),
])
app = create_app(cfg)
with app.test_client() as client:
resp = client.get("/")
assert resp.status_code == 200
assert b"Vigilar" in resp.data
assert b"Test" in resp.data
def test_settings_loads():
cfg = VigilarConfig()
app = create_app(cfg)
with app.test_client() as client:
resp = client.get("/system/settings")
assert resp.status_code == 200
assert b"Settings" in resp.data
def test_kiosk_loads():
cfg = VigilarConfig(cameras=[
CameraConfig(id="cam1", display_name="Cam 1", rtsp_url="rtsp://a"),
])
app = create_app(cfg)
with app.test_client() as client:
resp = client.get("/kiosk/")
assert resp.status_code == 200
assert b"Cam 1" in resp.data
assert b"kiosk-grid" in resp.data
def test_system_status_api():
cfg = VigilarConfig()
app = create_app(cfg)
with app.test_client() as client:
resp = client.get("/system/status")
assert resp.status_code == 200
data = resp.get_json()
assert "arm_state" in data
def test_config_api():
cfg = VigilarConfig()
app = create_app(cfg)
with app.test_client() as client:
resp = client.get("/system/api/config")
assert resp.status_code == 200
data = resp.get_json()
assert "system" in data
assert "cameras" in data
# Secrets should be redacted
assert "password_hash" not in data.get("web", {})
def test_camera_status_api():
cfg = VigilarConfig(cameras=[
CameraConfig(id="test", display_name="Test", rtsp_url="rtsp://localhost"),
])
app = create_app(cfg)
with app.test_client() as client:
resp = client.get("/cameras/api/status")
assert resp.status_code == 200
data = resp.get_json()
assert len(data) == 1
assert data[0]["id"] == "test"
def test_events_page_loads():
cfg = VigilarConfig()
app = create_app(cfg)
with app.test_client() as client:
resp = client.get("/events/")
assert resp.status_code == 200
assert b"Event Log" in resp.data
def test_sensors_page_loads():
cfg = VigilarConfig()
app = create_app(cfg)
with app.test_client() as client:
resp = client.get("/sensors/")
assert resp.status_code == 200
def test_recordings_page_loads():
cfg = VigilarConfig()
app = create_app(cfg)
with app.test_client() as client:
resp = client.get("/recordings/")
assert resp.status_code == 200
def test_system_status_reflects_fsm_arm_state(tmp_path, monkeypatch):
"""system_status must read the current arm state from the DB,
not return a hardcoded stub. Regression guard for the web-to-FSM
async flow introduced in issue #2."""
from vigilar.config import SystemConfig, VigilarConfig
import vigilar.storage.db as db_module
from vigilar.storage.db import get_db_path
from vigilar.storage.schema import metadata
from vigilar.storage.queries import insert_arm_state
from vigilar.web.app import create_app
from sqlalchemy import create_engine
data_dir = tmp_path / "data"
data_dir.mkdir()
cfg = VigilarConfig(system=SystemConfig(data_dir=str(data_dir)))
# Build an isolated engine (bypass the module-level singleton)
db_path = get_db_path(str(data_dir))
isolated_engine = create_engine(f"sqlite:///{db_path}", echo=False)
metadata.create_all(isolated_engine)
insert_arm_state(isolated_engine, "ARMED_AWAY", "test", None)
# Patch the singleton so the blueprint's get_engine() returns our engine
monkeypatch.setattr(db_module, "_engine", isolated_engine)
app = create_app(cfg)
with app.test_client() as c:
resp = c.get("/system/status")
assert resp.status_code == 200
assert resp.get_json()["arm_state"] == "ARMED_AWAY"