Files
markitect-main/tests/unit/prompts/test_change_detector.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

167 lines
5.8 KiB
Python

"""
Unit tests for ChangeDetector.
Tests change detection, recording, change types, and no-change cases.
"""
import pytest
import tempfile
from pathlib import Path
from markitect.prompts.models import Artifact, ArtifactType, calculate_content_digest
from markitect.prompts.incremental.detector import ChangeDetector
from markitect.prompts.incremental.models import ChangeType
@pytest.fixture
def temp_db():
"""Create temporary database for testing."""
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
db_path = f.name
yield db_path
Path(db_path).unlink(missing_ok=True)
@pytest.fixture
def detector(temp_db):
"""Create ChangeDetector instance."""
return ChangeDetector(temp_db)
def _make_artifact(content="original content"):
"""Helper to create an in-memory artifact."""
return Artifact.create(
space_id="space-1",
name="test-artifact",
content=content,
artifact_type=ArtifactType.CONTENT,
)
class TestDetectChange:
"""Tests for detecting content changes."""
def test_detect_modification(self, detector):
"""Test detecting a content modification."""
artifact = _make_artifact("original content")
change = detector.detect_change(artifact, "modified content")
assert change is not None
assert change.artifact_id == artifact.id
assert change.old_digest == artifact.content_digest
assert change.new_digest == calculate_content_digest("modified content")
assert change.change_type == ChangeType.MODIFIED
def test_no_change_returns_none(self, detector):
"""Test that identical content returns None."""
artifact = _make_artifact("same content")
change = detector.detect_change(artifact, "same content")
assert change is None
def test_detect_whitespace_change(self, detector):
"""Test detecting whitespace-only changes."""
artifact = _make_artifact("content")
change = detector.detect_change(artifact, "content ")
assert change is not None
assert change.change_type == ChangeType.MODIFIED
def test_detect_empty_to_content(self, detector):
"""Test detecting change from empty to content."""
artifact = _make_artifact("")
change = detector.detect_change(artifact, "new content")
assert change is not None
assert change.change_type == ChangeType.MODIFIED
class TestDetectCreation:
"""Tests for recording artifact creation."""
def test_detect_creation(self, detector):
"""Test creation change record."""
change = detector.detect_creation("artifact-123", "new content")
assert change.artifact_id == "artifact-123"
assert change.old_digest is None
assert change.new_digest == calculate_content_digest("new content")
assert change.change_type == ChangeType.CREATED
def test_creation_has_unique_id(self, detector):
"""Test that each creation gets a unique ID."""
change1 = detector.detect_creation("art-1", "content")
change2 = detector.detect_creation("art-2", "content")
assert change1.id != change2.id
class TestDetectDeletion:
"""Tests for recording artifact deletion."""
def test_detect_deletion(self, detector):
"""Test deletion change record."""
artifact = _make_artifact("content to delete")
change = detector.detect_deletion(artifact)
assert change.artifact_id == artifact.id
assert change.old_digest == artifact.content_digest
assert change.change_type == ChangeType.DELETED
class TestRecordChange:
"""Tests for persisting change records."""
def test_record_and_retrieve(self, detector):
"""Test recording a change and retrieving it."""
artifact = _make_artifact("original")
change = detector.detect_change(artifact, "modified")
assert change is not None
detector.record_change(change)
changes = detector.get_changes_for_artifact(artifact.id)
assert len(changes) == 1
assert changes[0].id == change.id
assert changes[0].artifact_id == artifact.id
assert changes[0].change_type == ChangeType.MODIFIED
def test_record_multiple_changes(self, detector):
"""Test recording multiple changes for same artifact."""
artifact = _make_artifact("v1")
change1 = detector.detect_change(artifact, "v2")
detector.record_change(change1)
# Simulate artifact update
artifact.update_content("v2")
change2 = detector.detect_change(artifact, "v3")
detector.record_change(change2)
changes = detector.get_changes_for_artifact(artifact.id)
assert len(changes) == 2
def test_get_changes_by_type(self, detector):
"""Test filtering changes by type."""
# Record a creation
creation = detector.detect_creation("art-new", "content")
detector.record_change(creation)
# Record a modification
artifact = _make_artifact("old")
modification = detector.detect_change(artifact, "new")
detector.record_change(modification)
created_changes = detector.get_changes_by_type(ChangeType.CREATED)
assert len(created_changes) == 1
assert created_changes[0].change_type == ChangeType.CREATED
modified_changes = detector.get_changes_by_type(ChangeType.MODIFIED)
assert len(modified_changes) == 1
assert modified_changes[0].change_type == ChangeType.MODIFIED
def test_no_changes_returns_empty(self, detector):
"""Test querying changes for artifact with none recorded."""
changes = detector.get_changes_for_artifact("nonexistent")
assert changes == []