"""Tier 3 — End-to-end navigation tests for the clone browser (issue #17). Full navigation flows over the ASGI stack with seeded DB state. No mocking — all requests go through the real route → DB → template pipeline. Cases: E01 Intel Hub → Clones list (dashboard link renders) E02 Clones list → Cluster detail (cl-row link resolves) E03 Detail → back to Clones (breadcrumb link works) E04 Tier filter round-trip: exact → near → all E05 Top filter round-trip: 50 pill → back to 20 E06 Empty repo path — no 500 at /intel or /intel/clones """ from __future__ import annotations import json 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 long_id from musehub.db.musehub_intel_models import MusehubIntelClones from tests.factories import create_repo _REF = long_id("a" * 64) _HASH_A = long_id("1" * 64) _HASH_B = long_id("2" * 64) async def _seed(session: AsyncSession, repo_id: str) -> None: """Seed one exact and one near cluster for navigation tests.""" for h, tier, mc in [(_HASH_A, "exact", 6), (_HASH_B, "near", 3)]: members = json.dumps([ {"address": f"src/a.py::fn{i}", "kind": "function", "language": "Python", "body_hash": long_id("a" * 64), "signature_id": long_id("b" * 64), "content_id": long_id("a" * 64)} for i in range(mc) ]) await session.execute( pg_insert(MusehubIntelClones) .values(repo_id=repo_id, cluster_hash=h, tier=tier, member_count=mc, members_json=members, ref=_REF) .on_conflict_do_update( index_elements=["repo_id", "cluster_hash"], set_={"tier": tier, "member_count": mc, "members_json": members}, ) ) await session.commit() @pytest_asyncio.fixture async def repo(db_session: AsyncSession) -> MusehubRepo: r = await create_repo(db_session, owner="e2euser", slug="clone-e2e") await _seed(db_session, str(r.repo_id)) return r @pytest_asyncio.fixture async def empty_repo(db_session: AsyncSession) -> MusehubRepo: return await create_repo(db_session, owner="e2euser", slug="empty-e2e") class TestClonesE2E: """Full navigation flows — no mocking.""" @pytest.mark.asyncio async def test_E01_dashboard_links_to_clones( self, client: AsyncClient, repo: MusehubRepo ) -> None: """Intel Hub page contains a link to /intel/clones.""" r = await client.get("/e2euser/clone-e2e/intel") assert r.status_code == 200 assert b"/intel/clones" in r.content @pytest.mark.asyncio async def test_E02_list_row_links_to_detail( self, client: AsyncClient, repo: MusehubRepo ) -> None: """A cluster row on the list page carries a link to its detail page.""" r = await client.get("/e2euser/clone-e2e/intel/clones") assert r.status_code == 200 assert b"intel/clones/detail?cluster=" in r.content @pytest.mark.asyncio async def test_E03_detail_back_link_to_clones( self, client: AsyncClient, repo: MusehubRepo ) -> None: """Detail page breadcrumb links back to the clones list.""" r = await client.get( f"/e2euser/clone-e2e/intel/clones/detail?cluster={_HASH_A}" ) assert r.status_code == 200 assert "← Clones" in r.text assert b"/intel/clones" in r.content @pytest.mark.asyncio async def test_E04_tier_filter_round_trip( self, client: AsyncClient, repo: MusehubRepo ) -> None: """Tier filter correctly switches between exact, near, and all.""" r_exact = await client.get("/e2euser/clone-e2e/intel/clones?tier=exact") assert r_exact.status_code == 200 assert b"cl-badge--exact" in r_exact.content assert b"cl-badge--near" not in r_exact.content r_near = await client.get("/e2euser/clone-e2e/intel/clones?tier=near") assert r_near.status_code == 200 assert b"cl-badge--near" in r_near.content assert b"cl-badge--exact" not in r_near.content r_all = await client.get("/e2euser/clone-e2e/intel/clones") assert r_all.status_code == 200 assert b"cl-badge--exact" in r_all.content assert b"cl-badge--near" in r_all.content @pytest.mark.asyncio async def test_E05_top_filter_round_trip( self, client: AsyncClient, repo: MusehubRepo ) -> None: """Top filter renders the active pill correctly.""" r50 = await client.get("/e2euser/clone-e2e/intel/clones?top=50") assert r50.status_code == 200 assert b"top=50" in r50.content r20 = await client.get("/e2euser/clone-e2e/intel/clones?top=20") assert r20.status_code == 200 assert b"top=20" in r20.content @pytest.mark.asyncio async def test_E06_empty_repo_no_500( self, client: AsyncClient, empty_repo: MusehubRepo ) -> None: """Empty repo returns 200 at both /intel and /intel/clones — no 500.""" r_dash = await client.get("/e2euser/empty-e2e/intel") assert r_dash.status_code == 200 r_list = await client.get("/e2euser/empty-e2e/intel/clones") assert r_list.status_code == 200 assert b"intel.code.clones" in r_list.content