Implement all three explode-implode variants with full CLI integration: 🔧 Variant Implementations: - FlatVariant: Encapsulates existing flat structure behavior - HierarchicalVariant: Numbered directory structures (01_, 02_, 03_) - SemanticVariant: Content-based organization (intro, chapters, appendices) 🏭 Factory System: - VariantFactory: Centralized variant creation and management - Auto-detection algorithms with confidence scoring - Content analysis for variant recommendation 🖥️ CLI Integration: - Enhanced md-explode command with --variant parameter - Enhanced md-implode command with auto-detection - Improved error handling and user feedback 🧪 Comprehensive Testing: - 22 unit tests covering all variant functionality - Roundtrip validation ensuring perfect reversibility - Performance testing with large documents - Error handling and edge case coverage 📊 Key Features: - Three distinct organization strategies - Automatic variant detection from directory structures - Full backward compatibility with existing behavior - Extensible architecture for future variants - Manifest-based reversibility Files Added: - markitect/explode_variants/flat_variant.py - markitect/explode_variants/hierarchical_variant.py - markitect/explode_variants/semantic_variant.py - markitect/explode_variants/variant_factory.py - tests/test_issue_149_explode_implode_variants.py - tests/test_issue_149_roundtrip_validation.py - cost_notes/issue_149_cost_2025-10-12.md Files Modified: - markitect/explode_variants/__init__.py (updated exports) - markitect/plugins/builtin/markdown_commands.py (CLI integration) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
452 lines
15 KiB
Python
452 lines
15 KiB
Python
"""
|
|
Test suite for Issue #149 - Phase 2: Implement Explode-Implode Variants
|
|
|
|
Tests all three variant implementations (flat, hierarchical, semantic) with
|
|
comprehensive explode-implode operations, roundtrip validation, and CLI integration.
|
|
"""
|
|
|
|
import pytest
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from markitect.explode_variants import (
|
|
ExplodeVariant, ExplodeOptions, ImplodeOptions,
|
|
FlatVariant, HierarchicalVariant, SemanticVariant,
|
|
VariantFactory, get_variant_factory, create_variant
|
|
)
|
|
|
|
|
|
class TestFlatVariant:
|
|
"""Test the FlatVariant implementation."""
|
|
|
|
def test_flat_variant_initialization(self):
|
|
"""Test FlatVariant initialization."""
|
|
variant = FlatVariant()
|
|
assert variant.variant_type == ExplodeVariant.FLAT
|
|
assert variant.name == "Flat Structure"
|
|
assert "directories based on h1 headings" in variant.description
|
|
|
|
def test_flat_variant_explode_basic(self):
|
|
"""Test basic explosion with flat variant."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create test markdown file
|
|
test_content = """# Introduction
|
|
|
|
This is the introduction.
|
|
|
|
## Overview
|
|
|
|
Some overview content.
|
|
|
|
# Chapter 1
|
|
|
|
First chapter content.
|
|
|
|
## Section 1.1
|
|
|
|
Section content here.
|
|
|
|
# Conclusion
|
|
|
|
Final thoughts.
|
|
"""
|
|
|
|
input_file = temp_path / "test.md"
|
|
input_file.write_text(test_content, encoding='utf-8')
|
|
|
|
variant = FlatVariant()
|
|
options = ExplodeOptions(
|
|
variant=ExplodeVariant.FLAT,
|
|
create_manifest=True
|
|
)
|
|
|
|
result = variant.explode(input_file, options)
|
|
|
|
assert result.success
|
|
assert result.variant_used == ExplodeVariant.FLAT
|
|
assert result.output_directory.exists()
|
|
assert result.manifest_path is not None
|
|
assert result.manifest_path.exists()
|
|
assert len(result.files_created) > 0
|
|
|
|
def test_flat_variant_can_handle_directory(self):
|
|
"""Test flat variant directory detection."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create flat structure
|
|
(temp_path / "introduction").mkdir()
|
|
(temp_path / "introduction" / "index.md").write_text("# Introduction")
|
|
(temp_path / "chapter_1").mkdir()
|
|
(temp_path / "chapter_1" / "index.md").write_text("# Chapter 1")
|
|
|
|
variant = FlatVariant()
|
|
assert variant.can_handle_directory(temp_path)
|
|
|
|
def test_flat_variant_detection_patterns(self):
|
|
"""Test flat variant detection patterns."""
|
|
variant = FlatVariant()
|
|
patterns = variant.get_detection_patterns()
|
|
|
|
assert patterns["manifest_type"] == "flat"
|
|
assert "numbered_directory_ratio" in patterns
|
|
assert "fallback_score" in patterns
|
|
|
|
|
|
class TestHierarchicalVariant:
|
|
"""Test the HierarchicalVariant implementation."""
|
|
|
|
def test_hierarchical_variant_initialization(self):
|
|
"""Test HierarchicalVariant initialization."""
|
|
variant = HierarchicalVariant()
|
|
assert variant.variant_type == ExplodeVariant.HIERARCHICAL
|
|
assert variant.name == "Hierarchical Structure"
|
|
assert "numbered directory structures" in variant.description
|
|
|
|
def test_hierarchical_variant_explode_basic(self):
|
|
"""Test basic explosion with hierarchical variant."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create test markdown file
|
|
test_content = """# Getting Started
|
|
|
|
Introduction to the system.
|
|
|
|
## Installation
|
|
|
|
How to install.
|
|
|
|
## Configuration
|
|
|
|
How to configure.
|
|
|
|
# Advanced Topics
|
|
|
|
Advanced material.
|
|
|
|
## Performance
|
|
|
|
Performance considerations.
|
|
|
|
# Conclusion
|
|
|
|
Final notes.
|
|
"""
|
|
|
|
input_file = temp_path / "guide.md"
|
|
input_file.write_text(test_content, encoding='utf-8')
|
|
|
|
variant = HierarchicalVariant()
|
|
options = ExplodeOptions(
|
|
variant=ExplodeVariant.HIERARCHICAL,
|
|
create_manifest=True
|
|
)
|
|
|
|
result = variant.explode(input_file, options)
|
|
|
|
assert result.success
|
|
assert result.variant_used == ExplodeVariant.HIERARCHICAL
|
|
assert result.output_directory.exists()
|
|
assert result.manifest_path is not None
|
|
|
|
# Check for numbered directories
|
|
subdirs = [d for d in result.output_directory.iterdir() if d.is_dir()]
|
|
numbered_dirs = [d for d in subdirs if d.name.startswith(('01_', '02_', '03_'))]
|
|
assert len(numbered_dirs) > 0
|
|
|
|
def test_hierarchical_variant_can_handle_directory(self):
|
|
"""Test hierarchical variant directory detection."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create hierarchical structure
|
|
(temp_path / "01_introduction").mkdir()
|
|
(temp_path / "01_introduction" / "index.md").write_text("# Introduction")
|
|
(temp_path / "02_chapter_one").mkdir()
|
|
(temp_path / "02_chapter_one" / "index.md").write_text("# Chapter One")
|
|
|
|
variant = HierarchicalVariant()
|
|
assert variant.can_handle_directory(temp_path)
|
|
|
|
def test_hierarchical_variant_detection_patterns(self):
|
|
"""Test hierarchical variant detection patterns."""
|
|
variant = HierarchicalVariant()
|
|
patterns = variant.get_detection_patterns()
|
|
|
|
assert patterns["manifest_type"] == "hierarchical"
|
|
assert "numbered_directory_ratio" in patterns
|
|
assert patterns["numbered_directory_ratio"]["min"] == 0.6
|
|
|
|
|
|
class TestSemanticVariant:
|
|
"""Test the SemanticVariant implementation."""
|
|
|
|
def test_semantic_variant_initialization(self):
|
|
"""Test SemanticVariant initialization."""
|
|
variant = SemanticVariant()
|
|
assert variant.variant_type == ExplodeVariant.SEMANTIC
|
|
assert variant.name == "Semantic Structure"
|
|
assert "content-based directory groupings" in variant.description
|
|
|
|
def test_semantic_variant_explode_basic(self):
|
|
"""Test basic explosion with semantic variant."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create test markdown file with semantic content
|
|
test_content = """# Introduction
|
|
|
|
Welcome to this comprehensive guide.
|
|
|
|
# Tutorial: Getting Started
|
|
|
|
This tutorial will walk you through the basics.
|
|
|
|
## Step 1: Installation
|
|
|
|
Install the software.
|
|
|
|
## Step 2: Configuration
|
|
|
|
Configure your environment.
|
|
|
|
# Reference: API Documentation
|
|
|
|
Complete API reference.
|
|
|
|
## Function Listing
|
|
|
|
List of all functions.
|
|
|
|
# Appendix A: Troubleshooting
|
|
|
|
Common issues and solutions.
|
|
|
|
# Conclusion
|
|
|
|
Final thoughts and summary.
|
|
"""
|
|
|
|
input_file = temp_path / "manual.md"
|
|
input_file.write_text(test_content, encoding='utf-8')
|
|
|
|
variant = SemanticVariant()
|
|
options = ExplodeOptions(
|
|
variant=ExplodeVariant.SEMANTIC,
|
|
create_manifest=True
|
|
)
|
|
|
|
result = variant.explode(input_file, options)
|
|
|
|
assert result.success
|
|
assert result.variant_used == ExplodeVariant.SEMANTIC
|
|
assert result.output_directory.exists()
|
|
assert result.manifest_path is not None
|
|
|
|
# Check for semantic directories
|
|
subdirs = [d.name for d in result.output_directory.iterdir() if d.is_dir()]
|
|
semantic_dirs = [d for d in subdirs if d in ['introduction', 'tutorials', 'reference', 'appendices', 'conclusion']]
|
|
assert len(semantic_dirs) > 0
|
|
|
|
def test_semantic_variant_can_handle_directory(self):
|
|
"""Test semantic variant directory detection."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create semantic structure
|
|
(temp_path / "introduction").mkdir()
|
|
(temp_path / "introduction" / "overview.md").write_text("# Overview")
|
|
(temp_path / "chapters").mkdir()
|
|
(temp_path / "chapters" / "basics.md").write_text("# Basics")
|
|
(temp_path / "appendices").mkdir()
|
|
(temp_path / "appendices" / "glossary.md").write_text("# Glossary")
|
|
|
|
variant = SemanticVariant()
|
|
assert variant.can_handle_directory(temp_path)
|
|
|
|
def test_semantic_variant_detection_patterns(self):
|
|
"""Test semantic variant detection patterns."""
|
|
variant = SemanticVariant()
|
|
patterns = variant.get_detection_patterns()
|
|
|
|
assert patterns["manifest_type"] == "semantic"
|
|
assert "semantic_directory_ratio" in patterns
|
|
assert patterns["semantic_directory_ratio"]["min"] == 0.4
|
|
|
|
|
|
class TestVariantFactory:
|
|
"""Test the VariantFactory functionality."""
|
|
|
|
def test_variant_factory_initialization(self):
|
|
"""Test VariantFactory initialization."""
|
|
factory = VariantFactory()
|
|
assert factory is not None
|
|
|
|
# Test that all built-in variants are registered
|
|
stats = factory.get_variant_statistics()
|
|
assert stats['total_variants'] >= 3
|
|
assert ExplodeVariant.FLAT in stats['variant_types']
|
|
assert ExplodeVariant.HIERARCHICAL in stats['variant_types']
|
|
assert ExplodeVariant.SEMANTIC in stats['variant_types']
|
|
|
|
def test_variant_factory_create_variant(self):
|
|
"""Test creating variants through factory."""
|
|
factory = VariantFactory()
|
|
|
|
flat_variant = factory.create_variant(ExplodeVariant.FLAT)
|
|
assert isinstance(flat_variant, FlatVariant)
|
|
|
|
hierarchical_variant = factory.create_variant(ExplodeVariant.HIERARCHICAL)
|
|
assert isinstance(hierarchical_variant, HierarchicalVariant)
|
|
|
|
semantic_variant = factory.create_variant(ExplodeVariant.SEMANTIC)
|
|
assert isinstance(semantic_variant, SemanticVariant)
|
|
|
|
def test_variant_factory_detect_variant(self):
|
|
"""Test variant detection through factory."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create a numbered directory structure
|
|
(temp_path / "01_intro").mkdir()
|
|
(temp_path / "02_main").mkdir()
|
|
(temp_path / "03_end").mkdir()
|
|
|
|
factory = VariantFactory()
|
|
result = factory.detect_variant(temp_path)
|
|
|
|
assert result.variant is not None
|
|
# Should detect hierarchical due to numbered directories
|
|
assert result.variant in [ExplodeVariant.HIERARCHICAL, ExplodeVariant.FLAT]
|
|
|
|
def test_variant_factory_convenience_functions(self):
|
|
"""Test convenience functions."""
|
|
# Test global factory
|
|
factory = get_variant_factory()
|
|
assert isinstance(factory, VariantFactory)
|
|
|
|
# Test create_variant convenience function
|
|
variant = create_variant(ExplodeVariant.FLAT)
|
|
assert isinstance(variant, FlatVariant)
|
|
|
|
def test_variant_factory_list_available_variants(self):
|
|
"""Test listing available variants."""
|
|
factory = VariantFactory()
|
|
variants_info = factory.list_available_variants()
|
|
|
|
assert len(variants_info) >= 3
|
|
|
|
# Check that required fields are present
|
|
for info in variants_info:
|
|
assert 'type' in info
|
|
assert 'name' in info
|
|
assert 'description' in info
|
|
assert 'detection_patterns' in info
|
|
|
|
def test_variant_factory_get_best_variant_for_content(self):
|
|
"""Test content-based variant recommendation."""
|
|
factory = VariantFactory()
|
|
|
|
# Content with numbered sections (should suggest hierarchical)
|
|
numbered_content = """# 1. Introduction
|
|
# 2. Main Content
|
|
# 3. Conclusion"""
|
|
|
|
result = factory.get_best_variant_for_content(numbered_content)
|
|
assert result in [ExplodeVariant.HIERARCHICAL, ExplodeVariant.FLAT]
|
|
|
|
# Content with semantic keywords (should suggest semantic)
|
|
semantic_content = """# Introduction
|
|
# Tutorial: Getting Started
|
|
# Reference Manual
|
|
# Appendix A"""
|
|
|
|
result = factory.get_best_variant_for_content(semantic_content)
|
|
assert result in [ExplodeVariant.SEMANTIC, ExplodeVariant.FLAT]
|
|
|
|
|
|
class TestVariantIntegration:
|
|
"""Test integration between variants and CLI commands."""
|
|
|
|
def test_explode_options_validation(self):
|
|
"""Test ExplodeOptions validation."""
|
|
# Valid options
|
|
options = ExplodeOptions(variant=ExplodeVariant.FLAT)
|
|
assert options.variant == ExplodeVariant.FLAT
|
|
assert options.create_manifest is True # default
|
|
|
|
# Custom options
|
|
custom_options = ExplodeOptions(
|
|
variant=ExplodeVariant.HIERARCHICAL,
|
|
max_depth=5,
|
|
create_manifest=False,
|
|
dry_run=True
|
|
)
|
|
assert custom_options.max_depth == 5
|
|
assert custom_options.create_manifest is False
|
|
assert custom_options.dry_run is True
|
|
|
|
def test_implode_options_validation(self):
|
|
"""Test ImplodeOptions validation."""
|
|
# Default options
|
|
options = ImplodeOptions()
|
|
assert options.preserve_front_matter is True # default
|
|
assert options.section_spacing == 2 # default
|
|
|
|
# Custom options
|
|
custom_options = ImplodeOptions(
|
|
output_file=Path("/tmp/output.md"),
|
|
section_spacing=3,
|
|
dry_run=True
|
|
)
|
|
assert custom_options.output_file == Path("/tmp/output.md")
|
|
assert custom_options.section_spacing == 3
|
|
assert custom_options.dry_run is True
|
|
|
|
def test_error_handling(self):
|
|
"""Test error handling in variants."""
|
|
variant = FlatVariant()
|
|
|
|
# Test with non-existent file
|
|
options = ExplodeOptions(variant=ExplodeVariant.FLAT)
|
|
result = variant.explode(Path("/nonexistent/file.md"), options)
|
|
|
|
assert not result.success
|
|
assert len(result.errors) > 0
|
|
assert "does not exist" in result.errors[0].lower()
|
|
|
|
def test_manifest_integration(self):
|
|
"""Test manifest creation and reading integration."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create test file
|
|
test_content = "# Test\n\nContent here."
|
|
input_file = temp_path / "test.md"
|
|
input_file.write_text(test_content, encoding='utf-8')
|
|
|
|
# Test each variant creates a manifest
|
|
for variant_type in [ExplodeVariant.FLAT, ExplodeVariant.HIERARCHICAL, ExplodeVariant.SEMANTIC]:
|
|
variant = create_variant(variant_type)
|
|
options = ExplodeOptions(
|
|
variant=variant_type,
|
|
output_dir=temp_path / f"test_{variant_type.value}",
|
|
create_manifest=True
|
|
)
|
|
|
|
result = variant.explode(input_file, options)
|
|
|
|
assert result.success
|
|
assert result.manifest_path is not None
|
|
assert result.manifest_path.exists()
|
|
|
|
# Verify manifest contains correct variant type
|
|
manifest_content = result.manifest_path.read_text(encoding='utf-8')
|
|
assert f"explosion_type: {variant_type.value}" in manifest_content
|
|
|
|
|
|
if __name__ == '__main__':
|
|
pytest.main([__file__, "-v"]) |