diff --git a/confsync/tests/test_merge.py b/confsync/tests/test_merge.py new file mode 100644 index 0000000..b89d2bb --- /dev/null +++ b/confsync/tests/test_merge.py @@ -0,0 +1,209 @@ +"""Tests for merge strategies.""" + +import pytest +from confsync.core.merger import ( + Merger, + KeepLocalStrategy, + KeepRemoteStrategy, + KeepCommonStrategy, + ThreeWayMergeStrategy, + UnionMergeStrategy, + MergeConflict, +) + + +class TestMergeStrategies: + """Tests for individual merge strategies.""" + + def test_keep_local_strategy(self): + """Test keep local strategy.""" + strategy = KeepLocalStrategy() + + local = "local content" + remote = "remote content" + + success, merged, conflict = strategy.merge(local, remote) + + assert success + assert merged == local + assert conflict is None + + def test_keep_remote_strategy(self): + """Test keep remote strategy.""" + strategy = KeepRemoteStrategy() + + local = "local content" + remote = "remote content" + + success, merged, conflict = strategy.merge(local, remote) + + assert success + assert merged == remote + assert conflict is None + + def test_keep_common_strategy(self): + """Test keep common strategy.""" + strategy = KeepCommonStrategy() + + local = "line1\nline2\nline3" + remote = "line2\nline3\nline4" + + success, merged, conflict = strategy.merge(local, remote) + + assert success + assert "line2" in merged + assert "line3" in merged + assert "line1" not in merged + assert "line4" not in merged + + def test_three_way_merge_identical(self): + """Test three-way merge with identical content.""" + strategy = ThreeWayMergeStrategy() + + content = "same content" + success, merged, conflict = strategy.merge(content, content, content) + + assert success + assert merged == content + assert conflict is None + + def test_three_way_merge_no_conflicts(self): + """Test three-way merge without conflicts.""" + strategy = ThreeWayMergeStrategy() + + base = "same content" + local = "same content" + remote = "same content" + + success, merged, conflict = strategy.merge(local, remote, base) + + assert success + assert conflict is None + + def test_union_merge_strategy(self): + """Test union merge strategy.""" + strategy = UnionMergeStrategy() + + local = "line1\nline2\nline3" + remote = "line3\nline4\nline5" + + success, merged, conflict = strategy.merge(local, remote) + + assert success + assert "line1" in merged + assert "line2" in merged + assert "line3" in merged + assert "line4" in merged + assert "line5" in merged + + def test_union_merge_removes_duplicates(self): + """Test union merge removes duplicate lines.""" + strategy = UnionMergeStrategy() + + local = "line1\nline2\nline2" + remote = "line2\nline3" + + success, merged, conflict = strategy.merge(local, remote) + + assert success + lines = merged.split('\n') + assert lines.count("line2") == 1 + + +class TestMergeConflict: + """Tests for merge conflict handling.""" + + def test_format_conflict(self): + """Test conflict formatting.""" + conflict = MergeConflict( + file_path="/test/file.txt", + local_lines=["local line 1", "local line 2"], + remote_lines=["remote line 1", "remote line 2"], + base_lines=["base line 1"], + ) + + formatted = conflict.format_conflict() + + assert "<<<<<<< LOCAL" in formatted + assert "=======" in formatted + assert ">>>>>>> REMOTE" in formatted + assert "local line 1" in formatted + assert "remote line 1" in formatted + + +class TestMerger: + """Tests for the main Merger class.""" + + def test_get_strategy(self): + """Test getting strategy by name.""" + merger = Merger() + + strategy = merger.get_strategy("keep_local") + assert isinstance(strategy, KeepLocalStrategy) + + strategy = merger.get_strategy("three_way") + assert isinstance(strategy, ThreeWayMergeStrategy) + + def test_merge_file_with_strategy(self): + """Test merging a single file with strategy.""" + merger = Merger() + + local = "local config" + remote = "remote config" + + success, merged, conflict = merger.merge_file( + local, remote, strategy_name="keep_local" + ) + + assert success + assert merged == "local config" + + def test_merge_multiple_files(self): + """Test merging multiple files at once.""" + merger = Merger() + + files = [ + { + "path": "/test/file1.txt", + "local": "local1", + "remote": "remote1", + "strategy": "keep_local", + }, + { + "path": "/test/file2.txt", + "local": "local2", + "remote": "remote2", + "strategy": "keep_remote", + }, + ] + + results = merger.merge_multiple(files) + + assert "/test/file1.txt" in results + assert "/test/file2.txt" in results + + +class TestMergeResult: + """Tests for merge result model.""" + + def test_merge_result_serialization(self): + """Test MergeResult serialization.""" + from confsync.models.config_models import MergeResult + + result = MergeResult( + success=True, + merged_files={"file1": "merged content"}, + conflicts={}, + unresolved=[], + strategy_used="three_way", + message="Merge successful", + ) + + data = result.to_dict() + + assert data["success"] is True + assert data["strategy_used"] == "three_way" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"])