""" Tier 3 — E2E (SSR) tests for the enriched repo card component. These tests exercise the full HTTP path: a real ASGI client hits the domain detail route, the route calls enrich_repo_cards(), and we assert the rendered HTML contains the expected enrichment signals. No JS execution — all signals must be server-side rendered. Test IDs -------- T300 — domain detail page returns 200 and contains rc-card markup T301 — pulse sparkline SVG is rendered for repos with commits T302 — health badge class matches actual health signal in HTML T303 — autonomy stat renders correct percentage for an all-agent repo T304 — hottest symbol name appears in rc-intel row T305 — blast leader name appears in rc-intel row T306 — repos with no intel data render clean badge and zero autonomy T307 — ?format=json response is unaffected by enrichment (no crash) """ from __future__ import annotations import secrets from datetime import datetime, timedelta, timezone import pytest from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import text from musehub.db.musehub_domain_models import MusehubDomain from musehub.db.musehub_intel_models import MusehubIntelDead, MusehubSymbolIntel from musehub.db.musehub_repo_models import MusehubRepo from musehub.core.genesis import compute_identity_id, compute_repo_id from tests.factories import create_commit, create_repo # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _utc_now() -> datetime: return datetime.now(tz=timezone.utc) def _domain_id() -> str: return f"sha256:{secrets.token_hex(32)}" async def _make_domain( db: AsyncSession, *, author_slug: str = "testauthor", slug: str = "testdomain", display_name: str = "Test Domain", ) -> MusehubDomain: """Seed a MusehubDomain and return it.""" domain = MusehubDomain( domain_id=_domain_id(), author_slug=author_slug, slug=slug, display_name=display_name, description="A test domain", version="0.1.0", viewer_type="code", capabilities={ "dimensions": [{"name": "symbol", "description": "Symbol dimension"}], "kinds": ["function"], "merge_semantics": "ot", }, ) db.add(domain) await db.commit() await db.refresh(domain) return domain async def _attach_repo_to_domain( db: AsyncSession, repo: MusehubRepo, domain: MusehubDomain, ) -> None: """Link a repo to a domain by setting domain_id.""" await db.execute( text("UPDATE musehub_repos SET domain_id = :did WHERE repo_id = :rid"), {"did": domain.domain_id, "rid": repo.repo_id}, ) await db.commit() async def _make_public_repo( db: AsyncSession, *, owner: str = "testowner", slug: str | None = None, ) -> MusehubRepo: """Seed a public repo using the factory helper.""" repo = await create_repo(db, visibility="public") if slug: # Patch slug for readable assertions await db.execute( text("UPDATE musehub_repos SET slug = :s, owner = :o WHERE repo_id = :rid"), {"s": slug, "o": owner, "rid": repo.repo_id}, ) await db.commit() await db.refresh(repo) return repo async def _add_agent_commit(db: AsyncSession, repo_id: str) -> None: """Insert a commit with agent_id set.""" commit = await create_commit(db, repo_id, timestamp=_utc_now()) await db.execute( text("UPDATE musehub_commits SET agent_id = 'claude-code' WHERE commit_id = :cid"), {"cid": commit.commit_id}, ) await db.commit() async def _insert_symbol_intel( db: AsyncSession, repo_id: str, address: str, churn_30d: int = 0, blast: int = 0, ) -> None: row = MusehubSymbolIntel( repo_id=repo_id, address=address, churn_30d=churn_30d, blast=blast ) db.add(row) await db.commit() async def _insert_dead(db: AsyncSession, repo_id: str, address: str) -> None: from musehub.db.musehub_intel_models import MusehubIntelDead row = MusehubIntelDead( repo_id=repo_id, address=address, kind="function", confidence="high", ref="main", ) db.add(row) await db.commit() def _domain_url(author_slug: str, slug: str) -> str: return f"/domains/@{author_slug}/{slug}" # --------------------------------------------------------------------------- # T300 — page returns 200 with rc-card markup # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_t300_domain_detail_returns_rc_cards( client: AsyncClient, db_session: AsyncSession, ) -> None: """T300: GET /domains/@author/slug returns 200 and renders rc-card elements.""" domain = await _make_domain(db_session) repo = await _make_public_repo(db_session) await _attach_repo_to_domain(db_session, repo, domain) resp = await client.get(_domain_url(domain.author_slug, domain.slug)) assert resp.status_code == 200 assert "text/html" in resp.headers["content-type"] assert "rc-card" in resp.text # --------------------------------------------------------------------------- # T301 — sparkline SVG rendered when commits exist # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_t301_sparkline_rendered_for_repo_with_commits( client: AsyncClient, db_session: AsyncSession, ) -> None: """T301: a repo with recent commits renders a element.""" domain = await _make_domain(db_session, slug="sparktest") repo = await _make_public_repo(db_session) await _attach_repo_to_domain(db_session, repo, domain) await create_commit(db_session, repo.repo_id, timestamp=_utc_now()) resp = await client.get(_domain_url(domain.author_slug, domain.slug)) assert resp.status_code == 200 assert 'class="rc-sparkline"' in resp.text # --------------------------------------------------------------------------- # T302 — health badge class matches signal # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_t302_health_badge_risk_when_errors( client: AsyncClient, db_session: AsyncSession, ) -> None: """T302: health badge gauge aria-label is "risk" when breakage errors exist.""" from musehub.db.musehub_intel_models import MusehubIntelBreakageMeta domain = await _make_domain(db_session, slug="healthtest") repo = await _make_public_repo(db_session) await _attach_repo_to_domain(db_session, repo, domain) db_session.add(MusehubIntelBreakageMeta( repo_id=repo.repo_id, total_issues=3, error_count=3, warning_count=0, file_count=1, ref="main", )) await db_session.commit() resp = await client.get(_domain_url(domain.author_slug, domain.slug)) assert resp.status_code == 200 assert 'aria-label="risk"' in resp.text @pytest.mark.asyncio async def test_t302b_health_badge_warn_when_dead( client: AsyncClient, db_session: AsyncSession, ) -> None: """T302b: health badge gauge aria-label is "warn" when dead symbols exist.""" domain = await _make_domain(db_session, slug="warntest") repo = await _make_public_repo(db_session) await _attach_repo_to_domain(db_session, repo, domain) await _insert_dead(db_session, repo.repo_id, "src/old.py::stale_fn") resp = await client.get(_domain_url(domain.author_slug, domain.slug)) assert resp.status_code == 200 assert 'aria-label="warn"' in resp.text # --------------------------------------------------------------------------- # T303 — autonomy percentage rendered correctly # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_t303_autonomy_pct_rendered_for_all_agent_repo( client: AsyncClient, db_session: AsyncSession, ) -> None: """T303: a repo with only agent commits renders '100%' in the autonomy stat.""" domain = await _make_domain(db_session, slug="autonomytest") repo = await _make_public_repo(db_session) await _attach_repo_to_domain(db_session, repo, domain) for _ in range(3): await _add_agent_commit(db_session, repo.repo_id) resp = await client.get(_domain_url(domain.author_slug, domain.slug)) assert resp.status_code == 200 assert "100%" in resp.text assert "autonomy" in resp.text # --------------------------------------------------------------------------- # T304 — hottest symbol name in rc-intel row # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_t304_hottest_symbol_rendered( client: AsyncClient, db_session: AsyncSession, ) -> None: """T304: the hottest symbol's short name appears in an rc-intel row.""" domain = await _make_domain(db_session, slug="hottesttest") repo = await _make_public_repo(db_session) await _attach_repo_to_domain(db_session, repo, domain) await _insert_symbol_intel( db_session, repo.repo_id, "src/core.py::compute_totals", churn_30d=42 ) resp = await client.get(_domain_url(domain.author_slug, domain.slug)) assert resp.status_code == 200 assert "compute_totals" in resp.text assert "hottest" in resp.text # --------------------------------------------------------------------------- # T305 — blast leader name in rc-intel row # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_t305_blast_leader_rendered( client: AsyncClient, db_session: AsyncSession, ) -> None: """T305: the blast leader's short name appears in an rc-intel row.""" domain = await _make_domain(db_session, slug="blasttest") repo = await _make_public_repo(db_session) await _attach_repo_to_domain(db_session, repo, domain) await _insert_symbol_intel( db_session, repo.repo_id, "src/api.py::dispatch_event", blast=512 ) resp = await client.get(_domain_url(domain.author_slug, domain.slug)) assert resp.status_code == 200 assert "dispatch_event" in resp.text assert "blast" in resp.text # --------------------------------------------------------------------------- # T306 — clean / zero enrichment for repo with no intel # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_t306_clean_card_when_no_intel( client: AsyncClient, db_session: AsyncSession, ) -> None: """T306: a repo with zero intel data renders gauge aria-label="clean", no crash.""" domain = await _make_domain(db_session, slug="cleantest") repo = await _make_public_repo(db_session) await _attach_repo_to_domain(db_session, repo, domain) resp = await client.get(_domain_url(domain.author_slug, domain.slug)) assert resp.status_code == 200 assert 'aria-label="clean"' in resp.text # No intel rows → no hottest/blast section rendered assert "hottest" not in resp.text # --------------------------------------------------------------------------- # T307 — ?format=json unaffected by enrichment # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_t307_json_format_not_broken_by_enrichment( client: AsyncClient, db_session: AsyncSession, ) -> None: """T307: ?format=json still returns valid JSON after enrichment was wired up.""" domain = await _make_domain(db_session, slug="jsontest") repo = await _make_public_repo(db_session) await _attach_repo_to_domain(db_session, repo, domain) resp = await client.get( _domain_url(domain.author_slug, domain.slug), params={"format": "json"}, ) assert resp.status_code == 200 assert resp.headers["content-type"].startswith("application/json") data = resp.json() assert "domain" in data assert "repos" in data