gabriel / muse public
test_phase1_naming.py python
253 lines 8.7 KB
Raw
sha256:2eaa5d95f9d9383498e76947410a26e5a3ba23d182f339910c424cf88fad412b fix: try fetch/presign before fetch/mpack to avoid Cloudfla… Sonnet 4.6 patch 6 days ago
1 """Phase 1 of issue #12: naming fix — from_msgpack → from_dict.
2
3 Coverage
4 --------
5 - CommitRecord.from_dict accepts the same input as from_msgpack did
6 - SnapshotRecord.from_dict accepts the same input as from_msgpack did
7 - TagRecord.from_dict accepts the same input as from_msgpack did
8 - ReleaseRecord.from_dict accepts the same input as from_msgpack did
9 - SymbolHistoryEntry.from_dict accepts the same input as from_msgpack did
10 - No from_msgpack method exists on any storage record class
11 - import msgpack does not appear in storage modules (wire-only allowlist enforced)
12 """
13
14 from __future__ import annotations
15
16 import datetime
17 import pathlib
18
19 import pytest
20
21 from muse.core.commits import CommitRecord
22 from muse.core.snapshots import SnapshotRecord
23 from muse.core.tags import TagRecord
24 from muse.core.releases import ReleaseRecord
25 from muse.core.indices import SymbolHistoryEntry
26 from muse.core.types import MsgpackDict, long_id, fake_id
27
28 _NOW = datetime.datetime(2025, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)
29 _TS = _NOW.isoformat()
30 _CID = long_id("a" * 64)
31 _SID = long_id("b" * 64)
32 _RID = "test-repo"
33 _TID = fake_id("tag-id")
34
35
36 # ---------------------------------------------------------------------------
37 # CommitRecord.from_dict
38 # ---------------------------------------------------------------------------
39
40 class TestCommitRecordFromDict:
41 def _minimal(self) -> MsgpackDict:
42 return {
43 "commit_id": _CID,
44 "repo_id": _RID,
45 "branch": "main",
46 "snapshot_id": _SID,
47 "message": "test commit",
48 "committed_at": _TS,
49 }
50
51 def test_from_dict_exists(self) -> None:
52 assert hasattr(CommitRecord, "from_dict")
53
54 def test_from_msgpack_does_not_exist(self) -> None:
55 assert not hasattr(CommitRecord, "from_msgpack"), (
56 "CommitRecord.from_msgpack must be removed — use from_dict"
57 )
58
59 def test_roundtrip(self) -> None:
60 rec = CommitRecord.from_dict(self._minimal())
61 assert rec.branch == "main"
62 assert rec.message == "test commit"
63
64 def test_optional_fields_absent(self) -> None:
65 rec = CommitRecord.from_dict(self._minimal())
66 assert rec.parent_commit_id is None
67 assert rec.agent_id == ""
68
69 def test_raises_on_bad_committed_at(self) -> None:
70 d = self._minimal()
71 d["committed_at"] = "not-a-date"
72 with pytest.raises((ValueError, Exception)):
73 CommitRecord.from_dict(d)
74
75
76 # ---------------------------------------------------------------------------
77 # SnapshotRecord.from_dict
78 # ---------------------------------------------------------------------------
79
80 class TestSnapshotRecordFromDict:
81 def _minimal(self) -> MsgpackDict:
82 return {
83 "snapshot_id": _SID,
84 "repo_id": _RID,
85 "manifest": {},
86 "created_at": _TS,
87 }
88
89 def test_from_dict_exists(self) -> None:
90 assert hasattr(SnapshotRecord, "from_dict")
91
92 def test_from_msgpack_does_not_exist(self) -> None:
93 assert not hasattr(SnapshotRecord, "from_msgpack"), (
94 "SnapshotRecord.from_msgpack must be removed — use from_dict"
95 )
96
97 def test_roundtrip(self) -> None:
98 rec = SnapshotRecord.from_dict(self._minimal())
99 assert rec.snapshot_id == _SID
100 assert rec.manifest == {}
101
102 def test_raises_on_bad_created_at(self) -> None:
103 d = self._minimal()
104 d["created_at"] = "not-a-date"
105 with pytest.raises((ValueError, Exception)):
106 SnapshotRecord.from_dict(d)
107
108
109 # ---------------------------------------------------------------------------
110 # TagRecord.from_dict
111 # ---------------------------------------------------------------------------
112
113 class TestTagRecordFromDict:
114 def _minimal(self) -> MsgpackDict:
115 return {
116 "repo_id": _RID,
117 "tag_id": _TID,
118 "commit_id": _CID,
119 "tag": "v1.0.0",
120 "created_at": _TS,
121 "message": "",
122 "signature": "",
123 "signer_public_key": "",
124 "signer_key_id": "",
125 }
126
127 def test_from_dict_exists(self) -> None:
128 assert hasattr(TagRecord, "from_dict")
129
130 def test_from_msgpack_does_not_exist(self) -> None:
131 assert not hasattr(TagRecord, "from_msgpack"), (
132 "TagRecord.from_msgpack must be removed — use from_dict"
133 )
134
135 def test_roundtrip(self) -> None:
136 rec = TagRecord.from_dict(self._minimal())
137 assert rec.tag == "v1.0.0"
138 assert rec.commit_id == _CID
139
140
141 # ---------------------------------------------------------------------------
142 # ReleaseRecord.from_dict
143 # ---------------------------------------------------------------------------
144
145 class TestReleaseRecordFromDict:
146 def _minimal(self) -> MsgpackDict:
147 return {
148 "release_id": fake_id("rel-id"),
149 "repo_id": _RID,
150 "tag": "v1.0.0",
151 "semver": "1.0.0",
152 "channel": "stable",
153 "commit_id": _CID,
154 "title": "",
155 "notes": "",
156 "created_at": _TS,
157 "is_draft": False,
158 "gpg_signature": "",
159 }
160
161 def test_from_dict_exists(self) -> None:
162 assert hasattr(ReleaseRecord, "from_dict")
163
164 def test_from_msgpack_does_not_exist(self) -> None:
165 assert not hasattr(ReleaseRecord, "from_msgpack"), (
166 "ReleaseRecord.from_msgpack must be removed — use from_dict"
167 )
168
169 def test_roundtrip(self) -> None:
170 rec = ReleaseRecord.from_dict(self._minimal())
171 assert rec.tag == "v1.0.0"
172 assert rec.channel == "stable"
173
174
175 # ---------------------------------------------------------------------------
176 # SymbolHistoryEntry.from_dict
177 # ---------------------------------------------------------------------------
178
179 class TestSymbolHistoryEntryFromDict:
180 def _minimal(self) -> MsgpackDict:
181 return {
182 "commit_id": _CID,
183 "committed_at": _TS,
184 "op": "modified",
185 "content_id": "",
186 "body_hash": "",
187 "signature_id": "",
188 }
189
190 def test_from_dict_exists(self) -> None:
191 assert hasattr(SymbolHistoryEntry, "from_dict")
192
193 def test_from_msgpack_does_not_exist(self) -> None:
194 assert not hasattr(SymbolHistoryEntry, "from_msgpack"), (
195 "SymbolHistoryEntry.from_msgpack must be removed — use from_dict"
196 )
197
198 def test_roundtrip(self) -> None:
199 entry = SymbolHistoryEntry.from_dict(self._minimal())
200 assert entry.commit_id == _CID
201 assert entry.op == "modified"
202
203
204 # ---------------------------------------------------------------------------
205 # Lint: no from_msgpack attribute on any storage record class
206 # ---------------------------------------------------------------------------
207
208 _REPO_ROOT = pathlib.Path(__file__).parent.parent
209
210 _RECORD_CLASSES = [CommitRecord, SnapshotRecord, TagRecord, ReleaseRecord, SymbolHistoryEntry]
211
212
213 def _has_from_msgpack_call(path: pathlib.Path) -> list[int]:
214 """Return line numbers where .from_msgpack( appears in *path*."""
215 lines = []
216 for i, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
217 if ".from_msgpack(" in line and not line.strip().startswith("#"):
218 lines.append(i)
219 return lines
220
221
222 class TestNoFromMsgpackRemains:
223 def test_no_from_msgpack_on_record_classes(self) -> None:
224 """No storage record class may have a from_msgpack method."""
225 for cls in _RECORD_CLASSES:
226 assert not hasattr(cls, "from_msgpack"), (
227 f"{cls.__name__}.from_msgpack still exists — rename to from_dict"
228 )
229
230 def test_no_from_msgpack_calls_in_source(self) -> None:
231 """No source file under muse/ (non-test) may call .from_msgpack()."""
232 source_root = _REPO_ROOT / "muse"
233 violations: list[tuple[pathlib.Path, int]] = []
234 for path in source_root.rglob("*.py"):
235 for lineno in _has_from_msgpack_call(path):
236 violations.append((path.relative_to(_REPO_ROOT), lineno))
237 assert violations == [], (
238 f"Calls to .from_msgpack() found in source files: {violations}"
239 )
240
241 def test_no_from_msgpack_calls_in_tests(self) -> None:
242 """No test file may call .from_msgpack() on a record class."""
243 tests_root = _REPO_ROOT / "tests"
244 this_file = pathlib.Path(__file__).resolve()
245 violations: list[tuple[pathlib.Path, int]] = []
246 for path in tests_root.rglob("*.py"):
247 if path.resolve() == this_file:
248 continue # this file documents the naming contract
249 for lineno in _has_from_msgpack_call(path):
250 violations.append((path.relative_to(_REPO_ROOT), lineno))
251 assert violations == [], (
252 f"Calls to .from_msgpack() found in test files: {violations}"
253 )
File History 1 commit
sha256:2eaa5d95f9d9383498e76947410a26e5a3ba23d182f339910c424cf88fad412b fix: try fetch/presign before fetch/mpack to avoid Cloudfla… Sonnet 4.6 patch 6 days ago