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