Files
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

96 lines
3.7 KiB
Python

import json
import os
import stat
import pytest
from daynivo_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})