""" Unit tests for ImpactAnalyzer and metrics functions. Tests diff ratios, magnitude scoring, and threshold decisions. """ import pytest from markitect.prompts.incremental.metrics import ( structural_diff_ratio, line_diff_ratio, calculate_change_magnitude, ) from markitect.prompts.incremental.impact import ImpactAnalyzer from markitect.prompts.incremental.models import RecomputeConfig class TestStructuralDiffRatio: """Tests for structural_diff_ratio.""" def test_identical_content(self): """Test identical content returns 0.0.""" assert structural_diff_ratio("hello", "hello") == 0.0 def test_completely_different(self): """Test completely different content returns high ratio.""" ratio = structural_diff_ratio("aaa", "zzz") assert ratio > 0.5 def test_empty_strings(self): """Test both empty returns 0.0.""" assert structural_diff_ratio("", "") == 0.0 def test_one_empty(self): """Test one empty returns 1.0.""" assert structural_diff_ratio("", "content") == 1.0 assert structural_diff_ratio("content", "") == 1.0 def test_small_change(self): """Test small change returns low ratio.""" old = "The quick brown fox jumps over the lazy dog" new = "The quick brown fox leaps over the lazy dog" ratio = structural_diff_ratio(old, new) assert 0.0 < ratio < 0.5 def test_returns_float(self): """Test return value is float between 0 and 1.""" ratio = structural_diff_ratio("abc", "abd") assert isinstance(ratio, float) assert 0.0 <= ratio <= 1.0 class TestLineDiffRatio: """Tests for line_diff_ratio.""" def test_identical_lines(self): """Test identical multi-line content returns 0.0.""" content = "line1\nline2\nline3" assert line_diff_ratio(content, content) == 0.0 def test_one_line_changed(self): """Test changing one line of several.""" old = "line1\nline2\nline3" new = "line1\nmodified\nline3" ratio = line_diff_ratio(old, new) assert 0.0 < ratio < 1.0 def test_all_lines_changed(self): """Test all lines changed returns high ratio.""" old = "aaa\nbbb\nccc" new = "xxx\nyyy\nzzz" ratio = line_diff_ratio(old, new) assert ratio > 0.5 def test_empty_strings(self): """Test both empty returns 0.0.""" assert line_diff_ratio("", "") == 0.0 def test_one_empty(self): """Test one empty returns 1.0.""" assert line_diff_ratio("", "content") == 1.0 assert line_diff_ratio("content", "") == 1.0 class TestCalculateChangeMagnitude: """Tests for calculate_change_magnitude.""" def test_none_old_content(self): """Test None old_content (creation) returns 1.0.""" assert calculate_change_magnitude(None, "new content") == 1.0 def test_none_new_content(self): """Test None new_content (deletion) returns 1.0.""" assert calculate_change_magnitude("old content", None) == 1.0 def test_both_none(self): """Test both None returns 0.0.""" assert calculate_change_magnitude(None, None) == 0.0 def test_structural_method(self): """Test structural method (default).""" result = calculate_change_magnitude("abc", "abd", method="structural") assert 0.0 < result < 1.0 def test_line_method(self): """Test line method.""" result = calculate_change_magnitude("abc\ndef", "abc\nxyz", method="line") assert 0.0 < result < 1.0 def test_identical_content(self): """Test identical content returns 0.0.""" assert calculate_change_magnitude("same", "same") == 0.0 class TestImpactAnalyzer: """Tests for ImpactAnalyzer class.""" @pytest.fixture def analyzer(self): """Create ImpactAnalyzer instance.""" return ImpactAnalyzer() def test_calculate_magnitude(self, analyzer): """Test magnitude calculation delegates to metrics.""" result = analyzer.calculate_magnitude("old", "new") assert isinstance(result, float) assert 0.0 <= result <= 1.0 def test_calculate_magnitude_creation(self, analyzer): """Test magnitude for creation.""" assert analyzer.calculate_magnitude(None, "new") == 1.0 def test_calculate_magnitude_identical(self, analyzer): """Test magnitude for identical content.""" assert analyzer.calculate_magnitude("same", "same") == 0.0 def test_should_recompute_above_threshold(self, analyzer): """Test recompute when magnitude exceeds threshold.""" config = RecomputeConfig(impact_threshold=0.3) assert analyzer.should_recompute(0.5, config) is True def test_should_recompute_at_threshold(self, analyzer): """Test recompute when magnitude equals threshold.""" config = RecomputeConfig(impact_threshold=0.5) assert analyzer.should_recompute(0.5, config) is True def test_should_not_recompute_below_threshold(self, analyzer): """Test no recompute when magnitude below threshold.""" config = RecomputeConfig(impact_threshold=0.5) assert analyzer.should_recompute(0.3, config) is False def test_zero_threshold_always_recomputes(self, analyzer): """Test zero threshold means any change triggers recompute.""" config = RecomputeConfig(impact_threshold=0.0) assert analyzer.should_recompute(0.0, config) is True assert analyzer.should_recompute(0.01, config) is True def test_high_threshold_only_major_changes(self, analyzer): """Test high threshold only triggers on major changes.""" config = RecomputeConfig(impact_threshold=0.9) assert analyzer.should_recompute(0.5, config) is False assert analyzer.should_recompute(0.95, config) is True