mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-29 11:27:43 +08:00
310 lines
8.8 KiB
Python
310 lines
8.8 KiB
Python
"""
|
|
验证 LRCLIB 歌词查询、匹配和返回值映射。
|
|
"""
|
|
|
|
from app.domain.context import MusicInfo
|
|
from app.modules.lrclib import LrclibModule
|
|
|
|
|
|
class _FakeLrclibResponse:
|
|
"""
|
|
模拟 LRCLIB HTTP 响应,供解析、缓存和限流测试复用。
|
|
"""
|
|
|
|
def __init__(self, payload, status_code=200, headers=None):
|
|
self._payload = payload
|
|
self.status_code = status_code
|
|
self.headers = headers or {}
|
|
self.text = ""
|
|
self.closed = False
|
|
|
|
def json(self):
|
|
"""
|
|
返回预设 JSON 负载。
|
|
"""
|
|
return self._payload
|
|
|
|
def close(self):
|
|
"""
|
|
记录响应已关闭。
|
|
"""
|
|
self.closed = True
|
|
|
|
def __bool__(self):
|
|
"""
|
|
复现 requests.Response 对 4xx/5xx 响应返回 False 的行为。
|
|
"""
|
|
return self.status_code < 400
|
|
|
|
|
|
def test_music_lyrics_prefers_exact_signature_and_synced_lyrics(monkeypatch) -> None:
|
|
"""
|
|
元数据完整时应调用精确接口并优先返回同步歌词。
|
|
"""
|
|
module = LrclibModule()
|
|
requested = []
|
|
|
|
def fake_request(path, params=None):
|
|
"""
|
|
记录精确查询参数并返回同步歌词。
|
|
"""
|
|
requested.append((path, params))
|
|
return {
|
|
"id": 3396226,
|
|
"instrumental": False,
|
|
"plainLyrics": "plain",
|
|
"syncedLyrics": "[00:01.00]synced",
|
|
}
|
|
|
|
monkeypatch.setattr(module, "_request_json", fake_request)
|
|
|
|
lyrics = module.music_lyrics(
|
|
MusicInfo(
|
|
title="I Want to Live",
|
|
artists=["Borislav Slavov"],
|
|
album="Baldur's Gate 3",
|
|
duration=233,
|
|
)
|
|
)
|
|
|
|
assert lyrics is not None
|
|
assert lyrics.provider == "lrclib"
|
|
assert lyrics.provider_id == "3396226"
|
|
assert lyrics.content == "[00:01.00]synced"
|
|
assert lyrics.extension == ".lrc"
|
|
assert requested == [
|
|
(
|
|
"/api/get",
|
|
{
|
|
"track_name": "I Want to Live",
|
|
"artist_name": "Borislav Slavov",
|
|
"album_name": "Baldur's Gate 3",
|
|
"duration": 233,
|
|
},
|
|
)
|
|
]
|
|
|
|
|
|
def test_music_lyrics_search_fallback_rejects_wrong_duration(monkeypatch) -> None:
|
|
"""
|
|
精确接口未命中后只能选取标题、艺术家和时长均可信的搜索结果。
|
|
"""
|
|
module = LrclibModule()
|
|
|
|
def fake_request(path, params=None):
|
|
"""
|
|
精确查询返回未命中,搜索返回一条错误版本和一条正确版本。
|
|
"""
|
|
if path == "/api/get":
|
|
return {}
|
|
return [
|
|
{
|
|
"id": 1,
|
|
"trackName": "晴天",
|
|
"artistName": "周杰伦",
|
|
"albumName": "演唱会",
|
|
"duration": 310,
|
|
"plainLyrics": "wrong",
|
|
},
|
|
{
|
|
"id": 2,
|
|
"trackName": "晴天",
|
|
"artistName": "周杰伦",
|
|
"albumName": "叶惠美",
|
|
"duration": 269,
|
|
"plainLyrics": "correct",
|
|
},
|
|
]
|
|
|
|
monkeypatch.setattr(module, "_request_json", fake_request)
|
|
|
|
lyrics = module.music_lyrics(
|
|
MusicInfo(
|
|
title="晴天",
|
|
artists=["周杰伦"],
|
|
album="叶惠美",
|
|
duration=270,
|
|
)
|
|
)
|
|
|
|
assert lyrics is not None
|
|
assert lyrics.provider_id == "2"
|
|
assert lyrics.content == "correct"
|
|
assert lyrics.extension == ".txt"
|
|
|
|
|
|
def test_music_lyrics_queries_simplified_metadata(monkeypatch) -> None:
|
|
"""
|
|
繁体音乐信息应使用简体标题、艺术家和专辑查询 LRCLIB。
|
|
"""
|
|
module = LrclibModule()
|
|
requested = []
|
|
|
|
def fake_request(path, params=None):
|
|
"""
|
|
记录归一后的精确查询参数并返回歌词。
|
|
"""
|
|
requested.append((path, params))
|
|
return {
|
|
"id": 3,
|
|
"plainLyrics": "安静的歌词",
|
|
}
|
|
|
|
monkeypatch.setattr(module, "_request_json", fake_request)
|
|
lyrics = module.music_lyrics(
|
|
MusicInfo(
|
|
title="安靜",
|
|
artists=["周杰倫"],
|
|
album="范特西",
|
|
duration=334,
|
|
)
|
|
)
|
|
|
|
assert lyrics is not None
|
|
assert lyrics.provider_id == "3"
|
|
assert requested == [
|
|
(
|
|
"/api/get",
|
|
{
|
|
"track_name": "安静",
|
|
"artist_name": "周杰伦",
|
|
"album_name": "范特西",
|
|
"duration": 334,
|
|
},
|
|
)
|
|
]
|
|
|
|
|
|
def test_music_lyrics_search_matches_traditional_candidate(monkeypatch) -> None:
|
|
"""
|
|
搜索结果使用繁体艺术家或标题时仍应匹配简体查询身份。
|
|
"""
|
|
module = LrclibModule()
|
|
|
|
def fake_request(path, params=None):
|
|
"""
|
|
模拟精确接口未命中和繁体搜索结果。
|
|
"""
|
|
if path == "/api/get":
|
|
return {}
|
|
return [
|
|
{
|
|
"id": 4,
|
|
"trackName": "安靜",
|
|
"artistName": "周杰倫",
|
|
"albumName": "范特西",
|
|
"duration": 334,
|
|
"plainLyrics": "安静的歌词",
|
|
}
|
|
]
|
|
|
|
monkeypatch.setattr(module, "_request_json", fake_request)
|
|
lyrics = module.music_lyrics(
|
|
MusicInfo(title="安靜", artists=["周杰倫"], album="范特西", duration=334)
|
|
)
|
|
|
|
assert lyrics is not None
|
|
assert lyrics.provider_id == "4"
|
|
|
|
|
|
def test_request_json_honors_retry_after_once(monkeypatch) -> None:
|
|
"""
|
|
LRCLIB 返回带 Retry-After 的过载响应时应等待并串行重试一次。
|
|
"""
|
|
responses = iter(
|
|
[
|
|
_FakeLrclibResponse(None, status_code=503, headers={"Retry-After": "2"}),
|
|
_FakeLrclibResponse([{"id": 1}], status_code=200),
|
|
]
|
|
)
|
|
sleeps = []
|
|
monkeypatch.setattr(LrclibModule, "_request_once", lambda *_args, **_kwargs: next(responses))
|
|
monkeypatch.setattr("app.modules.lrclib.time.sleep", lambda seconds: sleeps.append(seconds))
|
|
LrclibModule._request_json.cache_clear()
|
|
|
|
result = LrclibModule._request_json(
|
|
"/api/search",
|
|
params={"track_name": "晴天", "artist_name": "周杰伦"},
|
|
)
|
|
|
|
assert result == [{"id": 1}]
|
|
assert sleeps == [2.0]
|
|
|
|
|
|
def test_request_json_retry_network_failure_is_not_cached(monkeypatch) -> None:
|
|
"""
|
|
过载重试遇到网络失败时应安全返回且不把失败写入缓存。
|
|
"""
|
|
responses = iter(
|
|
[
|
|
_FakeLrclibResponse(None, status_code=429, headers={"Retry-After": "0"}),
|
|
None,
|
|
_FakeLrclibResponse([{"id": 3}], status_code=200),
|
|
]
|
|
)
|
|
monkeypatch.setattr(LrclibModule, "_request_once", lambda *_args, **_kwargs: next(responses))
|
|
monkeypatch.setattr("app.modules.lrclib.time.sleep", lambda _seconds: None)
|
|
LrclibModule._request_json.cache_clear()
|
|
params = {"track_name": "retry"}
|
|
|
|
first = LrclibModule._request_json("/api/search", params=params)
|
|
second = LrclibModule._request_json("/api/search", params=params)
|
|
|
|
assert first is None
|
|
assert second == [{"id": 3}]
|
|
|
|
|
|
def test_request_json_caches_not_found_response(monkeypatch) -> None:
|
|
"""
|
|
未匹配结果也应缓存,避免整库重复刮削持续请求同一首歌。
|
|
"""
|
|
calls = {"count": 0}
|
|
|
|
def fake_request(*_args, **_kwargs):
|
|
"""
|
|
记录请求次数并返回 404。
|
|
"""
|
|
calls["count"] += 1
|
|
return _FakeLrclibResponse(None, status_code=404)
|
|
|
|
monkeypatch.setattr(LrclibModule, "_request_once", fake_request)
|
|
LrclibModule._request_json.cache_clear()
|
|
|
|
first = LrclibModule._request_json("/api/get", params={"track_name": "missing"})
|
|
second = LrclibModule._request_json("/api/get", params={"track_name": "missing"})
|
|
|
|
assert first == second == {}
|
|
assert calls["count"] == 1
|
|
|
|
|
|
def test_request_json_caches_success_response(monkeypatch) -> None:
|
|
"""
|
|
成功歌词响应应进入有界 TLRU 缓存,相同签名只访问一次外部 API。
|
|
"""
|
|
calls = {"count": 0}
|
|
|
|
def fake_request(*_args, **_kwargs):
|
|
"""
|
|
记录请求次数并返回固定歌词。
|
|
"""
|
|
calls["count"] += 1
|
|
return _FakeLrclibResponse(
|
|
{"id": 2, "syncedLyrics": "[00:01.00]晴天"},
|
|
status_code=200,
|
|
)
|
|
|
|
monkeypatch.setattr(LrclibModule, "_request_once", fake_request)
|
|
LrclibModule._request_json.cache_clear()
|
|
params = {
|
|
"track_name": "晴天",
|
|
"artist_name": "周杰伦",
|
|
"album_name": "叶惠美",
|
|
"duration": 269,
|
|
}
|
|
|
|
first = LrclibModule._request_json("/api/get", params=params)
|
|
second = LrclibModule._request_json("/api/get", params=params)
|
|
|
|
assert first == second == {"id": 2, "syncedLyrics": "[00:01.00]晴天"}
|
|
assert calls["count"] == 1
|