gabriel / musehub public
test_repo_card_state.py python
185 lines 7.5 KB
Raw
sha256:34035d72cef530c1ab9d6a6f53be18d803dde705fc3157617d70352a96d0747b Merge branch 'fix/wire-push-external-parent-manifest' into dev Human 8 days ago
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"
File History 14 commits
sha256:34035d72cef530c1ab9d6a6f53be18d803dde705fc3157617d70352a96d0747b Merge branch 'fix/wire-push-external-parent-manifest' into dev Human 8 days ago
sha256:fc04e4cae9e1774d6a21b65c45daeed0e6787eb581d13aa1b03bfe9384a34226 Merge branch 'fix/two-column-scroll-layout' into dev Human 8 days ago
sha256:408916fc5973ba59c6e4eebaa80ebdcc801c0a63205651e25009d11548f79454 chore: bump version to 0.2.0.dev2 — nightly.2, matching muse Sonnet 4.6 patch 11 days ago
sha256:d035733f21ccff27735fddebfbbe0ed24565a32a22db8de5885402262671ecd2 chore: bump version to 0.2.0rc15 for musehub#113 fix release Sonnet 4.6 patch 14 days ago
sha256:0032d6cfa33bc3c8367436ad768e7dd0e339b4332153160247da8266cb5fa352 Merge branch 'task/version-tags-phase3-server' into dev Human 17 days ago
sha256:4669620efda9ff41c55bdefd1f7bfe1c239d468428744c84ead9957e5a003a53 merge: rescue snapshot-recovery hardening (c00aa21d) into d… Opus 4.8 minor 29 days ago
sha256:a59da49c4611b970fc4b6ae48678ce4943261c213a07ddbd73ce9201df869b4a fix: remove false-positive proposal_comments index drop fro… Sonnet 4.6 patch 33 days ago
sha256:0a240d6dbff234f07d98a28a4a9a68db702f3f9ff9260196f24219bdb1c0b6f3 feat: render markdown mists as HTML with heading anchor links Sonnet 4.6 patch 34 days ago
sha256:24a7d47486ebc4ebd1832830580e177ec6f877b48dced8c000e198cdec4ce9d6 Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 35 days ago
sha256:b9ff931d147e0114a1f17060f415b89ed551c170a91ff226c70437aa5c85f9ee Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 35 days ago
sha256:d1122d21e73471879b460037b22c0b50fded7c423444a176f248428f75dac39c Merge 'task/fix-issue-pagination-cursor' into 'dev' — propo… Human 35 days ago
sha256:01e18975e73d2b3cd5b6db7929c895bef9aa6e0d4391dc5b2adfc548b41318dd Merge 'feat/adding-debug-logs-to-staging' into 'dev' — prop… Human 35 days ago
sha256:6b1949fc2797ca4c1936a637a4cbfec828ef56cf52398a2e74ca3c4f494e728f fix: use wire_bytes not mpack_bytes_raw in compute_object_b… Sonnet 4.6 patch 48 days ago
sha256:b99f2455dc346966d040133f5203297e6e3ef5803a93728a2c30568d0a0f7583 rename: delta_add → delta_upsert across wire format, models… Sonnet 4.6 patch 50 days ago