mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 03:56:43 +08:00
- ChainBase 拆分为 RecognitionMixin/MessageProcessingMixin/NotificationMixin - TransferChain 拆分为 7 个功能 mixin(_mixins.py),SubscribeChain 音乐订阅域拆出 _music.py - 斜杠命令交互四件套收敛为 InteractionChainMixin 委托,会话管理器移至 application 层,chain 层不再 re-export - 模块基础类收敛到 app/modules/_base(notification/mediaserver 语义重命名) - 清理 app/chain/__init__.py 24 个未使用导入,修正 49 处测试 patch 目标到实际命名空间 - 兼容层 legacy 符号不再并入 __all__,根治 schemas 初始化反向拉起 application.transfer 的循环导入 - 修复 bangumi 集数为字符串时 set_bangumi_info 抛 TypeError - 新增重复代码等架构门禁测试;capability 清单校验排除下划线内部目录
147 lines
5.5 KiB
Python
147 lines
5.5 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""识别测试类请求的数据源选择回归测试。
|
||
|
||
识别测试(名称测试)前端只传请求级数据源 media_source,不携带 media_id;
|
||
该路径必须按名称在指定数据源内识别,而不是被"显式身份必须成对"规则拦截。
|
||
"""
|
||
import asyncio
|
||
from unittest.mock import AsyncMock, Mock, patch
|
||
|
||
from app.chain import ChainBase
|
||
from app.domain.context import MediaInfo
|
||
from app.domain.metainfo import MetaInfo
|
||
from app.schemas.types import MediaSource, MediaType
|
||
|
||
# 用户反馈的识别测试失败样例
|
||
FAILING_TITLE = "[ANI] 關於我轉生變成史萊姆這檔事 第四季 - 90 [1080P][Baha][WEB-DL][AAC AVC][CHT].mp4"
|
||
|
||
|
||
def _tmdb_media() -> MediaInfo:
|
||
"""构造带 TMDB 身份的识别结果。"""
|
||
return MediaInfo(
|
||
media_source=MediaSource.TMDB,
|
||
media_id="120089",
|
||
tmdb_id=120089,
|
||
title="关于我转生变成史莱姆这档事",
|
||
type=MediaType.TV,
|
||
)
|
||
|
||
|
||
def test_recognize_media_with_source_only_uses_name_search():
|
||
"""仅传 media_source 时应按名称在指定数据源识别,不得直接拒绝。"""
|
||
chain = ChainBase()
|
||
meta = MetaInfo(FAILING_TITLE)
|
||
captured = {}
|
||
|
||
def fake_native(module_kwargs, cache):
|
||
captured.update(module_kwargs)
|
||
return _tmdb_media()
|
||
|
||
with patch.object(chain, "_run_native_media_recognize", side_effect=fake_native), \
|
||
patch.object(chain, "_supplement_media_recognize", side_effect=lambda **kw: kw["mediainfo"]), \
|
||
patch("app.chain._recognition.MoviePilotServerHelper"):
|
||
result = chain.recognize_media(meta=meta, media_source=MediaSource.TMDB, cache=False)
|
||
|
||
assert result is not None
|
||
assert result.tmdb_id == 120089
|
||
# 数据源约束透传到模块层,且无显式 ID
|
||
assert captured["media_source"] == MediaSource.TMDB
|
||
assert captured["media_id"] is None
|
||
# 名称识别应沿用 meta 推断的类型
|
||
assert captured["mtype"] == MediaType.TV
|
||
|
||
|
||
def test_async_recognize_media_with_source_only_uses_name_search():
|
||
"""异步入口与同步入口保持同一语义。"""
|
||
chain = ChainBase()
|
||
meta = MetaInfo(FAILING_TITLE)
|
||
captured = {}
|
||
|
||
async def fake_native(module_kwargs, cache):
|
||
captured.update(module_kwargs)
|
||
return _tmdb_media()
|
||
|
||
async def fake_supplement(**kwargs):
|
||
return kwargs["mediainfo"]
|
||
|
||
# 异步路径上报共享识别为协程,需要可 await 的桩
|
||
helper = Mock()
|
||
helper.async_report_recognize_share = AsyncMock()
|
||
|
||
with patch.object(chain, "_async_run_native_media_recognize", side_effect=fake_native), \
|
||
patch.object(chain, "_async_supplement_media_recognize", side_effect=fake_supplement), \
|
||
patch("app.chain._recognition.MoviePilotServerHelper", helper):
|
||
result = asyncio.run(
|
||
chain.async_recognize_media(meta=meta, media_source=MediaSource.TMDB, cache=False)
|
||
)
|
||
|
||
assert result is not None
|
||
assert result.tmdb_id == 120089
|
||
assert captured["media_source"] == MediaSource.TMDB
|
||
assert captured["media_id"] is None
|
||
|
||
|
||
def test_recognize_media_accepts_string_source_only():
|
||
"""第三方客户端可能传字符串数据源,应规范为枚举后按名称识别。"""
|
||
chain = ChainBase()
|
||
meta = MetaInfo(FAILING_TITLE)
|
||
captured = {}
|
||
|
||
def fake_native(module_kwargs, cache):
|
||
captured.update(module_kwargs)
|
||
return _tmdb_media()
|
||
|
||
with patch.object(chain, "_run_native_media_recognize", side_effect=fake_native), \
|
||
patch.object(chain, "_supplement_media_recognize", side_effect=lambda **kw: kw["mediainfo"]), \
|
||
patch("app.chain._recognition.MoviePilotServerHelper"):
|
||
result = chain.recognize_media(meta=meta, media_source="themoviedb", cache=False)
|
||
|
||
assert result is not None
|
||
assert captured["media_source"] == MediaSource.TMDB
|
||
|
||
|
||
def test_recognize_media_meta_identity_same_source_uses_id():
|
||
"""仅传数据源且 meta 自带同源身份(如 {tmdbid=})时应直接按身份识别。"""
|
||
chain = ChainBase()
|
||
meta = MetaInfo("空之境界 第五章 矛盾螺旋 (2008) {tmdbid=23155}")
|
||
captured = {}
|
||
|
||
def fake_native(module_kwargs, cache):
|
||
captured.update(module_kwargs)
|
||
return _tmdb_media()
|
||
|
||
with patch.object(chain, "_run_native_media_recognize", side_effect=fake_native), \
|
||
patch.object(chain, "_supplement_media_recognize", side_effect=lambda **kw: kw["mediainfo"]), \
|
||
patch("app.chain._recognition.MoviePilotServerHelper"):
|
||
result = chain.recognize_media(meta=meta, media_source=MediaSource.TMDB, cache=False)
|
||
|
||
assert result is not None
|
||
assert captured["media_source"] == MediaSource.TMDB
|
||
assert captured["media_id"] == "23155"
|
||
|
||
|
||
def test_recognize_media_media_id_without_source_still_rejected():
|
||
"""显式 media_id 缺少有效来源时仍应拒绝,成对约束不放宽。"""
|
||
chain = ChainBase()
|
||
native = Mock()
|
||
|
||
with patch.object(chain, "_run_native_media_recognize", native):
|
||
result = chain.recognize_media(meta=MetaInfo("任意标题"), media_id="12345")
|
||
|
||
assert result is None
|
||
native.assert_not_called()
|
||
|
||
|
||
def test_recognize_media_invalid_pair_still_rejected():
|
||
"""media_id 为 0 等无效值与来源组合仍应拒绝。"""
|
||
chain = ChainBase()
|
||
native = Mock()
|
||
|
||
with patch.object(chain, "_run_native_media_recognize", native):
|
||
result = chain.recognize_media(
|
||
meta=MetaInfo("任意标题"), media_source=MediaSource.TMDB, media_id="0"
|
||
)
|
||
|
||
assert result is None
|
||
native.assert_not_called()
|