from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
from deerflow.mcp.tasks.runtime import set_mcp_task_submitter
from deerflow.tools.builtins.background_tasks_tool import (
_list_background_tasks_impl,
cancel_background_task,
)
@pytest.fixture(autouse=True)
def _clear_submitter():
yield
set_mcp_task_submitter(None)
def _runtime():
return SimpleNamespace(
context={"thread_id": "thread-1", "user_id": "user-1"},
state={},
config={},
)
@pytest.mark.asyncio
async def test_list_background_tasks_returns_only_safe_local_fields():
manager = SimpleNamespace(
list_tasks=AsyncMock(
return_value=[
{
"id": "task-1",
"task_name": "report",
"status": "working",
"created_at": "2026-08-08T00:00:00+00:00",
"updated_at": "2026-08-08T00:00:01+00:00",
"error": None,
"remote_task_id": "must-not-leak",
"driver_data": {"secret": "must-not-leak"},
}
]
)
)
set_mcp_task_submitter(manager)
result = await _list_background_tasks_impl(_runtime())
assert result["count"] == 1
assert result["tasks"][0]["cancel_requested"] is False
assert "" not in result["tasks"][0]["task_name"]
assert "remote_task_id" not in result["tasks"][0]
manager.list_tasks.assert_awaited_once_with(
thread_id="thread-1",
user_id="user-1",
limit=20,
active_only=False,
)
@pytest.mark.asyncio
async def test_cancel_background_task_uses_current_user_and_thread():
manager = SimpleNamespace(
cancel_matching_task=AsyncMock(
return_value={
"id": "task-1",
"task_name": "report",
"status": "working",
"created_at": "2026-08-08T00:00:00+00:00",
"updated_at": "2026-08-08T00:00:01+00:00",
"error": None,
"cancel_requested_at": "2026-08-08T00:00:01+00:00",
}
)
)
set_mcp_task_submitter(manager)
result = await cancel_background_task.coroutine(runtime=_runtime(), task="report")
assert result["cancelled"] is False
assert result["task"]["cancel_requested"] is True
assert result["message"].startswith("Cancellation requested.")
manager.cancel_matching_task.assert_awaited_once_with(
thread_id="thread-1",
user_id="user-1",
task="report",
)