Newer
Older
navi-1 / tests / integration / conftest.py
"""Integration test fixtures — FastAPI app with mocked dependencies."""

from typing import AsyncGenerator

import pytest
from fastapi.testclient import TestClient

from navi.auth import User
from navi.core.events import StreamEnd, TextDelta
from navi.core.registry import BackendRegistry
from navi.core.session import InMemorySessionStore, Session
from navi.llm.base import Message
from tests.conftest_factory import FakeLLMBackend, make_profile_registry, make_registry_with_tools


class FakeAgent:
    """Deterministic agent for integration tests.

    Yields pre-configured events via run_stream().
    run() returns a fixed string.
    """

    def __init__(self, stream_events=None, run_response="Hello") -> None:
        self._stream_events = stream_events or []
        self._run_response = run_response

    async def run(self, session_id: str, user_message: str, images=None) -> str:
        return self._run_response

    async def run_stream(self, session_id, user_message, images=None, display_message=None):
        for ev in self._stream_events:
            yield ev


@pytest.fixture
def mock_deps(monkeypatch):
    """Create an AppContainer with mocked stores and attach it to app.state."""
    import navi.config as _config

    # Ensure database_url is set so _make_memory_store doesn't raise
    monkeypatch.setattr(
        _config, "settings", _config.Settings(database_url="postgresql://fake")
    )

    store = InMemorySessionStore()
    profiles = make_profile_registry()
    tools = make_registry_with_tools()
    backends = BackendRegistry()
    backends.register("ollama", FakeLLMBackend())

    # Wire a FakePool onto the in-memory store so admin endpoints that call
    # store._get_pool() can work in integration tests.
    from tests.conftest_factory import FakeConnection, FakePool
    _fake_conn = FakeConnection()
    _fake_pool = FakePool(_fake_conn)

    async def _fake_get_pool():
        return _fake_pool

    store._get_pool = _fake_get_pool
    store._pool = _fake_pool

    # Wire scheduler with FakePool so recall endpoints work in integration tests
    from tests.conftest_factory import make_scheduler_with_pool
    _fake_scheduler = make_scheduler_with_pool(_fake_conn)

    # Build container directly — no more module-level singletons
    from navi.core.container import AppContainer
    container = AppContainer(
        database=None,
        memory_store=None,
        session_store=store,
        kv_store=None,
        scheduler=_fake_scheduler,
        tool_registry=tools,
        profile_registry=profiles,
        backend_registry=backends,
        cp_registry=None,
        workers=[],
        mcp_manager=None,
    )

    fake_agent = FakeAgent()
    container._agent = fake_agent

    from navi.main import app
    app.state.container = container

    from navi.api.deps import set_container
    set_container(container)

    # Patch auth dependencies so tests don't need real OAuth.
    fake_user = User(id="test-user", email="test@example.com", display_name="Test", role="admin", permissions=[])

    from navi.api.deps import get_current_user, require_admin, require_permission, require_user
    from navi.auth.deps import get_current_user_ws

    app.dependency_overrides[get_current_user] = lambda: fake_user
    async def _fake_get_user_ws(websocket):
        return fake_user
    monkeypatch.setattr("navi.auth.deps.get_current_user_ws", _fake_get_user_ws)
    monkeypatch.setattr("navi.api.deps.get_current_user_ws", _fake_get_user_ws)
    app.dependency_overrides[get_current_user_ws] = lambda: fake_user
    app.dependency_overrides[require_user] = lambda: fake_user
    app.dependency_overrides[require_admin] = lambda: fake_user
    app.dependency_overrides[require_permission] = lambda p: fake_user

    # Patch get_agent in routes that import it directly (messages.py)
    monkeypatch.setattr("navi.api.routes.messages.get_agent", lambda: fake_agent)

    return {
        "session_store": store,
        "profiles": profiles,
        "tools": tools,
        "backends": backends,
        "agent": fake_agent,
        "fake_conn": _fake_conn,
        "fake_pool": _fake_pool,
        "scheduler": _fake_scheduler,
    }


@pytest.fixture
def client(mock_deps):
    """FastAPI TestClient with mocked dependencies."""
    from navi.main import app

    return TestClient(app)


@pytest.fixture
def make_session(mock_deps):
    """Helper to create a session in the mocked store."""
    store = mock_deps["session_store"]

    async def _make(profile_id="secretary", messages=None):
        session = await store.create(profile_id)
        if messages:
            for m in messages:
                session.messages.append(m)
            await store.save(session)
        return session

    return _make