Files
markitect-main/tests/unit/prompts/test_impact_analyzer.py
tegwick bd1d05ba79 feat(prompts): implement Phase 6 - Incremental Execution (FR-7, FR-8)
Add change detection, structural diff-based impact analysis,
configurable-depth incremental recomputation with circular suppression,
and impact debt tracking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 13:18:27 +01:00

163 lines
5.9 KiB
Python

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