from __future__ import annotations from pathlib import Path from stat import S_IMODE import pytest from game_data_capture.config import ConfigError, ServerConfig, parse_config from game_data_capture.paths import ( PathConfigurationError, prepare_private_directories, prepare_private_file, resolve_runtime_paths, ) def test_config_uses_typed_defaults() -> None: config = parse_config({}) assert config.server == ServerConfig(host="127.0.0.1", port=8765) assert config.lifecycle.instance_name == "shd-capture" assert config.limits.max_queue_items == 64 @pytest.mark.parametrize("host", ["0.0.0.0", "192.168.1.2", "localhost", "example.test"]) def test_config_rejects_non_loopback_binding(host: str) -> None: with pytest.raises(ConfigError, match="loopback IP literal"): parse_config({"server": {"host": host}}) @pytest.mark.parametrize( ("document", "message"), [ ({"server": {"port": 80}}, "server.port"), ({"limits": {"max_queue_items": 257}}, "limits.max_queue_items"), ({"lifecycle": {"instance_name": "SHD Capture"}}, "lifecycle.instance_name"), ({"openai": {"api_key_env": "openai-key"}}, "openai.api_key_env"), ], ) def test_config_rejects_invalid_bounded_values(document: dict[str, object], message: str) -> None: with pytest.raises(ConfigError, match=message): parse_config(document) def test_xdg_paths_are_resolved_without_writing(tmp_path: Path) -> None: root = tmp_path / "xdg" paths = resolve_runtime_paths( { "HOME": str(tmp_path / "home"), "XDG_CONFIG_HOME": str(root / "config"), "XDG_DATA_HOME": str(root / "data"), "XDG_CACHE_HOME": str(root / "cache"), "XDG_RUNTIME_DIR": str(root / "runtime"), } ) assert paths.runtime_dir == root / "runtime" / "shd-capture" assert not root.exists() def test_xdg_paths_reject_relative_runtime_path() -> None: with pytest.raises(PathConfigurationError, match="XDG runtime"): resolve_runtime_paths({"HOME": "/tmp/home", "XDG_RUNTIME_DIR": "relative"}) def test_runtime_directories_and_lock_file_are_owner_only(tmp_path: Path) -> None: paths = resolve_runtime_paths( { "HOME": str(tmp_path), "XDG_CONFIG_HOME": str(tmp_path / "config"), "XDG_DATA_HOME": str(tmp_path / "data"), "XDG_CACHE_HOME": str(tmp_path / "cache"), "XDG_RUNTIME_DIR": str(tmp_path / "runtime"), } ) prepare_private_directories(paths) prepare_private_file(paths.instance_lock_file) assert S_IMODE(paths.runtime_dir.stat().st_mode) == 0o700 assert S_IMODE(paths.instance_lock_file.stat().st_mode) == 0o600