markitect-main/tests/unit/prompts/test_impact_analyzer.py

163 lines
5.9 KiB
Python
Raw Normal View History

"""
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