fix(music): separate album and recording subscriptions

This commit is contained in:
jxxghp
2026-08-09 23:30:31 +08:00
parent aa4023e0a5
commit 5572b2dc64
12 changed files with 520 additions and 58 deletions
+102 -2
View File
@@ -2,7 +2,7 @@ from types import SimpleNamespace
from unittest.mock import Mock, patch
from app.chain.subscribe import SubscribeChain, build_subscribe_meta
from app.core.context import MUSIC_ENTITY_ALBUM, Context, TorrentInfo
from app.core.context import MUSIC_ENTITY_ALBUM, MUSIC_ENTITY_RECORDING, Context, TorrentInfo
from app.core.meta import MetaMusic
from app.core.context import MusicInfo
from app.schemas.types import MediaType
@@ -97,7 +97,7 @@ def test_music_subscribe_reuses_search_download_and_finish_flow():
chain._search_music_subscribe(subscribe)
search_chain.search_by_title.assert_called_once_with(
title="周杰伦 叶惠美",
title="周杰伦 晴天",
sites=[],
mtype=MediaType.MUSIC,
rule_groups=[],
@@ -253,6 +253,47 @@ def test_album_subscription_finishes_only_after_confirmed_full_pack():
) is True
def test_music_subscribe_target_validation_enforces_entity_semantics():
"""单曲无需专辑曲目数,专辑必须有总曲目数,艺术家和实体错配均不可订阅。"""
recording = _music_info()
recording.total_tracks = 11
album = MusicInfo(
source="musicbrainz",
media_id="release-group-1",
music_type=MUSIC_ENTITY_ALBUM,
title="叶惠美",
album="叶惠美",
total_tracks=11,
)
assert SubscribeChain._validate_music_subscribe_target(
recording, MUSIC_ENTITY_RECORDING
) is None
assert SubscribeChain._validate_music_subscribe_target(album, MUSIC_ENTITY_ALBUM) is None
assert "类型不匹配" in (
SubscribeChain._validate_music_subscribe_target(album, MUSIC_ENTITY_RECORDING) or ""
)
album.total_tracks = None
assert "总曲目数未知" in (
SubscribeChain._validate_music_subscribe_target(album, MUSIC_ENTITY_ALBUM) or ""
)
assert "仅支持单曲或专辑" in (
SubscribeChain._validate_music_subscribe_target(recording, "artist") or ""
)
def test_recording_target_sync_clears_stale_album_track_count():
"""旧单曲订阅若误存所属专辑曲目数,刷新元数据时应主动清空。"""
subscribe = _subscribe(total_tracks=11)
subscribe_oper = Mock()
with patch("app.chain.subscribe.SubscribeOper", return_value=subscribe_oper):
SubscribeChain._sync_music_subscribe_target(subscribe, _music_info())
subscribe_oper.update.assert_called_once_with(subscribe.id, {"total_tracks": None})
assert subscribe.total_tracks is None
def test_subscribe_add_music_uses_unified_recognize_by_meta():
"""音乐订阅新增应走统一 recognize_by_meta,并把媒体身份落到 MetaMusic 上。"""
target = _music_info()
@@ -284,6 +325,29 @@ def test_subscribe_add_music_uses_unified_recognize_by_meta():
assert media_chain.recognize_by_meta.call_args.kwargs["source"] == "musicbrainz"
def test_subscribe_add_rejects_music_entity_mismatch_before_database_write():
"""请求专辑却识别为单曲时必须中止,不能创建完成语义错误的订阅。"""
media_chain = Mock()
media_chain.recognize_by_meta.return_value = _music_info()
subscribe_oper = Mock()
with patch("app.chain.subscribe.MediaChain", return_value=media_chain), \
patch("app.chain.subscribe.SubscribeOper", return_value=subscribe_oper):
sid, err_msg = SubscribeChain().add(
title="叶惠美",
year="2003",
mtype=MediaType.MUSIC,
media_source="musicbrainz",
media_id="recording-1",
music_type=MUSIC_ENTITY_ALBUM,
message=False,
)
assert sid is None
assert "类型不匹配" in err_msg
subscribe_oper.add.assert_not_called()
def test_subscribe_add_music_fails_fast_on_offline_fallback():
"""统一识别返回离线兜底(无远端 source)时订阅应直接失败,不写入数据库。"""
offline = MusicInfo(title="未知曲目", artists=["未知艺术家"])
@@ -304,3 +368,39 @@ def test_subscribe_add_music_fails_fast_on_offline_fallback():
assert sid is None
assert err_msg == "未识别到媒体信息"
subscribe_oper.add.assert_not_called()
def test_follow_preserves_album_entity_and_track_count():
"""Follow 专辑分享不得走影视标题解析,并须保留整专完成判定字段。"""
share = {
"share_uid": "follow-user",
"name": "叶惠美",
"type": MediaType.MUSIC.value,
"year": "2003",
"media_source": "musicbrainz",
"media_id": "release-group-1",
"music_type": MUSIC_ENTITY_ALBUM,
"total_tracks": 11,
"filter_groups": [],
}
subscribe_oper = Mock()
subscribe_oper.exists.return_value = False
subscribe_oper.exist_history.return_value = False
system_config = Mock()
system_config.get.return_value = ["follow-user"]
with patch("app.chain.subscribe.SubscribeOper", return_value=subscribe_oper), \
patch("app.chain.subscribe.SystemConfigOper", return_value=system_config), \
patch(
"app.chain.subscribe.MoviePilotServerHelper.get_subscribe_shares",
return_value=[share],
), \
patch("app.chain.subscribe.MetaInfo") as video_meta, \
patch.object(SubscribeChain, "add", return_value=(1, "")) as add:
SubscribeChain.follow()
video_meta.assert_not_called()
assert subscribe_oper.exists.call_args.kwargs["music_type"] == MUSIC_ENTITY_ALBUM
assert subscribe_oper.exist_history.call_args.kwargs["music_type"] == MUSIC_ENTITY_ALBUM
assert add.call_args.kwargs["music_type"] == MUSIC_ENTITY_ALBUM
assert add.call_args.kwargs["total_tracks"] == 11