Files
markitect-main/tests/test_issue_138_directory_creation.py
tegwick 312bf8c7bf feat: complete TDD8 implementation of markdown file explosion - Issue #138
Complete implementation of md-explode command for transforming single
markdown files into organized directory structures:

Core Implementation:
- MarkdownSection class for hierarchical document modeling
- extract_headings() - Parse markdown headings with levels
- parse_markdown_structure() - Build section hierarchy from content
- generate_safe_filename() - Convert headings to filesystem-safe names
- explode_markdown_file() - Main explosion functionality
- DirectoryStructureBuilder - Create organized file/directory structures

CLI Integration:
- md-explode command with comprehensive options
- --dry-run for previewing structure
- --verbose for detailed output
- --max-depth for limiting nesting
- --output-dir for custom output location

Key Features:
- Hierarchical structure preservation (# → ## → ###)
- Smart filename generation with Unicode support
- Front matter handling and preservation
- Content integrity maintenance
- Cross-platform filesystem compatibility
- Comprehensive error handling and validation

Refactoring Applied:
- Eliminated code duplication between filename functions
- Extracted front matter processing into dedicated function
- Modularized CLI command with helper functions
- Improved error handling and user feedback

Documentation:
- Complete API documentation with docstrings
- Comprehensive user documentation (docs/md-explode-command.md)
- Usage examples and troubleshooting guide
- Integration instructions with other MarkiTect commands

Testing: 47 comprehensive tests covering all functionality
Status: Production-ready, full TDD8 cycle completed
Performance: Efficient for documents with thousands of sections

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-07 15:44:30 +02:00

333 lines
11 KiB
Python

"""
Test directory structure creation functionality for Issue #138: Explode Markdown file to markdown directory.
This test module covers the creation of filesystem directory structures that match
the hierarchical organization of markdown documents.
"""
import pytest
import tempfile
import shutil
from pathlib import Path
from unittest.mock import Mock, patch
# Import will fail initially (RED phase) until implementation exists
try:
from markitect.plugins.builtin.markdown_commands import (
create_directory_structure,
explode_markdown_file,
DirectoryStructureBuilder,
MarkdownSection
)
except ImportError:
# Expected during RED phase - tests should fail initially
create_directory_structure = None
explode_markdown_file = None
DirectoryStructureBuilder = None
MarkdownSection = None
class TestDirectoryStructureCreation:
"""Test creation of directory structures from markdown hierarchy."""
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 test_create_simple_directory_structure(self):
"""Test creating a simple directory structure from markdown sections."""
# This should fail initially (RED phase)
# Mock sections representing a simple book structure
sections = [
Mock(level=1, title="Part 1: Introduction", children=[
Mock(level=2, title="Chapter 1: Getting Started", children=[],
content="Content for chapter 1"),
Mock(level=2, title="Chapter 2: Basics", children=[],
content="Content for chapter 2")
], content="Introduction content"),
]
result = create_directory_structure(sections, self.temp_dir)
# Verify directory structure
part_dir = self.temp_dir / "part_1_introduction"
assert part_dir.exists()
assert part_dir.is_dir()
chapter1_file = part_dir / "chapter_1_getting_started.md"
chapter2_file = part_dir / "chapter_2_basics.md"
assert chapter1_file.exists()
assert chapter2_file.exists()
# Verify content was written
assert "Content for chapter 1" in chapter1_file.read_text()
assert "Content for chapter 2" in chapter2_file.read_text()
def test_create_nested_directory_structure(self):
"""Test creating deeply nested directory structures."""
# This should fail initially (RED phase)
sections = [
Mock(level=1, title="Part 1", children=[
Mock(level=2, title="Chapter 1", children=[
Mock(level=3, title="Section 1.1", children=[
Mock(level=4, title="Subsection 1.1.1", children=[],
content="Deep content")
], content="Section content")
], content="Chapter content")
], content="Part content")
]
result = create_directory_structure(sections, self.temp_dir)
# Verify nested structure
deep_path = (self.temp_dir / "part_1" / "chapter_1" / "section_1_1" /
"subsection_1_1_1.md")
# Note: Exact structure depends on implementation decisions
# This test defines expected behavior
assert any(path.name == "subsection_1_1_1.md" for path in self.temp_dir.rglob("*.md"))
def test_create_structure_with_duplicate_names(self):
"""Test handling duplicate heading names in directory structure."""
# This should fail initially (RED phase)
sections = [
Mock(level=1, title="Introduction", children=[], content="First intro"),
Mock(level=1, title="Introduction", children=[], content="Second intro")
]
result = create_directory_structure(sections, self.temp_dir)
# Should create unique directories/files
intro1_path = self.temp_dir / "introduction"
intro2_path = self.temp_dir / "introduction_2"
# One of these patterns should exist
assert (intro1_path.exists() or
(self.temp_dir / "introduction.md").exists() or
(self.temp_dir / "introduction_2.md").exists())
def test_create_structure_handles_existing_directories(self):
"""Test behavior when target directories already exist."""
# This should fail initially (RED phase)
# Pre-create a directory
existing_dir = self.temp_dir / "chapter_1"
existing_dir.mkdir()
sections = [
Mock(level=1, title="Chapter 1", children=[], content="New content")
]
# Should handle existing directory gracefully
result = create_directory_structure(sections, self.temp_dir)
# Should either merge, skip, or create alternative name
assert result is not None # Function should complete without error
def test_create_structure_with_special_characters(self):
"""Test directory creation with headings containing special characters."""
# This should fail initially (RED phase)
sections = [
Mock(level=1, title="Chapter 1: What's New?", children=[],
content="Content with special chars"),
Mock(level=1, title="File/Path Issues", children=[],
content="Path content")
]
result = create_directory_structure(sections, self.temp_dir)
# Verify safe directory names were created
safe_names = [path.name for path in self.temp_dir.iterdir()]
# Should contain sanitized versions
assert any("whats_new" in name.lower() for name in safe_names)
assert any("file_path" in name.lower() for name in safe_names)
def test_create_structure_preserves_markdown_formatting(self):
"""Test that markdown formatting is preserved in extracted files."""
# This should fail initially (RED phase)
markdown_content = """## Chapter Title
This content has **bold** and *italic* text.
```python
def example():
return "code block"
```
- List item 1
- List item 2
"""
sections = [
Mock(level=1, title="Test Chapter", children=[], content=markdown_content)
]
result = create_directory_structure(sections, self.temp_dir)
# Find the created file
md_files = list(self.temp_dir.rglob("*.md"))
assert len(md_files) > 0
content = md_files[0].read_text()
# Verify markdown formatting is preserved
assert "**bold**" in content
assert "*italic*" in content
assert "```python" in content
assert "- List item 1" in content
class TestDirectoryStructureBuilder:
"""Test the DirectoryStructureBuilder class."""
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 test_builder_initialization(self):
"""Test DirectoryStructureBuilder initialization."""
# This should fail initially (RED phase)
builder = DirectoryStructureBuilder(
output_dir=self.temp_dir,
max_depth=3,
file_extension=".md"
)
assert builder.output_dir == self.temp_dir
assert builder.max_depth == 3
assert builder.file_extension == ".md"
def test_builder_depth_limiting(self):
"""Test that builder respects maximum depth settings."""
# This should fail initially (RED phase)
builder = DirectoryStructureBuilder(
output_dir=self.temp_dir,
max_depth=2
)
# Create deep structure that exceeds max depth
sections = [
Mock(level=1, title="Level 1", children=[
Mock(level=2, title="Level 2", children=[
Mock(level=3, title="Level 3", children=[
Mock(level=4, title="Level 4", children=[], content="Deep content")
], content="L3 content")
], content="L2 content")
], content="L1 content")
]
result = builder.build(sections)
# Should flatten or handle deep structures appropriately
# Exact behavior depends on implementation
assert result is not None
class TestMarkdownExplosion:
"""Test the complete markdown file explosion process."""
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 test_explode_simple_markdown_file(self):
"""Test complete explosion of a simple markdown file."""
# This should fail initially (RED phase)
markdown_content = """# Part 1: Introduction
This is the introduction to our document.
## Chapter 1: Getting Started
Here's how to get started.
### Section 1.1: Installation
Installation instructions.
## Chapter 2: Advanced Usage
Advanced topics.
"""
# Create input file
input_file = self.temp_dir / "input.md"
input_file.write_text(markdown_content)
# Create output directory
output_dir = self.temp_dir / "exploded"
# Explode the file
result = explode_markdown_file(input_file, output_dir)
# Verify structure was created
assert output_dir.exists()
assert len(list(output_dir.rglob("*.md"))) > 0
# Verify content distribution
md_files = list(output_dir.rglob("*.md"))
all_content = ""
for md_file in md_files:
all_content += md_file.read_text()
# Original content should be distributed across files
assert "This is the introduction" in all_content
assert "Here's how to get started" in all_content
assert "Installation instructions" in all_content
def test_explode_file_with_front_matter(self):
"""Test explosion of file with YAML front matter."""
# This should fail initially (RED phase)
markdown_content = """---
title: "My Document"
author: "Test Author"
---
# Chapter 1
Content here.
"""
input_file = self.temp_dir / "input.md"
input_file.write_text(markdown_content)
output_dir = self.temp_dir / "exploded"
result = explode_markdown_file(input_file, output_dir)
# Front matter should be handled appropriately
# (preserved in root, copied to sections, or handled per implementation)
assert result is not None
def test_explode_file_error_handling(self):
"""Test error handling for invalid inputs."""
# This should fail initially (RED phase)
# Non-existent input file
with pytest.raises(FileNotFoundError):
explode_markdown_file(Path("nonexistent.md"), self.temp_dir)
# Invalid output directory
with pytest.raises((PermissionError, OSError)):
explode_markdown_file(Path("test.md"), Path("/invalid/path"))