mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 03:56:43 +08:00
130 lines
3.8 KiB
Python
130 lines
3.8 KiB
Python
"""用户配置快照与异步写入合同测试。"""
|
|
|
|
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
|