mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-01 04:47:03 +08:00
fix(ci): restore architecture gate parity
This commit is contained in:
@@ -101,6 +101,24 @@ MediaSourceQuery = Annotated[
|
||||
]
|
||||
|
||||
|
||||
def _get_search_result_source(obj: Union[_SchemaMediaInfo, _SchemaMediaPerson, dict]) -> Any:
|
||||
"""读取影视、人物或音乐搜索结果中的媒体来源标识。"""
|
||||
if isinstance(obj, dict):
|
||||
return obj.get("media_source") or obj.get("source")
|
||||
return getattr(obj, "media_source", None) or getattr(obj, "source", None)
|
||||
|
||||
|
||||
def _serialize_search_result(obj: Any) -> dict:
|
||||
"""将域对象或 Pydantic 人物对象转换为统一搜索响应字典。"""
|
||||
if isinstance(obj, dict):
|
||||
return obj
|
||||
if hasattr(obj, "to_dict"):
|
||||
return obj.to_dict()
|
||||
if hasattr(obj, "model_dump"):
|
||||
return obj.model_dump()
|
||||
return {}
|
||||
|
||||
|
||||
def _is_valid_source_media_id(
|
||||
media_source: Optional[MediaSource],
|
||||
media_id: str,
|
||||
@@ -301,24 +319,6 @@ async def search(
|
||||
:return: 搜索结果列表
|
||||
"""
|
||||
|
||||
def __get_source(obj: Union[_SchemaMediaInfo, _SchemaMediaPerson, dict]):
|
||||
"""
|
||||
获取对象属性
|
||||
"""
|
||||
if isinstance(obj, dict):
|
||||
return obj.get("media_source") or obj.get("source")
|
||||
return getattr(obj, "media_source", None) or getattr(obj, "source", None)
|
||||
|
||||
def __serialize_search_result(obj: Any) -> dict:
|
||||
"""将域对象或 Pydantic 人物对象转换为统一搜索响应字典。"""
|
||||
if isinstance(obj, dict):
|
||||
return obj
|
||||
if hasattr(obj, "to_dict"):
|
||||
return obj.to_dict()
|
||||
if hasattr(obj, "model_dump"):
|
||||
return obj.model_dump()
|
||||
return {}
|
||||
|
||||
# 直接函数调用也可能绕过 FastAPI/Pydantic,仅在该测试与内部兼容边界补一次规范化。
|
||||
selected_sources = (
|
||||
media_source
|
||||
@@ -332,7 +332,7 @@ async def search(
|
||||
is_music = type == "music" or any(is_music_media_source(source) for source in selected_sources)
|
||||
if type == "person":
|
||||
persons = await media_chain.async_search_persons(name=title, media_source=source_selection)
|
||||
result = [__serialize_search_result(person) for person in persons or []]
|
||||
result = [_serialize_search_result(person) for person in persons or []]
|
||||
elif is_music:
|
||||
music_types = (music_type,) if music_type else (MUSIC_ENTITY_RECORDING, MUSIC_ENTITY_ALBUM)
|
||||
filtered_music_results = await media_chain.async_search_music(
|
||||
@@ -350,7 +350,7 @@ async def search(
|
||||
result = [collection.to_dict() for collection in collections] if collections else []
|
||||
else: # person
|
||||
persons = await media_chain.async_search_persons(name=title, media_source=source_selection)
|
||||
result = [__serialize_search_result(person) for person in persons or []]
|
||||
result = [_serialize_search_result(person) for person in persons or []]
|
||||
|
||||
if not result:
|
||||
return []
|
||||
@@ -360,7 +360,7 @@ async def search(
|
||||
setting_order = search_source.split(",") if search_source else []
|
||||
sort_order = {source: index for index, source in enumerate(setting_order)}
|
||||
|
||||
sorted_result = sorted(result, key=lambda x: sort_order.get(__get_source(x), 4))
|
||||
sorted_result = sorted(result, key=lambda x: sort_order.get(_get_search_result_source(x), 4))
|
||||
return sorted_result[(page - 1) * count : page * count]
|
||||
|
||||
|
||||
|
||||
@@ -1245,7 +1245,7 @@
|
||||
"owner": "moviepilot-api",
|
||||
"path": "/api/v1/media/search",
|
||||
"reason": "Executable through moviepilot_api; exact inputs are generated into MCP tools/list and SKILL.md.",
|
||||
"summary": "搜索媒体/人物信息",
|
||||
"summary": "搜索媒体/人物/艺术家信息",
|
||||
"tags": [
|
||||
"media"
|
||||
]
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
| `GET` | `/api/v1/media/recognize_file` | media | `gateway` | media.recognize_file | 识别媒体信息(文件) |
|
||||
| `GET` | `/api/v1/media/recognize_file2` | media | `alternate-auth-duplicate` | moviepilot-api | 识别文件媒体信息(API_TOKEN) |
|
||||
| `POST` | `/api/v1/media/scrape/{storage}` | media | `gateway` | media.scrape | 刮削媒体信息 |
|
||||
| `GET` | `/api/v1/media/search` | media | `gateway` | media.person.search, media.search | 搜索媒体/人物信息 |
|
||||
| `GET` | `/api/v1/media/search` | media | `gateway` | media.person.search, media.search | 搜索媒体/人物/艺术家信息 |
|
||||
| `GET` | `/api/v1/media/seasons` | media | `gateway` | media.seasons | 查询媒体季信息 |
|
||||
| `GET` | `/api/v1/media/source` | media | `gateway` | media.sources | 获取媒体数据源 |
|
||||
| `GET` | `/api/v1/media/{media_id}` | media | `gateway` | media.detail | 查询媒体详情 |
|
||||
|
||||
@@ -18,7 +18,7 @@ from app.api.endpoints.music import (
|
||||
from app.domain.context import MusicAlbumInfo, MusicArtistInfo, MusicInfo, MusicRelease
|
||||
from app.schemas.mediaserver import ExistMediaInfo
|
||||
from app.schemas.music import MusicLibraryStatusRequest, MusicRecognizeRequest
|
||||
from app.schemas.types import MediaSource, MediaType
|
||||
from app.schemas.types import MUSIC_ENTITY_ALBUM, MUSIC_ENTITY_RECORDING, MediaSource, MediaType
|
||||
|
||||
|
||||
def test_music_routes_are_registered():
|
||||
@@ -68,11 +68,11 @@ def test_music_routes_are_registered():
|
||||
|
||||
|
||||
def test_media_search_routes_music_through_common_catalog_entry():
|
||||
"""音乐与影视应调用同一媒体搜索入口,仅传入不同的媒体类型。"""
|
||||
"""音乐搜索应通过统一媒体链路调用音乐搜索入口。"""
|
||||
|
||||
chain = Mock()
|
||||
chain.async_search = AsyncMock(
|
||||
return_value=(None, [
|
||||
chain.async_search_music = AsyncMock(
|
||||
return_value=[
|
||||
MusicInfo(
|
||||
media_source="musicbrainz",
|
||||
media_id="recording-1",
|
||||
@@ -82,7 +82,7 @@ def test_media_search_routes_music_through_common_catalog_entry():
|
||||
release_date="2003-07-31",
|
||||
category="Album / Studio",
|
||||
)
|
||||
])
|
||||
]
|
||||
)
|
||||
|
||||
with (
|
||||
@@ -101,14 +101,19 @@ def test_media_search_routes_music_through_common_catalog_entry():
|
||||
assert result[0]["media_id"] == "recording-1"
|
||||
assert result[0]["music_type"] == "recording"
|
||||
assert result[0]["title"] == "晴天"
|
||||
chain.async_search.assert_awaited_once_with(title="晴天", limit=30, mtype=MediaType.MUSIC, media_source=None)
|
||||
chain.async_search_music.assert_awaited_once_with(
|
||||
query="晴天",
|
||||
limit=30,
|
||||
media_source=None,
|
||||
music_types=(MUSIC_ENTITY_RECORDING, MUSIC_ENTITY_ALBUM),
|
||||
)
|
||||
media_chain.assert_called_once()
|
||||
|
||||
|
||||
def test_media_search_forwards_explicit_music_source():
|
||||
"""统一音乐搜索应把显式选择的可扩展音乐源转发给 MediaChain。"""
|
||||
chain = Mock()
|
||||
chain.async_search = AsyncMock(return_value=(None, []))
|
||||
chain.async_search_music = AsyncMock(return_value=[])
|
||||
|
||||
with patch.object(media_endpoints, "MediaChain", return_value=chain):
|
||||
result = asyncio.run(
|
||||
@@ -122,11 +127,11 @@ def test_media_search_forwards_explicit_music_source():
|
||||
)
|
||||
|
||||
assert result == []
|
||||
chain.async_search.assert_awaited_once_with(
|
||||
title="Coldplay",
|
||||
mtype=MediaType.MUSIC,
|
||||
chain.async_search_music.assert_awaited_once_with(
|
||||
query="Coldplay",
|
||||
limit=20,
|
||||
media_source=(MediaSource.TheAudioDB,),
|
||||
music_types=(MUSIC_ENTITY_RECORDING, MUSIC_ENTITY_ALBUM),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user