"""TDD tests for the cherry_pick merge strategy. Cherry-pick applies the delta of each specified commit (parent→snapshot) in order onto the to_branch manifest. Only the changes introduced by those commits are applied — the rest of to_branch is untouched. """ from __future__ import annotations import pytest from musehub.services.proposal_merge_strategies import ( MergeResult, execute_merge_strategy, merge_cherry_pick, ) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _oid(name: str) -> str: """Stable fake object ID for test manifests.""" return f"sha256:{name:0<64}" # --------------------------------------------------------------------------- # TestMergeCherryPick # --------------------------------------------------------------------------- class TestMergeCherryPick: def test_single_commit_adds_file(self) -> None: to_manifest = {"src/a.py": _oid("a")} parent = {"src/a.py": _oid("a")} commit = {"src/a.py": _oid("a"), "src/b.py": _oid("b")} result = merge_cherry_pick(to_manifest, cherry_pick_commits=[(parent, commit)]) assert "src/b.py" in result.manifest assert result.files_added == 1 def test_single_commit_modifies_file(self) -> None: to_manifest = {"src/a.py": _oid("a_old")} parent = {"src/a.py": _oid("a_old")} commit = {"src/a.py": _oid("a_new")} result = merge_cherry_pick(to_manifest, cherry_pick_commits=[(parent, commit)]) assert result.manifest["src/a.py"] == _oid("a_new") assert result.files_modified == 1 def test_single_commit_removes_file(self) -> None: to_manifest = {"src/a.py": _oid("a"), "src/b.py": _oid("b")} parent = {"src/a.py": _oid("a"), "src/b.py": _oid("b")} commit = {"src/a.py": _oid("a")} # b removed result = merge_cherry_pick(to_manifest, cherry_pick_commits=[(parent, commit)]) assert "src/b.py" not in result.manifest assert result.files_removed == 1 def test_to_branch_files_untouched_when_not_in_delta(self) -> None: to_manifest = {"src/a.py": _oid("a"), "src/z.py": _oid("z")} parent = {"src/a.py": _oid("a")} commit = {"src/a.py": _oid("a2")} # only a changed result = merge_cherry_pick(to_manifest, cherry_pick_commits=[(parent, commit)]) assert result.manifest["src/z.py"] == _oid("z") def test_multiple_commits_applied_in_order(self) -> None: to_manifest = {"src/a.py": _oid("a")} # commit 1: add b.py parent1 = {"src/a.py": _oid("a")} commit1 = {"src/a.py": _oid("a"), "src/b.py": _oid("b1")} # commit 2: update b.py parent2 = {"src/a.py": _oid("a"), "src/b.py": _oid("b1")} commit2 = {"src/a.py": _oid("a"), "src/b.py": _oid("b2")} result = merge_cherry_pick( to_manifest, cherry_pick_commits=[(parent1, commit1), (parent2, commit2)], ) assert result.manifest["src/b.py"] == _oid("b2") def test_second_commit_sees_state_after_first(self) -> None: to_manifest = {} parent1 = {} commit1 = {"src/a.py": _oid("a")} parent2 = {"src/a.py": _oid("a")} commit2 = {"src/a.py": _oid("a"), "src/b.py": _oid("b")} result = merge_cherry_pick( to_manifest, cherry_pick_commits=[(parent1, commit1), (parent2, commit2)], ) assert "src/a.py" in result.manifest assert "src/b.py" in result.manifest def test_conflict_recorded_when_to_branch_modified_same_file(self) -> None: to_manifest = {"src/a.py": _oid("a_to")} # to_branch has its own version parent = {"src/a.py": _oid("a_base")} # commit diverged from a different base commit = {"src/a.py": _oid("a_pick")} result = merge_cherry_pick(to_manifest, cherry_pick_commits=[(parent, commit)]) assert len(result.conflicts) == 1 assert result.conflicts[0].path == "src/a.py" assert result.conflicts[0].resolution == "from_wins" def test_no_conflict_when_to_branch_has_same_content(self) -> None: to_manifest = {"src/a.py": _oid("a_pick")} # already at target state parent = {"src/a.py": _oid("a_base")} commit = {"src/a.py": _oid("a_pick")} result = merge_cherry_pick(to_manifest, cherry_pick_commits=[(parent, commit)]) assert result.conflicts == [] def test_strategy_name(self) -> None: to_manifest = {"src/a.py": _oid("a")} parent = {"src/a.py": _oid("a")} commit = {"src/a.py": _oid("a2")} result = merge_cherry_pick(to_manifest, cherry_pick_commits=[(parent, commit)]) assert result.strategy == "cherry_pick" def test_empty_commits_raises(self) -> None: with pytest.raises(ValueError, match="cherry_pick"): merge_cherry_pick({"src/a.py": _oid("a")}, cherry_pick_commits=[]) def test_domains_merged_populated(self) -> None: to_manifest = {} parent = {} commit = {"src/main.py": _oid("m"), "tracks/beat.mid": _oid("b")} result = merge_cherry_pick(to_manifest, cherry_pick_commits=[(parent, commit)]) assert "code" in result.domains_merged assert "midi" in result.domains_merged def test_files_added_count_across_multiple_commits(self) -> None: to_manifest = {} parent1, commit1 = {}, {"a.py": _oid("a")} parent2, commit2 = {"a.py": _oid("a")}, {"a.py": _oid("a"), "b.py": _oid("b")} result = merge_cherry_pick( to_manifest, cherry_pick_commits=[(parent1, commit1), (parent2, commit2)], ) assert result.files_added == 2 # --------------------------------------------------------------------------- # TestStrategyRouterCherryPick # --------------------------------------------------------------------------- class TestStrategyRouterCherryPick: def test_routes_cherry_pick(self) -> None: to_manifest = {"src/a.py": _oid("a")} parent = {"src/a.py": _oid("a")} commit = {"src/a.py": _oid("a2")} result = execute_merge_strategy( "cherry_pick", to_manifest, {}, cherry_pick_commits=[(parent, commit)], ) assert result.strategy == "cherry_pick" assert result.manifest["src/a.py"] == _oid("a2") def test_cherry_pick_missing_commits_raises(self) -> None: with pytest.raises(ValueError, match="cherry_pick"): execute_merge_strategy("cherry_pick", {"a.py": _oid("a")}, {}) def test_cherry_pick_empty_commits_raises(self) -> None: with pytest.raises(ValueError, match="cherry_pick"): execute_merge_strategy( "cherry_pick", {"a.py": _oid("a")}, {}, cherry_pick_commits=[] )