Files
daynivo-kiosk-os/tests/test_agent.py
T
rothmanandClaude Opus 5.5 6733aacb6e
CI / test-bookworm (push) Successful in 29s
CI / test-latest (push) Successful in 33s
Rename the Python package to daynivo_kiosk
src/voxip_kiosk -> src/daynivo_kiosk, with every import, the launchers,
install.sh, the Makefile and the manager's gunicorn app path. An upgrade
replaces /usr/lib/voxip-kiosk/python as a whole, so the old package
disappears; tested by upgrading an installation of the previous commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:52:39 +01:00

205 lines
6.3 KiB
Python

import json
import pytest
from daynivo_kiosk.agent import Agent, HUNG_RELOAD_AFTER, HUNG_RESTART_AFTER
from daynivo_kiosk.cdp import CDPError
from daynivo_kiosk.config import save_config
class Clock:
def __init__(self):
self.t = 1000.0
def __call__(self):
return self.t
class BrowserStub:
def __init__(self, url):
self.url = url
self.navigations = []
self.reloads = 0
self.closed = False
self.responsive_answer = True
self.down = False
def main_page(self):
if self.down:
raise CDPError("down")
return {"url": self.url, "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/1"}
def new_page(self, url):
self.url = url
def navigate(self, url):
self.navigations.append(url)
self.url = url
def reload(self, ignore_cache=False):
self.reloads += 1
def responsive(self, ws=None, timeout=5):
return self.responsive_answer
def close_browser(self):
self.closed = True
class Probe:
def __init__(self):
self.ok = True
self.reason = ""
self.calls = 0
def __call__(self, url):
self.calls += 1
return self.ok, self.reason
@pytest.fixture
def env(paths):
paths.admin_file.write_text("{}") # setup finished
clock = Clock()
probe = Probe()
start = f"file://{paths.www_dir}/start.html?next=x"
browser = BrowserStub(start)
agent = Agent(paths, cdp=browser, probe_fn=probe, clock=clock, wall=clock, hostname=lambda: "kiosk",
addresses=lambda: {"eth0": ["192.168.1.20/24"], "wlan0": ["fe80::1/64"]})
return agent, browser, probe, clock, paths
def test_navigates_from_start_page_when_reachable(env):
agent, browser, probe, clock, paths = env
agent.tick()
assert browser.navigations == ["https://schermen-ijsselflat.voxip.nl"]
state = json.loads(paths.agent_state_file.read_text())
assert state["page"] == "start" and state["target_reachable"] is True
def test_offline_page_when_unreachable_then_recovers(env):
agent, browser, probe, clock, _ = env
probe.ok, probe.reason = False, "Naam niet gevonden (DNS)"
agent.tick()
assert "offline.html" in browser.url and "reason=Naam+niet+gevonden" in browser.url
navs = len(browser.navigations)
clock.t += 5
agent.tick() # still offline: no re-navigation (probe cached / page already offline)
assert len(browser.navigations) == navs
probe.ok = True
clock.t += 11
agent.tick()
assert browser.url == "https://schermen-ijsselflat.voxip.nl"
def test_chromium_error_page_is_replaced(env):
agent, browser, probe, clock, _ = env
browser.url = "chrome-error://chromewebdata/"
probe.ok = False
agent.tick()
assert "offline.html" in browser.url
def test_running_page_is_left_alone_when_the_server_is_unreachable(env):
"""Pages with their own offline cache (e.g. Daynivo displays) must keep running."""
agent, browser, probe, clock, _ = env
browser.url = "https://schermen-ijsselflat.voxip.nl"
agent.tick()
probe.ok = False
for _ in range(20):
clock.t += 61
agent.tick()
assert browser.navigations == []
assert browser.reloads == 0
def test_url_change_navigates_without_restart(env):
agent, browser, probe, clock, paths = env
browser.url = "https://schermen-ijsselflat.voxip.nl"
agent.tick()
save_config(paths.config_file, {"url": "https://nieuw.example.nl/scherm"})
agent.tick()
assert browser.navigations == ["https://nieuw.example.nl/scherm"]
assert not browser.closed
def test_refresh_interval_skips_when_unreachable(env):
agent, browser, probe, clock, paths = env
save_config(paths.config_file, {"refresh_interval": 60})
browser.url = "https://schermen-ijsselflat.voxip.nl"
agent.tick()
clock.t += 61
agent.tick()
assert browser.reloads == 1
probe.ok = False
clock.t += 61
agent.tick()
assert browser.reloads == 1
assert "overgeslagen" in agent.state["last_error"]["message"]
def test_hung_renderer_reload_then_browser_restart(env):
agent, browser, probe, clock, _ = env
browser.url = "https://schermen-ijsselflat.voxip.nl"
browser.responsive_answer = False
for _ in range(HUNG_RELOAD_AFTER):
agent.tick()
assert browser.reloads == 1 and not browser.closed
for _ in range(HUNG_RESTART_AFTER - HUNG_RELOAD_AFTER):
agent.tick()
assert browser.closed
def test_setup_page_until_password_exists(env):
agent, browser, probe, clock, paths = env
paths.admin_file.unlink()
paths.setup_code_file.write_text("ABCD-EFGH\n")
agent.tick()
assert "setup.html" in browser.url
assert "code=ABCD-EFGH" in browser.url and "ips=192.168.1.20" in browser.url and "port=8080" in browser.url
agent.tick()
assert len(browser.navigations) == 1 # no flicker: setup page is not reloaded every tick
paths.admin_file.write_text("{}")
agent.tick()
assert browser.url == "https://schermen-ijsselflat.voxip.nl"
def test_devtools_down_is_reported(env):
agent, browser, probe, clock, paths = env
browser.down = True
agent.tick()
assert json.loads(paths.agent_state_file.read_text())["browser"] == "unreachable"
def test_offline_page_never_receives_the_token(env, paths):
agent, browser, probe, _, _ = env
save_config(paths.config_file, {"url": "https://e.org/scherm?token=geheim123456"})
probe.ok, probe.reason = False, "Time-out"
agent.tick()
assert "offline.html" in browser.url
assert "geheim" not in browser.url and "3456" in browser.url
def test_offline_page_when_the_network_link_is_gone_then_back(env):
agent, browser, probe, clock, _ = env
network = {"eth0": ["192.168.1.20/24"]}
agent.addresses = lambda: dict(network)
browser.url = "https://schermen-ijsselflat.voxip.nl"
agent.tick()
assert browser.navigations == []
network.clear() # cable out
probe.ok = False
clock.t += 5
agent.tick()
assert "offline.html" in browser.url and "netwerkkabel" in browser.url
clock.t += 5
agent.tick()
assert len(browser.navigations) == 1 # no re-navigation while it stays down
network["eth0"] = ["192.168.1.20/24"] # cable back in
probe.ok = True
clock.t += 11
agent.tick()
assert browser.url == "https://schermen-ijsselflat.voxip.nl"