gabriel / musehub public
test_phase5_gravity_derived.py python
649 lines 23.9 KB
Raw
sha256:ef10830ce231e0a20efcb0e2586cb879471247e916616e6fdd0d51df459e2595 fix: typing audit — 0 violations, 0 untyped defs across all… Sonnet 4.6 minor ⚠ breaking 21 days ago
1 """TDD spec — GravityProvider rewrite: SQL-derived gravity, no muse CLI.
2
3 GravityProvider must compute gravity scores directly from the blast columns
4 already written by intel.code, rather than calling `muse code gravity`.
5
6 Formula (mirrors muse/muse/cli/commands/gravity.py exactly):
7
8 total = count of tracked-kind symbols for this repo
9 denom = max(1, total - 1) # exclude self, guard /0
10 gravity_pct = round(blast / denom * 100, 1)
11
12 Column mapping:
13 gravity_direct_dependents ← blast_direct
14 gravity_transitive_dependents ← blast (blast_direct + blast_cross)
15 gravity_pct ← round(blast / max(1, total - 1) * 100, 1)
16 gravity_max_depth — not derivable from blast; left NULL
17 gravity_depth_distribution — not derivable from blast; left NULL
18
19 Tracked kinds (denominator scope, matching gravity.py _TRACKED_KINDS):
20 function, async_function, method, async_method, class
21
22 Layers:
23 1. No subprocess — compute() never spawns a process
24 2. Formula — gravity_pct matches gravity.py rounding + denominator
25 3. Mapping — gravity_direct_dependents = blast_direct;
26 gravity_transitive_dependents = blast
27 4. Denominator — untracked kinds (import, None) excluded from total
28 5. Edge: single — denom = max(1, 1-1) = 1, no /0
29 6. Writes only — rows that exist get updated; no new rows inserted
30 7. Preserve — churn/blast columns untouched after compute()
31 8. Idempotent — run twice yields identical rows, no duplicates
32 9. Empty — no blast data → returns []
33 10. Null max_depth — gravity_max_depth stays NULL (not derivable)
34 """
35 from __future__ import annotations
36
37 import pytest
38 import pytest_asyncio
39 from sqlalchemy.dialects.postgresql import insert as pg_insert
40 from sqlalchemy.ext.asyncio import AsyncSession
41 from sqlalchemy import select, func
42
43 from musehub.db.musehub_intel_models import MusehubSymbolIntel
44 from tests.factories import create_repo
45
46
47 _TRACKED_KINDS = ("function", "async_function", "method", "async_method", "class")
48
49
50 # ---------------------------------------------------------------------------
51 # Helpers
52 # ---------------------------------------------------------------------------
53
54 _SYMBOL_DEFAULTS = {
55 "churn": 0,
56 "churn_30d": 0,
57 "churn_90d": 0,
58 "blast": 0,
59 "blast_direct": 0,
60 "blast_cross": 0,
61 "blast_top": [],
62 "author_count": 0,
63 "gravity": 0.0,
64 "weekly": [0] * 12,
65 }
66
67
68 async def _seed_symbols(
69 session: AsyncSession,
70 repo_id: str,
71 symbols: list[dict],
72 ) -> None:
73 """Insert musehub_symbol_intel rows with blast + kind data."""
74 for s in symbols:
75 row = {**_SYMBOL_DEFAULTS, **s}
76 stmt = (
77 pg_insert(MusehubSymbolIntel)
78 .values(repo_id=repo_id, **row)
79 .on_conflict_do_update(
80 index_elements=["repo_id", "address"],
81 set_={k: v for k, v in row.items() if k != "address"},
82 )
83 )
84 await session.execute(stmt)
85 await session.flush()
86
87
88 async def _get_row(session: AsyncSession, repo_id: str, address: str) -> MusehubSymbolIntel | None:
89 result = await session.execute(
90 select(MusehubSymbolIntel).where(
91 MusehubSymbolIntel.repo_id == repo_id,
92 MusehubSymbolIntel.address == address,
93 )
94 )
95 return result.scalar_one_or_none()
96
97
98 def _gravity_pct(blast: int, total: int) -> float:
99 """Reference implementation — mirrors gravity.py exactly."""
100 denom = max(1, total - 1)
101 return round(blast / denom * 100, 1)
102
103
104 # ---------------------------------------------------------------------------
105 # Layer 1 — No subprocess
106 # ---------------------------------------------------------------------------
107
108 class TestNoSubprocess:
109
110 @pytest.mark.asyncio
111 async def test_P5_01_compute_never_spawns_subprocess(
112 self, db_session: AsyncSession
113 ) -> None:
114 """GravityProvider must not call muse CLI — pure SQL derivation."""
115 import asyncio
116 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
117
118 repo = await create_repo(db_session)
119 await _seed_symbols(db_session, repo.repo_id, [
120 {"address": "a.py::fn", "symbol_kind": "function",
121 "blast": 5, "blast_direct": 2, "blast_cross": 3},
122 ])
123
124 spawned: list[tuple] = []
125 original = asyncio.create_subprocess_exec
126
127 async def _spy(*args: typing.Any, **kwargs: typing.Any) -> None:
128 spawned.append(args)
129 return await original(*args, **kwargs)
130
131 import unittest.mock as mock
132 with mock.patch("asyncio.create_subprocess_exec", side_effect=_spy):
133 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
134 db_session, repo.repo_id, "ref",
135 {"owner": repo.owner, "slug": repo.slug},
136 )
137
138 assert spawned == [], (
139 f"GravityProvider spawned {len(spawned)} subprocess(es); expected 0. "
140 "Gravity must be derived from blast columns, not from muse CLI."
141 )
142
143
144 # ---------------------------------------------------------------------------
145 # Layer 2 — Formula: gravity_pct matches gravity.py exactly
146 # ---------------------------------------------------------------------------
147
148 class TestFormula:
149
150 @pytest.mark.asyncio
151 async def test_P5_02_gravity_pct_matches_formula(
152 self, db_session: AsyncSession
153 ) -> None:
154 """gravity_pct = round(blast / max(1, total-1) * 100, 1)."""
155 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
156
157 repo = await create_repo(db_session)
158 # 10 tracked symbols; symbol A has blast=9
159 symbols = [
160 {"address": f"a.py::fn{i}", "symbol_kind": "function",
161 "blast": 1, "blast_direct": 1, "blast_cross": 0}
162 for i in range(9)
163 ]
164 symbols.append(
165 {"address": "a.py::target", "symbol_kind": "function",
166 "blast": 9, "blast_direct": 3, "blast_cross": 6}
167 )
168 await _seed_symbols(db_session, repo.repo_id, symbols)
169
170 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
171 db_session, repo.repo_id, "ref", {},
172 )
173
174 row = await _get_row(db_session, repo.repo_id, "a.py::target")
175 assert row is not None
176 expected = _gravity_pct(blast=9, total=10) # round(9/9*100, 1) = 100.0
177 assert row.gravity_pct == pytest.approx(expected), (
178 f"gravity_pct={row.gravity_pct}, expected {expected}"
179 )
180
181 @pytest.mark.asyncio
182 async def test_P5_03_gravity_pct_fractional_rounding(
183 self, db_session: AsyncSession
184 ) -> None:
185 """Rounding to 1 decimal place matches Python round()."""
186 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
187
188 repo = await create_repo(db_session)
189 # 9 symbols total; target has blast=4 → 4/8*100 = 50.0
190 symbols = [
191 {"address": f"a.py::fn{i}", "symbol_kind": "method",
192 "blast": 0, "blast_direct": 0, "blast_cross": 0}
193 for i in range(8)
194 ]
195 symbols.append(
196 {"address": "a.py::target", "symbol_kind": "method",
197 "blast": 4, "blast_direct": 1, "blast_cross": 3}
198 )
199 await _seed_symbols(db_session, repo.repo_id, symbols)
200
201 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
202 db_session, repo.repo_id, "ref", {},
203 )
204
205 row = await _get_row(db_session, repo.repo_id, "a.py::target")
206 expected = _gravity_pct(blast=4, total=9) # round(4/8*100, 1) = 50.0
207 assert row.gravity_pct == pytest.approx(expected)
208
209 @pytest.mark.asyncio
210 async def test_P5_04_all_symbols_get_gravity_pct(
211 self, db_session: AsyncSession
212 ) -> None:
213 """Every tracked-kind row gets a gravity_pct, including blast=0 rows."""
214 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
215
216 repo = await create_repo(db_session)
217 await _seed_symbols(db_session, repo.repo_id, [
218 {"address": "a.py::fn_a", "symbol_kind": "function",
219 "blast": 3, "blast_direct": 1, "blast_cross": 2},
220 {"address": "a.py::fn_b", "symbol_kind": "function",
221 "blast": 0, "blast_direct": 0, "blast_cross": 0},
222 ])
223
224 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
225 db_session, repo.repo_id, "ref", {},
226 )
227
228 for addr in ("a.py::fn_a", "a.py::fn_b"):
229 row = await _get_row(db_session, repo.repo_id, addr)
230 assert row is not None
231 assert row.gravity_pct is not None, f"{addr} missing gravity_pct"
232
233
234 # ---------------------------------------------------------------------------
235 # Layer 3 — Column mapping
236 # ---------------------------------------------------------------------------
237
238 class TestColumnMapping:
239
240 @pytest.mark.asyncio
241 async def test_P5_05_gravity_direct_dependents_equals_blast_direct(
242 self, db_session: AsyncSession
243 ) -> None:
244 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
245
246 repo = await create_repo(db_session)
247 await _seed_symbols(db_session, repo.repo_id, [
248 {"address": "a.py::fn", "symbol_kind": "function",
249 "blast": 7, "blast_direct": 3, "blast_cross": 4},
250 ])
251
252 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
253 db_session, repo.repo_id, "ref", {},
254 )
255
256 row = await _get_row(db_session, repo.repo_id, "a.py::fn")
257 assert row.gravity_direct_dependents == 3
258
259 @pytest.mark.asyncio
260 async def test_P5_06_gravity_transitive_dependents_equals_blast(
261 self, db_session: AsyncSession
262 ) -> None:
263 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
264
265 repo = await create_repo(db_session)
266 await _seed_symbols(db_session, repo.repo_id, [
267 {"address": "a.py::fn", "symbol_kind": "function",
268 "blast": 7, "blast_direct": 3, "blast_cross": 4},
269 ])
270
271 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
272 db_session, repo.repo_id, "ref", {},
273 )
274
275 row = await _get_row(db_session, repo.repo_id, "a.py::fn")
276 assert row.gravity_transitive_dependents == 7 # blast = blast_direct + blast_cross
277
278
279 # ---------------------------------------------------------------------------
280 # Layer 4 — Denominator scope: only tracked kinds
281 # ---------------------------------------------------------------------------
282
283 class TestDenominator:
284
285 @pytest.mark.asyncio
286 async def test_P5_07_import_kind_excluded_from_denominator(
287 self, db_session: AsyncSession
288 ) -> None:
289 """import-kind rows don't count toward total_prod_symbols."""
290 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
291
292 repo = await create_repo(db_session)
293 # 2 tracked + 5 import = 2 tracked total for denominator
294 await _seed_symbols(db_session, repo.repo_id, [
295 {"address": "a.py::fn_a", "symbol_kind": "function",
296 "blast": 1, "blast_direct": 1, "blast_cross": 0},
297 {"address": "a.py::fn_b", "symbol_kind": "function",
298 "blast": 1, "blast_direct": 1, "blast_cross": 0},
299 ] + [
300 {"address": f"a.py::import_{i}", "symbol_kind": "import",
301 "blast": 0, "blast_direct": 0, "blast_cross": 0}
302 for i in range(5)
303 ])
304
305 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
306 db_session, repo.repo_id, "ref", {},
307 )
308
309 row = await _get_row(db_session, repo.repo_id, "a.py::fn_a")
310 # total tracked = 2, denom = max(1, 2-1) = 1
311 expected = _gravity_pct(blast=1, total=2)
312 assert row.gravity_pct == pytest.approx(expected)
313
314 @pytest.mark.asyncio
315 async def test_P5_08_null_kind_excluded_from_denominator(
316 self, db_session: AsyncSession
317 ) -> None:
318 """Rows with symbol_kind=NULL don't count toward total."""
319 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
320
321 repo = await create_repo(db_session)
322 await _seed_symbols(db_session, repo.repo_id, [
323 {"address": "a.py::fn", "symbol_kind": "function",
324 "blast": 1, "blast_direct": 1, "blast_cross": 0},
325 {"address": "a.py::unknown", "symbol_kind": None,
326 "blast": 0, "blast_direct": 0, "blast_cross": 0},
327 ])
328
329 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
330 db_session, repo.repo_id, "ref", {},
331 )
332
333 row = await _get_row(db_session, repo.repo_id, "a.py::fn")
334 # total tracked = 1, denom = max(1, 1-1) = 1
335 expected = _gravity_pct(blast=1, total=1)
336 assert row.gravity_pct == pytest.approx(expected)
337
338 @pytest.mark.asyncio
339 async def test_P5_09_all_tracked_kinds_count_in_denominator(
340 self, db_session: AsyncSession
341 ) -> None:
342 """All 5 tracked kinds contribute to the denominator."""
343 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
344
345 repo = await create_repo(db_session)
346 symbols = [
347 {"address": f"a.py::{kind}_sym", "symbol_kind": kind,
348 "blast": 0, "blast_direct": 0, "blast_cross": 0}
349 for kind in _TRACKED_KINDS
350 ]
351 symbols.append(
352 {"address": "a.py::target", "symbol_kind": "function",
353 "blast": 5, "blast_direct": 2, "blast_cross": 3}
354 )
355 await _seed_symbols(db_session, repo.repo_id, symbols)
356
357 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
358 db_session, repo.repo_id, "ref", {},
359 )
360
361 row = await _get_row(db_session, repo.repo_id, "a.py::target")
362 # 6 tracked symbols total (5 kinds + target itself)
363 expected = _gravity_pct(blast=5, total=6)
364 assert row.gravity_pct == pytest.approx(expected)
365
366
367 # ---------------------------------------------------------------------------
368 # Layer 5 — Edge: single symbol
369 # ---------------------------------------------------------------------------
370
371 class TestEdgeCases:
372
373 @pytest.mark.asyncio
374 async def test_P5_10_single_symbol_denom_is_one(
375 self, db_session: AsyncSession
376 ) -> None:
377 """Single symbol: denom = max(1, 1-1) = 1, no ZeroDivisionError."""
378 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
379
380 repo = await create_repo(db_session)
381 await _seed_symbols(db_session, repo.repo_id, [
382 {"address": "a.py::only", "symbol_kind": "function",
383 "blast": 0, "blast_direct": 0, "blast_cross": 0},
384 ])
385
386 results = await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
387 db_session, repo.repo_id, "ref", {},
388 )
389
390 row = await _get_row(db_session, repo.repo_id, "a.py::only")
391 assert row.gravity_pct == pytest.approx(0.0)
392 assert results != []
393
394 @pytest.mark.asyncio
395 async def test_P5_11_zero_blast_yields_zero_pct(
396 self, db_session: AsyncSession
397 ) -> None:
398 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
399
400 repo = await create_repo(db_session)
401 await _seed_symbols(db_session, repo.repo_id, [
402 {"address": "a.py::leaf", "symbol_kind": "function",
403 "blast": 0, "blast_direct": 0, "blast_cross": 0},
404 {"address": "b.py::other", "symbol_kind": "function",
405 "blast": 2, "blast_direct": 1, "blast_cross": 1},
406 ])
407
408 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
409 db_session, repo.repo_id, "ref", {},
410 )
411
412 row = await _get_row(db_session, repo.repo_id, "a.py::leaf")
413 assert row.gravity_pct == pytest.approx(0.0)
414
415
416 # ---------------------------------------------------------------------------
417 # Layer 6 — Writes only existing rows, no new inserts
418 # ---------------------------------------------------------------------------
419
420 class TestWriteBehavior:
421
422 @pytest.mark.asyncio
423 async def test_P5_12_row_count_unchanged_after_compute(
424 self, db_session: AsyncSession
425 ) -> None:
426 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
427
428 repo = await create_repo(db_session)
429 await _seed_symbols(db_session, repo.repo_id, [
430 {"address": "a.py::fn_a", "symbol_kind": "function",
431 "blast": 3, "blast_direct": 1, "blast_cross": 2},
432 {"address": "a.py::fn_b", "symbol_kind": "function",
433 "blast": 1, "blast_direct": 1, "blast_cross": 0},
434 ])
435
436 before = (await db_session.execute(
437 select(func.count()).where(
438 MusehubSymbolIntel.repo_id == repo.repo_id
439 )
440 )).scalar_one()
441
442 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
443 db_session, repo.repo_id, "ref", {},
444 )
445
446 after = (await db_session.execute(
447 select(func.count()).where(
448 MusehubSymbolIntel.repo_id == repo.repo_id
449 )
450 )).scalar_one()
451
452 assert after == before, (
453 f"Row count changed: {before} → {after}. "
454 "GravityProvider must update existing rows, not insert new ones."
455 )
456
457 @pytest.mark.asyncio
458 async def test_P5_13_returns_intel_results_tuple(
459 self, db_session: AsyncSession
460 ) -> None:
461 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
462
463 repo = await create_repo(db_session)
464 await _seed_symbols(db_session, repo.repo_id, [
465 {"address": "a.py::fn", "symbol_kind": "function",
466 "blast": 1, "blast_direct": 1, "blast_cross": 0},
467 ])
468
469 results = await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
470 db_session, repo.repo_id, "ref", {},
471 )
472
473 assert isinstance(results, list) and len(results) > 0
474 job_type, data = results[0]
475 assert job_type == "intel.code.gravity"
476 assert "count" in data
477 assert data["count"] >= 1
478
479
480 # ---------------------------------------------------------------------------
481 # Layer 7 — Preserve non-gravity columns
482 # ---------------------------------------------------------------------------
483
484 class TestPreserve:
485
486 @pytest.mark.asyncio
487 async def test_P5_14_churn_preserved_after_compute(
488 self, db_session: AsyncSession
489 ) -> None:
490 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
491
492 repo = await create_repo(db_session)
493 await _seed_symbols(db_session, repo.repo_id, [
494 {"address": "a.py::fn", "symbol_kind": "function",
495 "blast": 5, "blast_direct": 2, "blast_cross": 3,
496 "churn": 42, "churn_30d": 7},
497 ])
498
499 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
500 db_session, repo.repo_id, "ref", {},
501 )
502
503 row = await _get_row(db_session, repo.repo_id, "a.py::fn")
504 assert row.churn == 42, "churn must not be overwritten by gravity compute"
505 assert row.churn_30d == 7, "churn_30d must not be overwritten"
506
507 @pytest.mark.asyncio
508 async def test_P5_15_blast_columns_preserved_after_compute(
509 self, db_session: AsyncSession
510 ) -> None:
511 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
512
513 repo = await create_repo(db_session)
514 await _seed_symbols(db_session, repo.repo_id, [
515 {"address": "a.py::fn", "symbol_kind": "function",
516 "blast": 5, "blast_direct": 2, "blast_cross": 3},
517 ])
518
519 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
520 db_session, repo.repo_id, "ref", {},
521 )
522
523 row = await _get_row(db_session, repo.repo_id, "a.py::fn")
524 assert row.blast == 5
525 assert row.blast_direct == 2
526 assert row.blast_cross == 3
527
528
529 # ---------------------------------------------------------------------------
530 # Layer 8 — Idempotent
531 # ---------------------------------------------------------------------------
532
533 class TestIdempotent:
534
535 @pytest.mark.asyncio
536 async def test_P5_16_second_run_yields_same_pct(
537 self, db_session: AsyncSession
538 ) -> None:
539 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
540
541 repo = await create_repo(db_session)
542 await _seed_symbols(db_session, repo.repo_id, [
543 {"address": "a.py::fn", "symbol_kind": "function",
544 "blast": 3, "blast_direct": 1, "blast_cross": 2},
545 ])
546
547 for _ in range(3):
548 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
549 db_session, repo.repo_id, "ref", {},
550 )
551
552 row = await _get_row(db_session, repo.repo_id, "a.py::fn")
553 expected = _gravity_pct(blast=3, total=1)
554 assert row.gravity_pct == pytest.approx(expected)
555
556 count = (await db_session.execute(
557 select(func.count()).where(
558 MusehubSymbolIntel.repo_id == repo.repo_id
559 )
560 )).scalar_one()
561 assert count == 1
562
563
564 # ---------------------------------------------------------------------------
565 # Layer 9 — Empty: no tracked rows → returns []
566 # ---------------------------------------------------------------------------
567
568 class TestEmpty:
569
570 @pytest.mark.asyncio
571 async def test_P5_17_no_rows_returns_empty(
572 self, db_session: AsyncSession
573 ) -> None:
574 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
575
576 repo = await create_repo(db_session)
577
578 results = await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
579 db_session, repo.repo_id, "ref", {},
580 )
581
582 assert results == []
583
584 @pytest.mark.asyncio
585 async def test_P5_18_only_import_kind_rows_returns_empty(
586 self, db_session: AsyncSession
587 ) -> None:
588 """No tracked-kind rows means no gravity to compute."""
589 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
590
591 repo = await create_repo(db_session)
592 await _seed_symbols(db_session, repo.repo_id, [
593 {"address": f"a.py::import_{i}", "symbol_kind": "import",
594 "blast": 0, "blast_direct": 0, "blast_cross": 0}
595 for i in range(3)
596 ])
597
598 results = await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
599 db_session, repo.repo_id, "ref", {},
600 )
601
602 assert results == []
603
604
605 # ---------------------------------------------------------------------------
606 # Layer 10 — max_depth and depth_distribution not derivable
607 # ---------------------------------------------------------------------------
608
609 class TestNotDerivable:
610
611 @pytest.mark.asyncio
612 async def test_P5_19_gravity_max_depth_stays_null(
613 self, db_session: AsyncSession
614 ) -> None:
615 """gravity_max_depth is not derivable from blast columns."""
616 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
617
618 repo = await create_repo(db_session)
619 await _seed_symbols(db_session, repo.repo_id, [
620 {"address": "a.py::fn", "symbol_kind": "function",
621 "blast": 5, "blast_direct": 2, "blast_cross": 3},
622 ])
623
624 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
625 db_session, repo.repo_id, "ref", {},
626 )
627
628 row = await _get_row(db_session, repo.repo_id, "a.py::fn")
629 assert row.gravity_max_depth is None
630
631 @pytest.mark.asyncio
632 async def test_P5_20_gravity_depth_distribution_stays_null(
633 self, db_session: AsyncSession
634 ) -> None:
635 """gravity_depth_distribution is not derivable from blast columns."""
636 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
637
638 repo = await create_repo(db_session)
639 await _seed_symbols(db_session, repo.repo_id, [
640 {"address": "a.py::fn", "symbol_kind": "function",
641 "blast": 5, "blast_direct": 2, "blast_cross": 3},
642 ])
643
644 await _PROVIDER_REGISTRY["intel.code.gravity"].compute(
645 db_session, repo.repo_id, "ref", {},
646 )
647
648 row = await _get_row(db_session, repo.repo_id, "a.py::fn")
649 assert row.gravity_depth_distribution is None
File History 1 commit
sha256:ef10830ce231e0a20efcb0e2586cb879471247e916616e6fdd0d51df459e2595 fix: typing audit — 0 violations, 0 untyped defs across all… Sonnet 4.6 minor 21 days ago