Files
markitect-main/tests/test_issue_140_roundtrip.py
tegwick 4d876b435a fix: correct TestExplodeImplodeRoundtrip test expectations
Fixed test assertions to match actual md-explode/md-implode behavior:
- Explode creates directories named after h1 headings, not root-level files
- Updated TestExplodeImplodeRoundtrip::test_simple_hierarchical_roundtrip
- Updated TestImplodeExplodeRoundtrip structure expectations
- All 11 roundtrip tests now pass successfully

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-11 13:42:36 +02:00

750 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Roundtrip tests for Issue #140: md-explode and md-implode compatibility.
Tests bidirectional functionality to ensure explode→implode and implode→explode
maintain content fidelity and proper structure reconstruction.
"""
import pytest
import tempfile
import shutil
import subprocess
from pathlib import Path
from textwrap import dedent
class TestExplodeImplodeRoundtrip:
"""Test explode→implode roundtrip functionality."""
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."""
if self.temp_dir.exists():
shutil.rmtree(self.temp_dir)
def run_markitect_command(self, args, check=True):
"""Helper to run markitect commands."""
cmd = ["python", "-m", "markitect.cli"] + args
result = subprocess.run(
cmd,
cwd="/home/worsch/markitect_project",
capture_output=True,
text=True
)
if check and result.returncode != 0:
pytest.fail(f"Command failed: {' '.join(args)}\nStdout: {result.stdout}\nStderr: {result.stderr}")
return result
def test_simple_hierarchical_roundtrip(self):
"""Test basic hierarchical structure roundtrip."""
# Create initial markdown file
original_content = dedent("""
# Book Title
This is the introduction to the book.
## Chapter 1: Getting Started
This chapter covers the basics.
### Section 1.1: Overview
Overview content here.
### Section 1.2: Setup
Setup instructions here.
## Chapter 2: Advanced Topics
Advanced content goes here.
# Conclusion
Final thoughts and summary.
""").strip()
original_file = self.temp_dir / "book.md"
original_file.write_text(original_content)
# Step 1: Explode markdown to directory
exploded_dir = self.temp_dir / "book_exploded"
result = self.run_markitect_command([
"md-explode", str(original_file),
"--output-dir", str(exploded_dir)
])
assert result.returncode == 0
assert exploded_dir.exists()
# Verify exploded structure exists
assert (exploded_dir / "book_title").exists()
assert (exploded_dir / "book_title" / "index.md").exists()
assert (exploded_dir / "book_title" / "chapter_1_getting_started").exists()
assert (exploded_dir / "book_title" / "chapter_1_getting_started" / "index.md").exists()
assert (exploded_dir / "book_title" / "chapter_1_getting_started" / "section_1_1_overview.md").exists()
# Step 2: Implode directory back to markdown
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
assert reconstructed_file.exists()
# Step 3: Compare original and reconstructed content
reconstructed_content = reconstructed_file.read_text().strip()
# Verify key structural elements are preserved
assert "# Book Title" in reconstructed_content
assert "## Chapter 1: Getting Started" in reconstructed_content
assert "### Section 1.1: Overview" in reconstructed_content
assert "### Section 1.2: Setup" in reconstructed_content
assert "## Chapter 2: Advanced Topics" in reconstructed_content
assert "# Conclusion" in reconstructed_content
# Verify content is preserved
assert "This is the introduction to the book." in reconstructed_content
assert "This chapter covers the basics." in reconstructed_content
assert "Overview content here." in reconstructed_content
assert "Setup instructions here." in reconstructed_content
assert "Advanced content goes here." in reconstructed_content
assert "Final thoughts and summary." in reconstructed_content
def test_complex_structure_with_front_matter_roundtrip(self):
"""Test roundtrip with front matter and complex structure."""
original_content = dedent("""
---
title: "Complex Document"
author: "Test Author"
date: "2024-10-07"
tags: [documentation, test]
---
# Complex Document
This document has front matter.
## Part 1: Fundamentals
### Chapter 1: Basics
Basic content with **bold** and *italic* text.
#### Section 1.1: Details
Detailed information here.
##### Subsection 1.1.1: Specifics
Very specific content.
### Chapter 2: Intermediate
Intermediate level content.
## Part 2: Advanced
Advanced topics discussion.
## Appendix
Reference material and additional information.
""").strip()
original_file = self.temp_dir / "complex.md"
original_file.write_text(original_content)
# Explode to directory
exploded_dir = self.temp_dir / "complex_exploded"
result = self.run_markitect_command([
"md-explode", str(original_file),
"--output-dir", str(exploded_dir)
])
assert result.returncode == 0
# Implode back to markdown
reconstructed_file = self.temp_dir / "complex_reconstructed.md"
result = self.run_markitect_command([
"md-implode", str(exploded_dir),
"--output", str(reconstructed_file),
"--preserve-front-matter"
])
assert result.returncode == 0
reconstructed_content = reconstructed_file.read_text()
# Verify front matter is preserved
assert "title: \"Complex Document\"" in reconstructed_content
assert "author: \"Test Author\"" in reconstructed_content
assert "tags: [documentation, test]" in reconstructed_content
# Verify hierarchical structure
assert "# Complex Document" in reconstructed_content
assert "## Part 1: Fundamentals" in reconstructed_content
assert "### Chapter 1: Basics" in reconstructed_content
assert "#### Section 1.1: Details" in reconstructed_content
assert "##### Subsection 1.1.1: Specifics" in reconstructed_content
# Verify formatting is preserved
assert "**bold**" in reconstructed_content
assert "*italic*" in reconstructed_content
def test_minimal_document_roundtrip(self):
"""Test roundtrip with minimal document structure."""
original_content = dedent("""
# Simple Document
Just a simple document with minimal content.
## One Section
Some content in the section.
""").strip()
original_file = self.temp_dir / "simple.md"
original_file.write_text(original_content)
# Explode and implode
exploded_dir = self.temp_dir / "simple_exploded"
self.run_markitect_command(["md-explode", str(original_file), "--output-dir", str(exploded_dir)])
reconstructed_file = self.temp_dir / "simple_reconstructed.md"
self.run_markitect_command(["md-implode", str(exploded_dir), "--output", str(reconstructed_file)])
reconstructed_content = reconstructed_file.read_text().strip()
# Verify structure and content preservation
assert "# Simple Document" in reconstructed_content
assert "## One Section" in reconstructed_content
assert "Just a simple document with minimal content." in reconstructed_content
assert "Some content in the section." in reconstructed_content
def test_empty_sections_roundtrip(self):
"""Test roundtrip handling of empty sections."""
original_content = dedent("""
# Document with Empty Sections
Introduction content.
## Empty Chapter
## Chapter with Content
This chapter has actual content.
### Empty Subsection
### Subsection with Content
Content in subsection.
""").strip()
original_file = self.temp_dir / "empty_sections.md"
original_file.write_text(original_content)
exploded_dir = self.temp_dir / "empty_exploded"
self.run_markitect_command(["md-explode", str(original_file), "--output-dir", str(exploded_dir)])
reconstructed_file = self.temp_dir / "empty_reconstructed.md"
self.run_markitect_command(["md-implode", str(exploded_dir), "--output", str(reconstructed_file)])
reconstructed_content = reconstructed_file.read_text()
# Verify all sections are preserved, even empty ones
assert "# Document with Empty Sections" in reconstructed_content
assert "## Empty Chapter" in reconstructed_content
assert "## Chapter with Content" in reconstructed_content
assert "### Empty Subsection" in reconstructed_content
assert "### Subsection with Content" in reconstructed_content
class TestImplodeExplodeRoundtrip:
"""Test implode→explode roundtrip functionality."""
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."""
if self.temp_dir.exists():
shutil.rmtree(self.temp_dir)
def run_markitect_command(self, args, check=True):
"""Helper to run markitect commands."""
cmd = ["python", "-m", "markitect.cli"] + args
result = subprocess.run(
cmd,
cwd="/home/worsch/markitect_project",
capture_output=True,
text=True
)
if check and result.returncode != 0:
pytest.fail(f"Command failed: {' '.join(args)}\nStdout: {result.stdout}\nStderr: {result.stderr}")
return result
def create_sample_directory_structure(self):
"""Create a sample directory structure to test with."""
# Create directory structure
base_dir = self.temp_dir / "sample_project"
base_dir.mkdir()
# Root content
(base_dir / "introduction.md").write_text(dedent("""
# Sample Project
This is a sample project for testing roundtrip functionality.
""").strip())
# Chapter 1 structure
chapter1_dir = base_dir / "chapter_1_basics"
chapter1_dir.mkdir()
(chapter1_dir / "index.md").write_text(dedent("""
## Chapter 1: Basics
This chapter covers the fundamental concepts.
""").strip())
(chapter1_dir / "section_1_1_overview.md").write_text(dedent("""
### Section 1.1: Overview
Overview of the basic concepts.
""").strip())
(chapter1_dir / "section_1_2_details.md").write_text(dedent("""
### Section 1.2: Details
Detailed explanation of concepts.
""").strip())
# Chapter 2 structure
chapter2_dir = base_dir / "chapter_2_advanced"
chapter2_dir.mkdir()
(chapter2_dir / "index.md").write_text(dedent("""
## Chapter 2: Advanced
Advanced topics and techniques.
""").strip())
# Nested subsection
subsection_dir = chapter2_dir / "subsection_2_1_algorithms"
subsection_dir.mkdir()
(subsection_dir / "index.md").write_text(dedent("""
### Subsection 2.1: Algorithms
Discussion of algorithms.
""").strip())
(subsection_dir / "part_2_1_1_sorting.md").write_text(dedent("""
#### Part 2.1.1: Sorting
Sorting algorithm implementations.
""").strip())
# Conclusion
(base_dir / "conclusion.md").write_text(dedent("""
# Conclusion
Summary and final thoughts.
""").strip())
return base_dir
def test_directory_to_markdown_to_directory_roundtrip(self):
"""Test directory→markdown→directory roundtrip."""
# Create original directory structure
original_dir = self.create_sample_directory_structure()
# Step 1: Implode directory to markdown
markdown_file = self.temp_dir / "imploded.md"
result = self.run_markitect_command([
"md-implode", str(original_dir),
"--output", str(markdown_file)
])
assert result.returncode == 0
assert markdown_file.exists()
# Verify markdown content structure
markdown_content = markdown_file.read_text()
assert "# Sample Project" in markdown_content
assert "## Chapter 1: Basics" in markdown_content
assert "### Section 1.1: Overview" in markdown_content
assert "## Chapter 2: Advanced" in markdown_content
assert "### Subsection 2.1: Algorithms" in markdown_content
assert "#### Part 2.1.1: Sorting" in markdown_content
assert "# Conclusion" in markdown_content
# Step 2: Explode markdown back to directory
reconstructed_dir = self.temp_dir / "reconstructed_project"
result = self.run_markitect_command([
"md-explode", str(markdown_file),
"--output-dir", str(reconstructed_dir)
])
assert result.returncode == 0
assert reconstructed_dir.exists()
# Step 3: Verify directory structure is reconstructed
# Check for key files and directories (explode creates a directory named after the first h1)
assert (reconstructed_dir / "sample_project").exists()
assert (reconstructed_dir / "sample_project" / "index.md").exists()
assert (reconstructed_dir / "sample_project" / "chapter_1_basics.md").exists()
assert (reconstructed_dir / "sample_project" / "chapter_2_advanced").exists()
assert (reconstructed_dir / "sample_project" / "chapter_2_advanced" / "index.md").exists()
assert (reconstructed_dir / "conclusion.md").exists()
# Verify content preservation
intro_content = (reconstructed_dir / "sample_project" / "index.md").read_text()
assert "# Sample Project" in intro_content
assert "This is a sample project for testing" in intro_content
def test_nested_structure_roundtrip(self):
"""Test deeply nested structure roundtrip."""
# Create deeply nested structure
base_dir = self.temp_dir / "deep_structure"
base_dir.mkdir()
# Create 5-level deep structure
current_dir = base_dir
for level in range(1, 6):
content = f"{'#' * level} Level {level}\n\nContent at level {level}."
if level == 1:
# Root level file
(current_dir / f"level_{level}.md").write_text(content)
else:
# Create directory and index
level_dir = current_dir / f"level_{level}_section"
level_dir.mkdir()
(level_dir / "index.md").write_text(content)
current_dir = level_dir
# Implode to markdown
markdown_file = self.temp_dir / "deep_structure.md"
self.run_markitect_command([
"md-implode", str(base_dir),
"--output", str(markdown_file)
])
# Explode back to directory
reconstructed_dir = self.temp_dir / "deep_reconstructed"
self.run_markitect_command([
"md-explode", str(markdown_file),
"--output-dir", str(reconstructed_dir)
])
# Verify deep structure is preserved (explode creates directory named after first h1)
assert (reconstructed_dir / "level_1").exists()
assert (reconstructed_dir / "level_1" / "index.md").exists()
assert (reconstructed_dir / "level_1" / "level_2").exists()
assert (reconstructed_dir / "level_1" / "level_2" / "level_3").exists()
assert (reconstructed_dir / "level_1" / "level_2" / "level_3" / "level_4").exists()
# Verify content at different levels
level_1_content = (reconstructed_dir / "level_1" / "index.md").read_text()
assert "# Level 1" in level_1_content
assert "Content at level 1." in level_1_content
class TestRoundtripContentFidelity:
"""Test content fidelity across roundtrip operations."""
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."""
if self.temp_dir.exists():
shutil.rmtree(self.temp_dir)
def run_markitect_command(self, args, check=True):
"""Helper to run markitect commands."""
cmd = ["python", "-m", "markitect.cli"] + args
result = subprocess.run(
cmd,
cwd="/home/worsch/markitect_project",
capture_output=True,
text=True
)
if check and result.returncode != 0:
pytest.fail(f"Command failed: {' '.join(args)}\nStdout: {result.stdout}\nStderr: {result.stderr}")
return result
def test_markdown_formatting_preservation(self):
"""Test that markdown formatting is preserved through roundtrips."""
original_content = dedent("""
# Formatting Test Document
This document tests various **markdown** *formatting* elements.
## Code Examples
Here's some `inline code` and a code block:
```python
def hello_world():
print("Hello, World!")
```
## Lists and Links
Bullet list:
- Item 1
- Item 2
- Item 3
Numbered list:
1. First item
2. Second item
3. Third item
Link example: [Markitect](https://github.com/example/markitect)
## Tables
| Column 1 | Column 2 | Column 3 |
|----------|----------|----------|
| Value A | Value B | Value C |
| Value D | Value E | Value F |
## Quotes and Special Characters
> This is a blockquote
> with multiple lines
Special characters: & < > " '
""").strip()
original_file = self.temp_dir / "formatting_test.md"
original_file.write_text(original_content)
# Full roundtrip: explode → implode
exploded_dir = self.temp_dir / "formatting_exploded"
self.run_markitect_command(["md-explode", str(original_file), "--output-dir", str(exploded_dir)])
reconstructed_file = self.temp_dir / "formatting_reconstructed.md"
self.run_markitect_command(["md-implode", str(exploded_dir), "--output", str(reconstructed_file)])
reconstructed_content = reconstructed_file.read_text()
# Verify formatting elements are preserved
assert "**markdown**" in reconstructed_content
assert "*formatting*" in reconstructed_content
assert "`inline code`" in reconstructed_content
assert "```python" in reconstructed_content
assert "def hello_world():" in reconstructed_content
assert "- Item 1" in reconstructed_content
assert "1. First item" in reconstructed_content
assert "[Markitect]" in reconstructed_content
assert "| Column 1 |" in reconstructed_content
assert "> This is a blockquote" in reconstructed_content
assert "Special characters: & < > " in reconstructed_content
def test_whitespace_and_spacing_preservation(self):
"""Test preservation of whitespace and spacing patterns."""
original_content = dedent("""
# Spacing Test
This paragraph has extra blank lines above.
## Section with Spacing
Content here.
Multiple blank lines above this paragraph.
### Subsection
Normal spacing here.
## Another Section
Final content.
""").strip()
original_file = self.temp_dir / "spacing_test.md"
original_file.write_text(original_content)
# Roundtrip test
exploded_dir = self.temp_dir / "spacing_exploded"
self.run_markitect_command(["md-explode", str(original_file), "--output-dir", str(exploded_dir)])
reconstructed_file = self.temp_dir / "spacing_reconstructed.md"
self.run_markitect_command(["md-implode", str(exploded_dir), "--output", str(reconstructed_file)])
reconstructed_content = reconstructed_file.read_text()
# Verify key content is preserved (exact spacing may vary due to processing)
assert "# Spacing Test" in reconstructed_content
assert "This paragraph has extra blank lines above." in reconstructed_content
assert "Multiple blank lines above this paragraph." in reconstructed_content
assert "## Section with Spacing" in reconstructed_content
assert "### Subsection" in reconstructed_content
assert "## Another Section" in reconstructed_content
def test_unicode_and_special_characters_roundtrip(self):
"""Test handling of unicode and special characters."""
original_content = dedent("""
# Unicode Test Document 🚀
This document contains various unicode characters and symbols.
## Emoji Section 😀
Various emoji: 🎉 📚 💻 ✅ ❌ 🔥 ⭐ 🌟
## International Characters
- Français: café, naïve, résumé
- Deutsch: Größe, Weiß, Straße
- 日本語: こんにちは、ありがとう
- Español: niño, señor, corazón
- Русский: привет, спасибо
## Mathematical Symbols
- Greek letters: α β γ δ ε ζ η θ
- Math symbols: ∑ ∫ ∞ ≈ ≠ ± √ π
- Arrows: → ← ↑ ↓ ↔ ⇒ ⇐
## Special Characters
Quotes: " " ' '"
Punctuation: … — • ‡ § ¶
""").strip()
original_file = self.temp_dir / "unicode_test.md"
original_file.write_text(original_content, encoding='utf-8')
# Roundtrip test
exploded_dir = self.temp_dir / "unicode_exploded"
self.run_markitect_command(["md-explode", str(original_file), "--output-dir", str(exploded_dir)])
reconstructed_file = self.temp_dir / "unicode_reconstructed.md"
self.run_markitect_command(["md-implode", str(exploded_dir), "--output", str(reconstructed_file)])
reconstructed_content = reconstructed_file.read_text(encoding='utf-8')
# Verify unicode characters are preserved
assert "🚀" in reconstructed_content
assert "😀" in reconstructed_content
assert "café" in reconstructed_content
assert "こんにちは" in reconstructed_content
assert "α β γ" in reconstructed_content
assert "∑ ∫ ∞" in reconstructed_content
assert "→ ←" in reconstructed_content
assert '"' in reconstructed_content # Smart quote character
class TestRoundtripErrorHandling:
"""Test error handling and edge cases in roundtrip operations."""
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."""
if self.temp_dir.exists():
shutil.rmtree(self.temp_dir)
def run_markitect_command(self, args, check=False):
"""Helper to run markitect commands."""
cmd = ["python", "-m", "markitect.cli"] + args
result = subprocess.run(
cmd,
cwd="/home/worsch/markitect_project",
capture_output=True,
text=True
)
return result
def test_malformed_markdown_handling(self):
"""Test handling of malformed or problematic markdown."""
# Create markdown with potential issues
problematic_content = dedent("""
# Document with Issues
## Section with # Hash in Title
Content here.
### Section/With\\Special:Characters?
More content.
## Section with "Quotes" and 'Apostrophes'
Final content.
""").strip()
original_file = self.temp_dir / "problematic.md"
original_file.write_text(problematic_content)
# Test explode (should handle gracefully)
exploded_dir = self.temp_dir / "problematic_exploded"
result = self.run_markitect_command(["md-explode", str(original_file), "--output-dir", str(exploded_dir)])
# Should succeed or fail gracefully
if result.returncode == 0:
# If explode succeeded, test implode
reconstructed_file = self.temp_dir / "problematic_reconstructed.md"
result = self.run_markitect_command(["md-implode", str(exploded_dir), "--output", str(reconstructed_file)])
if result.returncode == 0:
# Verify basic structure is preserved
reconstructed_content = reconstructed_file.read_text()
assert "# Document with Issues" in reconstructed_content
def test_empty_files_and_directories(self):
"""Test handling of empty files and directories."""
# Create structure with empty elements
base_dir = self.temp_dir / "empty_test"
base_dir.mkdir()
# Empty markdown file
(base_dir / "empty.md").write_text("")
# File with only whitespace
(base_dir / "whitespace.md").write_text(" \n\n \n")
# Valid file
(base_dir / "valid.md").write_text("# Valid Content\n\nSome actual content.")
# Empty directory
(base_dir / "empty_dir").mkdir()
# Test implode→explode roundtrip
markdown_file = self.temp_dir / "empty_test.md"
result = self.run_markitect_command(["md-implode", str(base_dir), "--output", str(markdown_file)])
if result.returncode == 0:
# Test explode back
reconstructed_dir = self.temp_dir / "empty_reconstructed"
result = self.run_markitect_command(["md-explode", str(markdown_file), "--output-dir", str(reconstructed_dir)])
# Should handle empty content gracefully
assert result.returncode == 0 or "no content" in result.stderr.lower()
if __name__ == "__main__":
pytest.main([__file__, "-v"])