from __future__ import annotations from datetime import datetime from typing import Tuple, cast import pytest from fastapi import FastAPI, HTTPException from fastapi.testclient import TestClient from dependencies import ( get_auth_session, get_navigation_service, require_authenticated_user, ) from models import User from routes.navigation import router as navigation_router from services.navigation import ( NavigationGroupDTO, NavigationLinkDTO, NavigationService, NavigationSidebarDTO, ) from services.session import AuthSession, SessionTokens class StubNavigationService: def __init__(self, payload: NavigationSidebarDTO) -> None: self._payload = payload self.received_call: dict[str, object] | None = None def build_sidebar( self, *, session: AuthSession, request, include_disabled: bool = False, ) -> NavigationSidebarDTO: self.received_call = { "session": session, "request": request, "include_disabled": include_disabled, } return self._payload @pytest.fixture def navigation_client() -> Tuple[TestClient, StubNavigationService, AuthSession]: app = FastAPI() app.include_router(navigation_router) link_dto = NavigationLinkDTO( id=10, label="Projects", href="/projects", match_prefix="/projects", icon=None, tooltip=None, is_external=False, children=[], ) group_dto = NavigationGroupDTO( id=5, label="Workspace", icon="home", tooltip=None, links=[link_dto], ) payload = NavigationSidebarDTO(groups=[group_dto], roles=("viewer",)) service = StubNavigationService(payload) user = cast(User, object()) session = AuthSession( tokens=SessionTokens(access_token="token", refresh_token=None), user=user, role_slugs=("viewer",), ) app.dependency_overrides[require_authenticated_user] = lambda: user app.dependency_overrides[get_auth_session] = lambda: session app.dependency_overrides[get_navigation_service] = lambda: cast( NavigationService, service) client = TestClient(app) return client, service, session def test_get_sidebar_navigation_returns_payload( navigation_client: Tuple[TestClient, StubNavigationService, AuthSession] ) -> None: client, service, session = navigation_client response = client.get("/navigation/sidebar") assert response.status_code == 200 data = response.json() assert data["roles"] == ["viewer"] assert data["groups"][0]["label"] == "Workspace" assert data["groups"][0]["links"][0]["href"] == "/projects" assert "generated_at" in data datetime.fromisoformat(data["generated_at"]) assert service.received_call is not None assert service.received_call["session"] is session assert service.received_call["request"] is not None assert service.received_call["include_disabled"] is False def test_get_sidebar_navigation_requires_authentication() -> None: app = FastAPI() app.include_router(navigation_router) link_dto = NavigationLinkDTO( id=1, label="Placeholder", href="/placeholder", match_prefix="/placeholder", icon=None, tooltip=None, is_external=False, children=[], ) group_dto = NavigationGroupDTO( id=1, label="Group", icon=None, tooltip=None, links=[link_dto], ) payload = NavigationSidebarDTO(groups=[group_dto], roles=("anonymous",)) service = StubNavigationService(payload) def _deny() -> User: raise HTTPException(status_code=401, detail="Not authenticated") app.dependency_overrides[get_navigation_service] = lambda: cast( NavigationService, service) app.dependency_overrides[get_auth_session] = AuthSession.anonymous app.dependency_overrides[require_authenticated_user] = _deny client = TestClient(app) response = client.get("/navigation/sidebar") assert response.status_code == 401 assert response.json()["detail"] == "Not authenticated"