"""TDD: build_proposal_symbol_delta — structured delta algebra → grouped symbol diff. Covers the new `musehub.services.proposal_symbol_delta` module. Unit tests — pure function, no DB: test_empty_commits_returns_empty_delta test_insert_op_maps_to_added test_delete_op_maps_to_deleted test_replace_op_maps_to_modified test_mutate_op_maps_to_modified test_patch_op_maps_to_modified test_unknown_op_treated_as_modified test_file_level_op_without_double_colon_excluded test_address_parsed_into_file_and_symbol_name test_symbols_grouped_by_file test_total_count_is_sum_of_all_buckets test_deduplication_last_commit_wins net-op reduction: test_net_op_add_then_modify_stays_added test_net_op_add_then_delete_cancels_to_nothing test_net_op_modify_then_delete_is_deleted test_net_op_deleted_then_insert_is_modified test_multiple_commits_multiple_files test_breaking_changes_flagged_on_matching_entry test_by_file_contains_all_buckets_for_a_file test_by_file_sorts_added_before_modified_before_deleted """ from __future__ import annotations from datetime import datetime, timezone import pytest from musehub.db.musehub_repo_models import MusehubCommit from musehub.types.json_types import StrDict from musehub.services.proposal_symbol_delta import ( ProposalSymbolDelta, SymbolDeltaEntry, build_proposal_symbol_delta, ) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _commit( *ops: tuple[str, str], # (op_type, address) e.g. ("insert", "src/a.py::Fn") breaking: list[str] | None = None, timestamp: datetime | None = None, ) -> MusehubCommit: """Return a minimal object mimicking MusehubCommit with structured_delta.""" child_ops_by_file: dict[str, list[StrDict]] = {} for op_type, address in ops: file_path = address.split("::")[0] child_ops_by_file.setdefault(file_path, []).append( {"op": op_type, "address": address} ) structured_delta = { "ops": [ {"address": file_path, "child_ops": cops} for file_path, cops in child_ops_by_file.items() ] } class _FakeCommit: pass c = _FakeCommit() c.structured_delta = structured_delta # type: ignore[attr-defined] c.breaking_changes = breaking or [] # type: ignore[attr-defined] c.timestamp = timestamp or datetime.now(timezone.utc) # type: ignore[attr-defined] return c def _commit_no_delta(breaking: list[str] | None = None) -> MusehubCommit: class _FakeCommit: pass c = _FakeCommit() c.structured_delta = None # type: ignore[attr-defined] c.breaking_changes = breaking or [] # type: ignore[attr-defined] c.timestamp = datetime.now(timezone.utc) # type: ignore[attr-defined] return c # --------------------------------------------------------------------------- # Empty / null cases # --------------------------------------------------------------------------- def test_empty_commits_returns_empty_delta() -> None: delta = build_proposal_symbol_delta([]) assert delta.added == [] assert delta.modified == [] assert delta.deleted == [] assert delta.by_file == {} assert delta.total == 0 def test_commit_with_no_structured_delta_skipped() -> None: delta = build_proposal_symbol_delta([_commit_no_delta()]) assert delta.total == 0 # --------------------------------------------------------------------------- # Op type mapping # --------------------------------------------------------------------------- def test_insert_op_maps_to_added() -> None: delta = build_proposal_symbol_delta([ _commit(("insert", "src/billing.py::compute_total")), ]) assert len(delta.added) == 1 assert delta.added[0].address == "src/billing.py::compute_total" assert delta.modified == [] assert delta.deleted == [] def test_delete_op_maps_to_deleted() -> None: delta = build_proposal_symbol_delta([ _commit(("delete", "src/billing.py::old_compute")), ]) assert len(delta.deleted) == 1 assert delta.deleted[0].address == "src/billing.py::old_compute" assert delta.added == [] assert delta.modified == [] def test_replace_op_maps_to_modified() -> None: delta = build_proposal_symbol_delta([ _commit(("replace", "src/auth.py::validate_token")), ]) assert len(delta.modified) == 1 assert delta.modified[0].address == "src/auth.py::validate_token" def test_mutate_op_maps_to_modified() -> None: delta = build_proposal_symbol_delta([ _commit(("mutate", "src/auth.py::Session")), ]) assert len(delta.modified) == 1 assert delta.modified[0].address == "src/auth.py::Session" def test_patch_op_maps_to_modified() -> None: delta = build_proposal_symbol_delta([ _commit(("patch", "src/core.py::Router")), ]) assert len(delta.modified) == 1 def test_unknown_op_treated_as_modified() -> None: delta = build_proposal_symbol_delta([ _commit(("update", "src/x.py::Foo")), ]) assert len(delta.modified) == 1 # --------------------------------------------------------------------------- # Address parsing # --------------------------------------------------------------------------- def test_file_level_op_without_double_colon_excluded() -> None: """A child_op whose address has no '::' is not a symbol — skip it.""" class _FakeCommit: structured_delta = { "ops": [ { "address": "src/billing.py", "child_ops": [ {"op": "insert", "address": "src/billing.py"}, # no :: ], } ] } breaking_changes: list[str] = [] timestamp = datetime.now(timezone.utc) delta = build_proposal_symbol_delta([_FakeCommit()]) assert delta.total == 0 def test_address_parsed_into_file_and_symbol_name() -> None: delta = build_proposal_symbol_delta([ _commit(("insert", "src/billing.py::compute_total")), ]) entry = delta.added[0] assert entry.file_path == "src/billing.py" assert entry.symbol_name == "compute_total" assert entry.address == "src/billing.py::compute_total" def test_address_with_multiple_colons_handled() -> None: """CSS pseudo-selector addresses like 'file.scss::--merged:hover' split on first '::'.""" delta = build_proposal_symbol_delta([ _commit(("delete", "src/components/_x.scss::--merged:hover")), ]) assert delta.deleted[0].file_path == "src/components/_x.scss" assert delta.deleted[0].symbol_name == "--merged:hover" # --------------------------------------------------------------------------- # Grouping # --------------------------------------------------------------------------- def test_symbols_grouped_by_file() -> None: delta = build_proposal_symbol_delta([ _commit( ("insert", "src/billing.py::compute_total"), ("replace", "src/auth.py::validate_token"), ("delete", "src/billing.py::old_compute"), ), ]) assert "src/billing.py" in delta.by_file assert "src/auth.py" in delta.by_file billing = delta.by_file["src/billing.py"] billing_addrs = {e.address for e in billing} assert "src/billing.py::compute_total" in billing_addrs assert "src/billing.py::old_compute" in billing_addrs auth = delta.by_file["src/auth.py"] assert auth[0].address == "src/auth.py::validate_token" def test_by_file_sorts_added_before_modified_before_deleted() -> None: delta = build_proposal_symbol_delta([ _commit( ("delete", "src/a.py::removed"), ("replace", "src/a.py::changed"), ("insert", "src/a.py::new_fn"), ), ]) entries = delta.by_file["src/a.py"] ops = [e.net_op for e in entries] # added first, then modified, then deleted assert ops.index("added") < ops.index("modified") assert ops.index("modified") < ops.index("deleted") def test_total_count_is_sum_of_all_buckets() -> None: delta = build_proposal_symbol_delta([ _commit( ("insert", "src/a.py::Fn1"), ("insert", "src/a.py::Fn2"), ("replace", "src/b.py::Fn3"), ("delete", "src/c.py::Fn4"), ), ]) assert delta.total == 4 assert len(delta.added) == 2 assert len(delta.modified) == 1 assert len(delta.deleted) == 1 # --------------------------------------------------------------------------- # Deduplication # --------------------------------------------------------------------------- def test_deduplication_last_commit_wins() -> None: """Same symbol in two commits → single entry with the net op.""" t1 = datetime(2026, 1, 1, tzinfo=timezone.utc) t2 = datetime(2026, 1, 2, tzinfo=timezone.utc) delta = build_proposal_symbol_delta([ _commit(("replace", "src/a.py::Fn"), timestamp=t1), _commit(("replace", "src/a.py::Fn"), timestamp=t2), ]) assert delta.total == 1 assert len(delta.modified) == 1 # --------------------------------------------------------------------------- # Net-op reduction # --------------------------------------------------------------------------- def test_net_op_add_then_modify_stays_added() -> None: """insert in c1, replace in c2 → still 'added' (new to this branch).""" t1 = datetime(2026, 1, 1, tzinfo=timezone.utc) t2 = datetime(2026, 1, 2, tzinfo=timezone.utc) delta = build_proposal_symbol_delta([ _commit(("insert", "src/a.py::Fn"), timestamp=t1), _commit(("replace", "src/a.py::Fn"), timestamp=t2), ]) assert len(delta.added) == 1 assert delta.modified == [] def test_net_op_add_then_delete_cancels_to_nothing() -> None: """insert in c1, delete in c2 → net zero, symbol not in any bucket.""" t1 = datetime(2026, 1, 1, tzinfo=timezone.utc) t2 = datetime(2026, 1, 2, tzinfo=timezone.utc) delta = build_proposal_symbol_delta([ _commit(("insert", "src/a.py::Fn"), timestamp=t1), _commit(("delete", "src/a.py::Fn"), timestamp=t2), ]) assert delta.total == 0 def test_net_op_modify_then_delete_is_deleted() -> None: t1 = datetime(2026, 1, 1, tzinfo=timezone.utc) t2 = datetime(2026, 1, 2, tzinfo=timezone.utc) delta = build_proposal_symbol_delta([ _commit(("replace", "src/a.py::Fn"), timestamp=t1), _commit(("delete", "src/a.py::Fn"), timestamp=t2), ]) assert len(delta.deleted) == 1 assert delta.added == [] assert delta.modified == [] def test_net_op_deleted_then_insert_is_modified() -> None: """delete in c1, insert in c2 → 'modified' (symbol re-added after removal).""" t1 = datetime(2026, 1, 1, tzinfo=timezone.utc) t2 = datetime(2026, 1, 2, tzinfo=timezone.utc) delta = build_proposal_symbol_delta([ _commit(("delete", "src/a.py::Fn"), timestamp=t1), _commit(("insert", "src/a.py::Fn"), timestamp=t2), ]) assert len(delta.modified) == 1 assert delta.added == [] assert delta.deleted == [] # --------------------------------------------------------------------------- # Multi-commit, multi-file # --------------------------------------------------------------------------- def test_multiple_commits_multiple_files() -> None: t1 = datetime(2026, 1, 1, tzinfo=timezone.utc) t2 = datetime(2026, 1, 2, tzinfo=timezone.utc) t3 = datetime(2026, 1, 3, tzinfo=timezone.utc) delta = build_proposal_symbol_delta([ _commit(("insert", "src/a.py::FnA"), ("replace", "src/b.py::FnB"), timestamp=t1), _commit(("insert", "src/c.py::FnC"), timestamp=t2), _commit(("delete", "src/b.py::FnB"), timestamp=t3), ]) assert len(delta.added) == 2 # FnA, FnC assert len(delta.deleted) == 1 # FnB (modified then deleted) assert delta.modified == [] assert "src/a.py" in delta.by_file assert "src/b.py" in delta.by_file assert "src/c.py" in delta.by_file # --------------------------------------------------------------------------- # Breaking changes # --------------------------------------------------------------------------- def test_breaking_changes_flagged_on_matching_entry() -> None: delta = build_proposal_symbol_delta( [_commit(("delete", "src/api.py::public_fn"))], breaking_changes=["src/api.py::public_fn"], ) assert delta.deleted[0].is_breaking is True def test_non_breaking_entry_not_flagged() -> None: delta = build_proposal_symbol_delta( [_commit(("replace", "src/api.py::internal_fn"))], breaking_changes=["src/api.py::public_fn"], ) assert delta.modified[0].is_breaking is False def test_breaking_changes_collected_from_commits() -> None: """Breaking changes embedded on commit records are surfaced.""" c = _commit(("delete", "src/api.py::old_fn"), breaking=["src/api.py::old_fn"]) delta = build_proposal_symbol_delta([c]) assert delta.deleted[0].is_breaking is True def test_breaking_flag_propagates_through_net_op_reduction() -> None: """A symbol flagged breaking in any commit stays breaking in net result.""" t1 = datetime(2026, 1, 1, tzinfo=timezone.utc) t2 = datetime(2026, 1, 2, tzinfo=timezone.utc) c1 = _commit(("replace", "src/a.py::Fn"), breaking=["src/a.py::Fn"], timestamp=t1) c2 = _commit(("replace", "src/a.py::Fn"), timestamp=t2) delta = build_proposal_symbol_delta([c1, c2]) assert delta.modified[0].is_breaking is True # --------------------------------------------------------------------------- # Return type contract # --------------------------------------------------------------------------- def test_returns_proposal_symbol_delta_instance() -> None: result = build_proposal_symbol_delta([]) assert isinstance(result, ProposalSymbolDelta) def test_entries_are_symbol_delta_entry_instances() -> None: delta = build_proposal_symbol_delta([ _commit(("insert", "src/a.py::Fn")), ]) assert isinstance(delta.added[0], SymbolDeltaEntry) # --------------------------------------------------------------------------- # Real delta shape — top-level symbol ops (no parent wrapper) # --------------------------------------------------------------------------- def test_top_level_symbol_op_no_parent_wrapper() -> None: """Real Muse deltas put symbol ops directly in ops[], not only in child_ops.""" class _FakeCommit: structured_delta = { "ops": [ {"op": "replace", "address": "src/billing.py::compute_total"}, {"op": "insert", "address": "src/billing.py::invoice_line"}, {"op": "delete", "address": "src/legacy.py::old_fn"}, ] } breaking_changes: list[str] = [] timestamp = datetime(2026, 1, 1, tzinfo=timezone.utc) delta = build_proposal_symbol_delta([_FakeCommit()]) assert len(delta.modified) == 1 assert delta.modified[0].address == "src/billing.py::compute_total" assert len(delta.added) == 1 assert delta.added[0].address == "src/billing.py::invoice_line" assert len(delta.deleted) == 1 assert delta.deleted[0].address == "src/legacy.py::old_fn" def test_mixed_top_level_and_child_ops() -> None: """Top-level symbol ops and child_ops inside patch ops both processed.""" class _FakeCommit: structured_delta = { "ops": [ {"op": "replace", "address": "src/auth.py::validate"}, { "op": "patch", "address": "src/billing.py", "child_ops": [ {"op": "insert", "address": "src/billing.py::new_fn"}, ], }, ] } breaking_changes: list[str] = [] timestamp = datetime(2026, 1, 1, tzinfo=timezone.utc) delta = build_proposal_symbol_delta([_FakeCommit()]) assert len(delta.modified) == 1 assert delta.modified[0].address == "src/auth.py::validate" assert len(delta.added) == 1 assert delta.added[0].address == "src/billing.py::new_fn"