gabriel / musehub public
test_clones_state_integrity.py python
231 lines 7.9 KB
Raw
sha256:a34090cc4a394a78bd72cbbe34b08cc59525141e19135b6c0ab154f10611b9ef debug(push/stream): instrument O-frame decode path with INF… Sonnet 4.6 patch 121 days ago
1 """Tier 5 — State integrity tests for musehub_intel_clones (issue #17).
2
3 Validates that the DB table invariants hold under normal and pathological
4 conditions: JSON parseability, tier enum, count/content agreement, upsert
5 idempotency, stale-row update, and CASCADE delete behavior.
6
7 Cases:
8 SI01 All DB rows have parseable members_json
9 SI02 All tier values are exactly "exact" or "near"
10 SI03 member_count matches len(json.loads(members_json)) for each row
11 SI04 ClonesProvider upsert is idempotent — running twice = same row count
12 SI05 ClonesProvider upsert updates existing row on re-run with new count
13 SI06 CASCADE delete — deleting repo removes all clones rows
14 """
15 from __future__ import annotations
16
17 import json
18 from unittest.mock import AsyncMock, patch
19
20 import pytest
21 import pytest_asyncio
22 import sqlalchemy as sa
23 from sqlalchemy.dialects.postgresql import insert as pg_insert
24 from sqlalchemy.ext.asyncio import AsyncSession
25
26 from musehub.db import musehub_models as dbm
27 from muse.core.types import long_id
28 from tests.factories import create_repo
29
30 _REF = long_id("a" * 64)
31
32
33 def _make_members(n: int, single_file: bool = False) -> str:
34 return json.dumps([
35 {
36 "address": f"src/{'a' if single_file else chr(97+i%4)}.py::fn_{i}",
37 "kind": "function",
38 "language": "Python",
39 "body_hash": long_id("a" * 64),
40 "signature_id": long_id("b" * 64),
41 "content_id": long_id("a" * 64),
42 }
43 for i in range(n)
44 ])
45
46
47 async def _insert(
48 session: AsyncSession,
49 repo_id: str,
50 cluster_hash: str,
51 tier: str = "exact",
52 member_count: int = 2,
53 members_json: str | None = None,
54 ) -> None:
55 mj = members_json if members_json is not None else _make_members(member_count)
56 await session.execute(
57 pg_insert(dbm.MusehubIntelClones)
58 .values(
59 repo_id=repo_id,
60 cluster_hash=cluster_hash,
61 tier=tier,
62 member_count=member_count,
63 members_json=mj,
64 ref=_REF,
65 )
66 .on_conflict_do_update(
67 index_elements=["repo_id", "cluster_hash"],
68 set_={"tier": tier, "member_count": member_count, "members_json": mj},
69 )
70 )
71 await session.commit()
72
73
74 @pytest_asyncio.fixture
75 async def repo(db_session: AsyncSession):
76 return await create_repo(db_session, owner="siuser", slug="state-integrity")
77
78
79 class TestClonesStateIntegrity:
80
81 @pytest.mark.asyncio
82 async def test_SI01_all_rows_parseable_members_json(
83 self, db_session: AsyncSession, repo
84 ) -> None:
85 """Every members_json stored in the DB must deserialise without error."""
86 for i in range(5):
87 await _insert(
88 db_session, str(repo.repo_id),
89 cluster_hash=f"sha256:si01{str(i).zfill(60)}",
90 member_count=i + 2,
91 members_json=_make_members(i + 2),
92 )
93
94 result = await db_session.execute(
95 sa.select(dbm.MusehubIntelClones).where(
96 dbm.MusehubIntelClones.repo_id == str(repo.repo_id)
97 )
98 )
99 rows = result.scalars().all()
100 for row in rows:
101 try:
102 parsed = json.loads(row.members_json)
103 assert isinstance(parsed, list)
104 except json.JSONDecodeError as exc:
105 pytest.fail(f"Unparseable members_json for {row.cluster_hash}: {exc}")
106
107 @pytest.mark.asyncio
108 async def test_SI02_tier_values_in_valid_set(
109 self, db_session: AsyncSession, repo
110 ) -> None:
111 """All tier values must be exactly 'exact' or 'near'."""
112 for tier in ("exact", "near", "exact"):
113 await _insert(
114 db_session, str(repo.repo_id),
115 cluster_hash=f"sha256:si02{tier[:1]}{str(id(tier)).zfill(59)}",
116 tier=tier,
117 )
118
119 result = await db_session.execute(
120 sa.select(dbm.MusehubIntelClones.tier).where(
121 dbm.MusehubIntelClones.repo_id == str(repo.repo_id)
122 )
123 )
124 for (tier,) in result.all():
125 assert tier in ("exact", "near"), f"Unexpected tier value: {tier!r}"
126
127 @pytest.mark.asyncio
128 async def test_SI03_member_count_matches_json_length(
129 self, db_session: AsyncSession, repo
130 ) -> None:
131 """member_count must equal the number of entries in members_json."""
132 for n in (2, 5, 10):
133 await _insert(
134 db_session, str(repo.repo_id),
135 cluster_hash=f"sha256:si03n{n}{str(n).zfill(58)}",
136 member_count=n,
137 members_json=_make_members(n),
138 )
139
140 result = await db_session.execute(
141 sa.select(dbm.MusehubIntelClones).where(
142 dbm.MusehubIntelClones.repo_id == str(repo.repo_id)
143 )
144 )
145 for row in result.scalars().all():
146 actual = len(json.loads(row.members_json))
147 assert actual == row.member_count, (
148 f"{row.cluster_hash}: member_count={row.member_count} "
149 f"but members_json has {actual} entries"
150 )
151
152 @pytest.mark.asyncio
153 async def test_SI04_upsert_is_idempotent(
154 self, db_session: AsyncSession, repo
155 ) -> None:
156 """Inserting the same cluster twice leaves exactly one row."""
157 h = long_id("4" * 64)
158 mj = _make_members(3)
159 for _ in range(2):
160 await _insert(
161 db_session, str(repo.repo_id),
162 cluster_hash=h, member_count=3, members_json=mj,
163 )
164
165 count_result = await db_session.execute(
166 sa.select(sa.func.count())
167 .select_from(dbm.MusehubIntelClones)
168 .where(
169 dbm.MusehubIntelClones.repo_id == str(repo.repo_id),
170 dbm.MusehubIntelClones.cluster_hash == h,
171 )
172 )
173 assert count_result.scalar_one() == 1
174
175 @pytest.mark.asyncio
176 async def test_SI05_upsert_updates_existing_row(
177 self, db_session: AsyncSession, repo
178 ) -> None:
179 """Re-running with a new member_count updates the existing row."""
180 h = long_id("5" * 64)
181 await _insert(db_session, str(repo.repo_id), cluster_hash=h, member_count=2)
182 new_mj = _make_members(7)
183 await _insert(
184 db_session, str(repo.repo_id),
185 cluster_hash=h, member_count=7, members_json=new_mj,
186 )
187
188 result = await db_session.execute(
189 sa.select(dbm.MusehubIntelClones).where(
190 dbm.MusehubIntelClones.repo_id == str(repo.repo_id),
191 dbm.MusehubIntelClones.cluster_hash == h,
192 )
193 )
194 row = result.scalar_one()
195 assert row.member_count == 7
196 assert len(json.loads(row.members_json)) == 7
197
198 @pytest.mark.asyncio
199 async def test_SI06_cascade_delete_removes_clones(
200 self, db_session: AsyncSession, repo
201 ) -> None:
202 """Deleting the repo cascades and removes all associated clone rows."""
203 for i in range(3):
204 await _insert(
205 db_session, str(repo.repo_id),
206 cluster_hash=f"sha256:si06{str(i).zfill(60)}",
207 )
208
209 # Verify rows exist
210 before = await db_session.execute(
211 sa.select(sa.func.count())
212 .select_from(dbm.MusehubIntelClones)
213 .where(dbm.MusehubIntelClones.repo_id == str(repo.repo_id))
214 )
215 assert before.scalar_one() == 3
216
217 # Delete the repo
218 await db_session.execute(
219 sa.delete(dbm.MusehubRepo).where(
220 dbm.MusehubRepo.repo_id == str(repo.repo_id)
221 )
222 )
223 await db_session.commit()
224
225 # Clones rows must be gone
226 after = await db_session.execute(
227 sa.select(sa.func.count())
228 .select_from(dbm.MusehubIntelClones)
229 .where(dbm.MusehubIntelClones.repo_id == str(repo.repo_id))
230 )
231 assert after.scalar_one() == 0
File History 1 commit
sha256:a34090cc4a394a78bd72cbbe34b08cc59525141e19135b6c0ab154f10611b9ef debug(push/stream): instrument O-frame decode path with INF… Sonnet 4.6 patch 121 days ago