diff --git a/app/domain/context.py b/app/domain/context.py index 1de5a13c4..ff6010a47 100644 --- a/app/domain/context.py +++ b/app/domain/context.py @@ -1623,11 +1623,18 @@ class MediaInfo: if episodes_count: self.seasons[season] = list(range(1, episodes_count + 1)) # 季年份 - if self.type == MediaType.TV and not self.season_years: - season = self.season if self.season is not None else 1 - self.season_years = { - season: self.year - } + if not self.season_years: + raw_season_years = info.get("season_years") + if isinstance(raw_season_years, dict): + # 豆瓣榜单偶尔用 null 表示未知年份,避免污染统一响应模型 + self.season_years = { + season: str(year) + for season, year in raw_season_years.items() + if year is not None + } + if self.type == MediaType.TV and not self.season_years and self.year: + season = self.season if self.season is not None else 1 + self.season_years = {season: self.year} # 风格 if not self.genres: self.genres = [{"id": genre, "name": genre} for genre in info.get("genres") or []] @@ -1642,6 +1649,8 @@ class MediaInfo: self.production_countries = [{"id": country, "name": country} for country in info.get("countries") or []] # 剩余属性赋值 for key, value in info.items(): + if key == "season_years": + continue if not value: continue if not hasattr(self, key): diff --git a/tests/test_media_response_models.py b/tests/test_media_response_models.py index cbef8af03..336284aaa 100644 --- a/tests/test_media_response_models.py +++ b/tests/test_media_response_models.py @@ -93,6 +93,34 @@ async def test_media_response_accepts_cross_source_credit_shapes() -> None: assert media["directors"][1]["name"] == "导演乙" +@pytest.mark.asyncio +async def test_douban_media_response_filters_unknown_season_years() -> None: + """豆瓣榜单的未知季年份不应导致整个媒体列表响应校验失败。""" + media = CoreMediaInfo(douban_info={ + "id": "1292052", + "title": "示例电影", + "type": "movie", + "season_years": {1: None, 2: 2025}, + }) + router = ResponseAPIRouter() + + @router.get("/discover/douban_movies", response_model=list[schemas.MediaInfo]) + def discover_douban_movies() -> list[dict]: + """返回包含未知季年份的豆瓣榜单条目。""" + return [media.to_dict()] + + app = FastAPI() + app.include_router(router) + async with AsyncClient( + transport=ASGITransport(app=app), + base_url="http://testserver", + ) as client: + response = await client.get("/discover/douban_movies") + + assert response.status_code == 200 + assert response.json()["data"][0]["season_years"] == {"2": "2025"} + + @pytest.mark.asyncio async def test_media_response_accepts_legacy_source_key() -> None: """媒体身份重构前缓存的旧格式条目(source + media_id)应被归一化并正常响应。"""