fix: improve FlatVariant bridge method and add consolidated roundtrip tests
🔧 Fixes: - Fix FlatVariant bridge method to properly create temp files for implode operations - Resolve placeholder content issue in roundtrip tests - Exclude manifest.md from processed files list 🧪 Testing: - Add comprehensive consolidated roundtrip test suite - Test all variants with CLI integration - Include error handling and edge case testing 📊 Status: - Legacy roundtrip tests: 10/11 passing (1 architectural difference) - Variant system core functionality: Working - CLI integration: Minor issues to resolve Files Added: - tests/test_roundtrip_consolidated.py Files Modified: - markitect/explode_variants/flat_variant.py 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -300,29 +300,42 @@ class FlatVariant(BaseVariant):
|
|||||||
try:
|
try:
|
||||||
from markitect.plugins.builtin.markdown_commands import cli_implode_directory
|
from markitect.plugins.builtin.markdown_commands import cli_implode_directory
|
||||||
|
|
||||||
# Use existing implode logic
|
# Create a temporary file for the existing implode logic
|
||||||
|
import tempfile
|
||||||
|
with tempfile.NamedTemporaryFile(mode='w+', suffix='.md', delete=False) as temp_file:
|
||||||
|
temp_path = Path(temp_file.name)
|
||||||
|
|
||||||
|
# Use existing implode logic with actual file creation
|
||||||
result = cli_implode_directory(
|
result = cli_implode_directory(
|
||||||
input_dir=input_directory,
|
input_dir=input_directory,
|
||||||
output_file=options.output_file or Path("/tmp/temp.md"),
|
output_file=temp_path,
|
||||||
dry_run=True, # We handle file writing ourselves
|
dry_run=False, # Actually create the file so we can read it
|
||||||
verbose=options.verbose,
|
verbose=options.verbose,
|
||||||
overwrite=options.overwrite,
|
overwrite=True, # Always overwrite temp file
|
||||||
preserve_front_matter=options.preserve_front_matter,
|
preserve_front_matter=options.preserve_front_matter,
|
||||||
section_spacing=options.section_spacing
|
section_spacing=options.section_spacing
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.success:
|
if result.success and temp_path.exists():
|
||||||
# Read the content that would have been written
|
# Read the generated content
|
||||||
temp_file = options.output_file or Path("/tmp/temp.md")
|
content = temp_path.read_text(encoding='utf-8')
|
||||||
if temp_file.exists():
|
# Exclude manifest from processed files
|
||||||
content = temp_file.read_text(encoding='utf-8')
|
files_processed = [f for f in input_directory.glob("**/*.md") if f.name != "manifest.md"]
|
||||||
else:
|
|
||||||
content = "# Imploded Content\n\n(Content generation in progress...)"
|
# Clean up temp file
|
||||||
|
try:
|
||||||
|
temp_path.unlink()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
files_processed = list(input_directory.glob("**/*.md"))
|
|
||||||
return content, files_processed
|
return content, files_processed
|
||||||
else:
|
else:
|
||||||
raise Exception(result.error_message or "Implosion failed")
|
# Clean up temp file
|
||||||
|
try:
|
||||||
|
temp_path.unlink()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
raise Exception(result.error_message if hasattr(result, 'error_message') else "Implosion failed")
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Fallback basic implementation for testing
|
# Fallback basic implementation for testing
|
||||||
|
|||||||
443
tests/test_roundtrip_consolidated.py
Normal file
443
tests/test_roundtrip_consolidated.py
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
"""
|
||||||
|
Consolidated Roundtrip Tests for Enhanced Explode-Implode System
|
||||||
|
|
||||||
|
This test suite consolidates and updates all roundtrip tests to work with the new
|
||||||
|
variant system, ensuring backward compatibility while testing new functionality.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import tempfile
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
|
||||||
|
from markitect.explode_variants import ExplodeVariant, get_variant_factory
|
||||||
|
|
||||||
|
|
||||||
|
class TestRoundtripBase:
|
||||||
|
"""Base class for roundtrip tests with common utilities."""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
"""Set up temporary directory for each test."""
|
||||||
|
self.temp_dir = Path(tempfile.mkdtemp())
|
||||||
|
|
||||||
|
def teardown_method(self):
|
||||||
|
"""Clean up temporary directory after each test."""
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
def run_markitect_command(self, args: List[str]) -> subprocess.CompletedProcess:
|
||||||
|
"""Run a markitect command and return the result."""
|
||||||
|
cmd = ["python", "-m", "markitect.cli"] + args
|
||||||
|
return subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
cwd="/home/worsch/markitect_project"
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_basic_structure_preservation(self, original: str, reconstructed: str) -> Dict[str, Any]:
|
||||||
|
"""Validate that basic document structure is preserved."""
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Extract headings from both documents
|
||||||
|
orig_headings = re.findall(r'^(#+)\s+(.+)', original, re.MULTILINE)
|
||||||
|
recon_headings = re.findall(r'^(#+)\s+(.+)', reconstructed, re.MULTILINE)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'original_heading_count': len(orig_headings),
|
||||||
|
'reconstructed_heading_count': len(recon_headings),
|
||||||
|
'headings_preserved': len(orig_headings) == len(recon_headings),
|
||||||
|
'original_headings': orig_headings,
|
||||||
|
'reconstructed_headings': recon_headings
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestVariantRoundtrips(TestRoundtripBase):
|
||||||
|
"""Test roundtrips with all variants using CLI commands."""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_document(self):
|
||||||
|
"""Sample document for testing."""
|
||||||
|
return """# Book Title
|
||||||
|
|
||||||
|
This is the introduction to our book.
|
||||||
|
|
||||||
|
## Chapter 1: Getting Started
|
||||||
|
|
||||||
|
Welcome to the first chapter.
|
||||||
|
|
||||||
|
### Section 1.1: Overview
|
||||||
|
|
||||||
|
Basic overview content.
|
||||||
|
|
||||||
|
### Section 1.2: Setup
|
||||||
|
|
||||||
|
Setup instructions here.
|
||||||
|
|
||||||
|
## Chapter 2: Advanced Topics
|
||||||
|
|
||||||
|
More advanced material.
|
||||||
|
|
||||||
|
### Section 2.1: Deep Dive
|
||||||
|
|
||||||
|
Detailed explanations.
|
||||||
|
|
||||||
|
# Conclusion
|
||||||
|
|
||||||
|
Final thoughts and summary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_flat_variant_cli_roundtrip(self, sample_document):
|
||||||
|
"""Test flat variant roundtrip using CLI commands."""
|
||||||
|
self._test_variant_roundtrip(sample_document, "flat")
|
||||||
|
|
||||||
|
def test_hierarchical_variant_cli_roundtrip(self, sample_document):
|
||||||
|
"""Test hierarchical variant roundtrip using CLI commands."""
|
||||||
|
self._test_variant_roundtrip(sample_document, "hierarchical")
|
||||||
|
|
||||||
|
def test_semantic_variant_cli_roundtrip(self, sample_document):
|
||||||
|
"""Test semantic variant roundtrip using CLI commands."""
|
||||||
|
self._test_variant_roundtrip(sample_document, "semantic")
|
||||||
|
|
||||||
|
def _test_variant_roundtrip(self, content: str, variant: str):
|
||||||
|
"""Generic variant roundtrip test."""
|
||||||
|
# Step 1: Create original file
|
||||||
|
original_file = self.temp_dir / f"test_{variant}.md"
|
||||||
|
original_file.write_text(content, encoding='utf-8')
|
||||||
|
|
||||||
|
# Step 2: Explode using specific variant
|
||||||
|
exploded_dir = self.temp_dir / f"test_{variant}.mdd"
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-explode", str(original_file),
|
||||||
|
"--variant", variant,
|
||||||
|
"--output-dir", str(exploded_dir)
|
||||||
|
])
|
||||||
|
assert result.returncode == 0, f"Explode failed: {result.stderr}"
|
||||||
|
assert exploded_dir.exists()
|
||||||
|
|
||||||
|
# Verify manifest was created
|
||||||
|
manifest_file = exploded_dir / "manifest.md"
|
||||||
|
assert manifest_file.exists()
|
||||||
|
|
||||||
|
# Step 3: Implode back (should auto-detect variant)
|
||||||
|
reconstructed_file = self.temp_dir / f"reconstructed_{variant}.md"
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-implode", str(exploded_dir),
|
||||||
|
"--output", str(reconstructed_file)
|
||||||
|
])
|
||||||
|
assert result.returncode == 0, f"Implode failed: {result.stderr}"
|
||||||
|
assert reconstructed_file.exists()
|
||||||
|
|
||||||
|
# Step 4: Validate content preservation
|
||||||
|
reconstructed_content = reconstructed_file.read_text(encoding='utf-8')
|
||||||
|
validation = self.validate_basic_structure_preservation(content, reconstructed_content)
|
||||||
|
|
||||||
|
assert validation['headings_preserved'], f"Headings not preserved in {variant} variant"
|
||||||
|
|
||||||
|
# Verify key content is present
|
||||||
|
assert "# Book Title" in reconstructed_content
|
||||||
|
assert "## Chapter 1: Getting Started" in reconstructed_content
|
||||||
|
assert "### Section 1.1: Overview" in reconstructed_content
|
||||||
|
assert "# Conclusion" in reconstructed_content
|
||||||
|
|
||||||
|
|
||||||
|
class TestBackwardCompatibilityRoundtrips(TestRoundtripBase):
|
||||||
|
"""Test backward compatibility with legacy behavior."""
|
||||||
|
|
||||||
|
def test_default_behavior_roundtrip(self):
|
||||||
|
"""Test that default behavior (flat variant) works like before."""
|
||||||
|
content = """# Introduction
|
||||||
|
|
||||||
|
Basic introduction content.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Overview section.
|
||||||
|
|
||||||
|
# Main Content
|
||||||
|
|
||||||
|
Main content here.
|
||||||
|
|
||||||
|
# Conclusion
|
||||||
|
|
||||||
|
Final thoughts.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create original file
|
||||||
|
original_file = self.temp_dir / "test.md"
|
||||||
|
original_file.write_text(content, encoding='utf-8')
|
||||||
|
|
||||||
|
# Explode without specifying variant (should default to flat)
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-explode", str(original_file)
|
||||||
|
])
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
# Should create .mdd directory with manifest
|
||||||
|
exploded_dir = original_file.with_suffix('.mdd')
|
||||||
|
assert exploded_dir.exists()
|
||||||
|
assert (exploded_dir / "manifest.md").exists()
|
||||||
|
|
||||||
|
# Implode back
|
||||||
|
reconstructed_file = self.temp_dir / "reconstructed.md"
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-implode", str(exploded_dir),
|
||||||
|
"--output", str(reconstructed_file)
|
||||||
|
])
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
# Validate content
|
||||||
|
reconstructed_content = reconstructed_file.read_text(encoding='utf-8')
|
||||||
|
assert "# Introduction" in reconstructed_content
|
||||||
|
assert "# Main Content" in reconstructed_content
|
||||||
|
assert "# Conclusion" in reconstructed_content
|
||||||
|
|
||||||
|
def test_legacy_exploded_directory_handling(self):
|
||||||
|
"""Test that legacy exploded directories can still be imploded."""
|
||||||
|
# Create a structure that looks like legacy exploded content
|
||||||
|
legacy_dir = self.temp_dir / "legacy_structure"
|
||||||
|
legacy_dir.mkdir()
|
||||||
|
|
||||||
|
# Create some markdown files without manifest
|
||||||
|
(legacy_dir / "intro.md").write_text("# Introduction\n\nIntro content.")
|
||||||
|
(legacy_dir / "chapter1.md").write_text("# Chapter 1\n\nChapter content.")
|
||||||
|
(legacy_dir / "conclusion.md").write_text("# Conclusion\n\nFinal thoughts.")
|
||||||
|
|
||||||
|
# Should still be able to implode
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-implode", str(legacy_dir)
|
||||||
|
])
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
# Check that output file was created
|
||||||
|
output_file = legacy_dir.parent / f"{legacy_dir.name}_imploded.md"
|
||||||
|
assert output_file.exists()
|
||||||
|
|
||||||
|
content = output_file.read_text(encoding='utf-8')
|
||||||
|
assert "# Introduction" in content
|
||||||
|
assert "# Chapter 1" in content
|
||||||
|
assert "# Conclusion" in content
|
||||||
|
|
||||||
|
|
||||||
|
class TestComplexRoundtrips(TestRoundtripBase):
|
||||||
|
"""Test roundtrips with complex content and features."""
|
||||||
|
|
||||||
|
def test_front_matter_preservation_roundtrip(self):
|
||||||
|
"""Test that front matter is preserved through roundtrips."""
|
||||||
|
content_with_fm = """---
|
||||||
|
title: "Test Document"
|
||||||
|
author: "Test Author"
|
||||||
|
tags: ["test", "markdown"]
|
||||||
|
version: 1.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Main Content
|
||||||
|
|
||||||
|
This document has front matter.
|
||||||
|
|
||||||
|
## Section 1
|
||||||
|
|
||||||
|
Content here.
|
||||||
|
|
||||||
|
# Conclusion
|
||||||
|
|
||||||
|
End of document.
|
||||||
|
"""
|
||||||
|
|
||||||
|
original_file = self.temp_dir / "test_fm.md"
|
||||||
|
original_file.write_text(content_with_fm, encoding='utf-8')
|
||||||
|
|
||||||
|
# Test with each variant
|
||||||
|
for variant in ["flat", "hierarchical", "semantic"]:
|
||||||
|
# Explode
|
||||||
|
exploded_dir = self.temp_dir / f"test_fm_{variant}.mdd"
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-explode", str(original_file),
|
||||||
|
"--variant", variant,
|
||||||
|
"--output-dir", str(exploded_dir)
|
||||||
|
])
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
# Implode
|
||||||
|
reconstructed_file = self.temp_dir / f"reconstructed_fm_{variant}.md"
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-implode", str(exploded_dir),
|
||||||
|
"--output", str(reconstructed_file)
|
||||||
|
])
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
# Verify front matter preservation
|
||||||
|
reconstructed_content = reconstructed_file.read_text(encoding='utf-8')
|
||||||
|
assert 'title: "Test Document"' in reconstructed_content
|
||||||
|
assert 'author: "Test Author"' in reconstructed_content
|
||||||
|
assert "tags:" in reconstructed_content
|
||||||
|
|
||||||
|
def test_unicode_and_special_characters_roundtrip(self):
|
||||||
|
"""Test roundtrip with unicode and special characters."""
|
||||||
|
unicode_content = """# Tëst Dócümënt
|
||||||
|
|
||||||
|
This document contains ünïcödë characters.
|
||||||
|
|
||||||
|
## Spëcïál Chàráctërs
|
||||||
|
|
||||||
|
- Émojis: 🚀 📝 ✅
|
||||||
|
- Symbols: © ® ™ € £ ¥
|
||||||
|
- Math: ∑ ∞ π √ ≈ ≠
|
||||||
|
|
||||||
|
### Çødë Blöck
|
||||||
|
|
||||||
|
```python
|
||||||
|
def hëllö_wörld():
|
||||||
|
print("Hëllö, Wörld! 🌍")
|
||||||
|
```
|
||||||
|
|
||||||
|
# Cönclüsïön
|
||||||
|
|
||||||
|
End öf tëst.
|
||||||
|
"""
|
||||||
|
|
||||||
|
original_file = self.temp_dir / "unicode_test.md"
|
||||||
|
original_file.write_text(unicode_content, encoding='utf-8')
|
||||||
|
|
||||||
|
# Test with flat variant
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-explode", str(original_file),
|
||||||
|
"--variant", "flat"
|
||||||
|
])
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
exploded_dir = original_file.with_suffix('.mdd')
|
||||||
|
assert exploded_dir.exists()
|
||||||
|
|
||||||
|
# Implode back
|
||||||
|
reconstructed_file = self.temp_dir / "unicode_reconstructed.md"
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-implode", str(exploded_dir),
|
||||||
|
"--output", str(reconstructed_file)
|
||||||
|
])
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
# Verify unicode preservation
|
||||||
|
reconstructed_content = reconstructed_file.read_text(encoding='utf-8')
|
||||||
|
assert "Tëst Dócümënt" in reconstructed_content
|
||||||
|
assert "🚀 📝 ✅" in reconstructed_content
|
||||||
|
assert "hëllö_wörld" in reconstructed_content
|
||||||
|
|
||||||
|
def test_large_document_roundtrip(self):
|
||||||
|
"""Test roundtrip with a large document."""
|
||||||
|
# Generate large content
|
||||||
|
large_content = "# Large Document Test\n\nThis tests performance with large documents.\n\n"
|
||||||
|
|
||||||
|
for chapter in range(1, 11): # 10 chapters
|
||||||
|
large_content += f"# Chapter {chapter}\n\n"
|
||||||
|
large_content += f"This is chapter {chapter} content.\n\n"
|
||||||
|
|
||||||
|
for section in range(1, 6): # 5 sections per chapter
|
||||||
|
large_content += f"## Section {chapter}.{section}\n\n"
|
||||||
|
large_content += f"Content for section {chapter}.{section}.\n\n"
|
||||||
|
large_content += "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " * 20
|
||||||
|
large_content += "\n\n"
|
||||||
|
|
||||||
|
large_content += "# Conclusion\n\nEnd of large document.\n"
|
||||||
|
|
||||||
|
original_file = self.temp_dir / "large_doc.md"
|
||||||
|
original_file.write_text(large_content, encoding='utf-8')
|
||||||
|
|
||||||
|
# Test with hierarchical variant (most complex)
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-explode", str(original_file),
|
||||||
|
"--variant", "hierarchical"
|
||||||
|
])
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
exploded_dir = original_file.with_suffix('.mdd')
|
||||||
|
assert exploded_dir.exists()
|
||||||
|
|
||||||
|
# Verify many files were created
|
||||||
|
md_files = list(exploded_dir.glob("**/*.md"))
|
||||||
|
assert len(md_files) > 10 # Should have many files
|
||||||
|
|
||||||
|
# Implode back
|
||||||
|
reconstructed_file = self.temp_dir / "large_reconstructed.md"
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-implode", str(exploded_dir),
|
||||||
|
"--output", str(reconstructed_file)
|
||||||
|
])
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
# Verify structure preservation
|
||||||
|
reconstructed_content = reconstructed_file.read_text(encoding='utf-8')
|
||||||
|
validation = self.validate_basic_structure_preservation(large_content, reconstructed_content)
|
||||||
|
assert validation['headings_preserved']
|
||||||
|
|
||||||
|
|
||||||
|
class TestErrorHandlingRoundtrips(TestRoundtripBase):
|
||||||
|
"""Test error handling in roundtrip scenarios."""
|
||||||
|
|
||||||
|
def test_malformed_markdown_handling(self):
|
||||||
|
"""Test handling of malformed markdown."""
|
||||||
|
malformed_content = """# Valid Header
|
||||||
|
|
||||||
|
Some content here.
|
||||||
|
|
||||||
|
## Another header
|
||||||
|
|
||||||
|
# Missing spacing
|
||||||
|
No space before content.
|
||||||
|
|
||||||
|
###Too many hashes without space
|
||||||
|
|
||||||
|
# Final header
|
||||||
|
"""
|
||||||
|
|
||||||
|
original_file = self.temp_dir / "malformed.md"
|
||||||
|
original_file.write_text(malformed_content, encoding='utf-8')
|
||||||
|
|
||||||
|
# Should still work despite malformed content
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-explode", str(original_file)
|
||||||
|
])
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
exploded_dir = original_file.with_suffix('.mdd')
|
||||||
|
assert exploded_dir.exists()
|
||||||
|
|
||||||
|
# Should be able to implode back
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-implode", str(exploded_dir)
|
||||||
|
])
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
def test_empty_content_handling(self):
|
||||||
|
"""Test handling of empty files and sections."""
|
||||||
|
empty_content = """# Empty Test
|
||||||
|
|
||||||
|
## Empty Section 1
|
||||||
|
|
||||||
|
## Empty Section 2
|
||||||
|
|
||||||
|
# Another Empty
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
original_file = self.temp_dir / "empty.md"
|
||||||
|
original_file.write_text(empty_content, encoding='utf-8')
|
||||||
|
|
||||||
|
# Should handle empty content gracefully
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-explode", str(original_file)
|
||||||
|
])
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
exploded_dir = original_file.with_suffix('.mdd')
|
||||||
|
assert exploded_dir.exists()
|
||||||
|
|
||||||
|
result = self.run_markitect_command([
|
||||||
|
"md-implode", str(exploded_dir)
|
||||||
|
])
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pytest.main([__file__, "-v"])
|
||||||
Reference in New Issue
Block a user