333 lines
11 KiB
Python
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"))
|