"""Tests for Phase 15a — Dashboard Frontend Foundation."""

from __future__ import annotations

import contextlib
from datetime import UTC, datetime, timedelta
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from backend.dashboard.middleware import (
    CustomerContext,
    DashboardAuthError,
    dashboard_auth_error_response,
    require_dashboard_auth,
)
from backend.dashboard.middleware.security_headers import (
    SecurityHeadersMiddleware,
)
from backend.dashboard.routes import router
from backend.dashboard.svg_helpers import (
    render_donut_svg,
    render_score_trend_svg,
    render_severity_bar_svg,
    render_sparkline_svg,
)
from fastapi import FastAPI
from fastapi.testclient import TestClient

# ---------------------------------------------------------------------------
# Patch targets — definition site (lesson 1.43)
# ---------------------------------------------------------------------------

_PATCH_SESSION_GET = "backend.dashboard.session.get_session"
_PATCH_SESSION_COOKIE = "backend.dashboard.session.get_cookie_name"
_PATCH_SESSION_VALIDATE = "backend.dashboard.session.validate_csrf"
_PATCH_FIRESTORE_DB = "backend.shared.firestore.get_db"
_PATCH_FIRESTORE_GET_KEY = "backend.shared.firestore.get_license_key"
_PATCH_FIRESTORE_UPDATE = "backend.shared.firestore.update_customer"


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _ctx(
    tier: str = "team",
    customer_id: str = "cus_test",
    email: str = "user@example.com",
    cancellation_pending: bool = False,
    role: str = "owner",
) -> CustomerContext:
    return CustomerContext(
        customer_id=customer_id,
        email=email,
        tier=tier,
        role=role,
        member_email=email,
        features=["hosted_dashboard", "shared_scan_links"],
        project_limit=1,
        batch_limit=5,
        cancellation_pending=cancellation_pending,
        subscription_expires_at="2027-01-01T00:00:00+00:00",
        consent_accepted=True,
        tos_version="1.0",
        privacy_version="1.0",
    )


def _session_data(
    csrf_token: str = "test-csrf-token",
    theme: str | None = None,
) -> dict[str, Any]:
    return {
        "session_id": "sess_abc123",
        "customer_id": "cus_test",
        "email": "user@example.com",
        "tier": "team",
        "csrf_token": csrf_token,
        "ip_hash": "fakehash",
        "user_agent_hash": "fakehash",
        "created_at": datetime.now(UTC).isoformat(),
        "last_accessed_at": datetime.now(UTC).isoformat(),
        "expires_at": (datetime.now(UTC) + timedelta(days=7)),
        "theme_preference": theme,
    }


def _mock_firestore_db() -> MagicMock:
    """Create chainable mock Firestore DB."""
    mock_doc = MagicMock()
    mock_doc.set = AsyncMock()
    mock_doc.update = AsyncMock()
    inner_coll = MagicMock()
    inner_coll.document = MagicMock(return_value=mock_doc)
    mock_doc.collection = MagicMock(return_value=inner_coll)
    mock_coll = MagicMock()
    mock_coll.document = MagicMock(return_value=mock_doc)
    mock_db = MagicMock()
    mock_db.collection = MagicMock(return_value=mock_coll)
    return mock_db


def _build_app(
    auth_ctx: CustomerContext | None = None,
    auth_error: DashboardAuthError | None = None,
) -> FastAPI:
    """Build test app with auth dependency overridden."""
    from starlette.staticfiles import StaticFiles

    app = FastAPI()
    app.add_middleware(SecurityHeadersMiddleware)
    app.include_router(router)

    with contextlib.suppress(Exception):
        app.mount(
            "/static",
            StaticFiles(directory="backend/dashboard/static"),
            name="static",
        )

    @app.exception_handler(DashboardAuthError)
    async def _handler(request: object, exc: DashboardAuthError) -> object:
        return dashboard_auth_error_response(exc, request)  # type: ignore[arg-type]

    if auth_error is not None:

        async def _raise_auth() -> CustomerContext:
            raise auth_error

        app.dependency_overrides[require_dashboard_auth] = _raise_auth
    elif auth_ctx is not None:
        app.dependency_overrides[require_dashboard_auth] = lambda: auth_ctx

    return app


@pytest.fixture
def client() -> TestClient:
    """Client with default team-tier auth."""
    app = _build_app(auth_ctx=_ctx())
    return TestClient(app, raise_server_exceptions=False)


# ---------------------------------------------------------------------------
# SVG helper tests
# ---------------------------------------------------------------------------


class TestDonutSVG:
    @pytest.mark.parametrize(
        ("score", "expected_color"),
        [
            (30, "#dc2626"),
            (60, "#ea580c"),
            (80, "#2563eb"),
            (95, "#16a34a"),
        ],
    )
    def test_donut_score_colors(self, score: float, expected_color: str) -> None:
        svg = render_donut_svg(score)
        assert expected_color in svg
        assert "<svg" in svg
        assert "</svg>" in svg

    def test_donut_zero_and_hundred(self) -> None:
        svg_zero = render_donut_svg(0)
        assert "0%" in svg_zero
        assert "#dc2626" in svg_zero

        svg_hundred = render_donut_svg(100)
        assert "100%" in svg_hundred
        assert "#16a34a" in svg_hundred


class TestSparklineSVG:
    def test_sparkline_empty(self) -> None:
        svg = render_sparkline_svg([])
        assert "<svg" in svg
        assert "</svg>" in svg
        assert "polyline" not in svg

    def test_sparkline_single_point(self) -> None:
        svg = render_sparkline_svg([42.0])
        assert "<svg" in svg
        assert "polyline" in svg


class TestSeverityBarSVG:
    def test_severity_bar_zero_counts(self) -> None:
        svg = render_severity_bar_svg({"critical": 0, "high": 0, "medium": 0, "low": 0})
        assert "<svg" in svg
        assert "rect" not in svg

    def test_severity_bar_proportions(self) -> None:
        svg = render_severity_bar_svg(
            {"critical": 10, "high": 20, "medium": 30, "low": 40},
            width=200,
        )
        assert "#dc2626" in svg
        assert "#ea580c" in svg
        assert "#854d0e" in svg
        assert "#2563eb" in svg
        assert svg.count("<rect") == 4


class TestScoreTrendSVG:
    def test_score_trend_no_drift(self) -> None:
        history = [
            {"score": 80.0, "scanned_at": "2025-01-01T00:00:00+00:00"},
            {"score": 85.0, "scanned_at": "2025-02-01T00:00:00+00:00"},
        ]
        svg = render_score_trend_svg(history, [], show_events=True)
        assert 'fill="#dc2626" opacity="0.8"' not in svg
        assert "polyline" in svg

    def test_score_trend_zone_bands(self) -> None:
        history = [
            {"score": 30.0, "scanned_at": "2025-01-01T00:00:00+00:00"},
            {"score": 90.0, "scanned_at": "2025-03-01T00:00:00+00:00"},
        ]
        svg = render_score_trend_svg(history, [])
        assert svg.count("opacity") >= 3


# ---------------------------------------------------------------------------
# Security headers tests
# ---------------------------------------------------------------------------


class TestSecurityHeaders:
    def _get_with_mock_db(self, client: TestClient) -> Any:
        """GET /dashboard/ with Firestore mocked to return empty projects."""
        mock_db = _mock_firestore_db()

        async def _empty_stream() -> Any:
            return
            yield  # pragma: no cover

        mock_query = MagicMock()
        mock_query.where = MagicMock(return_value=mock_query)
        mock_query.order_by = MagicMock(return_value=mock_query)
        mock_query.stream = _empty_stream

        mock_proj_coll = MagicMock()
        mock_proj_coll.where = MagicMock(return_value=mock_query)

        mock_cust_doc = MagicMock()
        mock_cust_doc.collection = MagicMock(return_value=mock_proj_coll)

        mock_cust_coll = MagicMock()
        mock_cust_coll.document = MagicMock(return_value=mock_cust_doc)
        mock_db.collection = MagicMock(return_value=mock_cust_coll)

        with (
            patch(_PATCH_FIRESTORE_DB, return_value=mock_db),
            patch("backend.dashboard.cache.get_cached_projects", return_value=None),
            patch("backend.dashboard.cache.set_cached_projects"),
        ):
            return client.get("/dashboard/")

    def test_security_headers_present(self, client: TestClient) -> None:
        resp = self._get_with_mock_db(client)
        expected_headers = [
            "content-security-policy",
            "strict-transport-security",
            "x-content-type-options",
            "x-frame-options",
            "referrer-policy",
            "permissions-policy",
            "cross-origin-opener-policy",
        ]
        for header in expected_headers:
            assert header in resp.headers, f"Missing header: {header}"

    def test_csp_header_values(self, client: TestClient) -> None:
        resp = self._get_with_mock_db(client)
        csp = resp.headers["content-security-policy"]
        assert "default-src 'none'" in csp
        assert "script-src 'self' 'unsafe-eval'" in csp
        assert "frame-ancestors 'none'" in csp
        assert "upgrade-insecure-requests" in csp


# ---------------------------------------------------------------------------
# Theme preference tests
# ---------------------------------------------------------------------------


class TestThemePreference:
    def test_theme_toggle_persist(self, client: TestClient) -> None:
        session = _session_data()
        mock_db = _mock_firestore_db()

        with (
            patch(_PATCH_SESSION_COOKIE, return_value="__Host-session"),
            patch(_PATCH_SESSION_GET, new_callable=AsyncMock, return_value=session),
            patch(_PATCH_SESSION_VALIDATE, return_value=True),
            patch(_PATCH_FIRESTORE_DB, return_value=mock_db),
        ):
            client.cookies.set("__Host-session", "encrypted")
            resp = client.put(
                "/dashboard/partials/theme-preference",
                json={"theme": "dark"},
                headers={"X-CSRF-Token": "test-csrf-token"},
            )

        assert resp.status_code == 204
        mock_db.collection.return_value.document.return_value.update.assert_called_once_with(
            {"theme_preference": "dark"}
        )

    def test_theme_toggle_invalid(self, client: TestClient) -> None:
        session = _session_data()

        with (
            patch(_PATCH_SESSION_COOKIE, return_value="__Host-session"),
            patch(_PATCH_SESSION_GET, new_callable=AsyncMock, return_value=session),
            patch(_PATCH_SESSION_VALIDATE, return_value=True),
        ):
            client.cookies.set("__Host-session", "encrypted")
            resp = client.put(
                "/dashboard/partials/theme-preference",
                json={"theme": "purple"},
                headers={"X-CSRF-Token": "test-csrf-token"},
            )

        assert resp.status_code == 422


# ---------------------------------------------------------------------------
# Consent tests
# ---------------------------------------------------------------------------


class TestConsent:
    def test_consent_accept_writes_firestore(self, client: TestClient) -> None:
        session = _session_data()
        mock_db = _mock_firestore_db()

        with (
            patch(_PATCH_SESSION_COOKIE, return_value="__Host-session"),
            patch(_PATCH_SESSION_GET, new_callable=AsyncMock, return_value=session),
            patch(_PATCH_SESSION_VALIDATE, return_value=True),
            patch(_PATCH_FIRESTORE_DB, return_value=mock_db),
        ):
            client.cookies.set("__Host-session", "encrypted")
            resp = client.post(
                "/dashboard/consent/accept",
                json={"tos_version": "1.0", "privacy_version": "1.0"},
                headers={"X-CSRF-Token": "test-csrf-token"},
            )

        assert resp.status_code == 204
        cust_doc = mock_db.collection.return_value.document.return_value
        inner_doc = cust_doc.collection.return_value.document.return_value
        inner_doc.set.assert_called_once()
        call_data = inner_doc.set.call_args[0][0]
        assert "dashboard_tos_accepted_at" in call_data
        assert call_data["dashboard_tos_version"] == "1.0"

    def test_consent_accept_idempotent(self, client: TestClient) -> None:
        session = _session_data()
        mock_db = _mock_firestore_db()

        with (
            patch(_PATCH_SESSION_COOKIE, return_value="__Host-session"),
            patch(_PATCH_SESSION_GET, new_callable=AsyncMock, return_value=session),
            patch(_PATCH_SESSION_VALIDATE, return_value=True),
            patch(_PATCH_FIRESTORE_DB, return_value=mock_db),
        ):
            client.cookies.set("__Host-session", "encrypted")
            for _ in range(2):
                resp = client.post(
                    "/dashboard/consent/accept",
                    json={"tos_version": "1.0", "privacy_version": "1.0"},
                    headers={"X-CSRF-Token": "test-csrf-token"},
                )
                assert resp.status_code == 204


# ---------------------------------------------------------------------------
# CSRF tests
# ---------------------------------------------------------------------------


class TestCSRF:
    def test_csrf_required_on_post(self, client: TestClient) -> None:
        session = _session_data()
        with (
            patch(_PATCH_SESSION_COOKIE, return_value="__Host-session"),
            patch(_PATCH_SESSION_GET, new_callable=AsyncMock, return_value=session),
            patch(_PATCH_SESSION_VALIDATE, return_value=False),
        ):
            client.cookies.set("__Host-session", "encrypted")
            resp = client.post(
                "/dashboard/consent/accept",
                json={"tos_version": "1.0"},
            )
        assert resp.status_code == 403

    def test_csrf_required_on_put(self, client: TestClient) -> None:
        session = _session_data()
        with (
            patch(_PATCH_SESSION_COOKIE, return_value="__Host-session"),
            patch(_PATCH_SESSION_GET, new_callable=AsyncMock, return_value=session),
            patch(_PATCH_SESSION_VALIDATE, return_value=False),
        ):
            client.cookies.set("__Host-session", "encrypted")
            resp = client.put(
                "/dashboard/partials/theme-preference",
                json={"theme": "dark"},
            )
        assert resp.status_code == 403

    def test_csrf_required_on_delete(self, client: TestClient) -> None:
        session = _session_data()
        with (
            patch(_PATCH_SESSION_COOKIE, return_value="__Host-session"),
            patch(_PATCH_SESSION_GET, new_callable=AsyncMock, return_value=session),
            patch(_PATCH_SESSION_VALIDATE, return_value=False),
        ):
            client.cookies.set("__Host-session", "encrypted")
            resp = client.delete(
                "/dashboard/projects/proj1/shared-links/link1",
            )
        assert resp.status_code == 403

    def test_csrf_htmx_header(self, client: TestClient) -> None:
        session = _session_data()
        mock_db = _mock_firestore_db()

        with (
            patch(_PATCH_SESSION_COOKIE, return_value="__Host-session"),
            patch(_PATCH_SESSION_GET, new_callable=AsyncMock, return_value=session),
            patch(_PATCH_SESSION_VALIDATE, return_value=True),
            patch(_PATCH_FIRESTORE_DB, return_value=mock_db),
        ):
            client.cookies.set("__Host-session", "encrypted")
            resp = client.put(
                "/dashboard/partials/theme-preference",
                json={"theme": "dark"},
                headers={"X-CSRF-Token": "test-csrf-token"},
            )
        assert resp.status_code == 204

    def test_csrf_form_field(self, client: TestClient) -> None:
        session = _session_data()
        mock_db = _mock_firestore_db()

        with (
            patch(_PATCH_SESSION_COOKIE, return_value="__Host-session"),
            patch(_PATCH_SESSION_GET, new_callable=AsyncMock, return_value=session),
            patch(_PATCH_SESSION_VALIDATE, return_value=True),
            patch(_PATCH_FIRESTORE_DB, return_value=mock_db),
        ):
            client.cookies.set("__Host-session", "encrypted")
            resp = client.post(
                "/dashboard/consent/accept",
                data={
                    "_csrf": "test-csrf-token",
                    "tos_version": "1.0",
                    "privacy_version": "1.0",
                },
            )
        assert resp.status_code == 204


# ---------------------------------------------------------------------------
# Auth middleware tests
# ---------------------------------------------------------------------------


class TestAuthMiddleware:
    def test_dashboard_requires_auth(self) -> None:
        app = _build_app(auth_error=DashboardAuthError(redirect=True))
        c = TestClient(app, raise_server_exceptions=False)
        resp = c.get("/dashboard/", follow_redirects=False)
        assert resp.status_code == 302

    def test_dashboard_htmx_expired_session(self) -> None:
        app = _build_app(auth_error=DashboardAuthError(redirect=True, htmx=True))
        c = TestClient(app, raise_server_exceptions=False)
        resp = c.get(
            "/dashboard/",
            headers={"HX-Request": "true"},
        )
        # htmx-aware redirect returns 200 with HX-Redirect header
        assert resp.status_code in (200, 302)

    def test_dashboard_tier_gate(self) -> None:
        app = _build_app(
            auth_error=DashboardAuthError(
                redirect=False,
                message="Dashboard not available on community tier.",
            )
        )
        c = TestClient(app, raise_server_exceptions=False)
        resp = c.get("/dashboard/")
        assert resp.status_code == 403

    def test_post_cancellation_grace(self) -> None:
        ctx = _ctx(cancellation_pending=True, role="viewer")
        app = _build_app(auth_ctx=ctx)
        c = TestClient(app, raise_server_exceptions=False)

        mock_db = _mock_firestore_db()

        async def _empty_stream() -> Any:
            return
            yield  # pragma: no cover

        mock_query = MagicMock()
        mock_query.where = MagicMock(return_value=mock_query)
        mock_query.order_by = MagicMock(return_value=mock_query)
        mock_query.stream = _empty_stream

        mock_proj_coll = MagicMock()
        mock_proj_coll.where = MagicMock(return_value=mock_query)

        mock_cust_doc = MagicMock()
        mock_cust_doc.collection = MagicMock(return_value=mock_proj_coll)

        mock_cust_coll = MagicMock()
        mock_cust_coll.document = MagicMock(return_value=mock_cust_doc)
        mock_db.collection = MagicMock(return_value=mock_cust_coll)

        with (
            patch(_PATCH_FIRESTORE_DB, return_value=mock_db),
            patch("backend.dashboard.cache.get_cached_projects", return_value=None),
            patch("backend.dashboard.cache.set_cached_projects"),
        ):
            resp = c.get("/dashboard/")
        assert resp.status_code == 200


# ---------------------------------------------------------------------------
# API key regeneration tests
# ---------------------------------------------------------------------------


class TestRegenerateAPIKey:
    def test_regenerate_api_key(self, client: TestClient) -> None:
        session = _session_data()
        mock_db = _mock_firestore_db()

        with (
            patch(_PATCH_SESSION_COOKIE, return_value="__Host-session"),
            patch(_PATCH_SESSION_GET, new_callable=AsyncMock, return_value=session),
            patch(_PATCH_SESSION_VALIDATE, return_value=True),
            patch(_PATCH_FIRESTORE_DB, return_value=mock_db),
        ):
            client.cookies.set("__Host-session", "encrypted")
            resp = client.post(
                "/dashboard/settings/regenerate-api-key",
                headers={"X-CSRF-Token": "test-csrf-token"},
            )

        assert resp.status_code == 200
        data = resp.json()
        assert "api_key_last4" in data
        assert len(data["api_key_last4"]) == 4

        call_args = (
            mock_db.collection.return_value.document.return_value.update.call_args[0][0]
        )
        assert "api_key_hash" in call_args
        assert len(call_args["api_key_hash"]) == 64


# ---------------------------------------------------------------------------
# Link account tests
# ---------------------------------------------------------------------------


class TestLinkAccount:
    def test_link_account_valid_key(self) -> None:
        # link-account has no auth dependency — use bare app
        app = _build_app(auth_ctx=_ctx())
        c = TestClient(app, raise_server_exceptions=False)
        session = _session_data()

        with (
            patch(_PATCH_SESSION_COOKIE, return_value="__Host-session"),
            patch(_PATCH_SESSION_GET, new_callable=AsyncMock, return_value=session),
            patch(_PATCH_SESSION_VALIDATE, return_value=True),
            patch(
                _PATCH_FIRESTORE_GET_KEY,
                new_callable=AsyncMock,
                return_value={"customer_id": "cus_test", "status": "active"},
            ),
            patch(_PATCH_FIRESTORE_UPDATE, new_callable=AsyncMock) as mock_upd,
        ):
            c.cookies.set("__Host-session", "encrypted")
            resp = c.post(
                "/dashboard/link-account",
                data={
                    "_csrf": "test-csrf-token",
                    "license_key": "cf_live_" + "a" * 32,
                },
                follow_redirects=False,
            )

        assert resp.status_code == 303
        assert resp.headers["location"] == "/dashboard/"
        mock_upd.assert_called_once()

    def test_link_account_invalid_key(self) -> None:
        app = _build_app(auth_ctx=_ctx())
        c = TestClient(app, raise_server_exceptions=False)
        session = _session_data()

        with (
            patch(_PATCH_SESSION_COOKIE, return_value="__Host-session"),
            patch(_PATCH_SESSION_GET, new_callable=AsyncMock, return_value=session),
        ):
            c.cookies.set("__Host-session", "encrypted")
            resp = c.post(
                "/dashboard/link-account",
                data={
                    "_csrf": "test-csrf-token",
                    "license_key": "bad_key_format",
                },
            )

        assert resp.status_code == 200
        assert "Invalid license key format" in resp.text


# ---------------------------------------------------------------------------
# Cache tests
# ---------------------------------------------------------------------------


class TestCache:
    def test_projects_cache_ttl(self) -> None:
        from backend.dashboard.cache import (
            get_cached_projects,
            set_cached_projects,
        )
        from backend.dashboard.context import DashboardProjectCard

        card = DashboardProjectCard(
            project_id="p1",
            project_label="Test",
            client_label=None,
            cloud="aws",
            frameworks=["soc2"],
            status="active",
            score=85.0,
            score_delta=2.0,
            passed=10,
            failed=2,
            severity_counts={"critical": 0, "high": 1, "medium": 1, "low": 0},
            last_scan_at="2025-01-01T00:00:00+00:00",
            drift_monitoring=False,
            drift_mode=None,
            drift_last_event_at=None,
            has_cloud_intelligence=False,
            cross_validation_score=None,
        )

        set_cached_projects("cust1", [card])
        result = get_cached_projects("cust1")
        assert result is not None
        assert len(result) == 1
        assert result[0].project_id == "p1"

        assert get_cached_projects("cust2") is None

    def test_findings_cache_ttl(self) -> None:
        from backend.dashboard.cache import (
            get_cached_findings,
            set_cached_findings,
        )

        findings: list[dict[str, Any]] = [{"id": "f1", "status": "fail"}]
        set_cached_findings("c1", "p1", "s1", findings)

        hit = get_cached_findings("c1", "p1", "s1")
        assert hit is not None
        assert hit[0]["id"] == "f1"

        assert get_cached_findings("c1", "p1", "s2") is None


# ---------------------------------------------------------------------------
# Template rendering tests
# ---------------------------------------------------------------------------


class TestBaseLayout:
    def _render_base(self, **overrides: Any) -> str:
        from jinja2 import Environment, FileSystemLoader, select_autoescape

        env = Environment(
            loader=FileSystemLoader("backend/dashboard/templates"),
            autoescape=select_autoescape(["html"]),
        )
        template = env.get_template("base.html")
        ctx: dict[str, Any] = {
            "csrf_token": "test-csrf-123",
            "theme_preference": "light",
            "show_consent_banner": False,
            "active_page": "projects",
            "customer_email": "user@example.com",
            "tier": "team",
        }
        ctx.update(overrides)
        return template.render(**ctx)

    def test_base_layout_csrf_meta(self) -> None:
        html = self._render_base()
        assert 'content="test-csrf-123"' in html
        assert 'name="csrf-token"' in html

    def test_base_layout_hx_headers(self) -> None:
        html = self._render_base()
        assert "hx-headers" in html
        assert "X-CSRF-Token" in html

    def test_base_layout_theme_attribute(self) -> None:
        html = self._render_base(theme_preference="dark")
        assert 'data-theme="dark"' in html

        html_light = self._render_base(theme_preference="light")
        assert 'data-theme="light"' in html_light

    def test_base_layout_skip_link(self) -> None:
        html = self._render_base()
        assert "Skip to main content" in html
        assert 'href="#main-content"' in html

    def test_base_layout_sr_announcer(self) -> None:
        html = self._render_base()
        assert 'id="sr-announcer"' in html
        assert 'aria-live="polite"' in html

    def test_base_layout_view_transition_meta(self) -> None:
        html = self._render_base()
        assert 'name="view-transition"' in html
        assert 'content="same-origin"' in html


# ---------------------------------------------------------------------------
# Route registration test
# ---------------------------------------------------------------------------


class TestRouteRegistration:
    def test_all_routes_registered(self) -> None:
        route_paths: set[str] = set()
        for route in router.routes:
            if hasattr(route, "path"):
                route_paths.add(route.path)  # type: ignore[union-attr]

        expected = [
            "/dashboard/",
            "/dashboard/projects/{project_id}",
            "/dashboard/projects/{project_id}/scans/{scan_id}",
            "/dashboard/settings",
            "/dashboard/shared/{share_token}",
            "/dashboard/consent/accept",
            "/dashboard/partials/theme-preference",
            "/dashboard/settings/regenerate-api-key",
            "/dashboard/link-account",
            "/dashboard/partials/project-card/{project_id}",
            "/dashboard/partials/findings/{project_id}",
            "/dashboard/partials/controls/{project_id}",
            "/dashboard/partials/scan-history/{project_id}",
            "/dashboard/partials/drift-timeline/{project_id}",
            "/dashboard/partials/score-trend/{project_id}",
            "/dashboard/partials/cloud-intelligence/{project_id}",
            "/dashboard/partials/alert-config/{project_id}",
            "/dashboard/partials/command-palette",
            "/dashboard/partials/shared-links/{project_id}",
            "/dashboard/projects/{project_id}/scans/{scan_id}/share",
            "/dashboard/projects/{project_id}/shared-links/{link_id}",
        ]

        for path in expected:
            assert path in route_paths, f"Missing route: {path}"
