"""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):
"""Patch navi.api.deps internal caches so FastAPI routes see mocked stores.
We patch the module-level singletons (_session_store, _registries, etc.)
rather than the getter functions because FastAPI's Depends() captures the
original function object at import time; replacing the attribute on the
module does not affect the reference stored in the route decorator.
"""
import navi.api.deps as deps
import navi.config as _config
# Ensure database_url is set so _make_memory_store doesn't raise
_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
# Patch internal singletons so original getter functions return our fakes
monkeypatch.setattr(deps, "_session_store", store)
monkeypatch.setattr(deps, "_memory_store", None)
monkeypatch.setattr(deps, "_registries", (tools, profiles, backends, None))
monkeypatch.setattr(deps, "_workers", [])
# 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)
monkeypatch.setattr(deps, "_scheduler", _fake_scheduler)
# Patch auth dependencies so tests don't need real OAuth.
# Use FastAPI's built-in dependency_overrides mechanism so the replacement
# is respected even though Depends() captured the original callable at import time.
fake_user = User(id="test-user", email="test@example.com", display_name="Test", role="admin", permissions=[])
from navi.main import app
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
# get_current_user_ws is called directly inside websocket_session (no Depends),
# so we monkeypatch the source module. Keep dependency_overrides for HTTP
# endpoints that still use Depends(get_current_user_ws).
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)
fake_agent = FakeAgent()
monkeypatch.setattr("navi.api.routes.messages.get_agent", lambda: fake_agent)
# websocket imports deps lazily inside the handler — no need to patch directly
# Prevent MCP connection attempts in tests (no real MCP servers available)
async def _fake_get_mcp_manager():
return None
monkeypatch.setattr(deps, "get_mcp_manager", _fake_get_mcp_manager)
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