"""Unit tests for the GET /sessions/{id}/tasks background-task list endpoint."""
import asyncio
import pytest
from fastapi import HTTPException
import navi.api.routes.sessions as sessions_mod
from navi.auth import User
from navi.core import tasks as tasks_mod
from navi.core.session import InMemorySessionStore
from navi.core.tasks import TaskManager
from navi.tools._internal.base import ToolContext, ToolResult
_FAKE_USER = User(id="test-user", email="test@example.com", display_name="Test",
role="admin", permissions=[])
@pytest.fixture(autouse=True)
def fresh_manager(monkeypatch):
manager = TaskManager()
monkeypatch.setattr(tasks_mod, "_manager", manager)
yield manager
for job in list(manager._jobs.values()):
if job.task is not None and not job.task.done():
job.task.cancel()
@pytest.fixture
async def store():
session_store = InMemorySessionStore()
await session_store.create("secretary")
return session_store
def make_ctx(session_id):
return ToolContext(
session_id=session_id, event_sink=None, stop_event=None, model=None,
user_id=None, user_role="admin", user_info=None, cwd=None,
)
async def _session_id(store: InMemorySessionStore) -> str:
sessions = await store.list_all()
return sessions[0].id
class TestListSessionTasks:
async def test_lists_jobs_with_summary(self, fresh_manager, store):
session_id = await _session_id(store)
async def factory(bg_ctx):
await asyncio.sleep(30)
job = fresh_manager.submit(
session_id, "terminal",
{"action": "run", "command": "sleep 60", "background": True},
factory, make_ctx(session_id),
)
result = await sessions_mod.list_session_tasks(session_id, store, _FAKE_USER)
fresh_manager.cancel(job)
assert result["session_id"] == session_id
assert len(result["tasks"]) == 1
task = result["tasks"][0]
assert task["task_id"] == job.task_id
assert task["tool"] == "terminal"
assert task["status"] == "running"
assert "sleep 60" in task["args_summary"]
# background flag must be stripped in the summary args
assert task["parent_tool_call_id"] is None
assert task["result_preview"] == "" or task["result_preview"] is None
async def test_session_scoped_and_shows_finished(self, fresh_manager, store):
session_id = await _session_id(store)
async def factory(bg_ctx):
return ToolResult(success=True, output="done 42")
job = fresh_manager.submit(
session_id, "code_exec", {"code": "x"}, factory, make_ctx(session_id))
other = fresh_manager.submit(
"other-session", "code_exec", {"code": "y"}, factory, make_ctx("other-session"))
await job.done.wait()
await other.done.wait()
result = await sessions_mod.list_session_tasks(session_id, store, _FAKE_USER)
assert [t["task_id"] for t in result["tasks"]] == [job.task_id]
task = result["tasks"][0]
assert task["status"] == "completed"
assert "done 42" in task["result_preview"]
assert task["finished_at"] is not None
async def test_unknown_session_404(self, fresh_manager, store):
with pytest.raises(HTTPException) as exc:
await sessions_mod.list_session_tasks("nope", store, _FAKE_USER)
assert exc.value.status_code == 404