gabriel / musehub public
test_phase3_gravity_provider.py python
116 lines 5.0 KB
Raw
sha256:1dd27e6d5c24550177b01b0a171e8375c07cf046b549e7683364706b6522d3bc docs(#139): mark all 11 checklist items resolved, document … Sonnet 5 12 days ago
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
File History 2 commits
sha256:1dd27e6d5c24550177b01b0a171e8375c07cf046b549e7683364706b6522d3bc docs(#139): mark all 11 checklist items resolved, document … Sonnet 5 12 days ago
sha256:649011bedd713e22f7dca4c4be94bdefbf3b10950d9fc80235928d0b0b823be8 docs: track issue #139 (two-column app-shell refactor) plan… Sonnet 5 12 days ago