mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-02 21:34:32 +08:00
test_chain_rate_limit.py and test_system_notification_dispatch.py installed a bare qbittorrentapi/transmission_rpc/psutil ModuleType into sys.modules at import time with sys.modules.setdefault. When either file is collected before a test that imports the real package in the same shard (for example tests/test_downloader_add_failure_message.py and its TorrentDictionary import), the placeholder wins and collection fails with "cannot import name ... (unknown location)". Any change to the test file set reshuffles the six CI shards and can surface this. Use app.testing.bootstrap.ensure_optional_stub, which keeps the real module when it is installed, and convert both unittest.TestCase files to pytest-native functions as the testing guide requires when touching a TestCase file. Record the resulting lower Ruff watermark. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
116 lines
4.1 KiB
Python
116 lines
4.1 KiB
Python
"""Chain 本地限流行为测试。"""
|
||
|
||
# ruff: noqa: E402 - 可选下载器模块必须在导入 Chain 前完成隔离。
|
||
|
||
from unittest.mock import Mock
|
||
|
||
import pytest
|
||
|
||
from app.testing.bootstrap import ensure_optional_stub
|
||
|
||
# 仅在可选依赖缺失时补占位;已安装时保留真实模块,避免污染同一进程里导入真实类型的用例。
|
||
ensure_optional_stub("qbittorrentapi", TorrentFilesList=list)
|
||
ensure_optional_stub("transmission_rpc", File=object)
|
||
|
||
from app.application.chain.context import ChainRuntimeContext
|
||
from app.chain.base import ChainBase
|
||
from app.runtime.extensions.module.dispatcher import ModuleInvocationDispatcher
|
||
from app.schemas.exception import RateLimitExceededException
|
||
|
||
|
||
class _LimitedModule:
|
||
"""模拟始终触发本地限流的宿主模块。"""
|
||
|
||
def get_name(self):
|
||
"""
|
||
返回测试模块名称。
|
||
"""
|
||
return "限流测试模块"
|
||
|
||
def get_priority(self):
|
||
"""
|
||
返回测试模块优先级。
|
||
"""
|
||
return 1
|
||
|
||
def limited_method(self, raise_exception: bool = False):
|
||
"""
|
||
模拟同步模块在本地限流期间跳过调用。
|
||
"""
|
||
raise RateLimitExceededException("[limited_method] 限流期间,跳过调用")
|
||
|
||
async def async_limited_method(self, raise_exception: bool = False):
|
||
"""
|
||
模拟异步模块在本地限流期间跳过调用。
|
||
"""
|
||
raise RateLimitExceededException("[async_limited_method] 限流期间,跳过调用")
|
||
|
||
|
||
@pytest.fixture
|
||
def chain() -> ChainBase:
|
||
"""构造隔离的 ChainBase,避免依赖真实模块和插件运行状态。"""
|
||
limited_module = _LimitedModule()
|
||
plugin_manager = Mock()
|
||
plugin_manager.get_plugin_modules.return_value = {}
|
||
module_manager = Mock()
|
||
module_manager.get_running_modules.return_value = [limited_module]
|
||
message_queue = Mock()
|
||
message_queue.bind.return_value = Mock()
|
||
return ChainBase(
|
||
ChainRuntimeContext(
|
||
module_manager=module_manager,
|
||
plugin_manager=plugin_manager,
|
||
event_manager=Mock(),
|
||
message_oper=Mock(),
|
||
message_helper=Mock(),
|
||
file_cache=Mock(),
|
||
async_file_cache=Mock(),
|
||
message_queue=message_queue,
|
||
module_dispatcher_factory=ModuleInvocationDispatcher,
|
||
site_repository=Mock(),
|
||
subscription_repository=Mock(),
|
||
subscription_mutation_scope=Mock(),
|
||
sync_subscription_mutation_scope=Mock(),
|
||
subscription_delete_scope=Mock(),
|
||
sync_subscription_delete_scope=Mock(),
|
||
subscription_completion_scope=Mock(),
|
||
rule_group_mutation_scope=Mock(),
|
||
site_reference_mutation_scope=Mock(),
|
||
download_history_repository=Mock(),
|
||
transfer_history_repository=Mock(),
|
||
transfer_admission_repository=Mock(),
|
||
transfer_execution_repository=Mock(),
|
||
media_server_repository=Mock(),
|
||
download_failure_repository=Mock(),
|
||
user_repository=Mock(),
|
||
)
|
||
)
|
||
|
||
|
||
def test_rate_limit_is_not_reported_as_system_error(chain: ChainBase) -> None:
|
||
"""本地限流跳过不应写入系统错误通知或事件。"""
|
||
result = chain.run_module("limited_method")
|
||
|
||
assert result is None
|
||
chain.messagehelper.put.assert_not_called()
|
||
chain.eventmanager.send_event.assert_not_called()
|
||
|
||
|
||
def test_rate_limit_can_still_be_raised_explicitly(chain: ChainBase) -> None:
|
||
"""调用方显式要求抛出异常时,限流异常应继续向上抛出。"""
|
||
with pytest.raises(RateLimitExceededException):
|
||
chain.run_module("limited_method", raise_exception=True)
|
||
|
||
chain.messagehelper.put.assert_not_called()
|
||
chain.eventmanager.send_event.assert_not_called()
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_async_rate_limit_is_not_reported_as_system_error(chain: ChainBase) -> None:
|
||
"""异步模块的本地限流跳过也不应触发系统错误路径。"""
|
||
result = await chain.async_run_module("async_limited_method")
|
||
|
||
assert result is None
|
||
chain.messagehelper.put.assert_not_called()
|
||
chain.eventmanager.send_event.assert_not_called()
|