mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
fix(async): offload file metadata checks
This commit is contained in:
@@ -274,3 +274,19 @@ def test_async_identify_music_by_fingerprint_uses_async_process_and_http(
|
||||
post_res.assert_awaited_once()
|
||||
assert post_res.await_args.kwargs["data"]["meta"] == "recordingids"
|
||||
assert response.closed is True
|
||||
|
||||
|
||||
def test_async_identify_skips_missing_file_after_threaded_check(monkeypatch):
|
||||
"""异步指纹入口应在线程中检查文件,并保持缺失文件的跳过语义。"""
|
||||
module = AcoustIdModule()
|
||||
module._fpcalc_path = "/usr/bin/fpcalc"
|
||||
check_file = AsyncMock(return_value=False)
|
||||
monkeypatch.setattr("app.modules.acoustid.run_in_threadpool", check_file)
|
||||
|
||||
result = asyncio.run(
|
||||
module.async_identify_music_by_fingerprint(Path("/music/missing.flac"))
|
||||
)
|
||||
|
||||
assert result is None
|
||||
check_file.assert_awaited_once()
|
||||
assert check_file.await_args.args[1] == Path("/music/missing.flac")
|
||||
|
||||
@@ -309,6 +309,31 @@ def test_scrape_metadata_rejects_invalid_media_source_before_file_access(tmp_pat
|
||||
assert "media_source" in payload["message"]
|
||||
|
||||
|
||||
def test_scrape_metadata_checks_local_path_in_agent_worker(tmp_path, monkeypatch):
|
||||
"""Agent 刮削的本地路径检查应通过受控存储线程执行。"""
|
||||
calls = []
|
||||
|
||||
async def fake_run_agent_blocking(bucket, func, *args, **kwargs):
|
||||
calls.append((bucket, func, args, kwargs))
|
||||
return False, False
|
||||
|
||||
monkeypatch.setattr("app.agent.tools.base.run_agent_blocking", fake_run_agent_blocking)
|
||||
tool = ScrapeMetadataTool(session_id="session-1", user_id="10001")
|
||||
|
||||
result = asyncio.run(
|
||||
tool.run(path=str(tmp_path / "missing"), storage="local")
|
||||
)
|
||||
|
||||
payload = json.loads(result)
|
||||
assert payload == {
|
||||
"success": False,
|
||||
"message": f"刮削路径不存在: {tmp_path / 'missing'}",
|
||||
}
|
||||
assert len(calls) == 1
|
||||
assert calls[0][0] == "storage"
|
||||
assert calls[0][2] == (tmp_path / "missing",)
|
||||
|
||||
|
||||
def test_query_artist_detail_marks_entity_as_non_subscribable():
|
||||
"""艺术家详情应明确标记为不可订阅,避免 Agent 混入获取流程。"""
|
||||
artist = MusicArtistInfo(
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from app.modules.discord.discord import Discord
|
||||
|
||||
|
||||
def test_send_file_checks_local_file_in_threadpool(monkeypatch):
|
||||
"""Discord 文件发送应把本地文件检查移出 Discord 事件循环。"""
|
||||
discord_client = Discord.__new__(Discord)
|
||||
channel = AsyncMock()
|
||||
discord_client._resolve_channel = AsyncMock(return_value=channel)
|
||||
check_file = AsyncMock(return_value=False)
|
||||
monkeypatch.setattr("app.modules.discord.discord.run_in_threadpool", check_file)
|
||||
|
||||
result = asyncio.run(
|
||||
discord_client._send_file(
|
||||
file_path="/tmp/missing.txt",
|
||||
title="标题",
|
||||
text=None,
|
||||
userid="user-1",
|
||||
file_name=None,
|
||||
original_chat_id=None,
|
||||
)
|
||||
)
|
||||
|
||||
assert result == (False, None)
|
||||
check_file.assert_awaited_once()
|
||||
assert check_file.await_args.args[1] == Path("/tmp/missing.txt")
|
||||
channel.send.assert_not_awaited()
|
||||
@@ -220,6 +220,52 @@ def test_async_recognize_album_directory_calls_async_module(
|
||||
run_module.assert_not_called()
|
||||
|
||||
|
||||
def test_async_recognize_album_directory_checks_path_in_threadpool(
|
||||
tmp_path,
|
||||
media_chain,
|
||||
monkeypatch,
|
||||
):
|
||||
"""异步专辑识别应把目录元数据检查移出事件循环。"""
|
||||
check_directory = AsyncMock(return_value=False)
|
||||
monkeypatch.setattr("app.chain.media.run_in_threadpool", check_directory)
|
||||
|
||||
result = asyncio.run(
|
||||
media_chain.async_recognize_music_album_directory(tmp_path / "missing")
|
||||
)
|
||||
|
||||
assert result == {}
|
||||
check_directory.assert_awaited_once()
|
||||
assert check_directory.await_args.args[1] == tmp_path / "missing"
|
||||
|
||||
|
||||
def test_async_album_fallback_propagates_cancellation_during_path_check(
|
||||
tmp_path,
|
||||
media_chain,
|
||||
monkeypatch,
|
||||
):
|
||||
"""异步专辑兜底不得吞掉文件检查被取消的信号。"""
|
||||
started = asyncio.Event()
|
||||
|
||||
async def wait_for_check(*_args, **_kwargs):
|
||||
"""模拟慢文件系统检查,直到调用方取消。"""
|
||||
started.set()
|
||||
await asyncio.Event().wait()
|
||||
|
||||
monkeypatch.setattr("app.chain.media.run_in_threadpool", wait_for_check)
|
||||
|
||||
async def exercise_cancellation():
|
||||
"""在同一事件循环中取消正在等待文件检查的调用。"""
|
||||
task = asyncio.create_task(
|
||||
media_chain._async_music_album_dir_fallback(tmp_path / "track.flac")
|
||||
)
|
||||
await started.wait()
|
||||
task.cancel()
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await task
|
||||
|
||||
asyncio.run(exercise_cancellation())
|
||||
|
||||
|
||||
def test_recognize_album_directory_skips_single_file(tmp_path, media_chain, monkeypatch):
|
||||
"""单文件目录不走专辑匹配,交给单曲识别链路。"""
|
||||
album_dir = tmp_path / "单曲"
|
||||
|
||||
Reference in New Issue
Block a user