mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-09 01:16:50 +08:00
fix(api): 修复音乐详情响应校验
This commit is contained in:
@@ -25,6 +25,7 @@ from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo, MetaInfoPath
|
||||
from app.schemas.category import CategoryConfig as _SchemaCategoryConfig
|
||||
from app.schemas.category import MediaCategoryMap as _SchemaMediaCategoryMap
|
||||
from app.schemas.context import MediaDetailResult as _SchemaMediaDetailResult
|
||||
from app.schemas.context import MediaEpisodeGroup as _SchemaMediaEpisodeGroup
|
||||
from app.schemas.context import MediaPerson as _SchemaMediaPerson
|
||||
from app.schemas.context import MediaSearchResults as _SchemaMediaSearchResults
|
||||
@@ -624,7 +625,7 @@ async def seasons(
|
||||
return []
|
||||
|
||||
|
||||
@router.get("/{media_id}", summary="查询媒体详情", response_model=_SchemaMediaInfo)
|
||||
@router.get("/{media_id}", summary="查询媒体详情", response_model=_SchemaMediaDetailResult)
|
||||
async def detail(
|
||||
media_id: str,
|
||||
media_source: MediaSource,
|
||||
@@ -632,7 +633,7 @@ async def detail(
|
||||
_: _SchemaTokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""
|
||||
根据媒体来源和原生 ID 查询媒体信息,type_name: 电影/电视剧
|
||||
根据媒体来源和原生 ID 查询影视或音乐信息
|
||||
"""
|
||||
mtype = MediaType(type_name)
|
||||
normalized_source, normalized_media_id = resolve_media_identity(
|
||||
|
||||
+12
-3
@@ -539,8 +539,8 @@ class MediaPerson(BaseModel):
|
||||
relation: Optional[str] = None
|
||||
|
||||
|
||||
def _media_search_result_kind(value: Any) -> str:
|
||||
"""按稳定字段区分音乐、人物与影视/合集搜索结果。"""
|
||||
def _media_result_kind(value: Any) -> str:
|
||||
"""按稳定字段区分音乐、人物与影视/合集结果。"""
|
||||
if isinstance(value, BaseModel):
|
||||
value = value.model_dump()
|
||||
if isinstance(value, dict):
|
||||
@@ -551,13 +551,22 @@ def _media_search_result_kind(value: Any) -> str:
|
||||
return "media"
|
||||
|
||||
|
||||
MediaDetailResult = Annotated[
|
||||
Union[
|
||||
Annotated[MusicInfo, Tag("music")],
|
||||
Annotated[MediaInfo, Tag("media")],
|
||||
],
|
||||
Discriminator(_media_result_kind),
|
||||
]
|
||||
|
||||
|
||||
MediaSearchResult = Annotated[
|
||||
Union[
|
||||
Annotated[MusicInfo, Tag("music")],
|
||||
Annotated[MediaPerson, Tag("person")],
|
||||
Annotated[MediaInfo, Tag("media")],
|
||||
],
|
||||
Discriminator(_media_search_result_kind),
|
||||
Discriminator(_media_result_kind),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -242,6 +242,7 @@ SCHEMA_EXPORTS = {
|
||||
'MediaCompany': ('app.schemas.context', 'MediaCompany'),
|
||||
'MediaCountry': ('app.schemas.context', 'MediaCountry'),
|
||||
'MediaCredit': ('app.schemas.context', 'MediaCredit'),
|
||||
'MediaDetailResult': ('app.schemas.context', 'MediaDetailResult'),
|
||||
'MediaEpisode': ('app.schemas.context', 'MediaEpisode'),
|
||||
'MediaEpisodeGroup': ('app.schemas.context', 'MediaEpisodeGroup'),
|
||||
'MediaEpisodeGroupNetwork': ('app.schemas.context', 'MediaEpisodeGroupNetwork'),
|
||||
|
||||
+1
-1
@@ -274,7 +274,7 @@ FastAPI 的 HTTP 异常和参数校验异常统一使用 `message`,不再返
|
||||
| GET | `/api/v1/media/search` | 按标题搜索媒体、合集、人物或音乐,参数:`title`、`type`、`page`、`count`,可重复传入可选 `media_source`;内置模块只处理自身支持的来源,插件模块可以处理其注册的扩展来源,旧客户端的逗号格式仅在输入边界兼容 |
|
||||
| GET | `/api/v1/media/recognize` | 识别标题,参数:`title`、`subtitle`、`custom_words`,可选 `media_source`;当 `title` 为含目录的媒体文件路径时,会合并父目录中的名称、年份等信息 |
|
||||
| GET | `/api/v1/media/recognize_file` | 识别文件路径,参数:`path`,可选 `media_source` |
|
||||
| GET | `/api/v1/media/{media_id}` | 按原生 ID 查询媒体详情;必填参数:`media_source`、`type_name`,其中 `media_source` 与路径中的 `media_id` 组成统一媒体身份 |
|
||||
| GET | `/api/v1/media/{media_id}` | 按原生 ID 查询影视或音乐详情;必填参数:`media_source`、`type_name`,其中 `media_source` 与路径中的 `media_id` 组成统一媒体身份,`type_name` 支持电影、电视剧和音乐 |
|
||||
| POST | `/api/v1/media/scrape/{storage}` | 刮削媒体元数据;请求体为 `FileItem`,可选查询参数 `media_source`、`media_id`、`type_name`(电影/电视剧/音乐)。音乐会按策略处理音频标签、封面和歌词 |
|
||||
| POST | `/api/v1/transfer/manual/target-path` | 按源文件与目录配置匹配手动整理目标路径;请求体为 `ManualTransferItem`,该接口不执行媒体识别 |
|
||||
| POST | `/api/v1/transfer/manual/history` | 查询文件、批量文件或目录命中的成功整理历史摘要,用于进入手动整理界面时显示重新整理状态 |
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
|
||||
from app import schemas
|
||||
from app.api.endpoints import media as media_endpoint
|
||||
from app.api.endpoints import mediaserver as mediaserver_endpoint
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.domain.context import MediaInfo as CoreMediaInfo
|
||||
from app.domain.context import MusicInfo as CoreMusicInfo
|
||||
from app.schemas.types import MediaSource, MediaType
|
||||
|
||||
|
||||
@@ -121,6 +125,43 @@ async def test_douban_media_response_filters_unknown_season_years() -> None:
|
||||
assert response.json()["data"][0]["season_years"] == {"2": "2025"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_media_detail_response_accepts_music_numeric_year(monkeypatch) -> None:
|
||||
"""通用详情接口应按音乐模型序列化 MusicBrainz 的整数年份。"""
|
||||
media_id = "b79e2ffd-7e44-4dbf-91f9-167c05d1fc91"
|
||||
music = CoreMusicInfo(
|
||||
media_source=MediaSource.MusicBrainz,
|
||||
media_id=media_id,
|
||||
title="示例单曲",
|
||||
artists=["示例歌手"],
|
||||
album="示例专辑",
|
||||
year=2004,
|
||||
)
|
||||
media_chain = Mock()
|
||||
media_chain.async_recognize_media = AsyncMock(return_value=music)
|
||||
media_chain.async_obtain_images = AsyncMock(return_value=None)
|
||||
monkeypatch.setattr(media_endpoint, "MediaChain", Mock(return_value=media_chain))
|
||||
|
||||
app = FastAPI()
|
||||
app.dependency_overrides[media_endpoint.verify_token] = lambda: None
|
||||
app.include_router(media_endpoint.router, prefix="/api/v1/media")
|
||||
async with AsyncClient(
|
||||
transport=ASGITransport(app=app),
|
||||
base_url="http://testserver",
|
||||
) as client:
|
||||
response = await client.get(
|
||||
f"/api/v1/media/{media_id}",
|
||||
params={"media_source": "musicbrainz", "type_name": "音乐"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
result = response.json()["data"]
|
||||
assert result["type"] == MediaType.MUSIC.value
|
||||
assert result["music_type"] == "recording"
|
||||
assert result["artists"] == ["示例歌手"]
|
||||
assert result["year"] == 2004
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_media_response_accepts_legacy_source_key() -> None:
|
||||
"""媒体身份重构前缓存的旧格式条目(source + media_id)应被归一化并正常响应。"""
|
||||
|
||||
Reference in New Issue
Block a user