import json import re from pathlib import Path import pytest from voxip_kiosk.paths import Paths ROOT = Path(__file__).resolve().parents[1] @pytest.fixture def paths(tmp_path) -> Paths: p = Paths( config_dir=tmp_path / "etc", state_dir=tmp_path / "state", network_state_dir=tmp_path / "netstate", agent_run_dir=tmp_path / "run-agent", browser_run_dir=tmp_path / "run-browser", health_run_dir=tmp_path / "run-health", share_dir=tmp_path / "share", log_dir=tmp_path / "log", helper=tmp_path / "helper", nm_connections_dir=tmp_path / "nm", ) for d in (p.config_dir, p.state_dir, p.agent_run_dir, p.browser_run_dir, p.health_run_dir, p.share_dir / "www", p.log_dir, p.nm_connections_dir): d.mkdir(parents=True) (p.share_dir / "VERSION").write_text("9.9.9-test\n") default = json.loads((ROOT / "config" / "config.default.json").read_text()) p.config_file.write_text(json.dumps(default)) return p class FakeHelper: def __init__(self): self.calls = [] self.fail = None self.responses = {} def call(self, verb, *args, payload=None, timeout=60): from voxip_kiosk.helper_client import HelperError self.calls.append((verb, args, payload)) if self.fail: raise HelperError(self.fail) return {"ok": True, "message": f"{verb} ok", **self.responses.get(verb, {})} class FakeCDP: def __init__(self): self.calls = [] self.fail = None def _do(self, name, *args): from voxip_kiosk.cdp import CDPError self.calls.append((name, args)) if self.fail: raise CDPError(self.fail) def reload(self, ignore_cache=False): self._do("reload") def screenshot(self, quality=70): self._do("screenshot") return b"\xff\xd8fakejpeg" @pytest.fixture def fake_helper(): return FakeHelper() @pytest.fixture def fake_cdp(): return FakeCDP() @pytest.fixture def quiet_system(monkeypatch): """Keep the web tests independent of the machine they run on.""" from voxip_kiosk import netinfo, services, sysinfo monkeypatch.setattr(services, "summary", lambda: { "browser": {"unit": services.BROWSER_UNIT, "status": "ok", "label": "Actief", "restarts": "2", "since": ""}, "x": {"unit": services.X_UNIT, "status": "ok", "label": "Actief", "restarts": "0", "since": ""}, "agent": {"unit": services.AGENT_UNIT, "status": "ok", "label": "Actief", "restarts": "0", "since": ""}, "manager": {"unit": services.MANAGER_UNIT, "status": "ok", "label": "Actief", "restarts": "0", "since": ""}, }) monkeypatch.setattr(services, "recent_errors", lambda lines=20: []) monkeypatch.setattr(services, "journal", lambda source, lines=100, priority=None: f"log:{source}:{lines}:{priority}") monkeypatch.setattr(sysinfo, "ip_addresses", lambda: {"eth0": ["192.168.10.20/24"]}) monkeypatch.setattr(sysinfo, "throttled", lambda: {"raw": "0x50000", "now": False, "ever": True, "flags": ["Onderspanning opgetreden"]}) monkeypatch.setattr(netinfo, "overview", lambda: { "available": True, "wifi_country": "NL", "devices": { iface: {"interface": iface, "kind": kind, "present": True, "state": "connected", "state_label": "Verbonden", "connected": True, "connection": f"voxip-{iface}", "addresses": ["192.168.10.20/24"], "gateway": "192.168.10.1", "dns": ["192.168.10.1"], "hwaddr": "B8:27:EB:00:00:01", "profile": {"exists": True, "method": "dhcp", "address": "", "gateway": "", "dns": "", "ssid": "Kantoor", "hidden": False}} for iface, kind in (("eth0", "ethernet"), ("wlan0", "wifi")) }, }) @pytest.fixture def app(paths, fake_helper, fake_cdp, quiet_system): from voxip_kiosk.web import create_app application = create_app(paths, helper=fake_helper, cdp=fake_cdp) application.config["TESTING"] = True return application @pytest.fixture def client(app): return app.test_client() def csrf_from(response) -> str: match = re.search(r'name="csrf-token" content="([^"]+)"', response.get_data(as_text=True)) assert match, "csrf meta tag missing" return match.group(1) PASSWORD = "Correct-Paard-Batterij-42" @pytest.fixture def logged_in(app, client, paths): """A client with a configured admin password and an active session.""" app.extensions["voxip"].credentials.set_password(PASSWORD) page = client.get("/login") token = csrf_from(page) resp = client.post("/login", data={"password": PASSWORD, "csrf_token": token}) assert resp.status_code == 302 and resp.headers["Location"].endswith("/") return client def token(client) -> str: return csrf_from(client.get("/"))