"""TDD spec for Phase 5 — navigation + Intel Hub integration (issue #11). Verifies: - Back link to /intel on list page and empty-state - Page title contains "Blast Risk" - Detail page back link to /intel/blast-risk - Detail page title contains "Blast Risk" Layers: P5_01 list page — back link to /intel present P5_02 list empty-state — back link to /intel present P5_03 list page — title contains "Blast Risk" P5_04 detail page — back link to /intel/blast-risk present P5_05 detail page — title contains "Blast Risk" """ from __future__ import annotations import secrets import pytest import pytest_asyncio from httpx import AsyncClient from sqlalchemy.dialects.postgresql import insert as pg_insert from sqlalchemy.ext.asyncio import AsyncSession from muse.core.types import fake_id, long_id from musehub.db.musehub_intel_models import MusehubIntelBlastRisk from musehub.db.musehub_repo_models import MusehubRepo from tests.factories import create_repo def _uid() -> str: return fake_id(secrets.token_hex(16)) _OWNER = "testuser" _SLUG = "brnav" _REF = long_id("e" * 64) async def _seed_risk(session: AsyncSession, repo_id: str, *, address: str) -> None: stmt = ( pg_insert(MusehubIntelBlastRisk) .values( repo_id=repo_id, address=address, kind="function", risk="high", risk_score=60, impact_score=0.5, churn_score=0.5, test_gap_score=1.0, coupling_score=0.3, ref=_REF, ) .on_conflict_do_nothing() ) await session.execute(stmt) await session.flush() @pytest_asyncio.fixture async def nav_repo(db_session: AsyncSession) -> MusehubRepo: return await create_repo(db_session, owner=_OWNER, slug=_SLUG) @pytest_asyncio.fixture async def nav_repo_with_row(db_session: AsyncSession, nav_repo: MusehubRepo) -> MusehubRepo: repo_id = nav_repo.repo_id await db_session.commit() await _seed_risk(db_session, repo_id, address="pkg/nav.py::nav_fn") await db_session.commit() return nav_repo class TestBlastRiskNav: @pytest.mark.asyncio async def test_P5_01_list_page_has_back_link_to_intel( self, client: AsyncClient, nav_repo_with_row: MusehubRepo ) -> None: resp = await client.get(f"/{_OWNER}/{_SLUG}/intel/blast-risk") assert "/intel" in resp.text @pytest.mark.asyncio async def test_P5_02_empty_list_has_back_link_to_intel( self, client: AsyncClient, nav_repo: MusehubRepo ) -> None: resp = await client.get(f"/{_OWNER}/{_SLUG}/intel/blast-risk") assert "/intel" in resp.text @pytest.mark.asyncio async def test_P5_03_list_page_title_contains_blast_risk( self, client: AsyncClient, nav_repo: MusehubRepo ) -> None: resp = await client.get(f"/{_OWNER}/{_SLUG}/intel/blast-risk") assert "Blast Risk" in resp.text @pytest.mark.asyncio async def test_P5_04_detail_page_has_back_link_to_blast_risk( self, client: AsyncClient, nav_repo_with_row: MusehubRepo ) -> None: resp = await client.get( f"/{_OWNER}/{_SLUG}/intel/blast-risk/detail", params={"address": "pkg/nav.py::nav_fn"}, ) assert "blast-risk" in resp.text @pytest.mark.asyncio async def test_P5_05_detail_page_title_contains_blast_risk( self, client: AsyncClient, nav_repo_with_row: MusehubRepo ) -> None: resp = await client.get( f"/{_OWNER}/{_SLUG}/intel/blast-risk/detail", params={"address": "pkg/nav.py::nav_fn"}, ) assert "Blast Risk" in resp.text