gabriel / musehub public

test_repo_card_e2e.py file-level

at sha256:3 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 πŸ’₯ blast risk
sha256:0 fix: fall back to any indexed mpack in read_object_bytes when push mpac… · gabriel · Jun 17, 2026
1 """
2 Tier 3 β€” E2E (SSR) tests for the enriched repo card component.
3
4 These tests exercise the full HTTP path: a real ASGI client hits the domain
5 detail route, the route calls enrich_repo_cards(), and we assert the rendered
6 HTML contains the expected enrichment signals. No JS execution β€” all signals
7 must be server-side rendered.
8
9 Test IDs
10 --------
11 T300 β€” domain detail page returns 200 and contains rc-card markup
12 T301 β€” pulse sparkline SVG is rendered for repos with commits
13 T302 β€” health badge class matches actual health signal in HTML
14 T303 β€” autonomy stat renders correct percentage for an all-agent repo
15 T304 β€” hottest symbol name appears in rc-intel row
16 T305 β€” blast leader name appears in rc-intel row
17 T306 β€” repos with no intel data render clean badge and zero autonomy
18 T307 β€” ?format=json response is unaffected by enrichment (no crash)
19 """
20 from __future__ import annotations
21
22 import secrets
23 from datetime import datetime, timedelta, timezone
24
25 import pytest
26 from httpx import AsyncClient
27 from sqlalchemy.ext.asyncio import AsyncSession
28 from sqlalchemy import text
29
30 from musehub.db.musehub_domain_models import MusehubDomain
31 from musehub.db.musehub_intel_models import MusehubIntelDead, MusehubSymbolIntel
32 from musehub.db.musehub_repo_models import MusehubRepo
33 from musehub.core.genesis import compute_identity_id, compute_repo_id
34 from tests.factories import create_commit, create_repo
35
36
37 # ---------------------------------------------------------------------------
38 # Helpers
39 # ---------------------------------------------------------------------------
40
41 def _utc_now() -> datetime:
42 return datetime.now(tz=timezone.utc)
43
44
45 def _domain_id() -> str:
46 return f"sha256:{secrets.token_hex(32)}"
47
48
49 async def _make_domain(
50 db: AsyncSession,
51 *,
52 author_slug: str = "testauthor",
53 slug: str = "testdomain",
54 display_name: str = "Test Domain",
55 ) -> MusehubDomain:
56 """Seed a MusehubDomain and return it."""
57 domain = MusehubDomain(
58 domain_id=_domain_id(),
59 author_slug=author_slug,
60 slug=slug,
61 display_name=display_name,
62 description="A test domain",
63 version="0.1.0",
64 viewer_type="code",
65 capabilities={
66 "dimensions": [{"name": "symbol", "description": "Symbol dimension"}],
67 "kinds": ["function"],
68 "merge_semantics": "ot",
69 },
70 )
71 db.add(domain)
72 await db.commit()
73 await db.refresh(domain)
74 return domain
75
76
77 async def _attach_repo_to_domain(
78 db: AsyncSession,
79 repo: MusehubRepo,
80 domain: MusehubDomain,
81 ) -> None:
82 """Link a repo to a domain by setting domain_id."""
83 await db.execute(
84 text("UPDATE musehub_repos SET domain_id = :did WHERE repo_id = :rid"),
85 {"did": domain.domain_id, "rid": repo.repo_id},
86 )
87 await db.commit()
88
89
90 async def _make_public_repo(
91 db: AsyncSession,
92 *,
93 owner: str = "testowner",
94 slug: str | None = None,
95 ) -> MusehubRepo:
96 """Seed a public repo using the factory helper."""
97 repo = await create_repo(db, visibility="public")
98 if slug:
99 # Patch slug for readable assertions
100 await db.execute(
101 text("UPDATE musehub_repos SET slug = :s, owner = :o WHERE repo_id = :rid"),
102 {"s": slug, "o": owner, "rid": repo.repo_id},
103 )
104 await db.commit()
105 await db.refresh(repo)
106 return repo
107
108
109 async def _add_agent_commit(db: AsyncSession, repo_id: str) -> None:
110 """Insert a commit with agent_id set."""
111 commit = await create_commit(db, repo_id, timestamp=_utc_now())
112 await db.execute(
113 text("UPDATE musehub_commits SET agent_id = 'claude-code' WHERE commit_id = :cid"),
114 {"cid": commit.commit_id},
115 )
116 await db.commit()
117
118
119 async def _insert_symbol_intel(
120 db: AsyncSession,
121 repo_id: str,
122 address: str,
123 churn_30d: int = 0,
124 blast: int = 0,
125 ) -> None:
126 row = MusehubSymbolIntel(
127 repo_id=repo_id, address=address, churn_30d=churn_30d, blast=blast
128 )
129 db.add(row)
130 await db.commit()
131
132
133 async def _insert_dead(db: AsyncSession, repo_id: str, address: str) -> None:
134 from musehub.db.musehub_intel_models import MusehubIntelDead
135 row = MusehubIntelDead(
136 repo_id=repo_id,
137 address=address,
138 kind="function",
139 confidence="high",
140 ref="main",
141 )
142 db.add(row)
143 await db.commit()
144
145
146 def _domain_url(author_slug: str, slug: str) -> str:
147 return f"/domains/@{author_slug}/{slug}"
148
149
150 # ---------------------------------------------------------------------------
151 # T300 β€” page returns 200 with rc-card markup
152 # ---------------------------------------------------------------------------
153
154 @pytest.mark.asyncio
155 async def test_t300_domain_detail_returns_rc_cards(
156 client: AsyncClient,
157 db_session: AsyncSession,
158 ) -> None:
159 """T300: GET /domains/@author/slug returns 200 and renders rc-card elements."""
160 domain = await _make_domain(db_session)
161 repo = await _make_public_repo(db_session)
162 await _attach_repo_to_domain(db_session, repo, domain)
163
164 resp = await client.get(_domain_url(domain.author_slug, domain.slug))
165 assert resp.status_code == 200
166 assert "text/html" in resp.headers["content-type"]
167 assert "rc-card" in resp.text
168
169
170 # ---------------------------------------------------------------------------
171 # T301 β€” sparkline SVG rendered when commits exist
172 # ---------------------------------------------------------------------------
173
174 @pytest.mark.asyncio
175 async def test_t301_sparkline_rendered_for_repo_with_commits(
176 client: AsyncClient,
177 db_session: AsyncSession,
178 ) -> None:
179 """T301: a repo with recent commits renders a <svg class="rc-sparkline"> element."""
180 domain = await _make_domain(db_session, slug="sparktest")
181 repo = await _make_public_repo(db_session)
182 await _attach_repo_to_domain(db_session, repo, domain)
183 await create_commit(db_session, repo.repo_id, timestamp=_utc_now())
184
185 resp = await client.get(_domain_url(domain.author_slug, domain.slug))
186 assert resp.status_code == 200
187 assert 'class="rc-sparkline"' in resp.text
188
189
190 # ---------------------------------------------------------------------------
191 # T302 β€” health badge class matches signal
192 # ---------------------------------------------------------------------------
193
194 @pytest.mark.asyncio
195 async def test_t302_health_badge_risk_when_errors(
196 client: AsyncClient,
197 db_session: AsyncSession,
198 ) -> None:
199 """T302: health badge gauge aria-label is "risk" when breakage errors exist."""
200 from musehub.db.musehub_intel_models import MusehubIntelBreakageMeta
201 domain = await _make_domain(db_session, slug="healthtest")
202 repo = await _make_public_repo(db_session)
203 await _attach_repo_to_domain(db_session, repo, domain)
204
205 db_session.add(MusehubIntelBreakageMeta(
206 repo_id=repo.repo_id,
207 total_issues=3,
208 error_count=3,
209 warning_count=0,
210 file_count=1,
211 ref="main",
212 ))
213 await db_session.commit()
214
215 resp = await client.get(_domain_url(domain.author_slug, domain.slug))
216 assert resp.status_code == 200
217 assert 'aria-label="risk"' in resp.text
218
219
220 @pytest.mark.asyncio
221 async def test_t302b_health_badge_warn_when_dead(
222 client: AsyncClient,
223 db_session: AsyncSession,
224 ) -> None:
225 """T302b: health badge gauge aria-label is "warn" when dead symbols exist."""
226 domain = await _make_domain(db_session, slug="warntest")
227 repo = await _make_public_repo(db_session)
228 await _attach_repo_to_domain(db_session, repo, domain)
229 await _insert_dead(db_session, repo.repo_id, "src/old.py::stale_fn")
230
231 resp = await client.get(_domain_url(domain.author_slug, domain.slug))
232 assert resp.status_code == 200
233 assert 'aria-label="warn"' in resp.text
234
235
236 # ---------------------------------------------------------------------------
237 # T303 β€” autonomy percentage rendered correctly
238 # ---------------------------------------------------------------------------
239
240 @pytest.mark.asyncio
241 async def test_t303_autonomy_pct_rendered_for_all_agent_repo(
242 client: AsyncClient,
243 db_session: AsyncSession,
244 ) -> None:
245 """T303: a repo with only agent commits renders '100%' in the autonomy stat."""
246 domain = await _make_domain(db_session, slug="autonomytest")
247 repo = await _make_public_repo(db_session)
248 await _attach_repo_to_domain(db_session, repo, domain)
249
250 for _ in range(3):
251 await _add_agent_commit(db_session, repo.repo_id)
252
253 resp = await client.get(_domain_url(domain.author_slug, domain.slug))
254 assert resp.status_code == 200
255 assert "100%" in resp.text
256 assert "autonomy" in resp.text
257
258
259 # ---------------------------------------------------------------------------
260 # T304 β€” hottest symbol name in rc-intel row
261 # ---------------------------------------------------------------------------
262
263 @pytest.mark.asyncio
264 async def test_t304_hottest_symbol_rendered(
265 client: AsyncClient,
266 db_session: AsyncSession,
267 ) -> None:
268 """T304: the hottest symbol's short name appears in an rc-intel row."""
269 domain = await _make_domain(db_session, slug="hottesttest")
270 repo = await _make_public_repo(db_session)
271 await _attach_repo_to_domain(db_session, repo, domain)
272 await _insert_symbol_intel(
273 db_session, repo.repo_id, "src/core.py::compute_totals", churn_30d=42
274 )
275
276 resp = await client.get(_domain_url(domain.author_slug, domain.slug))
277 assert resp.status_code == 200
278 assert "compute_totals" in resp.text
279 assert "hottest" in resp.text
280
281
282 # ---------------------------------------------------------------------------
283 # T305 β€” blast leader name in rc-intel row
284 # ---------------------------------------------------------------------------
285
286 @pytest.mark.asyncio
287 async def test_t305_blast_leader_rendered(
288 client: AsyncClient,
289 db_session: AsyncSession,
290 ) -> None:
291 """T305: the blast leader's short name appears in an rc-intel row."""
292 domain = await _make_domain(db_session, slug="blasttest")
293 repo = await _make_public_repo(db_session)
294 await _attach_repo_to_domain(db_session, repo, domain)
295 await _insert_symbol_intel(
296 db_session, repo.repo_id, "src/api.py::dispatch_event", blast=512
297 )
298
299 resp = await client.get(_domain_url(domain.author_slug, domain.slug))
300 assert resp.status_code == 200
301 assert "dispatch_event" in resp.text
302 assert "blast" in resp.text
303
304
305 # ---------------------------------------------------------------------------
306 # T306 β€” clean / zero enrichment for repo with no intel
307 # ---------------------------------------------------------------------------
308
309 @pytest.mark.asyncio
310 async def test_t306_clean_card_when_no_intel(
311 client: AsyncClient,
312 db_session: AsyncSession,
313 ) -> None:
314 """T306: a repo with zero intel data renders gauge aria-label="clean", no crash."""
315 domain = await _make_domain(db_session, slug="cleantest")
316 repo = await _make_public_repo(db_session)
317 await _attach_repo_to_domain(db_session, repo, domain)
318
319 resp = await client.get(_domain_url(domain.author_slug, domain.slug))
320 assert resp.status_code == 200
321 assert 'aria-label="clean"' in resp.text
322 # No intel rows β†’ no hottest/blast section rendered
323 assert "hottest" not in resp.text
324
325
326 # ---------------------------------------------------------------------------
327 # T307 β€” ?format=json unaffected by enrichment
328 # ---------------------------------------------------------------------------
329
330 @pytest.mark.asyncio
331 async def test_t307_json_format_not_broken_by_enrichment(
332 client: AsyncClient,
333 db_session: AsyncSession,
334 ) -> None:
335 """T307: ?format=json still returns valid JSON after enrichment was wired up."""
336 domain = await _make_domain(db_session, slug="jsontest")
337 repo = await _make_public_repo(db_session)
338 await _attach_repo_to_domain(db_session, repo, domain)
339
340 resp = await client.get(
341 _domain_url(domain.author_slug, domain.slug),
342 params={"format": "json"},
343 )
344 assert resp.status_code == 200
345 assert resp.headers["content-type"].startswith("application/json")
346 data = resp.json()
347 assert "domain" in data
348 assert "repos" in data