gabriel / musehub public

test_fetch_mpack_prebuild.py file-level

at sha256:8 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 πŸ’₯ blast risk
sha256:8 Merge 'feat/9a-4-f7-overseer-provenance' into 'dev' β€” proposal: Phase 9… · gabriel · Sep 9, 2026
1 """TDD β€” fetch.mpack.prebuild job handler and wire_fetch_mpack cache (issue #92 Phases 2–5).
2
3 Test IDs:
4 FMC_07 Unit: mock wire_fetch_mpack, confirm cache rows written for each tip,
5 confirm existing fresh entries are skipped
6 FMC_08 Integration: insert a job row, run the handler, verify
7 MusehubFetchMPackCache row exists with correct repo_id/tip/mpack_id
8 FMC_13 Unit: cache hit returns presigned URL without entering blob-load path
9 FMC_14 Unit: cache miss builds and writes cache row
10 FMC_18 Integration: enqueue_push_intel inserts fetch.mpack.prebuild with branch tips
11 FMC_20 Unit: gc_fetch_mpack_cache deletes expired rows + R2 objects; fresh rows untouched
12 """
13 from __future__ import annotations
14
15 import hashlib
16 from datetime import datetime, timedelta, timezone
17 from unittest.mock import AsyncMock, patch
18
19 import pytest
20 from sqlalchemy import select
21 from sqlalchemy.ext.asyncio import AsyncSession
22
23 from musehub.core.genesis import compute_job_id
24 from musehub.db.musehub_jobs_models import MusehubBackgroundJob
25 from musehub.db.musehub_repo_models import MusehubFetchMPackCache
26 from musehub.services.musehub_gc import gc_fetch_mpack_cache
27 from musehub.services.musehub_jobs import enqueue_push_intel
28 from musehub.services.musehub_wire_fetch import process_fetch_mpack_prebuild_job, wire_fetch_mpack
29 from musehub.services.musehub_wire_shared import MPackNotReadyError
30 from tests.factories import create_branch, create_repo
31
32
33 def _now() -> datetime:
34 return datetime.now(tz=timezone.utc)
35
36
37 def _fake_commit_id(seed: str) -> str:
38 return "sha256:" + hashlib.sha256(seed.encode()).hexdigest()
39
40
41 def _fake_mpack_id(seed: str) -> str:
42 return "sha256:" + hashlib.sha256(f"mpack-{seed}".encode()).hexdigest()
43
44
45 async def _insert_job(
46 session: AsyncSession,
47 repo_id: str,
48 tip_commit_ids: list[str],
49 ) -> str:
50 now = _now()
51 job_id = compute_job_id(repo_id, "fetch.mpack.prebuild", now.isoformat())
52 session.add(MusehubBackgroundJob(
53 job_id=job_id,
54 repo_id=repo_id,
55 job_type="fetch.mpack.prebuild",
56 payload={"tip_commit_ids": tip_commit_ids},
57 status="pending",
58 created_at=now,
59 attempt=0,
60 ))
61 await session.flush()
62 return job_id
63
64
65 # ── FMC_07 ────────────────────────────────────────────────────────────────────
66
67 @pytest.mark.tier2
68 async def test_fmc_07_builds_uncached_tips_in_one_combined_mpack(db_session: AsyncSession) -> None:
69 """FMC_07a: the handler builds all uncached tips in a SINGLE combined wire_fetch_mpack
70 call (want=[all uncached tips]) β€” not one call per tip. The per-tip cache rows are
71 written inside wire_fetch_mpack (covered by FMC_14), so it is mocked here and we assert
72 the handler's orchestration + counts only."""
73 repo = await create_repo(db_session, owner="gabriel", visibility="public")
74 tip_a = _fake_commit_id("tip-a")
75 tip_b = _fake_commit_id("tip-b")
76 combined_mpack = _fake_mpack_id("combined")
77
78 # The handler reads live branch tips from MusehubBranch (Phase 1 fix) β€”
79 # create branches so the handler sees tip_a and tip_b as the live tip set.
80 await create_branch(db_session, repo.repo_id, name="main", head_commit_id=tip_a)
81 await create_branch(db_session, repo.repo_id, name="dev", head_commit_id=tip_b)
82 job_id = await _insert_job(db_session, repo.repo_id, [tip_a, tip_b])
83 await db_session.commit()
84
85 with patch(
86 "musehub.services.musehub_wire_fetch.wire_fetch_mpack",
87 new_callable=AsyncMock,
88 return_value={"mpack_id": combined_mpack, "mpack_url": "https://r2.example/c", "commit_count": 2, "blob_count": 5},
89 ) as mock_build:
90 result = await process_fetch_mpack_prebuild_job(db_session, job_id)
91 await db_session.commit()
92
93 # One combined build covering both uncached tips β€” not one call per tip.
94 assert mock_build.call_count == 1
95 assert set(mock_build.call_args.kwargs["want"]) == {tip_a, tip_b}
96 assert result["tips_requested"] == 2
97 assert result["tips_built"] == 2
98 assert result["tips_skipped"] == 0
99
100
101 @pytest.mark.tier2
102 async def test_fmc_07b_skips_tips_with_fresh_cache(db_session: AsyncSession) -> None:
103 """FMC_07b: prebuild skips ALL tips only when every tip shares the same cached mpack_id.
104
105 When one tip is already cached with a DIFFERENT mpack_id than would be built for a
106 new tip, the prebuild must rebuild ALL tips together so that every tip ends up
107 pointing to the same combined mpack_id β€” required for the clone cache-hit check
108 (len(mpack_ids)==1 across all want tips).
109 """
110 repo = await create_repo(db_session, owner="gabriel", visibility="public")
111 tip_cached = _fake_commit_id("tip-cached")
112 tip_new = _fake_commit_id("tip-new")
113 existing_mpack = _fake_mpack_id("existing")
114 new_mpack = _fake_mpack_id("new")
115
116 # Pre-populate a fresh cache entry for tip_cached (different mpack_id from what
117 # the combined build will produce β€” diverged state).
118 cache_id = hashlib.sha256((repo.repo_id + tip_cached).encode()).hexdigest()
119 db_session.add(MusehubFetchMPackCache(
120 cache_id=cache_id,
121 repo_id=repo.repo_id,
122 tip_commit_id=tip_cached,
123 mpack_id=existing_mpack,
124 created_at=_now(),
125 expires_at=_now() + timedelta(days=7),
126 ))
127 # The handler reads live branch tips from MusehubBranch (Phase 1 fix).
128 await create_branch(db_session, repo.repo_id, name="main", head_commit_id=tip_cached)
129 await create_branch(db_session, repo.repo_id, name="dev", head_commit_id=tip_new)
130 job_id = await _insert_job(db_session, repo.repo_id, [tip_cached, tip_new])
131 await db_session.commit()
132
133 with patch(
134 "musehub.services.musehub_wire_fetch.wire_fetch_mpack",
135 new_callable=AsyncMock,
136 return_value={"mpack_id": new_mpack, "mpack_url": "https://r2.example/new", "commit_count": 2, "blob_count": 2},
137 ) as mock_build:
138 result = await process_fetch_mpack_prebuild_job(db_session, job_id)
139 await db_session.commit()
140
141 # Both tips must be rebuilt together so they share one mpack_id.
142 assert mock_build.call_count == 1
143 actual_want = mock_build.call_args[1].get("want") or mock_build.call_args[0][2]
144 assert set(actual_want) == {tip_cached, tip_new}, (
145 f"want must include ALL tips; got {actual_want}"
146 )
147 assert result["tips_built"] == 2
148 assert result["tips_skipped"] == 0
149
150 # The previously-cached entry must be updated to the new combined mpack_id.
151 cached_row = (await db_session.execute(
152 select(MusehubFetchMPackCache)
153 .where(MusehubFetchMPackCache.repo_id == repo.repo_id)
154 .where(MusehubFetchMPackCache.tip_commit_id == tip_cached)
155 )).scalar_one()
156 assert cached_row.mpack_id == new_mpack, (
157 "existing cache entry must be updated to the combined mpack_id"
158 )
159
160
161 @pytest.mark.tier2
162 async def test_fmc_07c_empty_payload_is_a_noop(db_session: AsyncSession) -> None:
163 """FMC_07c: job with no tip_commit_ids returns zeros without calling wire_fetch_mpack."""
164 repo = await create_repo(db_session, owner="gabriel", visibility="public")
165 job_id = await _insert_job(db_session, repo.repo_id, [])
166 await db_session.commit()
167
168 with patch(
169 "musehub.services.musehub_wire_fetch.wire_fetch_mpack",
170 new_callable=AsyncMock,
171 ) as mock_build:
172 result = await process_fetch_mpack_prebuild_job(db_session, job_id)
173
174 assert mock_build.call_count == 0
175 assert result["tips_requested"] == 0
176 assert result["tips_built"] == 0
177
178
179 # ── FMC_08 ────────────────────────────────────────────────────────────────────
180
181 @pytest.mark.tier2
182 async def test_fmc_08_cache_row_has_correct_fields(db_session: AsyncSession) -> None:
183 """FMC_08: running the handler end to end (through a REAL wire_fetch_mpack) writes a
184 cache row with matching repo_id, tip, and mpack_id. Cache rows are written inside
185 wire_fetch_mpack, so this exercises the full handler -> build -> cache-write chain
186 rather than mocking the build away. Only the expensive externals (DAG walk, mpack
187 bytes, storage) are stubbed β€” same pattern as FMC_14."""
188 from types import SimpleNamespace
189
190 repo = await create_repo(db_session, owner="gabriel", visibility="public")
191 tip = _fake_commit_id("integration-tip")
192 built_mpack = _fake_mpack_id("integration")
193
194 # The handler reads live branch tips from MusehubBranch (Phase 1 fix).
195 await create_branch(db_session, repo.repo_id, name="main", head_commit_id=tip)
196 job_id = await _insert_job(db_session, repo.repo_id, [tip])
197 await db_session.commit()
198
199 mock_backend = AsyncMock()
200 mock_backend.put_mpack.return_value = None
201 mock_backend.presign_mpack_get.return_value = "https://r2.example/int"
202 mock_backend.delete.return_value = None
203 fake_proxy = SimpleNamespace(commit_id=tip, snapshot_id=None, parent_ids=[])
204
205 with patch("musehub.services.musehub_wire_fetch.get_backend", return_value=mock_backend), \
206 patch("musehub.services.musehub_wire_fetch._walk_commit_delta", new_callable=AsyncMock,
207 return_value={tip: fake_proxy}), \
208 patch("muse.core.mpack.build_wire_mpack", return_value=b"MUSE\x00fake-mpack"), \
209 patch("musehub.services.musehub_wire_fetch.blob_id", return_value=built_mpack):
210 result = await process_fetch_mpack_prebuild_job(db_session, job_id)
211 await db_session.commit()
212
213 assert result["tips_built"] == 1
214
215 row = (await db_session.execute(
216 select(MusehubFetchMPackCache)
217 .where(MusehubFetchMPackCache.repo_id == repo.repo_id)
218 .where(MusehubFetchMPackCache.tip_commit_id == tip)
219 )).scalar_one()
220
221 assert row.repo_id == repo.repo_id
222 assert row.tip_commit_id == tip
223 assert row.mpack_id == built_mpack
224 assert row.expires_at > _now()
225
226
227 # ── FMC_13 ────────────────────────────────────────────────────────────────────
228
229 @pytest.mark.tier2
230 async def test_fmc_13_cache_hit_returns_presigned_url_without_blob_load(db_session: AsyncSession) -> None:
231 """FMC_13: cache hit returns the presigned URL immediately; blob-load path is never entered."""
232 repo = await create_repo(db_session, owner="gabriel", visibility="public")
233 tip = _fake_commit_id("fmc13-tip")
234 cached_mpack = _fake_mpack_id("fmc13-tip")
235 expected_url = "https://r2.example/cached-fmc13"
236
237 # Pre-populate a fresh cache entry.
238 cache_id = hashlib.sha256((repo.repo_id + tip).encode()).hexdigest()
239 db_session.add(MusehubFetchMPackCache(
240 cache_id=cache_id,
241 repo_id=repo.repo_id,
242 tip_commit_id=tip,
243 mpack_id=cached_mpack,
244 created_at=_now(),
245 expires_at=_now() + timedelta(days=7),
246 ))
247 await db_session.commit()
248
249 mock_backend = AsyncMock()
250 mock_backend.presign_mpack_get.return_value = expected_url
251
252 with patch("musehub.services.musehub_wire_fetch.get_backend", return_value=mock_backend), \
253 patch("musehub.services.musehub_wire_fetch._walk_commit_delta", new_callable=AsyncMock) as mock_walk:
254 result = await wire_fetch_mpack(db_session, repo.repo_id, want=[tip], have=[])
255
256 # The expensive DAG walk must never have been called.
257 mock_walk.assert_not_called()
258 # The returned URL must match the presigned URL for the cached mpack.
259 assert result["mpack_url"] == expected_url
260 assert result["mpack_id"] == cached_mpack
261
262
263 # ── FMC_14 ────────────────────────────────────────────────────────────────────
264
265 @pytest.mark.tier2
266 async def test_fmc_14_cache_miss_builds_and_writes_cache_row(db_session: AsyncSession) -> None:
267 """FMC_14: on a cache miss, wire_fetch_mpack builds the mpack and writes a cache row."""
268 from types import SimpleNamespace
269
270 repo = await create_repo(db_session, owner="gabriel", visibility="public")
271 tip = _fake_commit_id("fmc14-tip")
272 built_mpack = _fake_mpack_id("fmc14-tip")
273 built_url = "https://r2.example/built-fmc14"
274
275 # No cache row exists β€” this is a cold miss.
276 mock_backend = AsyncMock()
277 mock_backend.put_mpack.return_value = None
278 mock_backend.presign_mpack_get.return_value = built_url
279 mock_backend.delete.return_value = None
280
281 # _walk_commit_delta returns one proxy commit with no snapshot so that
282 # wire_fetch_mpack proceeds to build an empty-but-valid mpack without
283 # needing real commit/snapshot/object rows in the DB.
284 fake_proxy = SimpleNamespace(commit_id=tip, snapshot_id=None, parent_ids=[])
285
286 with patch("musehub.services.musehub_wire_fetch.get_backend", return_value=mock_backend), \
287 patch("musehub.services.musehub_wire_fetch._walk_commit_delta", new_callable=AsyncMock,
288 return_value={tip: fake_proxy}), \
289 patch("muse.core.mpack.build_wire_mpack", return_value=b"MUSE\x00fake-mpack"), \
290 patch("musehub.services.musehub_wire_fetch.blob_id", return_value=built_mpack):
291 result = await wire_fetch_mpack(db_session, repo.repo_id, want=[tip], have=[], force_build=True)
292 await db_session.commit()
293
294 assert result["mpack_url"] == built_url
295 assert result["mpack_id"] == built_mpack
296
297 # The cache row must have been written.
298 row = (await db_session.execute(
299 select(MusehubFetchMPackCache)
300 .where(MusehubFetchMPackCache.repo_id == repo.repo_id)
301 .where(MusehubFetchMPackCache.tip_commit_id == tip)
302 )).scalar_one()
303 assert row.mpack_id == built_mpack
304 assert row.expires_at > _now()
305
306
307 # ── FMC_18 ────────────────────────────────────────────────────────────────────
308
309 @pytest.mark.tier2
310 async def test_fmc_18_enqueue_push_intel_creates_prebuild_job_with_branch_tips(
311 db_session: AsyncSession,
312 ) -> None:
313 """FMC_18: enqueue_push_intel enqueues fetch.mpack.prebuild with all branch tip commit IDs."""
314 repo = await create_repo(db_session, owner="gabriel", visibility="public")
315
316 tip_a = _fake_commit_id("branch-main")
317 tip_b = _fake_commit_id("branch-dev")
318 await create_branch(db_session, repo.repo_id, name="main", head_commit_id=tip_a)
319 await create_branch(db_session, repo.repo_id, name="dev", head_commit_id=tip_b)
320
321 await enqueue_push_intel(
322 db_session,
323 repo.repo_id,
324 head=tip_a,
325 domain_id=None,
326 branch="main",
327 )
328 await db_session.commit()
329
330 job_row = (await db_session.execute(
331 select(MusehubBackgroundJob)
332 .where(MusehubBackgroundJob.repo_id == repo.repo_id)
333 .where(MusehubBackgroundJob.job_type == "fetch.mpack.prebuild")
334 .where(MusehubBackgroundJob.status == "pending")
335 )).scalar_one()
336
337 tip_ids = set(job_row.payload.get("tip_commit_ids", []))
338 assert tip_a in tip_ids, f"main tip {tip_a[:20]} missing from payload"
339 assert tip_b in tip_ids, f"dev tip {tip_b[:20]} missing from payload"
340
341
342 # ── FMC_20 ────────────────────────────────────────────────────────────────────
343
344 @pytest.mark.tier2
345 async def test_fmc_20_gc_deletes_expired_rows_and_r2_objects_leaves_fresh_untouched(
346 db_session: AsyncSession,
347 ) -> None:
348 """FMC_20: gc_fetch_mpack_cache deletes expired rows + R2 objects; fresh rows survive."""
349 repo = await create_repo(db_session, owner="gabriel", visibility="public")
350
351 tip_expired_a = _fake_commit_id("expired-a")
352 tip_expired_b = _fake_commit_id("expired-b")
353 tip_fresh = _fake_commit_id("fresh")
354
355 mpack_expired_a = _fake_mpack_id("expired-a")
356 mpack_expired_b = _fake_mpack_id("expired-b")
357 mpack_fresh = _fake_mpack_id("fresh")
358
359 past = _now() - timedelta(days=1)
360 future = _now() + timedelta(days=6)
361
362 for tip, mpack, exp in [
363 (tip_expired_a, mpack_expired_a, past),
364 (tip_expired_b, mpack_expired_b, past),
365 (tip_fresh, mpack_fresh, future),
366 ]:
367 cache_id = hashlib.sha256((repo.repo_id + tip).encode()).hexdigest()
368 db_session.add(MusehubFetchMPackCache(
369 cache_id=cache_id,
370 repo_id=repo.repo_id,
371 tip_commit_id=tip,
372 mpack_id=mpack,
373 created_at=_now(),
374 expires_at=exp,
375 ))
376 await db_session.commit()
377
378 mock_backend = AsyncMock()
379 mock_backend.delete.return_value = None
380
381 with patch("musehub.services.musehub_gc.get_backend", return_value=mock_backend):
382 n_deleted = await gc_fetch_mpack_cache(db_session, repo.repo_id)
383 await db_session.commit()
384
385 assert n_deleted == 2
386
387 # R2 delete called exactly once for each expired mpack β€” in any order.
388 deleted_mpack_ids = {call.args[0] for call in mock_backend.delete.call_args_list}
389 assert mpack_expired_a in deleted_mpack_ids
390 assert mpack_expired_b in deleted_mpack_ids
391 assert mpack_fresh not in deleted_mpack_ids
392
393 # Expired rows gone from DB.
394 remaining = (await db_session.execute(
395 select(MusehubFetchMPackCache)
396 .where(MusehubFetchMPackCache.repo_id == repo.repo_id)
397 )).scalars().all()
398 remaining_tips = {r.tip_commit_id for r in remaining}
399 assert tip_expired_a not in remaining_tips
400 assert tip_expired_b not in remaining_tips
401
402 # Fresh row survives.
403 assert tip_fresh in remaining_tips
404
405
406 # ── FMC_21 ────────────────────────────────────────────────────────────────────
407 # Regression for musehub issue #39 (muse clone fails / hangs): a repo that goes
408 # stale (cache TTL expires with no further pushes) previously had no self-heal
409 # path β€” wire_fetch_mpack would raise MPackNotReadyError() forever on every
410 # retry, since nothing ever enqueued a new fetch.mpack.prebuild job for the
411 # current tips. Reproduced live against gabriel/muse on 2026-09-09: cache rows
412 # for both branch heads existed but had expired 5 days earlier, and the client
413 # retried the full 120s budget every single attempt with zero chance of success.
414
415 @pytest.mark.tier2
416 async def test_fmc_21_cache_miss_enqueues_prebuild_job(db_session: AsyncSession) -> None:
417 """FMC_21: on a real (non-force_build) cache miss, a fetch.mpack.prebuild job is enqueued."""
418 repo = await create_repo(db_session, owner="gabriel", visibility="public")
419 tip = _fake_commit_id("fmc21-tip")
420 await create_branch(db_session, repo.repo_id, name="main", head_commit_id=tip)
421
422 with pytest.raises(MPackNotReadyError):
423 await wire_fetch_mpack(db_session, repo.repo_id, want=[tip], have=[])
424 await db_session.commit()
425
426 job_row = (await db_session.execute(
427 select(MusehubBackgroundJob)
428 .where(MusehubBackgroundJob.repo_id == repo.repo_id)
429 .where(MusehubBackgroundJob.job_type == "fetch.mpack.prebuild")
430 .where(MusehubBackgroundJob.status == "pending")
431 )).scalar_one()
432 assert tip in set(job_row.payload.get("tip_commit_ids", []))
433
434
435 @pytest.mark.tier2
436 async def test_fmc_21b_repeated_cache_miss_does_not_duplicate_pending_job(db_session: AsyncSession) -> None:
437 """FMC_21b: a second MISS (e.g. the client's own retry loop) does not enqueue a duplicate job."""
438 repo = await create_repo(db_session, owner="gabriel", visibility="public")
439 tip = _fake_commit_id("fmc21b-tip")
440 await create_branch(db_session, repo.repo_id, name="main", head_commit_id=tip)
441
442 for _ in range(3):
443 with pytest.raises(MPackNotReadyError):
444 await wire_fetch_mpack(db_session, repo.repo_id, want=[tip], have=[])
445 await db_session.commit()
446
447 rows = (await db_session.execute(
448 select(MusehubBackgroundJob)
449 .where(MusehubBackgroundJob.repo_id == repo.repo_id)
450 .where(MusehubBackgroundJob.job_type == "fetch.mpack.prebuild")
451 .where(MusehubBackgroundJob.status == "pending")
452 )).scalars().all()
453 assert len(rows) == 1
454
455
456 @pytest.mark.tier2
457 async def test_fmc_21c_expired_cache_self_heals_end_to_end(db_session: AsyncSession) -> None:
458 """FMC_21c: expired cache -> MISS enqueues job -> running the job -> next fetch is a HIT.
459
460 This is the exact sequence that was broken for gabriel/muse: a dormant
461 repo's cache entry ages past its TTL and, before this fix, could never
462 recover without a fresh push.
463 """
464 from types import SimpleNamespace
465
466 repo = await create_repo(db_session, owner="gabriel", visibility="public")
467 tip = _fake_commit_id("fmc21c-tip")
468 stale_mpack = _fake_mpack_id("fmc21c-stale")
469 await create_branch(db_session, repo.repo_id, name="main", head_commit_id=tip)
470
471 # Seed an *expired* cache row -- exactly what we found live on staging.
472 cache_id = hashlib.sha256((repo.repo_id + tip).encode()).hexdigest()
473 db_session.add(MusehubFetchMPackCache(
474 cache_id=cache_id,
475 repo_id=repo.repo_id,
476 tip_commit_id=tip,
477 mpack_id=stale_mpack,
478 created_at=_now() - timedelta(days=8),
479 expires_at=_now() - timedelta(days=1),
480 ))
481 await db_session.commit()
482
483 # Step 1 -- MISS (expired row doesn't count as a hit) must self-heal.
484 with pytest.raises(MPackNotReadyError):
485 await wire_fetch_mpack(db_session, repo.repo_id, want=[tip], have=[])
486 await db_session.commit()
487
488 job_row = (await db_session.execute(
489 select(MusehubBackgroundJob)
490 .where(MusehubBackgroundJob.repo_id == repo.repo_id)
491 .where(MusehubBackgroundJob.job_type == "fetch.mpack.prebuild")
492 .where(MusehubBackgroundJob.status == "pending")
493 )).scalar_one()
494
495 # Step 2 -- run the job the worker would have picked up.
496 fresh_mpack = _fake_mpack_id("fmc21c-fresh")
497 fake_proxy = SimpleNamespace(commit_id=tip, snapshot_id=None, parent_ids=[])
498 mock_backend = AsyncMock()
499 mock_backend.put_mpack.return_value = None
500 mock_backend.presign_mpack_get.return_value = "https://r2.example/fresh-fmc21c"
501 mock_backend.delete.return_value = None
502
503 with patch("musehub.services.musehub_wire_fetch.get_backend", return_value=mock_backend), \
504 patch("musehub.services.musehub_wire_fetch._walk_commit_delta", new_callable=AsyncMock,
505 return_value={tip: fake_proxy}), \
506 patch("muse.core.mpack.build_wire_mpack", return_value=b"MUSE\x00fake-mpack"), \
507 patch("musehub.services.musehub_wire_fetch.blob_id", return_value=fresh_mpack):
508 await process_fetch_mpack_prebuild_job(db_session, job_row.job_id)
509 await db_session.commit()
510
511 # Step 3 -- the next fetch is now a real cache HIT on the freshly built mpack.
512 with patch("musehub.services.musehub_wire_fetch.get_backend", return_value=mock_backend), \
513 patch("musehub.services.musehub_wire_fetch._walk_commit_delta", new_callable=AsyncMock) as mock_walk:
514 result = await wire_fetch_mpack(db_session, repo.repo_id, want=[tip], have=[])
515
516 mock_walk.assert_not_called()
517 assert result["mpack_id"] == fresh_mpack
518 assert result["mpack_id"] != stale_mpack