Newer
Older
navi-1 / tests / integration / test_auth_disabled.py
"""Integration tests for NAVI_AUTH_ENABLED=false mode."""

import pytest
from fastapi.testclient import TestClient

from navi.core.registry import BackendRegistry
from navi.core.session import InMemorySessionStore
from navi.main import app
from tests.conftest_factory import FakeConnection, FakeLLMBackend, FakePool, make_profile_registry, make_registry_with_tools


@pytest.fixture
def no_auth_client(monkeypatch):
    """FastAPI TestClient with auth disabled and real auth dependencies."""
    import navi.config as _config

    new_settings = _config.Settings(
        database_url="postgresql://fake",
        navi_auth_enabled=False,
    )

    # Patch settings everywhere it is imported as a module-level name.
    monkeypatch.setattr("navi.config.settings", new_settings)
    monkeypatch.setattr("navi.auth.deps.settings", new_settings)
    monkeypatch.setattr("navi.api.routes.auth.settings", new_settings)
    monkeypatch.setattr("navi.api.routes.sessions.settings", new_settings)

    store = InMemorySessionStore()
    profiles = make_profile_registry()
    # Ensure the navi_code profile is available for default-profile tests.
    from navi.profiles import ALL_PROFILES

    if "navi_code" not in profiles._profiles:
        for p in ALL_PROFILES:
            if p.id == "navi_code":
                profiles.register(p)
                break

    tools = make_registry_with_tools()
    backends = BackendRegistry()
    backends.register("ollama", FakeLLMBackend())

    _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

    from tests.conftest_factory import make_scheduler_with_pool
    _fake_scheduler = make_scheduler_with_pool(_fake_conn)

    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 = object()  # routes requiring an agent are not exercised here
    container._agent = fake_agent

    app.state.container = container

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

    # Clear any auth overrides left by other fixtures in this app instance.
    for dep in (
        "get_current_user",
        "get_current_user_ws",
        "require_user",
        "require_admin",
        "require_permission",
    ):
        app.dependency_overrides.pop(getattr(__import__("navi.api.deps", fromlist=[dep]), dep), None)

    return TestClient(app), store


class TestNoAuthStatus:
    def test_auth_status_reports_disabled(self, no_auth_client):
        client, _ = no_auth_client
        response = client.get("/auth/status")
        assert response.status_code == 200
        data = response.json()
        assert data["enabled"] is False
        assert data["configured"] is False


class TestNoAuthSessions:
    @pytest.mark.anyio
    async def test_create_session_without_auth(self, no_auth_client):
        client, store = no_auth_client
        response = client.post("/sessions", json={"profile_id": "secretary"})
        assert response.status_code == 201
        data = response.json()
        assert "session_id" in data
        assert data["profile_id"] == "secretary"

        session = await store.get(data["session_id"])
        assert session is not None
        assert session.user_id == "anonymous"

    @pytest.mark.anyio
    async def test_list_sessions_without_auth(self, no_auth_client):
        client, store = no_auth_client
        create_resp = client.post("/sessions", json={"profile_id": "secretary"})
        session_id = create_resp.json()["session_id"]

        response = client.get("/sessions")
        assert response.status_code == 200
        sessions = response.json()
        assert any(s["session_id"] == session_id for s in sessions)

    @pytest.mark.anyio
    async def test_create_session_uses_default_profile(self, monkeypatch, no_auth_client):
        """When navi_default_profile_id is set, POST /sessions with no body uses it."""
        import navi.config as _config
        from navi.api.routes import sessions as sessions_mod

        new_settings = _config.Settings(
            database_url="postgresql://fake",
            navi_auth_enabled=False,
            navi_default_profile_id="navi_code",
        )
        monkeypatch.setattr("navi.config.settings", new_settings)
        monkeypatch.setattr("navi.auth.deps.settings", new_settings)
        monkeypatch.setattr("navi.api.routes.auth.settings", new_settings)
        monkeypatch.setattr(sessions_mod, "settings", new_settings)

        client, store = no_auth_client
        response = client.post("/sessions", json={})
        assert response.status_code == 201
        data = response.json()
        assert "session_id" in data
        assert data["profile_id"] == "navi_code"

        session = await store.get(data["session_id"])
        assert session is not None
        assert session.profile_id == "navi_code"


class TestNoAuthOAuthDisabled:
    def test_login_rejected_when_auth_disabled(self, no_auth_client):
        client, _ = no_auth_client
        response = client.get("/auth/login")
        assert response.status_code == 503
        assert "disabled" in response.text.lower()

    def test_callback_rejected_when_auth_disabled(self, no_auth_client):
        client, _ = no_auth_client
        response = client.get("/auth/callback?code=fake&state=fake")
        assert response.status_code == 503
        assert "disabled" in response.text.lower()