Files
rose-ash/shared/tests/test_config.py
giles e8bc228c7f
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m37s
Rebrand sexp → sx across web platform (173 files)
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>
2026-03-01 11:06:57 +00:00

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