diff --git a/app/db/subscribe_oper.py b/app/db/subscribe_oper.py index 0c3a3bd9c..d0b7f4794 100644 --- a/app/db/subscribe_oper.py +++ b/app/db/subscribe_oper.py @@ -21,6 +21,16 @@ def _normalize_integer_flags(payload: dict, fields: Tuple[str, ...] = INTEGER_FL return normalized_payload +def _normalize_year(year: Optional[int | str]) -> Optional[str]: + """ + 订阅表的 year 列为字符串类型,而识别链路的媒体年份可能是数字 + (音乐等来源),写库前统一转换为字符串避免数据库类型错误。 + """ + if year is None: + return None + return str(year) + + class SubscribeOper(DbOper): """ 订阅管理 @@ -55,7 +65,7 @@ class SubscribeOper(DbOper): subscribe = Subscribe.exists(self._db, **identity_params) kwargs.update({ "name": mediainfo.title, - "year": mediainfo.year, + "year": _normalize_year(mediainfo.year), "type": mediainfo.type.value, "tmdbid": mediainfo.tmdb_id, "imdbid": mediainfo.imdb_id, @@ -117,7 +127,7 @@ class SubscribeOper(DbOper): subscribe = await Subscribe.async_exists(self._db, **identity_params) kwargs.update({ "name": mediainfo.title, - "year": mediainfo.year, + "year": _normalize_year(mediainfo.year), "type": mediainfo.type.value, "tmdbid": mediainfo.tmdb_id, "imdbid": mediainfo.imdb_id, diff --git a/tests/test_subscribe_oper.py b/tests/test_subscribe_oper.py index 4a789aa5d..9e3731563 100644 --- a/tests/test_subscribe_oper.py +++ b/tests/test_subscribe_oper.py @@ -120,6 +120,29 @@ def test_music_subscribe_persists_release_cover_as_poster_and_backdrop(): assert payload["backdrop"] == media.cover_url +def test_music_subscribe_persists_numeric_year_as_string(): + """音乐识别链路的年份可能是数字,写库前必须转字符串避免 PostgreSQL 类型错误。""" + persisted = SimpleNamespace(id=93) + created = SimpleNamespace(create=MagicMock()) + media = MusicInfo( + source="musicbrainz", + media_id="2af54891-e954-40e8-8b90-b7e98c740f21", + title="心愿", + year=2025, + cover_url="https://coverartarchive.org/release/example/front-500", + ) + + with patch("app.db.subscribe_oper.Subscribe") as subscribe_model: + subscribe_model.exists.side_effect = [None, persisted] + subscribe_model.return_value = created + + sid, _ = SubscribeOper(db=object()).add(mediainfo=media, season=None) + + assert sid == 93 + payload = subscribe_model.call_args.kwargs + assert payload["year"] == "2025" + + @pytest.mark.parametrize("episode_group", [None, "eg-1"]) def test_async_add_scopes_duplicate_lookup_by_episode_group(episode_group): """异步新增与同步路径使用相同的剧集组身份契约。"""