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

import pytest
from fastapi.testclient import TestClient

from navi.auth import User
from navi.core.registry import BackendRegistry
from navi.core.session import InMemorySessionStore
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
    from navi.core.orchestrator import AgentSessionOrchestrator

    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,
    )
    container.orchestrator = AgentSessionOrchestrator(container)

    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