"""Integration coverage for the /navigation/sidebar endpoint. These tests validate role-based filtering, ordering, and disabled-link handling through the full FastAPI stack so future changes keep navigation behaviour under explicit test coverage. """ from __future__ import annotations from collections.abc import Callable from typing import Any, Mapping import pytest from fastapi.testclient import TestClient from models.navigation import NavigationGroup, NavigationLink from services.unit_of_work import UnitOfWork @pytest.fixture() def seed_navigation(unit_of_work_factory: Callable[[], UnitOfWork]) -> Callable[[], None]: def _seed() -> None: with unit_of_work_factory() as uow: repo = uow.navigation assert repo is not None workspace = repo.add_group( NavigationGroup( slug="workspace", label="Workspace", sort_order=10, ) ) insights = repo.add_group( NavigationGroup( slug="insights", label="Insights", sort_order=20, ) ) repo.add_link( NavigationLink( group_id=workspace.id, slug="projects", label="Projects", href_override="/projects", sort_order=5, required_roles=[], ) ) repo.add_link( NavigationLink( group_id=workspace.id, slug="admin-tools", label="Admin Tools", href_override="/admin/tools", sort_order=10, required_roles=["admin"], ) ) repo.add_link( NavigationLink( group_id=workspace.id, slug="disabled-link", label="Hidden", href_override="/hidden", sort_order=15, required_roles=[], is_enabled=False, ) ) repo.add_link( NavigationLink( group_id=insights.id, slug="reports", label="Reports", href_override="/reports", sort_order=1, required_roles=[], ) ) return _seed def _link_labels(group_json: Mapping[str, Any]) -> list[str]: return [link["label"] for link in group_json["links"]] def test_admin_session_receives_all_enabled_links( client: TestClient, seed_navigation: Callable[[], None], ) -> None: seed_navigation() response = client.get("/navigation/sidebar") assert response.status_code == 200 payload = response.json() assert [group["label"] for group in payload["groups"]] == [ "Workspace", "Insights", ] workspace, insights = payload["groups"] assert _link_labels(workspace) == ["Projects", "Admin Tools"] assert _link_labels(insights) == ["Reports"] assert payload["roles"] == ["admin"] def test_viewer_session_filters_admin_only_links( client: TestClient, seed_navigation: Callable[[], None], test_user_headers: Callable[[str | None], dict[str, str]], ) -> None: seed_navigation() response = client.get( "/navigation/sidebar", headers=test_user_headers("viewer"), ) assert response.status_code == 200 payload = response.json() assert [group["label"] for group in payload["groups"]] == [ "Workspace", "Insights", ] workspace, insights = payload["groups"] assert _link_labels(workspace) == ["Projects"] assert _link_labels(insights) == ["Reports"] assert payload["roles"] == ["viewer"] def test_anonymous_access_is_rejected( client: TestClient, seed_navigation: Callable[[], None], test_user_headers: Callable[[str | None], dict[str, str]], ) -> None: seed_navigation() response = client.get( "/navigation/sidebar", headers=test_user_headers("anonymous"), ) assert response.status_code == 401