gabriel / musehub public

test_repo_card_state.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 5 β€” State tests for enrich_repo_cards().
3
4 State tests verify that enrichment results are stable across repeated calls and
5 that incremental data changes (new commits, new dead symbols, resolved breakage)
6 are correctly reflected in the next enrichment β€” no stale caches, no phantom
7 rows from prior calls.
8
9 Test IDs
10 --------
11 T500 β€” two successive calls with no data change return identical results
12 T501 β€” adding a commit between calls increases pulse_buckets total count
13 T502 β€” adding a dead symbol between calls increments dead_count by 1
14 T503 β€” removing breakage meta between calls transitions health risk β†’ clean
15 T504 β€” adding an agent commit shifts autonomy_pct from 0 to 50
16 T505 β€” replacing hottest symbol (higher churn) swaps hottest_symbol in next call
17 """
18 from __future__ import annotations
19
20 from datetime import datetime, timezone
21
22 import pytest
23 from sqlalchemy.ext.asyncio import AsyncSession
24 from sqlalchemy import text
25
26 from musehub.db.musehub_intel_models import MusehubIntelBreakageMeta, MusehubIntelDead, MusehubSymbolIntel
27 from musehub.services.repo_card_enrichment import enrich_repo_cards
28 from tests.factories import create_commit, create_repo
29
30
31 def _utc_now() -> datetime:
32 return datetime.now(tz=timezone.utc)
33
34
35 # ---------------------------------------------------------------------------
36 # T500 β€” stability: identical result on two successive calls
37 # ---------------------------------------------------------------------------
38
39 @pytest.mark.asyncio
40 async def test_t500_successive_calls_are_stable(db_session: AsyncSession) -> None:
41 """T500: calling enrich_repo_cards twice with no intervening writes is idempotent."""
42 repo = await create_repo(db_session, visibility="public")
43 db_session.add(MusehubSymbolIntel(
44 repo_id=repo.repo_id, address="src/a.py::fn", churn_30d=5, blast=3
45 ))
46 await db_session.commit()
47
48 result_a = await enrich_repo_cards(db_session, [repo.repo_id])
49 result_b = await enrich_repo_cards(db_session, [repo.repo_id])
50
51 enc_a = result_a[repo.repo_id]
52 enc_b = result_b[repo.repo_id]
53
54 assert enc_a.autonomy_pct == enc_b.autonomy_pct
55 assert enc_a.dead_count == enc_b.dead_count
56 assert enc_a.health_status == enc_b.health_status
57 assert enc_a.hottest_symbol.address == enc_b.hottest_symbol.address
58 assert sum(b.count for b in enc_a.pulse_buckets) == sum(b.count for b in enc_b.pulse_buckets)
59
60
61 # ---------------------------------------------------------------------------
62 # T501 β€” new commit reflected in pulse after next call
63 # ---------------------------------------------------------------------------
64
65 @pytest.mark.asyncio
66 async def test_t501_new_commit_increases_pulse_total(db_session: AsyncSession) -> None:
67 """T501: inserting a commit between calls increments the pulse total count."""
68 repo = await create_repo(db_session, visibility="public")
69
70 result_before = await enrich_repo_cards(db_session, [repo.repo_id])
71 total_before = sum(b.count for b in result_before[repo.repo_id].pulse_buckets)
72
73 await create_commit(db_session, repo.repo_id, timestamp=_utc_now())
74
75 result_after = await enrich_repo_cards(db_session, [repo.repo_id])
76 total_after = sum(b.count for b in result_after[repo.repo_id].pulse_buckets)
77
78 assert total_after == total_before + 1
79
80
81 # ---------------------------------------------------------------------------
82 # T502 β€” new dead symbol increments dead_count
83 # ---------------------------------------------------------------------------
84
85 @pytest.mark.asyncio
86 async def test_t502_new_dead_symbol_increments_count(db_session: AsyncSession) -> None:
87 """T502: inserting a high-confidence dead symbol increments dead_count by 1."""
88 repo = await create_repo(db_session, visibility="public")
89
90 result_before = await enrich_repo_cards(db_session, [repo.repo_id])
91 dead_before = result_before[repo.repo_id].dead_count
92
93 db_session.add(MusehubIntelDead(
94 repo_id=repo.repo_id,
95 address="src/old.py::stale_fn",
96 kind="function",
97 confidence="high",
98 ref="main",
99 ))
100 await db_session.commit()
101
102 result_after = await enrich_repo_cards(db_session, [repo.repo_id])
103 dead_after = result_after[repo.repo_id].dead_count
104
105 assert dead_after == dead_before + 1
106
107
108 # ---------------------------------------------------------------------------
109 # T503 β€” removing breakage meta transitions health risk β†’ clean
110 # ---------------------------------------------------------------------------
111
112 @pytest.mark.asyncio
113 async def test_t503_removing_breakage_transitions_to_clean(db_session: AsyncSession) -> None:
114 """T503: deleting a breakage_meta row transitions health_status from risk to clean."""
115 repo = await create_repo(db_session, visibility="public")
116 db_session.add(MusehubIntelBreakageMeta(
117 repo_id=repo.repo_id,
118 total_issues=2,
119 error_count=2,
120 warning_count=0,
121 file_count=1,
122 ref="main",
123 ))
124 await db_session.commit()
125
126 result_risk = await enrich_repo_cards(db_session, [repo.repo_id])
127 assert result_risk[repo.repo_id].health_status == "risk"
128
129 await db_session.execute(
130 text("DELETE FROM musehub_intel_breakage_meta WHERE repo_id = :rid"),
131 {"rid": repo.repo_id},
132 )
133 await db_session.commit()
134
135 result_clean = await enrich_repo_cards(db_session, [repo.repo_id])
136 assert result_clean[repo.repo_id].health_status == "clean"
137
138
139 # ---------------------------------------------------------------------------
140 # T504 β€” adding agent commit shifts autonomy from 0 to 50
141 # ---------------------------------------------------------------------------
142
143 @pytest.mark.asyncio
144 async def test_t504_agent_commit_shifts_autonomy(db_session: AsyncSession) -> None:
145 """T504: adding one agent commit to a one-human-commit repo shifts autonomy to 50%."""
146 repo = await create_repo(db_session, visibility="public")
147 human_commit = await create_commit(db_session, repo.repo_id, timestamp=_utc_now())
148
149 result_before = await enrich_repo_cards(db_session, [repo.repo_id])
150 assert result_before[repo.repo_id].autonomy_pct == 0
151
152 agent_commit = await create_commit(db_session, repo.repo_id, timestamp=_utc_now())
153 await db_session.execute(
154 text("UPDATE musehub_commits SET agent_id = 'claude-code' WHERE commit_id = :cid"),
155 {"cid": agent_commit.commit_id},
156 )
157 await db_session.commit()
158
159 result_after = await enrich_repo_cards(db_session, [repo.repo_id])
160 assert result_after[repo.repo_id].autonomy_pct == 50
161
162
163 # ---------------------------------------------------------------------------
164 # T505 β€” replacing hottest symbol swaps result in next call
165 # ---------------------------------------------------------------------------
166
167 @pytest.mark.asyncio
168 async def test_t505_higher_churn_replaces_hottest(db_session: AsyncSession) -> None:
169 """T505: inserting a symbol with higher churn replaces the hottest in the next call."""
170 repo = await create_repo(db_session, visibility="public")
171 db_session.add(MusehubSymbolIntel(
172 repo_id=repo.repo_id, address="src/a.py::slow_fn", churn_30d=10, blast=0
173 ))
174 await db_session.commit()
175
176 result_before = await enrich_repo_cards(db_session, [repo.repo_id])
177 assert result_before[repo.repo_id].hottest_symbol.address == "src/a.py::slow_fn"
178
179 db_session.add(MusehubSymbolIntel(
180 repo_id=repo.repo_id, address="src/b.py::hot_fn", churn_30d=999, blast=0
181 ))
182 await db_session.commit()
183
184 result_after = await enrich_repo_cards(db_session, [repo.repo_id])
185 assert result_after[repo.repo_id].hottest_symbol.address == "src/b.py::hot_fn"