"""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