Rename all sexp directories, files, identifiers, and references to sx. artdag/ excluded (separate media processing DSL). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
153 lines
5.0 KiB
Python
153 lines
5.0 KiB
Python
"""Tests for config freeze/readonly enforcement."""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import os
|
|
import tempfile
|
|
from types import MappingProxyType
|
|
|
|
import pytest
|
|
|
|
from shared.config import _freeze
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _freeze
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestFreeze:
|
|
def test_freezes_dict(self):
|
|
result = _freeze({"a": 1, "b": 2})
|
|
assert isinstance(result, MappingProxyType)
|
|
assert result["a"] == 1
|
|
|
|
def test_frozen_dict_is_immutable(self):
|
|
result = _freeze({"a": 1})
|
|
with pytest.raises(TypeError):
|
|
result["a"] = 2
|
|
with pytest.raises(TypeError):
|
|
result["new"] = 3
|
|
|
|
def test_freezes_list_to_tuple(self):
|
|
result = _freeze([1, 2, 3])
|
|
assert isinstance(result, tuple)
|
|
assert result == (1, 2, 3)
|
|
|
|
def test_freezes_set_to_frozenset(self):
|
|
result = _freeze({1, 2, 3})
|
|
assert isinstance(result, frozenset)
|
|
assert result == frozenset({1, 2, 3})
|
|
|
|
def test_freezes_nested_dict(self):
|
|
result = _freeze({"a": {"b": {"c": 1}}})
|
|
assert isinstance(result, MappingProxyType)
|
|
assert isinstance(result["a"], MappingProxyType)
|
|
assert isinstance(result["a"]["b"], MappingProxyType)
|
|
assert result["a"]["b"]["c"] == 1
|
|
|
|
def test_freezes_dict_with_list(self):
|
|
result = _freeze({"items": [1, 2, 3]})
|
|
assert isinstance(result["items"], tuple)
|
|
|
|
def test_freezes_list_of_dicts(self):
|
|
result = _freeze([{"a": 1}, {"b": 2}])
|
|
assert isinstance(result, tuple)
|
|
assert isinstance(result[0], MappingProxyType)
|
|
|
|
def test_preserves_scalars(self):
|
|
assert _freeze(42) == 42
|
|
assert _freeze("hello") == "hello"
|
|
assert _freeze(3.14) == 3.14
|
|
assert _freeze(True) is True
|
|
assert _freeze(None) is None
|
|
|
|
def test_freezes_tuple_recursively(self):
|
|
result = _freeze(({"a": 1}, [2, 3]))
|
|
assert isinstance(result, tuple)
|
|
assert isinstance(result[0], MappingProxyType)
|
|
assert isinstance(result[1], tuple)
|
|
|
|
def test_complex_config_structure(self):
|
|
"""Simulates a real app-config.yaml structure."""
|
|
raw = {
|
|
"app_urls": {
|
|
"blog": "https://blog.rose-ash.com",
|
|
"market": "https://market.rose-ash.com",
|
|
},
|
|
"features": ["sx", "federation"],
|
|
"limits": {"max_upload": 10485760},
|
|
}
|
|
frozen = _freeze(raw)
|
|
assert frozen["app_urls"]["blog"] == "https://blog.rose-ash.com"
|
|
assert frozen["features"] == ("sx", "federation")
|
|
with pytest.raises(TypeError):
|
|
frozen["app_urls"]["blog"] = "changed"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# init_config / config / as_plain / pretty
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestConfigInit:
|
|
def test_init_and_read(self):
|
|
"""Test full init_config → config() → as_plain() → pretty() cycle."""
|
|
import shared.config as cfg
|
|
|
|
# Save original state
|
|
orig_frozen = cfg._data_frozen
|
|
orig_plain = cfg._data_plain
|
|
|
|
try:
|
|
# Reset state
|
|
cfg._data_frozen = None
|
|
cfg._data_plain = None
|
|
|
|
# Write a temp YAML file
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
|
|
f.write("app_urls:\n blog: https://blog.example.com\nport: 8001\n")
|
|
path = f.name
|
|
|
|
try:
|
|
asyncio.run(cfg.init_config(path, force=True))
|
|
|
|
c = cfg.config()
|
|
assert c["app_urls"]["blog"] == "https://blog.example.com"
|
|
assert c["port"] == 8001
|
|
assert isinstance(c, MappingProxyType)
|
|
|
|
plain = cfg.as_plain()
|
|
assert isinstance(plain, dict)
|
|
assert plain["port"] == 8001
|
|
# Modifying plain should not affect config
|
|
plain["port"] = 9999
|
|
assert cfg.config()["port"] == 8001
|
|
|
|
pretty_str = cfg.pretty()
|
|
assert "blog" in pretty_str
|
|
finally:
|
|
os.unlink(path)
|
|
finally:
|
|
# Restore original state
|
|
cfg._data_frozen = orig_frozen
|
|
cfg._data_plain = orig_plain
|
|
|
|
def test_config_raises_before_init(self):
|
|
import shared.config as cfg
|
|
orig = cfg._data_frozen
|
|
try:
|
|
cfg._data_frozen = None
|
|
with pytest.raises(RuntimeError, match="init_config"):
|
|
cfg.config()
|
|
finally:
|
|
cfg._data_frozen = orig
|
|
|
|
def test_file_not_found(self):
|
|
import shared.config as cfg
|
|
orig = cfg._data_frozen
|
|
try:
|
|
cfg._data_frozen = None
|
|
with pytest.raises(FileNotFoundError):
|
|
asyncio.run(cfg.init_config("/nonexistent/path.yaml", force=True))
|
|
finally:
|
|
cfg._data_frozen = orig
|