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

162 lines
5.1 KiB
Python

"""
系统内部用户消息分发与异步立即发送的上下文传播测试。
"""
# 可选三方模块必须在导入消息链前完成隔离。
# ruff: noqa: E402
import asyncio
from unittest.mock import patch
import pytest
from app.testing.bootstrap import ensure_optional_stub
# 仅在可选依赖缺失时补占位;已安装时保留真实模块,避免污染同一进程里导入真实类型的用例。
ensure_optional_stub("qbittorrentapi", TorrentFilesList=list)
ensure_optional_stub("transmission_rpc", File=object)
ensure_optional_stub("psutil")
from app.application.messaging.message import MessageQueueManager
from app.chain.message import MessageChain
from app.foundation.identity import (
SYSTEM_INTERNAL_USER_ID,
is_internal_user_id,
normalize_internal_user_id,
)
from app.runtime.correlation import correlation_scope, get_correlation_id
from app.schemas.message import Message
def _internal_message() -> Message:
"""
构造以系统内部用户为收件人的消息。
"""
return Message(
userid=SYSTEM_INTERNAL_USER_ID,
username="admin",
title="后台报告",
text="任务完成",
)
def test_internal_userid_identity_helpers() -> None:
"""
系统内部用户标识的判定与归一化。
"""
assert is_internal_user_id(SYSTEM_INTERNAL_USER_ID)
assert is_internal_user_id(" System ")
assert normalize_internal_user_id(SYSTEM_INTERNAL_USER_ID) is None
assert normalize_internal_user_id("10001") == "10001"
def test_post_message_normalizes_internal_userid_before_queueing() -> None:
"""
入队前应把系统内部用户归一化为空,不进入事件与队列载荷。
"""
chain = MessageChain()
message = _internal_message()
with patch("app.chain._messaging.MessageTemplateHelper.render", return_value=message), patch.object(
chain.messagehelper, "put"
), patch.object(chain.messageoper, "add"), patch.object(
chain.eventmanager, "send_event"
) as send_event, patch.object(
chain.user_repository,
"get_notification_settings",
return_value={"telegram_userid": "admin-1"},
), patch.object(
chain.messagequeue, "send_message"
) as send_message:
chain.post_message(message)
event_payload = send_event.call_args.kwargs["data"]
queued_message = send_message.call_args.kwargs["message"]
assert event_payload["userid"] is None
assert queued_message.userid is None
assert send_message.call_args.kwargs["immediately"] is False
def test_send_direct_message_normalizes_internal_userid() -> None:
"""
直接发送时同样归一化系统内部用户。
"""
chain = MessageChain()
with patch.object(chain, "run_module") as run_module:
chain.send_direct_message(_internal_message())
sent_message = run_module.call_args.kwargs["message"]
assert sent_message.userid is None
class _FakeLoop:
"""
同步模拟 asyncio 的 executor 提交,并记录提交时的上下文。
"""
def __init__(self, loop):
self.called = False
self.loop = loop
self.submission_correlation_id = None
def run_in_executor(self, _executor, func, *args):
"""
立即执行 worker 调用并返回已完成 Future。
"""
self.called = True
self.submission_correlation_id = get_correlation_id()
future = self.loop.create_future()
try:
func(*args)
# 捕获工作线程中的全部失败并回传主测试进行断言。
except BaseException as error: # pylint: disable=broad-exception-caught
future.set_exception(error)
else:
future.set_result(None)
return future
@pytest.mark.asyncio
async def test_async_send_message_uses_executor_for_immediate_send() -> None:
"""
异步立即发送不能在事件循环里直接执行同步渠道回调。
"""
manager = MessageQueueManager()
fake_loop = _FakeLoop(asyncio.get_running_loop())
observed = []
with patch(
"asyncio.get_running_loop",
return_value=fake_loop,
), patch.object(
manager,
"_send",
side_effect=lambda *_args, **_kwargs: observed.append(get_correlation_id()),
) as send:
with correlation_scope("message-request"):
await manager.async_send_message("payload", immediately=True)
assert fake_loop.called
assert fake_loop.submission_correlation_id is None
assert observed == ["message-request"]
send.assert_called_once_with("payload")
@pytest.mark.asyncio
async def test_async_send_message_preserves_call_context() -> None:
"""
异步立即发送的同步渠道回调应保留当前请求关联 ID。
"""
observed = []
manager = MessageQueueManager()
with patch.object(
manager,
"_send",
side_effect=lambda *_args, **_kwargs: observed.append(get_correlation_id()),
):
with correlation_scope("message-request"):
await manager.async_send_message("payload", immediately=True)
assert observed == ["message-request"]