Newer
Older
navi-1 / tests / integration / test_recall_api.py
@Eugene Sukhodolskiy Eugene Sukhodolskiy on 15 May 3 KB Add self-recall (scheduled callback) system
"""Integration tests for recall REST API endpoints."""

from datetime import datetime, timezone

import pytest

from navi.llm.base import Message
from tests.conftest_factory import FakeRecord


class TestRecallEndpoints:
    @pytest.mark.anyio
    async def test_get_recall(self, client, make_session, mock_deps):
        session = await make_session("secretary", [Message(role="user", content="hi")])
        scheduler = mock_deps["scheduler"]
        scheduler._pool._conn.enqueue(FakeRecord(
            id="r1", session_id=session.id, call_type="once",
            trigger_at=datetime(2026, 5, 20, 14, 0, 0, tzinfo=timezone.utc), interval_seconds=None,
            internal_comment="test", additional_context_message="ctx",
            status="pending", created_at=datetime(2026, 5, 15, 10, 0, 0, tzinfo=timezone.utc),
            updated_at=datetime(2026, 5, 15, 10, 0, 0, tzinfo=timezone.utc),
        ))

        response = client.get(f"/sessions/{session.id}/recall")
        assert response.status_code == 200
        data = response.json()
        assert data["id"] == "r1"
        assert data["call_type"] == "once"

    @pytest.mark.anyio
    async def test_get_recall_not_found(self, client, make_session, mock_deps):
        session = await make_session("secretary")
        scheduler = mock_deps["scheduler"]
        scheduler._pool._conn.enqueue(None)

        response = client.get(f"/sessions/{session.id}/recall")
        assert response.status_code == 200
        assert response.json() == {"recall": None}

    @pytest.mark.anyio
    async def test_cancel_recall(self, client, make_session, mock_deps):
        session = await make_session("secretary")
        scheduler = mock_deps["scheduler"]
        scheduler._pool._conn.enqueue("UPDATE 1")

        response = client.delete(f"/sessions/{session.id}/recall")
        assert response.status_code == 200
        data = response.json()
        assert data["ok"] is True

    @pytest.mark.anyio
    async def test_skip_recall(self, client, make_session, mock_deps):
        session = await make_session("secretary")
        scheduler = mock_deps["scheduler"]
        scheduler._pool._conn.enqueue("UPDATE 1")

        response = client.post(f"/sessions/{session.id}/recall/skip")
        assert response.status_code == 200
        data = response.json()
        assert data["ok"] is True

    @pytest.mark.anyio
    async def test_admin_list_recalls(self, client, mock_deps):
        scheduler = mock_deps["scheduler"]
        scheduler._pool._conn.enqueue([FakeRecord(
            id="r1", session_id="s1", call_type="once",
            trigger_at=datetime(2026, 5, 20, 14, 0, 0, tzinfo=timezone.utc), interval_seconds=None,
            internal_comment="test", additional_context_message="ctx",
            status="pending", created_at=datetime(2026, 5, 15, 10, 0, 0, tzinfo=timezone.utc),
            updated_at=datetime(2026, 5, 15, 10, 0, 0, tzinfo=timezone.utc),
        )])

        response = client.get("/admin/recalls")
        assert response.status_code == 200
        data = response.json()
        assert len(data["items"]) == 1
        assert data["items"][0]["id"] == "r1"

    @pytest.mark.anyio
    async def test_list_sessions_has_recall_flag(self, client, make_session, mock_deps):
        session = await make_session("secretary", [Message(role="user", content="hi")])
        scheduler = mock_deps["scheduler"]
        scheduler._pool._conn.enqueue([{"session_id": session.id}])

        response = client.get("/sessions")
        assert response.status_code == 200
        data = response.json()
        assert isinstance(data, list)
        found = next((s for s in data if s["session_id"] == session.id), None)
        assert found is not None
        assert found["has_pending_recall"] is True