- Smart change detection (add/modify/delete) - Version tracking with SHA256 hashes - Partial update packages (delta generation) - Diff report generation - Update application capability - 12 tests passing (100%) Files: - incremental_updater.py: Core update engine - test_incremental_updates.py: Full test coverage Features: - DocumentVersion tracking - ChangeSet detection - Update package generation - Diff reports with size changes - Resume from previous versions Week 2: 6/9 tasks complete (67%) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
341 lines
9.6 KiB
Python
341 lines
9.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Tests for incremental update functionality.
|
|
|
|
Validates:
|
|
- Change detection (add/modify/delete)
|
|
- Version tracking
|
|
- Update package generation
|
|
- Diff report generation
|
|
- Update application
|
|
"""
|
|
|
|
import pytest
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
import json
|
|
import time
|
|
|
|
# Add src to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
|
|
from skill_seekers.cli.incremental_updater import (
|
|
IncrementalUpdater,
|
|
DocumentVersion,
|
|
ChangeSet
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_skill_dir():
|
|
"""Create temporary skill directory for testing."""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
skill_dir = Path(tmpdir) / "test_skill"
|
|
skill_dir.mkdir()
|
|
|
|
# Create SKILL.md
|
|
skill_md = skill_dir / "SKILL.md"
|
|
skill_md.write_text("# Test Skill\n\nInitial content")
|
|
|
|
# Create references
|
|
refs_dir = skill_dir / "references"
|
|
refs_dir.mkdir()
|
|
|
|
ref1 = refs_dir / "getting_started.md"
|
|
ref1.write_text("# Getting Started\n\nInitial guide")
|
|
|
|
yield skill_dir
|
|
|
|
|
|
def test_initial_scan_all_added(temp_skill_dir):
|
|
"""Test first scan treats all files as added."""
|
|
updater = IncrementalUpdater(temp_skill_dir)
|
|
|
|
change_set = updater.detect_changes()
|
|
|
|
# First scan - everything is "added"
|
|
assert len(change_set.added) == 2 # SKILL.md + 1 ref
|
|
assert len(change_set.modified) == 0
|
|
assert len(change_set.deleted) == 0
|
|
assert change_set.has_changes
|
|
assert change_set.total_changes == 2
|
|
|
|
|
|
def test_no_changes_after_save(temp_skill_dir):
|
|
"""Test no changes detected after saving versions."""
|
|
updater = IncrementalUpdater(temp_skill_dir)
|
|
|
|
# First scan
|
|
change_set1 = updater.detect_changes()
|
|
updater.save_current_versions()
|
|
|
|
# Second scan (no changes)
|
|
updater2 = IncrementalUpdater(temp_skill_dir)
|
|
change_set2 = updater2.detect_changes()
|
|
|
|
assert len(change_set2.added) == 0
|
|
assert len(change_set2.modified) == 0
|
|
assert len(change_set2.deleted) == 0
|
|
assert len(change_set2.unchanged) == 2
|
|
assert not change_set2.has_changes
|
|
|
|
|
|
def test_detect_modified_file(temp_skill_dir):
|
|
"""Test detection of modified files."""
|
|
updater = IncrementalUpdater(temp_skill_dir)
|
|
|
|
# Initial scan and save
|
|
updater.detect_changes()
|
|
updater.save_current_versions()
|
|
|
|
# Modify a file
|
|
time.sleep(0.01) # Ensure timestamp changes
|
|
skill_md = temp_skill_dir / "SKILL.md"
|
|
skill_md.write_text("# Test Skill\n\nModified content")
|
|
|
|
# Detect changes
|
|
updater2 = IncrementalUpdater(temp_skill_dir)
|
|
change_set = updater2.detect_changes()
|
|
|
|
assert len(change_set.modified) == 1
|
|
assert len(change_set.added) == 0
|
|
assert len(change_set.deleted) == 0
|
|
assert change_set.modified[0].file_path == "SKILL.md"
|
|
assert change_set.modified[0].version == 2 # Incremented
|
|
|
|
|
|
def test_detect_added_file(temp_skill_dir):
|
|
"""Test detection of new files."""
|
|
updater = IncrementalUpdater(temp_skill_dir)
|
|
|
|
# Initial scan and save
|
|
updater.detect_changes()
|
|
updater.save_current_versions()
|
|
|
|
# Add new file
|
|
refs_dir = temp_skill_dir / "references"
|
|
new_ref = refs_dir / "api_reference.md"
|
|
new_ref.write_text("# API Reference\n\nNew documentation")
|
|
|
|
# Detect changes
|
|
updater2 = IncrementalUpdater(temp_skill_dir)
|
|
change_set = updater2.detect_changes()
|
|
|
|
assert len(change_set.added) == 1
|
|
assert len(change_set.modified) == 0
|
|
assert len(change_set.deleted) == 0
|
|
assert change_set.added[0].file_path == "references/api_reference.md"
|
|
|
|
|
|
def test_detect_deleted_file(temp_skill_dir):
|
|
"""Test detection of deleted files."""
|
|
updater = IncrementalUpdater(temp_skill_dir)
|
|
|
|
# Initial scan and save
|
|
updater.detect_changes()
|
|
updater.save_current_versions()
|
|
|
|
# Delete a file
|
|
ref_file = temp_skill_dir / "references" / "getting_started.md"
|
|
ref_file.unlink()
|
|
|
|
# Detect changes
|
|
updater2 = IncrementalUpdater(temp_skill_dir)
|
|
change_set = updater2.detect_changes()
|
|
|
|
assert len(change_set.deleted) == 1
|
|
assert len(change_set.added) == 0
|
|
assert len(change_set.modified) == 0
|
|
assert "references/getting_started.md" in change_set.deleted
|
|
|
|
|
|
def test_mixed_changes(temp_skill_dir):
|
|
"""Test detection of multiple types of changes."""
|
|
updater = IncrementalUpdater(temp_skill_dir)
|
|
|
|
# Initial scan and save
|
|
updater.detect_changes()
|
|
updater.save_current_versions()
|
|
|
|
# Make mixed changes
|
|
time.sleep(0.01)
|
|
|
|
# Modify SKILL.md
|
|
(temp_skill_dir / "SKILL.md").write_text("# Test Skill\n\nModified")
|
|
|
|
# Add new file
|
|
refs_dir = temp_skill_dir / "references"
|
|
(refs_dir / "new_file.md").write_text("# New File")
|
|
|
|
# Delete existing file
|
|
(refs_dir / "getting_started.md").unlink()
|
|
|
|
# Detect changes
|
|
updater2 = IncrementalUpdater(temp_skill_dir)
|
|
change_set = updater2.detect_changes()
|
|
|
|
assert len(change_set.modified) == 1
|
|
assert len(change_set.added) == 1
|
|
assert len(change_set.deleted) == 1
|
|
assert change_set.total_changes == 3
|
|
|
|
|
|
def test_generate_update_package(temp_skill_dir):
|
|
"""Test update package generation."""
|
|
updater = IncrementalUpdater(temp_skill_dir)
|
|
|
|
# Initial scan
|
|
updater.detect_changes()
|
|
updater.save_current_versions()
|
|
|
|
# Make a change
|
|
time.sleep(0.01)
|
|
(temp_skill_dir / "SKILL.md").write_text("# Modified")
|
|
|
|
# Detect and package
|
|
updater2 = IncrementalUpdater(temp_skill_dir)
|
|
change_set = updater2.detect_changes()
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
package_path = Path(tmpdir) / "update.json"
|
|
result_path = updater2.generate_update_package(change_set, package_path)
|
|
|
|
assert result_path.exists()
|
|
|
|
# Validate package structure
|
|
package_data = json.loads(result_path.read_text())
|
|
|
|
assert "metadata" in package_data
|
|
assert "changes" in package_data
|
|
assert package_data["metadata"]["total_changes"] == 1
|
|
assert "SKILL.md" in package_data["changes"]
|
|
assert package_data["changes"]["SKILL.md"]["action"] == "modify"
|
|
|
|
|
|
def test_diff_report_generation(temp_skill_dir):
|
|
"""Test diff report generation."""
|
|
updater = IncrementalUpdater(temp_skill_dir)
|
|
|
|
# Initial scan and save
|
|
updater.detect_changes()
|
|
updater.save_current_versions()
|
|
|
|
# Make changes
|
|
time.sleep(0.01)
|
|
(temp_skill_dir / "SKILL.md").write_text("# Modified content")
|
|
|
|
# Generate report
|
|
updater2 = IncrementalUpdater(temp_skill_dir)
|
|
change_set = updater2.detect_changes()
|
|
report = updater2.generate_diff_report(change_set)
|
|
|
|
assert "INCREMENTAL UPDATE REPORT" in report
|
|
assert "Modified: 1 files" in report
|
|
assert "SKILL.md" in report
|
|
|
|
|
|
def test_version_increment(temp_skill_dir):
|
|
"""Test version numbers increment correctly."""
|
|
updater = IncrementalUpdater(temp_skill_dir)
|
|
|
|
# Initial scan
|
|
change_set1 = updater.detect_changes()
|
|
updater.save_current_versions()
|
|
|
|
# All files should be version 1
|
|
for doc in change_set1.added:
|
|
assert doc.version == 1
|
|
|
|
# Modify and check version increments
|
|
time.sleep(0.01)
|
|
(temp_skill_dir / "SKILL.md").write_text("Modified once")
|
|
|
|
updater2 = IncrementalUpdater(temp_skill_dir)
|
|
change_set2 = updater2.detect_changes()
|
|
updater2.save_current_versions()
|
|
|
|
assert change_set2.modified[0].version == 2
|
|
|
|
# Modify again
|
|
time.sleep(0.01)
|
|
(temp_skill_dir / "SKILL.md").write_text("Modified twice")
|
|
|
|
updater3 = IncrementalUpdater(temp_skill_dir)
|
|
change_set3 = updater3.detect_changes()
|
|
|
|
assert change_set3.modified[0].version == 3
|
|
|
|
|
|
def test_apply_update_package(temp_skill_dir):
|
|
"""Test applying an update package."""
|
|
# Create initial state
|
|
updater = IncrementalUpdater(temp_skill_dir)
|
|
updater.detect_changes()
|
|
updater.save_current_versions()
|
|
|
|
# Create update package manually
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
package_path = Path(tmpdir) / "update.json"
|
|
|
|
update_data = {
|
|
"metadata": {
|
|
"timestamp": "2026-02-05T12:00:00",
|
|
"skill_name": "test_skill",
|
|
"change_summary": {"modified": 1},
|
|
"total_changes": 1
|
|
},
|
|
"changes": {
|
|
"SKILL.md": {
|
|
"action": "modify",
|
|
"version": 2,
|
|
"content": "# Updated Content\n\nApplied from package"
|
|
}
|
|
}
|
|
}
|
|
|
|
package_path.write_text(json.dumps(update_data))
|
|
|
|
# Apply update
|
|
success = updater.apply_update_package(package_path)
|
|
|
|
assert success
|
|
assert (temp_skill_dir / "SKILL.md").read_text() == "# Updated Content\n\nApplied from package"
|
|
|
|
|
|
def test_content_hash_consistency(temp_skill_dir):
|
|
"""Test content hash is consistent for same content."""
|
|
updater = IncrementalUpdater(temp_skill_dir)
|
|
|
|
# Get hash
|
|
skill_md = temp_skill_dir / "SKILL.md"
|
|
hash1 = updater._compute_file_hash(skill_md)
|
|
|
|
# Read and rewrite same content
|
|
content = skill_md.read_text()
|
|
skill_md.write_text(content)
|
|
|
|
hash2 = updater._compute_file_hash(skill_md)
|
|
|
|
# Hashes should be identical
|
|
assert hash1 == hash2
|
|
|
|
|
|
def test_empty_skill_directory():
|
|
"""Test handling empty skill directory."""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
empty_dir = Path(tmpdir) / "empty"
|
|
empty_dir.mkdir()
|
|
|
|
updater = IncrementalUpdater(empty_dir)
|
|
change_set = updater.detect_changes()
|
|
|
|
assert len(change_set.added) == 0
|
|
assert len(change_set.modified) == 0
|
|
assert len(change_set.deleted) == 0
|
|
assert not change_set.has_changes
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|