Files
markitect-main/tests/test_issue_148_core_infrastructure.py
tegwick a17c362653 feat: implement Issue #148 core infrastructure for explode-implode variants
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>
2025-10-12 20:17:41 +02:00

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