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>
This commit is contained in:
166
tests/unit/prompts/test_change_detector.py
Normal file
166
tests/unit/prompts/test_change_detector.py
Normal file
@@ -0,0 +1,166 @@
|
||||
"""
|
||||
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 == []
|
||||
Reference in New Issue
Block a user