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})