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

289 lines
8.0 KiB
Python

"""
验证订阅搜索命令的接纳与执行参数。
"""
import asyncio
from types import SimpleNamespace
from unittest.mock import Mock
import pytest
from app.api.dependencies import subscription as subscription_dependencies
from app.application.subscription.delete import SubscribeDeletionCandidate
from app.application.subscription.search import (
SearchSubscriptionsCommand,
SubscribeSearchActor,
SubscriptionSearchSubmission,
)
from app.runtime.tasks import TaskRegistry
class _Repository:
"""
提供手工订阅搜索测试需要的归属和列表数据。
"""
def __init__(self, candidate=None, subscribe_ids=None):
"""
保存预设单条候选和批量编号。
"""
self.candidate = candidate
self.subscribe_ids = subscribe_ids or []
self.list_calls = []
self.type_calls = []
async def get_candidate(self, _subscribe_id):
"""
返回预设订阅候选。
"""
return self.candidate
async def list_search_ids(self, username, state, mtype=None):
"""
记录访问范围并返回预设编号。
"""
self.list_calls.append((username, state))
self.type_calls.append(mtype)
return self.subscribe_ids
def _candidate(username):
"""
构造只包含归属信息的订阅候选。
"""
return SubscribeDeletionCandidate(
subscribe_id=7,
username=username,
event_payload={},
)
def _submitter(calls):
"""
构造记录目标并返回稳定安排结果的异步提交器。
"""
async def submit(subscribe_ids, single):
"""
记录一次轻量入队请求。
"""
calls.append((subscribe_ids, single))
return SubscriptionSearchSubmission(
batch_ids=("batch-1",),
target_count=len(subscribe_ids),
queued_count=len(subscribe_ids),
ongoing_count=0,
single=single,
)
return submit
@pytest.mark.asyncio
async def test_superuser_search_all_queues_every_active_subscription():
"""
管理员搜索全部订阅时读取全局活动订阅并一次提交。
"""
submitted = []
repository = _Repository(subscribe_ids=[2, 5])
command = SearchSubscriptionsCommand(
repository=repository,
submit_search=_submitter(submitted),
)
result = await command.execute(
SubscribeSearchActor(username="admin", is_superuser=True)
)
assert result is not None
assert result.target_count == 2
assert repository.list_calls == [(None, "R")]
assert submitted == [((2, 5), False)]
@pytest.mark.asyncio
async def test_regular_user_search_all_schedules_only_owned_subscriptions():
"""
普通用户搜索全部时把归属订阅合并为一次后台批次。
"""
submitted = []
repository = _Repository(subscribe_ids=[2, 5])
command = SearchSubscriptionsCommand(
repository=repository,
submit_search=_submitter(submitted),
)
result = await command.execute(
SubscribeSearchActor(username="alice", is_superuser=False)
)
assert result is not None
assert repository.list_calls == [("alice", "R")]
assert submitted == [((2, 5), False)]
@pytest.mark.asyncio
async def test_search_all_forwards_media_type_to_target_repository():
"""
按媒体类型搜索时把范围交给订阅读取端口裁剪。
"""
submitted = []
repository = _Repository(subscribe_ids=[2, 5])
command = SearchSubscriptionsCommand(
repository=repository,
submit_search=_submitter(submitted),
)
result = await command.execute(
SubscribeSearchActor(username="admin", is_superuser=True),
mtype="电视剧",
)
assert result is not None
assert repository.list_calls == [(None, "R")]
assert repository.type_calls == ["电视剧"]
assert submitted == [((2, 5), False)]
@pytest.mark.asyncio
async def test_regular_user_search_all_with_no_targets_does_not_schedule():
"""
普通用户没有可搜索订阅时不创建空后台任务。
"""
submitted = []
command = SearchSubscriptionsCommand(
repository=_Repository(),
submit_search=_submitter(submitted),
)
result = await command.execute(
SubscribeSearchActor(username="alice", is_superuser=False)
)
assert result is not None
assert result.target_count == 0
assert result.batch_ids == ()
assert submitted == []
@pytest.mark.asyncio
async def test_targeted_search_rejects_missing_or_other_users_subscription():
"""
单条搜索不得泄漏订阅是否属于其他普通用户。
"""
submitted = []
command = SearchSubscriptionsCommand(
repository=_Repository(candidate=_candidate("bob")),
submit_search=_submitter(submitted),
)
assert await command.execute(
SubscribeSearchActor(username="alice", is_superuser=False),
subscribe_id=7,
) is None
assert submitted == []
@pytest.mark.asyncio
async def test_targeted_search_schedules_accessible_subscription():
"""
归属用户搜索单条订阅时提交历史兼容参数。
"""
submitted = []
command = SearchSubscriptionsCommand(
repository=_Repository(candidate=_candidate("alice")),
submit_search=_submitter(submitted),
)
result = await command.execute(
SubscribeSearchActor(username="alice", is_superuser=False),
subscribe_id=7,
)
assert result is not None
assert result.single is True
assert submitted == [((7,), True)]
def test_submitted_search_wakes_short_cycle_queue(monkeypatch):
"""
手工搜索入队后立即唤醒短周期恢复任务。
"""
calls = []
monkeypatch.setattr(
subscription_dependencies,
"start_scheduler_job",
lambda job_id, **kwargs: calls.append((job_id, kwargs)),
)
subscription_dependencies._resume_submitted_subscription_search((2, 5))
assert calls == [
(
"subscribe_search_queue",
{"limit": 2, "manual_sids": (2, 5)},
),
]
@pytest.mark.asyncio
async def test_search_dependency_persists_before_waking_background_worker():
"""
请求适配器先等待持久入队完成,再登记后台恢复任务。
"""
registry = TaskRegistry()
calls = []
def create_sync(function, *args, owner, **kwargs):
"""
同步执行测试函数,并返回已经完成的 Future。
"""
calls.append((function, args, owner, kwargs))
future = asyncio.get_running_loop().create_future()
result = function(*args, **kwargs) if owner.endswith("enqueue") else None
future.set_result(result)
return future
registry.create_sync = Mock(side_effect=create_sync)
repository = _Repository(subscribe_ids=[2, 5])
search_repository = SimpleNamespace(
enqueue=Mock(
return_value=SimpleNamespace(
active_batch_ids=("batch-new", "batch-existing"),
created_count=1,
coalesced_count=1,
)
)
)
runtime = SimpleNamespace(
subscription=SimpleNamespace(
repository=lambda _db: repository,
search_repository=search_repository,
),
)
command = subscription_dependencies.get_search_subscriptions_command(
task_registry=registry,
db=object(),
runtime=runtime,
)
result = await command.execute(
SubscribeSearchActor(username="alice", is_superuser=False)
)
assert result is not None
assert result.batch_ids == ("batch-new", "batch-existing")
assert result.queued_count == 1
assert result.ongoing_count == 1
assert calls[0] == (
search_repository.enqueue,
(),
"api.subscribe.search.enqueue",
{"subscription_ids": (2, 5), "source": "manual", "priority": 100},
)
assert calls[1] == (
subscription_dependencies._resume_submitted_subscription_search,
((2, 5),),
"api.subscribe.search.run",
{},
)