mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 03:56:43 +08:00
149 lines
5.0 KiB
Python
149 lines
5.0 KiB
Python
"""ConfigReloadMixin 配置变更事件处理器解析的回归测试(Issue #6329)。
|
|
|
|
ConfigReloadMixin 动态生成的事件处理器,必须能被事件总线解析回实例方法,
|
|
不能因 __name__ 与类上方法名不一致而被静默跳过。
|
|
"""
|
|
|
|
import inspect
|
|
|
|
import pytest
|
|
|
|
from app.runtime.events import Event, EventHandlerBinding, eventmanager
|
|
from app.runtime.reload import ConfigReloadMixin
|
|
from app.schemas import ConfigChangeEventData
|
|
from app.schemas.types import EventType
|
|
|
|
|
|
class _ReloadRecorder(ConfigReloadMixin):
|
|
"""测试用同步重载子类,记录重载调用次数。"""
|
|
|
|
CONFIG_WATCH = {"TEST_RELOAD_KEY"}
|
|
|
|
def __init__(self):
|
|
self.reload_count = 0
|
|
|
|
def on_config_changed(self):
|
|
self.reload_count += 1
|
|
|
|
|
|
class _AsyncReloadRecorder(ConfigReloadMixin):
|
|
"""测试用异步重载子类,记录重载调用次数。"""
|
|
|
|
CONFIG_WATCH = {"TEST_RELOAD_KEY"}
|
|
|
|
def __init__(self):
|
|
self.reload_count = 0
|
|
|
|
async def on_config_changed(self):
|
|
self.reload_count += 1
|
|
|
|
|
|
@pytest.fixture(params=[_ReloadRecorder, _AsyncReloadRecorder])
|
|
def recorder(request, monkeypatch):
|
|
"""将生成的处理器绑定到测试实例,并在测试后移除全局监听。"""
|
|
recorder_cls = request.param
|
|
instance = recorder_cls()
|
|
|
|
def resolver(owner_class):
|
|
if owner_class is recorder_cls:
|
|
return EventHandlerBinding(instance=instance, owner_name=owner_class.__name__)
|
|
return None
|
|
|
|
# 隔离真实解析器,仅绑定测试实例
|
|
monkeypatch.setattr(
|
|
eventmanager,
|
|
"_EventManager__handler_instance_resolvers",
|
|
{"test_reload": resolver},
|
|
)
|
|
yield instance
|
|
# 移除类定义时注册的全局监听,避免影响其余测试
|
|
eventmanager.remove_event_listener(
|
|
EventType.ConfigChanged, recorder_cls.handle_config_changed
|
|
)
|
|
|
|
|
|
def _build_event(keys):
|
|
"""构造携带指定配置键的 ConfigChanged 事件。"""
|
|
return Event(EventType.ConfigChanged, ConfigChangeEventData(key=set(keys)))
|
|
|
|
|
|
async def _dispatch(instance, event):
|
|
"""按处理器类型走事件总线的同步或异步调用路径。"""
|
|
handler = instance.__class__.handle_config_changed
|
|
if inspect.iscoroutinefunction(handler):
|
|
await eventmanager._EventManager__invoke_handler_by_type_async(handler, event)
|
|
else:
|
|
eventmanager._EventManager__invoke_handler_by_type_sync(handler, event)
|
|
|
|
|
|
def test_generated_handler_exposes_method_name(recorder):
|
|
"""生成的处理器 __name__ 必须与类上的方法名一致,保证事件总线能解析到实例方法。"""
|
|
handler = recorder.__class__.handle_config_changed
|
|
assert handler.__name__ == "handle_config_changed"
|
|
assert handler.__qualname__ == f"{recorder.__class__.__name__}.handle_config_changed"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_config_changed_event_triggers_reload(recorder):
|
|
"""命中 CONFIG_WATCH 的配置变更事件必须触发子类重载逻辑。"""
|
|
await _dispatch(recorder, _build_event({"TEST_RELOAD_KEY"}))
|
|
assert recorder.reload_count == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unrelated_config_key_skips_reload(recorder):
|
|
"""未监听的配置键变更不得触发重载。"""
|
|
await _dispatch(recorder, _build_event({"OTHER_KEY"}))
|
|
assert recorder.reload_count == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_falls_back_to_qualname_when_name_mismatched(monkeypatch):
|
|
"""处理器 __name__ 与类上方法名不一致时,事件总线应回退 __qualname__ 末段解析。"""
|
|
instance = _ReloadRecorder()
|
|
|
|
def wrapper(event): # pylint: disable=unused-argument
|
|
"""模拟只同步了 __qualname__ 的动态处理器。"""
|
|
instance.on_config_changed()
|
|
|
|
wrapper.__module__ = _ReloadRecorder.__module__
|
|
wrapper.__qualname__ = f"{_ReloadRecorder.__name__}.handle_config_changed"
|
|
|
|
def resolver(owner_class):
|
|
if owner_class is _ReloadRecorder:
|
|
return EventHandlerBinding(instance=instance, owner_name=owner_class.__name__)
|
|
return None
|
|
|
|
monkeypatch.setattr(
|
|
eventmanager,
|
|
"_EventManager__handler_instance_resolvers",
|
|
{"test_reload": resolver},
|
|
)
|
|
|
|
await eventmanager._EventManager__invoke_handler_by_type_async(
|
|
wrapper, _build_event({"TEST_RELOAD_KEY"})
|
|
)
|
|
|
|
assert wrapper.__name__ == "wrapper"
|
|
assert instance.reload_count == 1
|
|
|
|
|
|
def test_externally_managed_reload_class_does_not_register_listener(monkeypatch):
|
|
"""外部统一管理配置生命周期时,Mixin 保留重载能力但不重复绑定事件。"""
|
|
registrations = []
|
|
monkeypatch.setattr(
|
|
eventmanager,
|
|
"add_event_listener",
|
|
lambda *args, **kwargs: registrations.append((args, kwargs)),
|
|
)
|
|
|
|
class _ExternallyManagedReloadRecorder(ConfigReloadMixin):
|
|
CONFIG_RELOAD_MANAGED_EXTERNALLY = True
|
|
CONFIG_WATCH = {"TEST_RELOAD_KEY"}
|
|
|
|
def on_config_changed(self):
|
|
pass
|
|
|
|
assert registrations == []
|
|
assert "handle_config_changed" not in _ExternallyManagedReloadRecorder.__dict__
|