Files
MoviePilot/tests/test_chain_rate_limit.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

129 lines
4.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Chain 本地限流行为测试。
"""
# 可选下载器模块必须在导入 Chain 前完成隔离。
# ruff: noqa: E402
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()