mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 15:38:19 +08:00
fix(agent): isolate web and storage blocking I/O
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import threading
|
||||
from types import SimpleNamespace
|
||||
|
||||
from app.agent.tools.impl.delete_transfer_history import DeleteTransferHistoryTool
|
||||
@@ -277,6 +278,130 @@ def test_delete_transfer_history_tool_only_treats_exact_move_as_reorganize_sourc
|
||||
]
|
||||
|
||||
|
||||
def test_delete_transfer_history_storage_work_runs_outside_event_loop(monkeypatch):
|
||||
"""整理历史的本地存储操作应在 storage worker 中执行。"""
|
||||
caller_thread = threading.get_ident()
|
||||
storage_threads = []
|
||||
history = SimpleNamespace(
|
||||
id=15,
|
||||
title="奔跑吧",
|
||||
src="/downloads/Keep.Running.mkv",
|
||||
status=True,
|
||||
mode="copy",
|
||||
dest_fileitem={
|
||||
"storage": "local",
|
||||
"path": "/library/奔跑吧 (2014)/Keep.Running.mkv",
|
||||
"name": "Keep.Running.mkv",
|
||||
"type": "file",
|
||||
},
|
||||
)
|
||||
|
||||
class FakeTransferHistoryOper:
|
||||
async def async_get(self, history_id):
|
||||
return history
|
||||
|
||||
async def async_delete(self, history_id):
|
||||
return None
|
||||
|
||||
class FakeStorageChain:
|
||||
def exists(self, fileitem):
|
||||
storage_threads.append(threading.get_ident())
|
||||
return True
|
||||
|
||||
def delete_media_file(self, fileitem):
|
||||
storage_threads.append(threading.get_ident())
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.agent.tools.impl.delete_transfer_history.TransferHistoryOper",
|
||||
FakeTransferHistoryOper,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.agent.tools.impl.delete_transfer_history.StorageChain",
|
||||
FakeStorageChain,
|
||||
)
|
||||
|
||||
result = asyncio.run(
|
||||
DeleteTransferHistoryTool(
|
||||
session_id="redo-session",
|
||||
user_id="10001",
|
||||
).run(history_id=15)
|
||||
)
|
||||
|
||||
assert "已删除整理历史记录" in result
|
||||
assert storage_threads
|
||||
assert all(thread_id != caller_thread for thread_id in storage_threads)
|
||||
|
||||
|
||||
def test_delete_transfer_history_cancellation_keeps_history_record(monkeypatch):
|
||||
"""取消等待存储清理时不得继续提交整理历史删除。"""
|
||||
started = threading.Event()
|
||||
release = threading.Event()
|
||||
finished = threading.Event()
|
||||
history = SimpleNamespace(
|
||||
id=16,
|
||||
title="奔跑吧",
|
||||
src="/downloads/Keep.Running.mkv",
|
||||
status=True,
|
||||
mode="copy",
|
||||
dest_fileitem={
|
||||
"storage": "local",
|
||||
"path": "/library/奔跑吧 (2014)/Keep.Running.mkv",
|
||||
"name": "Keep.Running.mkv",
|
||||
"type": "file",
|
||||
},
|
||||
)
|
||||
delete_history_calls = []
|
||||
|
||||
class FakeTransferHistoryOper:
|
||||
async def async_get(self, history_id):
|
||||
return history
|
||||
|
||||
async def async_delete(self, history_id):
|
||||
delete_history_calls.append(history_id)
|
||||
|
||||
class FakeStorageChain:
|
||||
def exists(self, fileitem):
|
||||
started.set()
|
||||
release.wait(timeout=1)
|
||||
return True
|
||||
|
||||
def delete_media_file(self, fileitem):
|
||||
finished.set()
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.agent.tools.impl.delete_transfer_history.TransferHistoryOper",
|
||||
FakeTransferHistoryOper,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.agent.tools.impl.delete_transfer_history.StorageChain",
|
||||
FakeStorageChain,
|
||||
)
|
||||
|
||||
async def scenario():
|
||||
task = asyncio.create_task(
|
||||
DeleteTransferHistoryTool(
|
||||
session_id="redo-session",
|
||||
user_id="10001",
|
||||
).run(history_id=16)
|
||||
)
|
||||
assert await asyncio.to_thread(started.wait, 1)
|
||||
task.cancel()
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
try:
|
||||
asyncio.run(scenario())
|
||||
finally:
|
||||
release.set()
|
||||
|
||||
assert delete_history_calls == []
|
||||
assert finished.wait(timeout=1)
|
||||
|
||||
|
||||
def test_manual_redo_context_uses_dest_path_for_successful_move_record():
|
||||
"""成功 move 记录重新整理时,旧目标文件才是可继续整理的输入路径。"""
|
||||
history = SimpleNamespace(
|
||||
|
||||
Reference in New Issue
Block a user