fix(ci): restore architecture gate parity

This commit is contained in:
jxxghp
2026-09-10 09:45:58 +08:00
parent fcc83e6bbe
commit 0a43a307c4
4 changed files with 38 additions and 33 deletions

View File

@@ -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]

View File

@@ -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"
]

View File

@@ -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 | 查询媒体详情 |

View File

@@ -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),
)