gabriel / musehub public

test_phase3_gravity_provider.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 """TDD spec for Phase 3, Part 2 β€” GravityProvider (issue #9).
2
3 New provider class ``GravityProvider`` with job type ``intel.code.gravity``.
4
5 Runs ``muse -C <repo_root> code gravity --json`` on push and upserts
6 the 6 gravity columns (gravity_pct, gravity_direct_dependents, gravity_transitive_dependents,
7 gravity_max_depth, gravity_depth_distribution, symbol_kind) into musehub_symbol_intel.
8
9 Key constraint: gravity upsert must NOT touch churn/blast columns.
10
11 Layers:
12 1. Registry β€” "intel.code.gravity" in _PROVIDER_REGISTRY
13 2. Protocol β€” provider instance satisfies IntelProvider
14 3. Dispatch β€” job_types_for_push("code") includes "intel.code.gravity"
15 4. Write β€” upserts all 6 gravity columns from muse output
16 5. Preserve β€” churn/blast untouched after gravity upsert
17 6. JSONB β€” depth_dist stored as dict, not string
18 7. Idempotent β€” run twice, one row with latest data
19 8. Multi-row β€” multiple symbols all upserted in one compute() call
20 9. Empty β€” empty symbols list returns [] gracefully
21 10. Error β€” non-zero subprocess exit returns [] gracefully
22 """
23 from __future__ import annotations
24
25 import json
26 import secrets
27 from unittest.mock import AsyncMock, patch
28
29 import pytest
30 from sqlalchemy import select
31 from sqlalchemy.ext.asyncio import AsyncSession
32
33 from muse.core.types import fake_id
34 from tests.factories import create_repo
35
36
37 def _uid() -> str:
38 return fake_id(secrets.token_hex(16))
39
40
41 def _mock_process(stdout: str, returncode: int = 0) -> AsyncMock:
42 proc = AsyncMock()
43 proc.returncode = returncode
44 proc.communicate = AsyncMock(return_value=(stdout.encode(), b""))
45 return proc
46
47
48 _GRAVITY_OUTPUT = json.dumps({
49 "total_production_symbols": 1883,
50 "max_depth": 9,
51 "symbols": [
52 {
53 "address": "musehub/storage/backends.py::S3Backend._key",
54 "name": "_key",
55 "kind": "method",
56 "file": "musehub/storage/backends.py",
57 "gravity_pct": 38.9,
58 "direct_dependents": 11,
59 "transitive_dependents": 733,
60 "max_depth": 6,
61 "depth_distribution": {"1": 11, "2": 484, "3": 197, "4": 35, "5": 5, "6": 1},
62 },
63 {
64 "address": "musehub/storage/backends.py::StorageBackend.get",
65 "name": "get",
66 "kind": "async_method",
67 "file": "musehub/storage/backends.py",
68 "gravity_pct": 36.2,
69 "direct_dependents": 424,
70 "transitive_dependents": 682,
71 "max_depth": 5,
72 "depth_distribution": {"1": 424, "2": 206, "3": 46, "4": 5, "5": 1},
73 },
74 ],
75 })
76
77
78 # ─────────────────────────────────────────────────────────────────────────────
79 # Layer 1 β€” Registry
80 # ─────────────────────────────────────────────────────────────────────────────
81
82 class TestGravityProviderRegistry:
83
84 def test_P3_29_gravity_in_provider_registry(self) -> None:
85 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY
86 assert "intel.code.gravity" in _PROVIDER_REGISTRY, (
87 "intel.code.gravity not registered in _PROVIDER_REGISTRY"
88 )
89
90 def test_P3_30_gravity_satisfies_intel_provider_protocol(self) -> None:
91 from musehub.services.musehub_intel_providers import _PROVIDER_REGISTRY, IntelProvider
92 provider = _PROVIDER_REGISTRY["intel.code.gravity"]
93 assert isinstance(provider, IntelProvider)
94
95
96 # ─────────────────────────────────────────────────────────────────────────────
97 # Layer 2 β€” Dispatch
98 # ─────────────────────────────────────────────────────────────────────────────
99
100 class TestGravityProviderDispatch:
101
102 def test_P3_31_job_types_for_push_code_includes_gravity(self) -> None:
103 from musehub.services.musehub_intel_providers import job_types_for_push
104 types = job_types_for_push("code")
105 assert "intel.code.gravity" in types
106
107 def test_P3_32_job_types_for_push_midi_excludes_gravity(self) -> None:
108 from musehub.services.musehub_intel_providers import job_types_for_push
109 assert "intel.code.gravity" not in job_types_for_push("midi")
110
111 def test_P3_33_job_types_for_push_legacy_types_intact(self) -> None:
112 from musehub.services.musehub_intel_providers import job_types_for_push
113 types = job_types_for_push("code")
114 for required in ("intel.structural", "intel.code", "gc"):
115 assert required in types, f"{required} missing from job_types_for_push('code')"
116