mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-16 03:23:54 +08:00
154 lines
5.2 KiB
Python
154 lines
5.2 KiB
Python
import asyncio
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
|
|
from app.agent import AgentManager, MoviePilotAgent
|
|
|
|
|
|
def test_execute_agent_propagates_task_cancellation():
|
|
"""取消 Agent 执行时应终止调用方任务,不能转换成普通完成结果。"""
|
|
started = asyncio.Event()
|
|
|
|
class _BlockingAgent:
|
|
"""等待取消的最小 LangGraph 替身。"""
|
|
|
|
async def ainvoke(self, _payload, config=None): # noqa: ARG002
|
|
"""阻塞到外层任务取消。"""
|
|
started.set()
|
|
await asyncio.Event().wait()
|
|
|
|
async def _run_scenario():
|
|
agent = MoviePilotAgent(session_id="session-1", user_id="10001")
|
|
agent._should_stream = lambda: False
|
|
agent._create_agent = AsyncMock(return_value=_BlockingAgent())
|
|
agent.stream_handler.stop_streaming = AsyncMock(return_value=(False, ""))
|
|
|
|
execution = asyncio.create_task(agent._execute_agent([]))
|
|
await asyncio.wait_for(started.wait(), timeout=1)
|
|
execution.cancel()
|
|
|
|
with pytest.raises(asyncio.CancelledError):
|
|
await execution
|
|
agent.stream_handler.stop_streaming.assert_awaited_once()
|
|
|
|
asyncio.run(_run_scenario())
|
|
|
|
|
|
def test_stop_current_task_cancels_waiters_and_allows_next_message():
|
|
"""停止会话应结束当前及排队请求,并允许同一会话继续处理消息。"""
|
|
|
|
async def _run_scenario():
|
|
manager = AgentManager()
|
|
started = asyncio.Event()
|
|
|
|
async def _block_current_task(_task):
|
|
started.set()
|
|
await asyncio.Event().wait()
|
|
|
|
manager._process_message_internal = _block_current_task
|
|
first_waiter = asyncio.create_task(
|
|
manager.process_message(
|
|
session_id="session-1",
|
|
user_id="10001",
|
|
message="first",
|
|
wait_for_completion=True,
|
|
)
|
|
)
|
|
await asyncio.wait_for(started.wait(), timeout=1)
|
|
second_waiter = asyncio.create_task(
|
|
manager.process_message(
|
|
session_id="session-1",
|
|
user_id="10001",
|
|
message="second",
|
|
wait_for_completion=True,
|
|
)
|
|
)
|
|
await asyncio.sleep(0)
|
|
|
|
try:
|
|
assert await asyncio.wait_for(
|
|
manager.stop_current_task("session-1"), timeout=1
|
|
) is True
|
|
with pytest.raises(asyncio.CancelledError):
|
|
await first_waiter
|
|
with pytest.raises(asyncio.CancelledError):
|
|
await asyncio.wait_for(second_waiter, timeout=1)
|
|
|
|
manager._process_message_internal = AsyncMock(return_value="resumed")
|
|
result = await asyncio.wait_for(
|
|
manager.process_message(
|
|
session_id="session-1",
|
|
user_id="10001",
|
|
message="next",
|
|
wait_for_completion=True,
|
|
),
|
|
timeout=1,
|
|
)
|
|
assert result == "resumed"
|
|
finally:
|
|
await manager.stop_current_task("session-1")
|
|
for waiter in (first_waiter, second_waiter):
|
|
if not waiter.done():
|
|
waiter.cancel()
|
|
await asyncio.gather(
|
|
first_waiter,
|
|
second_waiter,
|
|
return_exceptions=True,
|
|
)
|
|
|
|
asyncio.run(_run_scenario())
|
|
|
|
|
|
def test_stop_queues_new_message_until_cancellation_cleanup_finishes():
|
|
"""旧 worker 清理期间到达的新消息应保留,并在清理完成后执行。"""
|
|
|
|
async def _run_scenario():
|
|
manager = AgentManager()
|
|
current_started = asyncio.Event()
|
|
cancellation_cleanup_started = asyncio.Event()
|
|
release_cleanup = asyncio.Event()
|
|
|
|
async def _process(task):
|
|
if task.message == "current":
|
|
current_started.set()
|
|
await asyncio.Event().wait()
|
|
return "next-completed"
|
|
|
|
async def _finish_status(_task):
|
|
cancellation_cleanup_started.set()
|
|
await release_cleanup.wait()
|
|
|
|
manager._process_message_internal = _process
|
|
manager._finish_task_processing_status = _finish_status
|
|
current_waiter = asyncio.create_task(
|
|
manager.process_message(
|
|
session_id="session-1",
|
|
user_id="10001",
|
|
message="current",
|
|
wait_for_completion=True,
|
|
)
|
|
)
|
|
await asyncio.wait_for(current_started.wait(), timeout=1)
|
|
stop_task = asyncio.create_task(manager.stop_current_task("session-1"))
|
|
await asyncio.wait_for(cancellation_cleanup_started.wait(), timeout=1)
|
|
|
|
next_waiter = asyncio.create_task(
|
|
manager.process_message(
|
|
session_id="session-1",
|
|
user_id="10001",
|
|
message="next",
|
|
wait_for_completion=True,
|
|
)
|
|
)
|
|
await asyncio.sleep(0)
|
|
assert not next_waiter.done()
|
|
|
|
release_cleanup.set()
|
|
assert await asyncio.wait_for(stop_task, timeout=1) is True
|
|
assert await asyncio.wait_for(next_waiter, timeout=1) == "next-completed"
|
|
with pytest.raises(asyncio.CancelledError):
|
|
await current_waiter
|
|
|
|
asyncio.run(_run_scenario())
|