Complete implementation of Phase 1 core infrastructure: Core Infrastructure Components: - ExplodeVariant enum (flat, hierarchical, semantic) - ExplodeMode, ManifestVersion, DetectionConfidence enums - BaseVariant abstract class with common interface - ExplodeOptions, ImplodeOptions, ExplodeResult, ImplodeResult dataclasses Manifest System: - ManifestManager class for manifest.md creation and parsing - StructureEntry and ManifestData dataclasses - YAML front matter with complete metadata preservation - Validation and update mechanisms Variant Detection: - VariantDetector class with multiple detection strategies - Manifest-based detection (highest priority) - Directory naming pattern recognition - Semantic structure analysis with confidence scoring - Automatic fallback and combination logic Command Interface Updates: - md-explode: Added --variant parameter with [flat|hierarchical|semantic] - md-explode: Added --create-manifest/--no-manifest option - md-implode: Added --force-variant parameter for manual override - md-implode: Integrated auto-detection with verbose output - Updated help text and examples for both commands Test Coverage: - Comprehensive test suite with 21 test cases - Tests for all enums, dataclasses, and core functionality - ManifestManager creation, reading, and validation tests - VariantDetector pattern recognition and confidence tests - 100% test pass rate with robust edge case handling Infrastructure Features: - Backward compatibility maintained (flat variant default) - Graceful handling of unimplemented variants with user warnings - Extensible design for easy addition of new variants - Clear separation between infrastructure and implementation Success Criteria Met: ✅ ExplodeVariant enum with all planned variants ✅ ManifestManager creates and parses manifest.md files ✅ Commands accept variant parameters ✅ Auto-detection logic identifies variant types ✅ Unit tests achieve 100% pass rate ✅ Backward compatibility maintained Ready for Phase 2: Variant implementations (Issue #149) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
399 lines
14 KiB
Python
399 lines
14 KiB
Python
"""
|
|
Test suite for Issue #148 - Core Infrastructure for Explode-Implode Variants
|
|
|
|
Tests the foundational infrastructure components that support multiple
|
|
explode-implode variants with manifest-based reversibility.
|
|
"""
|
|
|
|
import pytest
|
|
import tempfile
|
|
import yaml
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
from markitect.explode_variants import (
|
|
ExplodeVariant, ExplodeMode, ManifestVersion, DetectionConfidence,
|
|
BaseVariant, ExplodeOptions, ImplodeOptions, ExplodeResult, ImplodeResult,
|
|
ManifestManager, ManifestData, StructureEntry,
|
|
VariantDetector, DetectionResult
|
|
)
|
|
|
|
|
|
class TestExplodeVariantEnum:
|
|
"""Test the ExplodeVariant enum and related enums."""
|
|
|
|
def test_explode_variant_values(self):
|
|
"""Test that all expected variants are available."""
|
|
assert ExplodeVariant.FLAT.value == "flat"
|
|
assert ExplodeVariant.HIERARCHICAL.value == "hierarchical"
|
|
assert ExplodeVariant.SEMANTIC.value == "semantic"
|
|
|
|
def test_explode_mode_values(self):
|
|
"""Test ExplodeMode enum values."""
|
|
assert ExplodeMode.STANDARD.value == "standard"
|
|
assert ExplodeMode.LEGACY.value == "legacy"
|
|
assert ExplodeMode.PREVIEW.value == "preview"
|
|
|
|
def test_detection_confidence_values(self):
|
|
"""Test DetectionConfidence enum values."""
|
|
assert DetectionConfidence.HIGH.value == "high"
|
|
assert DetectionConfidence.MEDIUM.value == "medium"
|
|
assert DetectionConfidence.LOW.value == "low"
|
|
assert DetectionConfidence.UNKNOWN.value == "unknown"
|
|
|
|
|
|
class TestStructureEntry:
|
|
"""Test the StructureEntry dataclass."""
|
|
|
|
def test_structure_entry_creation(self):
|
|
"""Test creating a StructureEntry."""
|
|
entry = StructureEntry(
|
|
type="h1",
|
|
title="Chapter 1",
|
|
path="chapter_1/index.md",
|
|
order=1,
|
|
parent=None,
|
|
level=1,
|
|
original_line=5
|
|
)
|
|
|
|
assert entry.type == "h1"
|
|
assert entry.title == "Chapter 1"
|
|
assert entry.path == "chapter_1/index.md"
|
|
assert entry.order == 1
|
|
assert entry.parent is None
|
|
assert entry.level == 1
|
|
assert entry.original_line == 5
|
|
|
|
def test_structure_entry_defaults(self):
|
|
"""Test StructureEntry with default values."""
|
|
entry = StructureEntry(
|
|
type="h2",
|
|
title="Section",
|
|
path="section.md",
|
|
order=2
|
|
)
|
|
|
|
assert entry.parent is None
|
|
assert entry.level == 1
|
|
assert entry.original_line is None
|
|
|
|
|
|
class TestManifestData:
|
|
"""Test the ManifestData dataclass."""
|
|
|
|
def test_manifest_data_creation(self):
|
|
"""Test creating ManifestData."""
|
|
manifest = ManifestData(
|
|
explosion_type="flat",
|
|
original_file="book.md",
|
|
created="2025-10-12T19:30:00Z",
|
|
markitect_version="0.1.0"
|
|
)
|
|
|
|
assert manifest.explosion_type == "flat"
|
|
assert manifest.original_file == "book.md"
|
|
assert manifest.created == "2025-10-12T19:30:00Z"
|
|
assert manifest.markitect_version == "0.1.0"
|
|
assert manifest.manifest_version == ManifestVersion.V1_0.value
|
|
|
|
|
|
class TestManifestManager:
|
|
"""Test the ManifestManager class."""
|
|
|
|
def test_manifest_manager_initialization(self):
|
|
"""Test ManifestManager initialization."""
|
|
manager = ManifestManager("0.1.0")
|
|
assert manager.markitect_version == "0.1.0"
|
|
assert manager.MANIFEST_FILENAME == "manifest.md"
|
|
|
|
def test_create_manifest(self):
|
|
"""Test creating a manifest file."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
manager = ManifestManager("0.1.0")
|
|
|
|
# Create test structure
|
|
structure = [
|
|
StructureEntry(
|
|
type="h1",
|
|
title="Book Title",
|
|
path="book_title/index.md",
|
|
order=1
|
|
),
|
|
StructureEntry(
|
|
type="h2",
|
|
title="Chapter 1",
|
|
path="book_title/chapter_1.md",
|
|
order=2,
|
|
parent="Book Title"
|
|
)
|
|
]
|
|
|
|
manifest_path = manager.create_manifest(
|
|
output_dir=temp_path,
|
|
original_file=Path("book.md"),
|
|
variant=ExplodeVariant.FLAT,
|
|
structure=structure,
|
|
preservation_options={
|
|
"front_matter": True,
|
|
"section_order": True,
|
|
"heading_levels": True
|
|
}
|
|
)
|
|
|
|
assert manifest_path.exists()
|
|
assert manifest_path.name == "manifest.md"
|
|
|
|
# Verify content
|
|
content = manifest_path.read_text(encoding='utf-8')
|
|
assert "explosion_type: flat" in content
|
|
assert "original_file: book.md" in content
|
|
assert "Book Title" in content
|
|
assert "Chapter 1" in content
|
|
|
|
def test_read_manifest(self):
|
|
"""Test reading a manifest file."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
manager = ManifestManager("0.1.0")
|
|
|
|
# Create manifest
|
|
structure = [
|
|
StructureEntry(
|
|
type="h1",
|
|
title="Test Title",
|
|
path="test_title/index.md",
|
|
order=1
|
|
)
|
|
]
|
|
|
|
manifest_path = manager.create_manifest(
|
|
output_dir=temp_path,
|
|
original_file=Path("test.md"),
|
|
variant=ExplodeVariant.HIERARCHICAL,
|
|
structure=structure
|
|
)
|
|
|
|
# Read manifest back
|
|
manifest_data = manager.read_manifest(temp_path)
|
|
|
|
assert manifest_data is not None
|
|
assert manifest_data.explosion_type == "hierarchical"
|
|
assert manifest_data.original_file == "test.md"
|
|
assert manifest_data.markitect_version == "0.1.0"
|
|
assert len(manifest_data.structure) == 1
|
|
assert manifest_data.structure[0].title == "Test Title"
|
|
|
|
def test_read_nonexistent_manifest(self):
|
|
"""Test reading manifest from directory without one."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
manager = ManifestManager("0.1.0")
|
|
|
|
manifest_data = manager.read_manifest(temp_path)
|
|
assert manifest_data is None
|
|
|
|
def test_validate_manifest(self):
|
|
"""Test manifest validation."""
|
|
manager = ManifestManager("0.1.0")
|
|
|
|
# Valid manifest
|
|
valid_manifest = ManifestData(
|
|
explosion_type="flat",
|
|
original_file="test.md",
|
|
created="2025-10-12T19:30:00Z",
|
|
markitect_version="0.1.0"
|
|
)
|
|
|
|
errors = manager.validate_manifest(valid_manifest)
|
|
assert len(errors) == 0
|
|
|
|
# Invalid manifest
|
|
invalid_manifest = ManifestData(
|
|
explosion_type="invalid_variant",
|
|
original_file="",
|
|
created="",
|
|
markitect_version="0.1.0"
|
|
)
|
|
|
|
errors = manager.validate_manifest(invalid_manifest)
|
|
assert len(errors) > 0
|
|
assert any("Invalid explosion_type" in error for error in errors)
|
|
assert any("Missing original_file" in error for error in errors)
|
|
|
|
|
|
class TestVariantDetector:
|
|
"""Test the VariantDetector class."""
|
|
|
|
def test_variant_detector_initialization(self):
|
|
"""Test VariantDetector initialization."""
|
|
detector = VariantDetector()
|
|
assert detector.manifest_manager is not None
|
|
|
|
def test_detect_variant_nonexistent_directory(self):
|
|
"""Test variant detection on nonexistent directory."""
|
|
detector = VariantDetector()
|
|
result = detector.detect_variant(Path("/nonexistent/path"))
|
|
|
|
assert result.variant is None
|
|
assert result.confidence == DetectionConfidence.UNKNOWN
|
|
assert result.score == 0.0
|
|
assert not result.manifest_found
|
|
assert "does not exist" in result.evidence[0]
|
|
|
|
def test_detect_variant_with_manifest(self):
|
|
"""Test variant detection when manifest is present."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create a manifest
|
|
manager = ManifestManager("0.1.0")
|
|
manager.create_manifest(
|
|
output_dir=temp_path,
|
|
original_file=Path("test.md"),
|
|
variant=ExplodeVariant.HIERARCHICAL,
|
|
structure=[]
|
|
)
|
|
|
|
detector = VariantDetector()
|
|
result = detector.detect_variant(temp_path)
|
|
|
|
assert result.variant == ExplodeVariant.HIERARCHICAL
|
|
assert result.confidence == DetectionConfidence.HIGH
|
|
assert result.score == 1.0
|
|
assert result.manifest_found
|
|
assert result.manifest_data is not None
|
|
|
|
def test_detect_variant_hierarchical_pattern(self):
|
|
"""Test variant detection based on hierarchical naming patterns."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create directories with numbered prefixes
|
|
(temp_path / "01_chapter_one").mkdir()
|
|
(temp_path / "02_chapter_two").mkdir()
|
|
(temp_path / "03_chapter_three").mkdir()
|
|
|
|
detector = VariantDetector()
|
|
result = detector.detect_variant(temp_path)
|
|
|
|
assert result.variant in [ExplodeVariant.HIERARCHICAL, ExplodeVariant.FLAT]
|
|
assert result.confidence in [DetectionConfidence.HIGH, DetectionConfidence.MEDIUM]
|
|
assert not result.manifest_found
|
|
|
|
def test_detect_variant_semantic_pattern(self):
|
|
"""Test variant detection based on semantic directory names."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create semantic directories
|
|
(temp_path / "parts").mkdir()
|
|
(temp_path / "chapters").mkdir()
|
|
(temp_path / "appendices").mkdir()
|
|
|
|
detector = VariantDetector()
|
|
result = detector.detect_variant(temp_path)
|
|
|
|
# Should detect semantic or fall back to flat
|
|
assert result.variant in [ExplodeVariant.SEMANTIC, ExplodeVariant.FLAT]
|
|
assert not result.manifest_found
|
|
|
|
def test_is_exploded_directory(self):
|
|
"""Test detection of exploded directory structures."""
|
|
detector = VariantDetector()
|
|
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Empty directory should not be detected as exploded
|
|
assert not detector.is_exploded_directory(temp_path)
|
|
|
|
# Directory with manifest should be detected
|
|
(temp_path / "manifest.md").write_text("test manifest")
|
|
assert detector.is_exploded_directory(temp_path)
|
|
|
|
# Clean up and test other patterns
|
|
(temp_path / "manifest.md").unlink()
|
|
|
|
# Directory with numbered subdirs and markdown should be detected
|
|
subdir = temp_path / "01_chapter"
|
|
subdir.mkdir()
|
|
(subdir / "index.md").write_text("test content")
|
|
assert detector.is_exploded_directory(temp_path)
|
|
|
|
|
|
class TestExplodeImplodeOptions:
|
|
"""Test the options dataclasses."""
|
|
|
|
def test_explode_options_defaults(self):
|
|
"""Test ExplodeOptions with defaults."""
|
|
options = ExplodeOptions(variant=ExplodeVariant.FLAT)
|
|
|
|
assert options.variant == ExplodeVariant.FLAT
|
|
assert options.mode == ExplodeMode.STANDARD
|
|
assert options.output_dir is None
|
|
assert options.max_depth is None
|
|
assert options.preserve_front_matter is True
|
|
assert options.section_spacing == 2
|
|
assert options.dry_run is False
|
|
assert options.verbose is False
|
|
assert options.create_manifest is True
|
|
|
|
def test_implode_options_defaults(self):
|
|
"""Test ImplodeOptions with defaults."""
|
|
options = ImplodeOptions()
|
|
|
|
assert options.output_file is None
|
|
assert options.force_variant is None
|
|
assert options.preserve_front_matter is True
|
|
assert options.section_spacing == 2
|
|
assert options.dry_run is False
|
|
assert options.verbose is False
|
|
assert options.overwrite is False
|
|
|
|
|
|
class TestResults:
|
|
"""Test the result dataclasses."""
|
|
|
|
def test_explode_result_creation(self):
|
|
"""Test creating an ExplodeResult."""
|
|
result = ExplodeResult(
|
|
success=True,
|
|
output_directory=Path("/test/output"),
|
|
files_created=[Path("file1.md"), Path("file2.md")],
|
|
manifest_path=Path("/test/output/manifest.md"),
|
|
warnings=["Warning 1"],
|
|
errors=[],
|
|
variant_used=ExplodeVariant.FLAT
|
|
)
|
|
|
|
assert result.success is True
|
|
assert result.output_directory == Path("/test/output")
|
|
assert len(result.files_created) == 2
|
|
assert result.manifest_path == Path("/test/output/manifest.md")
|
|
assert len(result.warnings) == 1
|
|
assert len(result.errors) == 0
|
|
assert result.variant_used == ExplodeVariant.FLAT
|
|
|
|
def test_implode_result_creation(self):
|
|
"""Test creating an ImplodeResult."""
|
|
result = ImplodeResult(
|
|
success=True,
|
|
output_file=Path("/test/output.md"),
|
|
files_processed=[Path("file1.md"), Path("file2.md")],
|
|
variant_detected=ExplodeVariant.HIERARCHICAL,
|
|
warnings=[],
|
|
errors=[]
|
|
)
|
|
|
|
assert result.success is True
|
|
assert result.output_file == Path("/test/output.md")
|
|
assert len(result.files_processed) == 2
|
|
assert result.variant_detected == ExplodeVariant.HIERARCHICAL
|
|
assert len(result.warnings) == 0
|
|
assert len(result.errors) == 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
pytest.main([__file__, "-v"]) |