Chromium kiosk without a desktop for the Raspberry Pi 3, a Flask web interface on port 8080 (kiosk, system and network management), a restricted root helper, network changes with automatic rollback, a healthcheck timer, install/update/uninstall scripts and a pi-gen image build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
3.6 KiB
Python
96 lines
3.6 KiB
Python
import json
|
|
import os
|
|
import stat
|
|
|
|
import pytest
|
|
|
|
from voxip_kiosk import config as c
|
|
|
|
|
|
def test_default_file_matches_defaults(paths):
|
|
assert c.load_config(paths.config_file) == c.DEFAULTS
|
|
# the default config file itself must be valid
|
|
c.validate(json.loads(paths.config_file.read_text()))
|
|
|
|
|
|
def test_missing_file_gives_defaults(tmp_path):
|
|
assert c.load_config(tmp_path / "nope.json") == c.DEFAULTS
|
|
|
|
|
|
def test_invalid_values_fall_back_per_key(paths, caplog):
|
|
paths.config_file.write_text(json.dumps({"url": "http://insecure", "rotation": 45, "device_name": "Hal", "zoom": "x"}))
|
|
conf = c.load_config(paths.config_file)
|
|
assert conf["url"] == c.DEFAULTS["url"]
|
|
assert conf["rotation"] == 0
|
|
assert conf["zoom"] == 1.0
|
|
assert conf["device_name"] == "Hal"
|
|
assert "Ongeldige waarde" in caplog.text
|
|
|
|
|
|
def test_corrupt_file_uses_backup(paths):
|
|
c.save_config(paths.config_file, {"device_name": "Eerste"})
|
|
c.save_config(paths.config_file, {"device_name": "Tweede"})
|
|
paths.config_file.write_text("{ kapot")
|
|
assert c.load_config(paths.config_file)["device_name"] == "Eerste"
|
|
|
|
|
|
def test_save_validates_everything_before_writing(paths):
|
|
before = paths.config_file.read_text()
|
|
with pytest.raises(c.ConfigError) as exc:
|
|
c.save_config(paths.config_file, {"url": "http://x", "refresh_interval": 5, "device_name": "OK"})
|
|
assert set(exc.value.errors) == {"url", "refresh_interval"}
|
|
assert paths.config_file.read_text() == before
|
|
|
|
|
|
def test_save_rejects_unknown_keys(paths):
|
|
with pytest.raises(c.ConfigError, match="Onbekende"):
|
|
c.save_config(paths.config_file, {"shell": "rm -rf /"})
|
|
|
|
|
|
def test_save_preserves_unknown_existing_keys_and_writes_backup(paths):
|
|
data = json.loads(paths.config_file.read_text())
|
|
data["future_option"] = {"x": 1}
|
|
paths.config_file.write_text(json.dumps(data))
|
|
new = c.save_config(paths.config_file, {"refresh_interval": "300", "screen_always_on": False})
|
|
assert new["refresh_interval"] == 300 and new["screen_always_on"] is False
|
|
stored = json.loads(paths.config_file.read_text())
|
|
assert stored["future_option"] == {"x": 1}
|
|
assert json.loads(paths.config_file.with_suffix(".json.bak").read_text())["refresh_interval"] == 0
|
|
|
|
|
|
def test_atomic_write_leaves_no_temp_files_and_sets_mode(paths):
|
|
c.save_config(paths.config_file, {"device_name": "X"})
|
|
leftovers = [p.name for p in paths.config_dir.iterdir() if p.name.endswith(".tmp")]
|
|
assert leftovers == []
|
|
assert stat.S_IMODE(os.stat(paths.config_file).st_mode) == 0o640
|
|
|
|
|
|
def test_atomic_write_failure_keeps_old_file(paths, monkeypatch):
|
|
before = paths.config_file.read_text()
|
|
|
|
def boom(*a, **k):
|
|
raise OSError("schijf vol")
|
|
|
|
monkeypatch.setattr(c.os, "replace", boom)
|
|
with pytest.raises(OSError):
|
|
c.atomic_write_json(paths.config_file, {"a": 1})
|
|
assert paths.config_file.read_text() == before
|
|
assert not [p for p in paths.config_dir.iterdir() if p.name.endswith(".tmp")]
|
|
|
|
|
|
@pytest.mark.parametrize("key, value, expected", [
|
|
("refresh_interval", 0, 0), ("refresh_interval", "60", 60), ("rotation", "270", 270),
|
|
("zoom", "1.25", 1.25), ("admin_port", 8443, 8443), ("browser_profile", "ephemeral", "ephemeral"),
|
|
])
|
|
def test_validate_coerces(key, value, expected):
|
|
assert c.validate({key: value})[key] == expected
|
|
|
|
|
|
@pytest.mark.parametrize("key, value", [
|
|
("refresh_interval", 10), ("refresh_interval", -1), ("rotation", 45), ("zoom", 9), ("admin_port", 80),
|
|
("admin_port", True), ("screen_always_on", "yes"), ("browser_profile", "disk"), ("screen_off_minutes", 0),
|
|
])
|
|
def test_validate_rejects(key, value):
|
|
with pytest.raises(c.ConfigError):
|
|
c.validate({key: value})
|