Files
MoviePilot/tests/test_music_matching.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

613 lines
25 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
音乐搜索的名称边界、别名和人工候选回归测试。
"""
import copy
import pickle
from pathlib import Path
import pytest
from app.chain.download import DownloadChain
from app.chain.media import MediaChain
from app.chain.search import SearchChain
from app.domain.context import Context, MusicAlbumInfo, MusicInfo
from app.domain.meta.metamusic import MetaMusic
from app.domain.meta.runtime import get_metainfo_accelerator
from app.domain.metainfo import MetaInfo, MetaInfoPath
from app.domain.music import (
match_music_resource,
music_album_matches,
music_isrc_matches,
music_title_matches,
music_version_matches,
music_year_matches,
)
from app.schemas.music import MusicMeta
from app.schemas.types import MediaType
@pytest.mark.parametrize("title", ["U2 - One Tree Hill FLAC", "U2 - Someone FLAC", "U2 - One - Tree Hill FLAC"])
def test_music_match_rejects_other_titles(title):
"""
短曲名不能将较长的另一首作品判为同一单曲。
"""
assert match_music_resource(MusicInfo(title="One", artists=["U2"]), title).status == "rejected"
@pytest.mark.parametrize("artist,title,wrong_title", [
("Lee", "Leeway", "way"),
("Rise", "Surprise", "Surp"),
("Lee", "HappyLee", "Happy"),
("Élan", "Élansong", "song"),
("Мир", "Мирный", "ный"),
])
def test_artist_affix_cannot_cut_into_a_title_word(artist, title, wrong_title):
"""
署名恰好是单词的一部分时,不得据此构造另一首歌的名称。
"""
resource = f"{artist} - {title} FLAC"
assert match_music_resource(MusicInfo(title=title, artists=[artist]), resource).status == "exact"
assert match_music_resource(MusicInfo(title=wrong_title, artists=[artist]), resource).status == "rejected"
@pytest.mark.parametrize("title", ["Lee - Song", "Song - Lee", "LEE: Song"])
def test_artist_affix_preserves_explicit_signature_boundaries(title):
"""
已解析字段中的完整署名有分隔符时,仍可去掉署名参与匹配。
"""
target = MusicInfo(title="Song", artists=["Lee"])
meta = MetaMusic(title=title, artists=["Lee"])
assert match_music_resource(target, title, meta=meta).status == "exact"
def test_artist_affix_supports_existing_cjk_signature_style():
"""
中文连写及“的”字署名形式仍参与同一套资源名称比较。
"""
target = MusicInfo(title="爱情电影主题曲", artists=["许茹芸"])
for title in ("许茹芸爱情电影主题曲", "许茹芸的爱情电影主题曲"):
meta = MetaMusic(title=title, artists=["许茹芸"])
assert match_music_resource(target, title, meta=meta).status == "exact"
@pytest.mark.parametrize("artist", ["Jay Chou", "周杰倫"])
def test_music_match_accepts_source_artist_aliases(artist):
"""
同一艺术家来源别名和繁简署名均可命中。
"""
music = MusicInfo(title="晴天", artists=["周杰伦"], artist_aliases=["Jay Chou"])
assert match_music_resource(music, f"{artist} - 晴天 FLAC").status == "exact"
@pytest.mark.parametrize("artist", ["VA", "V.A.", "群星"])
def test_music_match_accepts_compilation_credit(artist):
"""
合辑通用艺术家署名不应造成漏搜。
"""
music = MusicInfo(music_type="album", title="Test Compilation", artists=["Various Artists"])
assert match_music_resource(music, f"{artist} - Test Compilation FLAC").status == "exact"
@pytest.mark.parametrize("title,reason", [
("Jay Chou - 晴天 FLAC", "artist_unverified"),
("周杰伦 - 晴天 (Live) FLAC", "version_mismatch"),
])
def test_music_match_uncertain_identity_requires_confirmation(title, reason):
"""
未核验艺名和不同录音版本只能交给用户确认。
"""
result = match_music_resource(MusicInfo(title="晴天", artists=["周杰伦"]), title)
assert (result.status, result.reason) == ("candidate", reason)
def test_music_match_distinguishes_missing_evidence_and_related_album():
"""
缺艺术家、缺分类和所属专辑均不得直接绑定成目标单曲。
"""
music = MusicInfo(title="Get Lucky", artists=["Daft Punk"], album="Random Access Memories")
assert match_music_resource(music, "Daft Punk - Random Access Memories FLAC").status == "album"
assert match_music_resource(music, "Daft Punk - Get Lucky FLAC", category=None).reason == "category_unknown"
assert match_music_resource(MusicInfo(title="晴天"), "周杰伦 - 晴天 FLAC").reason == "target_artist_missing"
def test_music_match_handles_accents_and_edition_suffix():
"""
变音符不改变身份,缺少明确的目标发行版本则必须人工确认。
"""
assert match_music_resource(MusicInfo(title="Halo", artists=["Beyoncé"]), "Beyonce - Halo FLAC").status == "exact"
album = MusicInfo(music_type="album", title="Test Album (Deluxe Edition)", artists=["Artist"])
assert match_music_resource(album, "Artist - Test Album FLAC").reason == "edition_unverified"
def test_resource_parser_merges_subtitle_without_target_information():
"""
副标题补全艺术家、专辑和音质,资源元数据不携带目标 ID。
"""
meta = MetaMusic.parse_resource("晴天 FLAC", "演唱:周杰伦;专辑:叶惠美;24bit 96kHz")
assert meta.title == "晴天"
assert meta.artists == ["周杰伦"]
assert meta.album == "叶惠美"
assert meta.bit_depth == 24
assert meta.media_id is None
def test_resource_parser_reads_unlabelled_album_description():
"""
描述末尾的无冒号专辑字段也应保留作品证据。
"""
description = "Apple Music ALAC 宋冬野 2026 06 29 专辑 再想想"
meta = MetaMusic.parse_resource(
"Song DongYe 2026 AppleLossless Take Another Moment", description
)
assert (meta.title, meta.album, meta.artists, meta.year) == (
"AppleLossless Take Another Moment", "再想想", ["Song DongYe"], 2026
)
def test_album_match_uses_unlabelled_description_title_as_candidate():
"""
外文资源的中文专辑描述可形成候选,但未核验艺术家不能自动命中。
"""
title = "Song DongYe 2026 AppleLossless Take Another Moment"
description = "Apple Music ALAC 宋冬野 2026 06 29 专辑 再想想"
target = MusicInfo(music_type="album", title="再想想", artists=["宋冬野"], year=2026)
result = match_music_resource(target, title, description)
assert (result.status, result.reason) == ("candidate", "artist_unverified")
def test_resource_parser_preserves_title_evidence_and_parses_track_segments():
"""
冲突副标题不能覆盖标题艺术家,明确的曲序段则可用于区分专辑和单曲。
"""
meta = MetaMusic.parse_resource("Artist - Album - 01 - Song [FLAC]", "演唱:Other Artist")
assert (meta.artists, meta.album, meta.track_number, meta.title) == (["Artist"], "Album", 1, "Song")
live = MetaMusic.parse_resource("U2 - One (Live) FLAC")
assert live.version == "Live"
def test_resource_parser_keeps_bracketed_title():
"""
纯展示括号内的作品名不能与规格标签一同丢弃。
"""
meta = MetaMusic.parse_resource("【永遠・是朋友】24bit/96kHz", "專輯藝人:周華健;無損音樂")
assert meta.title and "永遠" in meta.title
assert meta.artists == ["周華健"]
@pytest.mark.parametrize("title", [
'Macavity (From The Motion Picture Soundtrack "Cats")',
'Beautiful Ghosts (From The Motion Picture "Cats")',
"Beautiful Ghosts《猫》原声插曲",
])
def test_soundtrack_credit_does_not_change_recording_title(title):
"""
影视来源说明不是歌名本体,不能让正确的 MusicBrainz 录音候选被拒绝。
"""
expected = "Macavity" if title.startswith("Macavity") else "Beautiful Ghosts"
assert music_title_matches(MusicInfo(title=expected), title)
@pytest.mark.parametrize("symbol", ["÷", "+", "=", "×", "−"])
def test_symbol_only_music_title_matches_itself_and_alias(symbol):
"""
纯符号作品名不能因文本归一化为空而失去自身和别名匹配。
"""
music = MusicInfo(title=symbol, title_aliases=["Readable Alias"])
assert music_title_matches(music, symbol)
assert music_title_matches(music, "Readable Alias")
assert not music_title_matches(music, "Different Title")
@pytest.mark.parametrize("symbol,alias,year", [
("÷", "Divide", 2017),
("+", "Plus", 2011),
("=", "Equals", 2021),
("×", "Multiply", 2017),
("−", "Subtract", 2023),
])
def test_symbol_only_album_matches_parsed_resource(symbol, alias, year):
"""
纯符号专辑经资源解析后仍应按原名或可信别名精确命中。
"""
music = MusicInfo(
music_type="album",
title=symbol,
album=symbol,
title_aliases=[alias],
album_aliases=[alias],
artists=["Ed Sheeran"],
year=year,
)
resource = f"Ed Sheeran - {symbol} - {year} - FLAC 分轨"
assert match_music_resource(music, resource).status == "exact"
def test_music_album_field_cannot_match_target_recording():
"""
结构化资源中的所属专辑不能冒充同名目标单曲。
"""
music = MusicInfo(title="Album", artists=["Artist"])
assert match_music_resource(music, "Artist - Album - 01 - Other Song FLAC").status == "rejected"
wanted = MusicInfo(title="Wanted Song", album="Album", artists=["Artist"])
assert match_music_resource(wanted, "Artist - Album - 01 - Other Song FLAC").status == "rejected"
@pytest.mark.parametrize("title", [
"周杰伦 - 晴天 FLAC",
"周杰伦 - 03 - 晴天 FLAC",
"周杰伦 - 03 - 叶惠美 FLAC",
])
def test_album_match_cannot_promote_track_from_subtitle(title):
"""
即使没有曲序,单曲所属专辑字段也不能证明资源是整张专辑。
"""
album = MusicInfo(music_type="album", title="叶惠美", artists=["周杰伦"])
match = match_music_resource(album, title, "专辑:叶惠美")
assert (match.status, match.reason) == ("candidate", "partial_album")
@pytest.mark.parametrize("title", ["周杰伦 - 叶惠美 FLAC", "周杰伦《叶惠美》FLAC", "Jay Chou - Ye Hui Mei FLAC"])
def test_album_match_keeps_primary_album_names_and_aliases(title):
"""
整专主标题及可信别名仍可精确命中,不因副标题补充专辑字段而降级。
"""
album = MusicInfo(music_type="album", title="叶惠美", title_aliases=["Ye Hui Mei"],
artists=["周杰伦"], artist_aliases=["Jay Chou"])
assert match_music_resource(album, title, "专辑:叶惠美").status == "exact"
@pytest.mark.parametrize("subtitle", ["版本:Live", "錄音版本:Live", "Version: Live", "[Live]", "演唱:周杰伦;(Live)"])
def test_resource_subtitle_version_is_matching_evidence(subtitle):
"""
明确的副标题版本只可匹配同一版本,不能自动绑定为普通录音。
"""
title = "周杰伦 - 晴天 FLAC"
meta = MetaMusic.parse_resource(title, subtitle)
assert meta.version == "Live"
ordinary = MusicInfo(title="晴天", artists=["周杰伦"])
assert match_music_resource(ordinary, title, subtitle).reason == "version_mismatch"
live = MusicInfo(title="晴天 (Live)", artists=["周杰伦"])
assert match_music_resource(live, title, subtitle).status == "exact"
def test_resource_title_version_has_priority_over_subtitle():
"""
标题版本已有明确证据时,冲突副标题不能覆盖;艺人字段也不是版本声明。
"""
assert MetaMusic.parse_resource("U2 - One (Live) FLAC", "版本:Remix").version == "Live"
assert MetaMusic.parse_resource("Song FLAC", "艺术家:Live").version is None
@pytest.mark.parametrize("expected,actual,matched", [
("Live 1999", "Live 2000", False),
("Live 1999-01-02", "Live 1999.01.03", False),
("Live 1999-01-02", "Live 1999/1/2", True),
("Live 1999", "Live 1999-01-02", True),
("Live", "Live 1999", True),
("Live 1999", "Live", True),
("mix 1999", "mix 2000", False),
("Live 1999-02-31", "Live 1999-03-01", True),
("Live 1999-01-02", "Live 1999-01-02", True),
("Live 1999年1月2日", "Live 1999-01-03", False),
("Live 1999-2001", "Live 2000", True),
("Live 1999-01-01 to 1999-01-03", "Live 1999-01-02", True),
])
def test_music_version_checks_only_explicit_conflicting_dates(expected, actual, matched):
"""
同类录音的明确日期或年份冲突仍须排除;缺失、部分日期或非法日期不能凭空补全。
"""
target = MusicInfo(title="1999", artists=["Artist"], version=expected)
meta = MetaMusic(title="1999", artists=["Artist"], version=actual)
assert music_version_matches(target, meta) is matched
def test_taylors_version_is_distinct_from_original_recording():
"""
Taylor's Version 是重新录制,不得与原版仅凭同名和同艺人互换。
"""
rerecorded = MusicInfo(
title="Back to December (Taylor's Version)",
artists=["Taylor Swift"],
)
original = MetaMusic(title="Back To December", artists=["Taylor Swift"])
same_version = MetaMusic(
title="Back To December (Taylor’s Version)",
artists=["Taylor Swift"],
)
assert music_version_matches(rerecorded, original) is False
assert music_version_matches(rerecorded, same_version) is True
def test_music_release_evidence_rejects_wrong_album_and_year():
"""
同名同艺人的录音仍须服从文件标签中的专辑本体和发行年份。
"""
info = MusicInfo(
title="Sparks Fly",
artists=["Taylor Swift"],
album="Speak Now",
year=2025,
)
meta = MetaMusic(
title="Sparks Fly",
artists=["Taylor Swift"],
album="Speak Now (Target Exclusive Deluxe Edition 2CD).CD1",
year=2010,
)
assert music_album_matches(info, meta.album) is True
assert music_year_matches(info, meta) is False
def test_music_resource_rejects_different_dated_live_recording():
"""
版本日期可来自标题括号,不将两场同名现场录音自动绑定成同一作品。
"""
target = MusicInfo(title="Song (Live 2001-05-02)", artists=["Artist"])
assert match_music_resource(target, "Artist - Song (Live 2001-05-03) FLAC").reason == "version_mismatch"
@pytest.mark.parametrize("code", [None, "", "Unknown", "N/A", "0", "---", "USABC260001", "UŚABC2600001"])
def test_invalid_isrc_cannot_be_strong_identity(code):
"""
空值、占位值与格式错误不能因为文本相同而绕过名称和版本匹配。
"""
assert music_isrc_matches(MusicInfo(isrc=code), MetaMusic(isrc=code)) is False
@pytest.mark.parametrize("code", ["USABC2600001", "us-abc-26-00001", "ISRC US-ABC-26-00001"])
def test_valid_isrc_accepts_standard_display_format(code):
"""
标准代码、大小写和用于展示的前缀分隔格式应指向同一录音身份。
"""
assert music_isrc_matches(MusicInfo(isrc="USABC2600001"), MetaMusic(isrc=code)) is True
@pytest.mark.parametrize("mtype,suffix", [(MediaType.MUSIC, " FLAC"), (None, ".flac")])
@pytest.mark.parametrize("global_words", [False, True])
def test_music_metainfo_applies_shared_recognition_words(monkeypatch, mtype, suffix, global_words):
"""
音乐名称复用影视识别词语法,音频后缀与显式类型入口行为一致。
"""
words = ["错误曲名 => 晴天"]
if global_words:
monkeypatch.setattr("app.domain.meta.words.get_custom_words", lambda: words)
meta = MetaInfo(f"周杰伦 - 错误曲名{suffix}", "版本:Live", mtype=mtype,
custom_words=None if global_words else words)
assert (meta.title, meta.artists, meta.version) == ("晴天", ["周杰伦"], "Live")
assert meta.audio_format == "FLAC"
assert meta.apply_words == words
assert MetaMusic.from_dict(meta.to_dict()).apply_words == words
assert MusicMeta.model_validate(meta.to_dict()).apply_words == words
def test_legacy_music_metainfo_keeps_empty_recognition_words():
"""
旧音乐缓存缺少识别词记录时仍能恢复,且实例之间不共享可变列表。
"""
old = MetaMusic.from_dict({"title": "晴天"})
assert old.apply_words == []
changed = MetaMusic(title="晴天")
changed.apply_words = ["错误曲名 => 晴天"]
assert old.apply_words == []
@pytest.mark.parametrize("suffix", ["mp2", "tta", "flac"])
def test_music_recognition_words_keep_original_file_format(suffix):
"""
识别词可能删掉文件后缀,但不能丢失或覆盖文件原本声明的音频格式。
"""
meta = MetaInfo(f"周杰伦 - 错误曲名.{suffix}", custom_words=[f"错误曲名\\.{suffix} => 晴天"])
assert meta.title == "晴天"
assert meta.audio_format == suffix.upper()
def test_music_artist_in_subtitle_cannot_override_conflicting_title_credit():
"""
另一位艺人的同名歌曲不能借副标题出现目标艺人而成为精确命中。
"""
music = MusicInfo(title="One", artists=["U2"])
assert match_music_resource(music, "Metallica - One FLAC", "Related artist: U2").reason == "artist_unverified"
def test_artist_name_is_not_a_recording_version():
"""
艺人名称含 Live 时,不能把署名误当作现场录音标记。
"""
music = MusicInfo(title="Song", artists=["Live"])
assert match_music_resource(music, "Live - Song FLAC").status == "exact"
def test_compilation_album_credit_does_not_prove_track_artist():
"""
单曲已经有明确艺人时,整专的合辑署名不构成同一录音的身份依据。
"""
music = MusicInfo(title="Song", artists=["Artist"], album_artist="Various Artists")
assert match_music_resource(music, "VA - Song FLAC").status == "candidate"
def test_distinct_album_artist_is_not_a_recording_artist_alias():
"""
演唱者与专辑艺术家分属不同实体时,不把专辑署名视为演唱者别名。
"""
music = MusicInfo(title="Song", artists=["Performer"], album_artist="Album Artist")
assert match_music_resource(music, "Album Artist - Song FLAC").status == "candidate"
def test_simplification_does_not_merge_recording_and_album_artist_aliases():
"""
文本展示转换不能把另一位专辑艺术家混入录音艺术家别名。
"""
original = MusicInfo(title="晴天", artists=["周杰倫"], album_artist="周華健")
simplified = MediaChain._simplify_recognized_music_info(original)
assert simplified.artist_aliases == ["周杰倫"]
@pytest.mark.parametrize("artist", ["AC/DC", "Earth, Wind & Fire"])
def test_compound_artist_credit_preserves_name_punctuation(artist):
"""
已知完整艺名含分隔符时,不能因资源解析拆段而误判为另一位艺人。
"""
music = MusicInfo(title="Song", artists=[artist])
assert match_music_resource(music, f"{artist} - Song FLAC").status == "exact"
@pytest.mark.parametrize("python_only", [False, True])
def test_resource_evidence_matches_python_and_native_parser_paths(monkeypatch, python_only):
"""
资源级补充规则必须同时适用于 Python 回退和真实 Rust 标题解析。
"""
if python_only:
monkeypatch.setattr("app.domain.meta.metamusic.get_metainfo_accelerator", lambda: None)
else:
accelerator = get_metainfo_accelerator()
if not accelerator or not accelerator.parse_metamusic("Artist - Song FLAC"):
pytest.skip("当前环境没有可用的 Rust 音乐解析器")
meta = MetaMusic.parse_resource("Artist - Album - 01 - Song FLAC", "24bit 96kHz")
assert (meta.title, meta.album, meta.track_number) == ("Song", "Album", 1)
assert meta.artists == ["Artist"]
assert (meta.bit_depth, meta.sample_rate) == (24, 96000)
bracketed = MetaMusic.parse_resource("【永遠・是朋友】24bit/96kHz", "專輯藝人:周華健")
assert bracketed.title and "永遠" in bracketed.title
assert bracketed.artists == ["周華健"]
live = MetaInfo("周杰伦 - 错误曲名.flac", "版本:Live", mtype=MediaType.MUSIC,
custom_words=["错误曲名 => 晴天"])
assert (live.title, live.version, live.audio_format) == ("晴天", "Live", "FLAC")
assert live.apply_words == ["错误曲名 => 晴天"]
album = MusicInfo(music_type="album", title="叶惠美", artists=["周杰伦"])
assert match_music_resource(album, "周杰伦 - 晴天 FLAC", "专辑:叶惠美").reason == "partial_album"
query = MetaMusic.parse_query("周杰伦 - 晴天 [Live] FLAC")
assert query.version == "Live"
filename = MetaInfoPath(Path("/music/周杰伦/叶惠美/晴天 [Live].flac"))
assert filename.version == "Live"
@pytest.mark.parametrize("factory", [MusicInfo, MusicAlbumInfo])
def test_old_music_cache_restores_alias_defaults(factory):
"""
旧 pickle 没有新增字段时应恢复空列表,继续支持标准序列化和专辑投影。
"""
info = factory(title="Album", artists=["Artist"])
for field in ("title_aliases", "artist_aliases", "album_aliases"):
info.__dict__.pop(field, None)
restored = pickle.loads(pickle.dumps(info))
assert restored.to_dict()["title_aliases"] == []
if isinstance(restored, MusicAlbumInfo):
assert restored.to_music_info().artist_aliases == []
def test_clearing_music_context_does_not_mutate_source_snapshot():
"""
共用结果裁剪只清理副本,不能清空模块缓存或调用方仍持有的原始响应。
"""
original = MusicInfo(title="Album", raw_data={"id": "source"})
duplicate = copy.copy(original)
duplicate.clear()
assert duplicate.raw_data == {}
assert original.raw_data == {"id": "source"}
def test_music_simplification_preserves_original_search_names():
"""
展示繁转简后必须仍能按原始标题与艺术家检索。
"""
original = MusicInfo(music_type="album", title="永遠是朋友", album="永遠是朋友", artists=["周華健"])
simplified = MediaChain._simplify_recognized_music_info(original)
keywords = SearchChain.music_site_keywords(simplified)
assert keywords[:2] == ["永远是朋友", "永遠是朋友"]
assert "周華健" in simplified.artist_aliases
assert MusicInfo.from_dict(simplified.to_dict()).title_aliases == ["永遠是朋友"]
def test_music_simplification_prefers_chinese_artist_alias(monkeypatch):
"""
开启音乐简体转换时,整理名称应优先使用同一艺人的中文别名。
"""
original = MusicInfo(
title="舞孃",
artists=["Jolin Tsai"],
artist_aliases=["Jolin Tsai", "蔡依林"],
)
monkeypatch.setattr("app.runtime.config.settings.MUSIC_METADATA_TO_SIMPLIFIED", True)
simplified = MediaChain._simplify_recognized_music_info(original)
assert simplified.artists == ["蔡依林"]
assert "Jolin Tsai" in simplified.artist_aliases
assert original.artists == ["Jolin Tsai"]
def test_music_simplification_keeps_artist_name_when_disabled(monkeypatch):
"""
关闭音乐简体转换时,不得因中文别名改变整理使用的艺术家名称。
"""
original = MusicInfo(
title="舞孃",
artists=["Jolin Tsai"],
artist_aliases=["Jolin Tsai", "蔡依林"],
)
monkeypatch.setattr("app.runtime.config.settings.MUSIC_METADATA_TO_SIMPLIFIED", False)
result = MediaChain._simplify_recognized_music_info(original)
assert result is original
assert result.artists == ["Jolin Tsai"]
def test_music_simplification_keeps_multi_artist_aliases_isolated(monkeypatch):
"""
多位艺术家同时整理时,中文别名必须按艺术家身份分别匹配。
"""
original = MusicInfo(
title="合作曲",
artists=["Jolin Tsai", "Jay Chou"],
artist_ids=["artist-1", "artist-2"],
artist_aliases=["Jolin Tsai", "蔡依林", "Jay Chou", "周杰伦"],
raw_data={
"artist-credit": [
{
"name": "Jolin Tsai",
"artist": {
"id": "artist-1",
"name": "Jolin Tsai",
"aliases": [{"name": "蔡依林"}],
},
},
{
"name": "Jay Chou",
"artist": {
"id": "artist-2",
"name": "Jay Chou",
"aliases": [{"name": "周杰伦"}],
},
},
],
},
)
monkeypatch.setattr("app.runtime.config.settings.MUSIC_METADATA_TO_SIMPLIFIED", True)
simplified = MediaChain._simplify_recognized_music_info(original)
assert simplified.artists == ["蔡依林", "周杰伦"]
def test_music_album_alias_is_used_for_search():
"""
匹配接受的作品别名也必须进入实际站点查询阶梯。
"""
music = MusicInfo(music_type="album", title="Ye Hui Mei", title_aliases=["叶惠美"], artists=["Jay Chou"])
assert "叶惠美" in SearchChain.music_site_keywords(music)
def test_automatic_batch_download_excludes_manual_music_candidates():
"""
人工候选即使被传入批量入口,也不得自动交给下载器。
"""
chain = object.__new__(DownloadChain)
assert chain._execute_batch_download([Context(match_status="candidate")]) == ([], None)
assert chain._execute_batch_download([Context(match_status="candidate", match_reason="related_album")]) == ([], None)