"""TDD — Phase 2 of issue #40: in-memory BFS sites migrated to walk_dag. Structural tests confirm each function uses walk_dag and has no inline BFS. Behavioural tests confirm the migrated functions produce correct output. P2-1 Structural — CouplingProvider.compute uses walk_dag; no inline deque P2-2 Structural — EntangleProvider.compute uses walk_dag; no inline deque P2-3 Structural — VelocityProvider.compute uses walk_dag; no inline deque P2-4 Structural — run_gc reachability uses walk_dag; no inline while queue P2-5 Structural — _is_fast_forward uses walk_dag; no inline while frontier P2-6 DELETED — _is_ancestor_in_bundle replaced by _is_fast_forward P2-7 Structural — find_common_ancestor uses walk_dag; no inline while P2-8 Behavioural — _is_fast_forward: True when remote_head is ancestor P2-9 Behavioural — _is_fast_forward: False when remote_head is unreachable P2-10 DELETED — _is_ancestor_in_bundle replaced by _is_fast_forward P2-11 DELETED — _is_ancestor_in_bundle replaced by _is_fast_forward P2-12 Behavioural — find_common_ancestor: returns LCA on diamond graph """ from __future__ import annotations import datetime import inspect from types import SimpleNamespace import pytest # --------------------------------------------------------------------------- # P2-1 Structural — CouplingProvider # --------------------------------------------------------------------------- def test_p2_1_coupling_provider_uses_walk_dag() -> None: """CouplingProvider.compute must delegate BFS; no inline deque BFS. Phase 4 extracted the walk into _load_commit_walk, so walk_dag now lives in the helper rather than in compute directly. The invariant is that compute itself has no inline deque and delegates to the shared helper. """ from musehub.services import musehub_intel_providers as mod src = inspect.getsource(mod.CouplingProvider.compute) assert "deque" not in src, ( "CouplingProvider.compute still has an inline deque." ) assert "_load_commit_walk" in src or "walk_dag" in src, ( "CouplingProvider.compute must delegate BFS to walk_dag or _load_commit_walk." ) # --------------------------------------------------------------------------- # P2-2 Structural — EntangleProvider # --------------------------------------------------------------------------- def test_p2_2_entangle_provider_uses_walk_dag() -> None: """EntangleProvider.compute must delegate BFS; no inline deque BFS.""" from musehub.services import musehub_intel_providers as mod src = inspect.getsource(mod.EntangleProvider.compute) assert "deque" not in src, ( "EntangleProvider.compute still has an inline deque." ) assert "_load_commit_walk" in src or "walk_dag" in src, ( "EntangleProvider.compute must delegate BFS to walk_dag or _load_commit_walk." ) # --------------------------------------------------------------------------- # P2-3 Structural — VelocityProvider # --------------------------------------------------------------------------- def test_p2_3_velocity_provider_uses_walk_dag() -> None: """VelocityProvider.compute must delegate BFS; no inline deque BFS.""" from musehub.services import musehub_intel_providers as mod src = inspect.getsource(mod.VelocityProvider.compute) assert "deque" not in src, ( "VelocityProvider.compute still has an inline deque." ) assert "_load_commit_walk" in src or "walk_dag" in src, ( "VelocityProvider.compute must delegate BFS to walk_dag or _load_commit_walk." ) # --------------------------------------------------------------------------- # P2-4 Structural — run_gc reachability # --------------------------------------------------------------------------- def test_p2_4_run_gc_uses_walk_dag() -> None: """run_gc reachability BFS must use walk_dag; no inline while queue.""" from musehub.services import musehub_gc as mod src = inspect.getsource(mod.run_gc) assert "walk_dag" in src, ( "run_gc must delegate reachability BFS to walk_dag." ) # The old pattern was `queue = list(heads)` then `while queue:` assert "while queue" not in src, ( "run_gc still has an inline while-queue BFS." ) # --------------------------------------------------------------------------- # P2-5 Structural — _is_fast_forward # --------------------------------------------------------------------------- def test_p2_5_is_fast_forward_uses_walk_dag() -> None: """_is_fast_forward must use walk_dag; no inline while frontier.""" from musehub.services import musehub_sync as mod src = inspect.getsource(mod._is_fast_forward) assert "walk_dag" in src, ( "_is_fast_forward must delegate BFS to walk_dag." ) assert "while frontier" not in src, ( "_is_fast_forward still has an inline while-frontier BFS." ) # --------------------------------------------------------------------------- # P2-7 Structural — find_common_ancestor # --------------------------------------------------------------------------- def test_p2_7_find_common_ancestor_uses_walk_dag() -> None: """find_common_ancestor must use walk_dag; no inline while frontier.""" from musehub.services import musehub_divergence as mod src = inspect.getsource(mod.find_common_ancestor) assert "walk_dag" in src, ( "find_common_ancestor must delegate graph walk to walk_dag." ) assert "while frontier" not in src, ( "find_common_ancestor still has an inline while-frontier loop." ) # --------------------------------------------------------------------------- # Helpers for behavioural tests # --------------------------------------------------------------------------- def _make_commit_input(commit_id: str, parent_ids: list[str]) -> None: """Minimal CommitInput stand-in (avoids DB imports).""" return SimpleNamespace(commit_id=commit_id, parent_ids=parent_ids) def _make_wire_commit(commit_id: str, p1: str | None = None, p2: str | None = None) -> None: """Minimal WireCommit stand-in.""" return SimpleNamespace( commit_id=commit_id, parent_commit_id=p1, parent2_commit_id=p2, ) def _make_musehub_commit(commit_id: str, parent_ids: list[str], timestamp: datetime.datetime | None = None) -> SimpleNamespace: """Minimal MusehubCommit stand-in.""" import datetime ts = timestamp or datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc) return SimpleNamespace(commit_id=commit_id, parent_ids=parent_ids, timestamp=ts) # --------------------------------------------------------------------------- # P2-8 Behavioural — _is_fast_forward: True case # --------------------------------------------------------------------------- def test_p2_8_is_fast_forward_true() -> None: """_is_fast_forward returns True when remote_head is an ancestor. Chain: C1 → C2 → C3 (HEAD). remote_head=C1 → True. """ from musehub.services.musehub_sync import _is_fast_forward commits = [ _make_commit_input("C3", ["C2"]), _make_commit_input("C2", ["C1"]), _make_commit_input("C1", []), ] assert _is_fast_forward("C1", "C3", commits) is True # --------------------------------------------------------------------------- # P2-9 Behavioural — _is_fast_forward: False case # --------------------------------------------------------------------------- def test_p2_9_is_fast_forward_false() -> None: """_is_fast_forward returns False when remote_head is not in ancestry. Chain: C1 → C2. remote_head=X (independent) → False. """ from musehub.services.musehub_sync import _is_fast_forward commits = [ _make_commit_input("C2", ["C1"]), _make_commit_input("C1", []), ] assert _is_fast_forward("X", "C2", commits) is False # --------------------------------------------------------------------------- # P2-12 Behavioural — find_common_ancestor: LCA on diamond graph # --------------------------------------------------------------------------- def test_p2_12_find_common_ancestor_diamond() -> None: """find_common_ancestor returns the LCA for a diamond graph. Diamond: BASE ← L1 ← L2 (branch A tip) ← R1 ← R2 (branch B tip) LCA of A and B is BASE. """ import datetime from musehub.services.musehub_divergence import find_common_ancestor t = lambda n: datetime.datetime(2026, 1, n, tzinfo=datetime.timezone.utc) a_commits = [ _make_musehub_commit("L2", ["L1"], t(4)), _make_musehub_commit("L1", ["BASE"], t(3)), _make_musehub_commit("BASE", [], t(1)), ] b_commits = [ _make_musehub_commit("R2", ["R1"], t(4)), _make_musehub_commit("R1", ["BASE"], t(2)), _make_musehub_commit("BASE", [], t(1)), ] result = find_common_ancestor(a_commits, b_commits) assert result == "BASE", f"Expected LCA=BASE, got {result}"