"""用户配置快照与异步写入合同测试。""" import asyncio import threading from types import SimpleNamespace import pytest from app.application.security.userconfig import UserConfigurationService from app.db.models.userconfig import UserConfig from app.db.oper.userconfig import UserConfigOper from app.foundation.singleton import Singleton class _ThreadDatabaseExecutor: """在线程中执行测试事务。""" async def run(self, operation): """执行并返回操作结果。""" return await asyncio.to_thread(operation) def _fresh_oper() -> UserConfigOper: """重置单例并显式加载用户配置快照。""" Singleton._instances.pop((UserConfigOper, (), frozenset()), None) oper = UserConfigOper() oper.load_snapshot() return oper def test_constructor_does_not_query_database(monkeypatch): """构造用户配置对象时不打开数据库会话。""" Singleton._instances.pop((UserConfigOper, (), frozenset()), None) monkeypatch.setattr( UserConfig, "list", lambda _db: pytest.fail("构造阶段不应查询数据库"), ) oper = UserConfigOper() with pytest.raises(RuntimeError, match="快照尚未加载"): oper.get("alice", "theme") def test_load_snapshot_publishes_complete_dictionary(monkeypatch): """重新加载期间读取方不会看到逐项构造的用户配置。""" Singleton._instances.pop((UserConfigOper, (), frozenset()), None) oper = UserConfigOper() monkeypatch.setattr( UserConfig, "list", lambda _db: [SimpleNamespace(username="alice", key="theme", value="old")], ) oper.load_snapshot() entered = threading.Event() release = threading.Event() def load_new_snapshot(_db): entered.set() release.wait(1) return [SimpleNamespace(username="alice", key="theme", value="new")] monkeypatch.setattr(UserConfig, "list", load_new_snapshot) thread = threading.Thread(target=oper.load_snapshot) thread.start() assert entered.wait(1) assert oper.get("alice", "theme") == "old" release.set() thread.join(1) assert thread.is_alive() is False assert oper.get("alice", "theme") == "new" @pytest.mark.asyncio async def test_async_write_uses_same_repository_rule(db) -> None: """异步入口提交后同步读取立即看到相同结果。""" oper = _fresh_oper() service = UserConfigurationService( oper, async_executor=_ThreadDatabaseExecutor(), ) await service.async_set("async-user", "theme", "dark") assert service.get("async-user", "theme") == "dark" assert UserConfig.get_by_key( db.session, username="async-user", key="theme", ).value == "dark" def test_existing_falsey_value_is_removed_from_db_but_kept_until_reload(db) -> None: """已有用户配置写入假值时删除记录,当前快照仍保留该假值直到重载。""" db.watermark(UserConfig) oper = _fresh_oper() oper.set("falsey-user", "enabled", True) oper.set("falsey-user", "enabled", False) assert UserConfig.get_by_key( db.session, username="falsey-user", key="enabled", ) is None assert oper.get("falsey-user", "enabled") is False oper.load_snapshot() assert oper.get("falsey-user", "enabled") is None def test_falsey_value_without_existing_row_is_persisted(db) -> None: """不存在的用户配置写入假值时保留记录,兼容历史写入规则。""" db.watermark(UserConfig) oper = _fresh_oper() oper.set("new-falsey-user", "enabled", False) persisted = UserConfig.get_by_key( db.session, username="new-falsey-user", key="enabled", ) assert persisted is not None assert persisted.value is False assert oper.get("new-falsey-user", "enabled") is False