Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
Asset Management System (Issue #142): - Add complete asset management framework with deduplication - Implement AssetManager, AssetRegistry, and AssetDeduplicator classes - Add AssetPackager for markdown document packaging - Create comprehensive test suite for all asset management components - Add asset constants and custom exceptions for robust error handling Markdown Processing Enhancements: - Update markdown_commands.py with improved functionality - Enhanced parsing and content aggregation capabilities - Improved filename encoding/decoding for special characters Test Suite Improvements: - Add comprehensive tests for Issue #138 markdown parsing - Enhance Issue #139 content aggregation and end-to-end testing - Complete test coverage for new asset management features Examples and Documentation: - Update BildungsKanonJon.md example with enhanced content - Generate corresponding HTML output for documentation - Add asset registry configuration Development Tools: - Add install script for simplified setup This commit represents a major enhancement to MarkiTect's asset handling capabilities with full test coverage and improved markdown processing. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
624 lines
20 KiB
Python
624 lines
20 KiB
Python
"""
|
|
Test end-to-end scenarios for Issue #139: Implode directory to a markdown file.
|
|
|
|
This test module covers comprehensive end-to-end testing including round-trip
|
|
testing with md-explode, processing of complex structures, and validation scenarios.
|
|
"""
|
|
|
|
import pytest
|
|
import tempfile
|
|
import shutil
|
|
import subprocess
|
|
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 (
|
|
explode_markdown_file,
|
|
implode_directory,
|
|
cli_implode_directory
|
|
)
|
|
from markitect.cli import cli
|
|
except ImportError:
|
|
# Expected during RED phase - tests should fail initially
|
|
explode_markdown_file = None
|
|
implode_directory = None
|
|
cli_implode_directory = None
|
|
cli = None
|
|
|
|
# Note: cli_explode_markdown doesn't exist, we use explode_markdown_file directly
|
|
def cli_explode_markdown(input_file, output_dir):
|
|
"""Wrapper for explode_markdown_file for testing."""
|
|
class MockResult:
|
|
def __init__(self, success, output_dir=None):
|
|
self.success = success
|
|
self.output_dir = output_dir
|
|
try:
|
|
result_dir = explode_markdown_file(input_file, output_dir)
|
|
return MockResult(True, result_dir)
|
|
except Exception:
|
|
return MockResult(False)
|
|
|
|
|
|
class TestEndToEndRoundTripTesting:
|
|
"""Test complete round-trip scenarios: original → explode → implode → compare."""
|
|
|
|
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_simple_document_round_trip(self):
|
|
"""Test round-trip with simple hierarchical document."""
|
|
# This should fail initially (RED phase)
|
|
|
|
original_content = """# Introduction
|
|
Welcome to the document.
|
|
|
|
## Chapter 1: Getting Started
|
|
This is the first chapter.
|
|
|
|
### Section 1.1: Installation
|
|
Install instructions here.
|
|
|
|
### Section 1.2: Configuration
|
|
Configuration details.
|
|
|
|
## Chapter 2: Advanced Topics
|
|
Advanced material here.
|
|
|
|
# Conclusion
|
|
Final thoughts.
|
|
"""
|
|
|
|
# Create original file
|
|
original_file = self.temp_dir / "original.md"
|
|
original_file.write_text(original_content)
|
|
|
|
# Step 1: Explode the document
|
|
exploded_dir = self.temp_dir / "exploded"
|
|
explode_result = cli_explode_markdown(
|
|
input_file=original_file,
|
|
output_dir=exploded_dir
|
|
)
|
|
assert explode_result.success == True
|
|
|
|
# Step 2: Implode back to markdown
|
|
imploded_file = self.temp_dir / "imploded.md"
|
|
implode_result = cli_implode_directory(
|
|
input_dir=exploded_dir,
|
|
output_file=imploded_file
|
|
)
|
|
assert implode_result.success == True
|
|
|
|
# Step 3: Compare results
|
|
imploded_content = imploded_file.read_text()
|
|
|
|
# Should preserve all major structural elements
|
|
assert "# Introduction" in imploded_content
|
|
assert "## Chapter 1: Getting Started" in imploded_content
|
|
assert "### Section 1.1: Installation" in imploded_content
|
|
assert "### Section 1.2: Configuration" in imploded_content
|
|
assert "## Chapter 2: Advanced Topics" in imploded_content
|
|
assert "# Conclusion" in imploded_content
|
|
|
|
# Should preserve content
|
|
assert "Welcome to the document." in imploded_content
|
|
assert "Install instructions here." in imploded_content
|
|
assert "Final thoughts." in imploded_content
|
|
|
|
def test_document_with_front_matter_round_trip(self):
|
|
"""Test round-trip preservation of YAML front matter."""
|
|
# This should fail initially (RED phase)
|
|
|
|
original_content = """---
|
|
title: "Test Document"
|
|
author: "Test Author"
|
|
date: "2023-01-01"
|
|
tags: ["documentation", "test"]
|
|
---
|
|
|
|
# Main Content
|
|
Document content here.
|
|
|
|
## Section 1
|
|
Section content.
|
|
"""
|
|
|
|
original_file = self.temp_dir / "with_frontmatter.md"
|
|
original_file.write_text(original_content)
|
|
|
|
# Explode → Implode
|
|
exploded_dir = self.temp_dir / "exploded_fm"
|
|
explode_result = cli_explode_markdown(original_file, exploded_dir)
|
|
assert explode_result.success == True
|
|
|
|
imploded_file = self.temp_dir / "imploded_fm.md"
|
|
implode_result = cli_implode_directory(exploded_dir, imploded_file)
|
|
assert implode_result.success == True
|
|
|
|
imploded_content = imploded_file.read_text()
|
|
|
|
# Should preserve front matter
|
|
assert imploded_content.startswith("---")
|
|
assert "title: \"Test Document\"" in imploded_content
|
|
assert "author: \"Test Author\"" in imploded_content
|
|
assert "tags:" in imploded_content
|
|
|
|
# Should preserve content structure
|
|
assert "# Main Content" in imploded_content
|
|
assert "## Section 1" in imploded_content
|
|
|
|
def test_complex_nested_structure_round_trip(self):
|
|
"""Test round-trip with deeply nested document structure."""
|
|
# This should fail initially (RED phase)
|
|
|
|
complex_content = """# Part 1: Fundamentals
|
|
|
|
Introduction to part 1.
|
|
|
|
## Chapter 1: Basics
|
|
|
|
Basic concepts.
|
|
|
|
### Section 1.1: Overview
|
|
Overview content.
|
|
|
|
#### Subsection 1.1.1: Details
|
|
Detailed information.
|
|
|
|
#### Subsection 1.1.2: Examples
|
|
Example content.
|
|
|
|
### Section 1.2: Implementation
|
|
Implementation details.
|
|
|
|
## Chapter 2: Intermediate
|
|
|
|
Intermediate concepts.
|
|
|
|
# Part 2: Advanced Topics
|
|
|
|
Advanced material.
|
|
|
|
## Chapter 3: Expert Level
|
|
|
|
Expert content here.
|
|
"""
|
|
|
|
original_file = self.temp_dir / "complex.md"
|
|
original_file.write_text(complex_content)
|
|
|
|
# Round-trip process
|
|
exploded_dir = self.temp_dir / "complex_exploded"
|
|
explode_result = cli_explode_markdown(original_file, exploded_dir)
|
|
assert explode_result.success == True
|
|
|
|
imploded_file = self.temp_dir / "complex_imploded.md"
|
|
implode_result = cli_implode_directory(exploded_dir, imploded_file)
|
|
assert implode_result.success == True
|
|
|
|
imploded_content = imploded_file.read_text()
|
|
|
|
# Should preserve all heading levels
|
|
assert "# Part 1: Fundamentals" in imploded_content
|
|
assert "## Chapter 1: Basics" in imploded_content
|
|
assert "### Section 1.1: Overview" in imploded_content
|
|
assert "#### Subsection 1.1.1: Details" in imploded_content
|
|
assert "#### Subsection 1.1.2: Examples" in imploded_content
|
|
assert "# Part 2: Advanced Topics" in imploded_content
|
|
|
|
def test_round_trip_preserves_markdown_formatting(self):
|
|
"""Test that round-trip preserves all markdown formatting elements."""
|
|
# This should fail initially (RED phase)
|
|
|
|
formatted_content = """# Document with Formatting
|
|
|
|
## Text Formatting
|
|
This has **bold text** and *italic text* and `inline code`.
|
|
|
|
## Code Blocks
|
|
Here's a code block:
|
|
|
|
```python
|
|
def example():
|
|
return "formatted code"
|
|
```
|
|
|
|
## Lists and Tables
|
|
- Item 1
|
|
- Item 2
|
|
- Nested item
|
|
|
|
| Header 1 | Header 2 |
|
|
|----------|----------|
|
|
| Cell 1 | Cell 2 |
|
|
|
|
## Links and Images
|
|
[Link text](http://example.com)
|
|

|
|
|
|
> This is a blockquote
|
|
|
|
---
|
|
|
|
Horizontal rule above.
|
|
"""
|
|
|
|
original_file = self.temp_dir / "formatted.md"
|
|
original_file.write_text(formatted_content)
|
|
|
|
# Round-trip
|
|
exploded_dir = self.temp_dir / "formatted_exploded"
|
|
explode_result = cli_explode_markdown(original_file, exploded_dir)
|
|
assert explode_result.success == True
|
|
|
|
imploded_file = self.temp_dir / "formatted_imploded.md"
|
|
implode_result = cli_implode_directory(exploded_dir, imploded_file)
|
|
assert implode_result.success == True
|
|
|
|
imploded_content = imploded_file.read_text()
|
|
|
|
# Should preserve all formatting
|
|
assert "**bold text**" in imploded_content
|
|
assert "*italic text*" in imploded_content
|
|
assert "`inline code`" in imploded_content
|
|
assert "```python" in imploded_content
|
|
assert "- Item 1" in imploded_content
|
|
assert "| Header 1 | Header 2 |" in imploded_content
|
|
assert "[Link text]" in imploded_content
|
|
assert "![Alt text]" in imploded_content
|
|
assert "> This is a blockquote" in imploded_content
|
|
assert "---" in imploded_content
|
|
|
|
|
|
class TestBookLikeStructureProcessing:
|
|
"""Test processing book-like structures with parts, chapters, and sections."""
|
|
|
|
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_process_book_structure_from_explode_output(self):
|
|
"""Test processing a book-like directory structure created by md-explode."""
|
|
# This should fail initially (RED phase)
|
|
|
|
# Simulate structure created by md-explode for a book
|
|
self._create_book_structure()
|
|
|
|
# Implode the structure
|
|
imploded_file = self.temp_dir / "reconstructed_book.md"
|
|
result = cli_implode_directory(
|
|
input_dir=self.temp_dir,
|
|
output_file=imploded_file
|
|
)
|
|
|
|
assert result.success == True
|
|
|
|
content = imploded_file.read_text()
|
|
|
|
# Should reconstruct proper book hierarchy
|
|
assert "# Part 1: Introduction" in content
|
|
assert "## Chapter 1: Getting Started" in content
|
|
assert "### Section 1.1: Installation" in content
|
|
assert "### Section 1.2: Setup" in content
|
|
assert "## Chapter 2: Basic Concepts" in content
|
|
assert "# Part 2: Advanced Topics" in content
|
|
assert "## Chapter 3: Expert Techniques" in content
|
|
|
|
def test_handle_book_with_mixed_content_types(self):
|
|
"""Test handling books with various content types (code, tables, images)."""
|
|
# This should fail initially (RED phase)
|
|
|
|
# Create structure with mixed content
|
|
self._create_mixed_content_book_structure()
|
|
|
|
imploded_file = self.temp_dir / "mixed_content_book.md"
|
|
result = cli_implode_directory(self.temp_dir, imploded_file)
|
|
|
|
assert result.success == True
|
|
|
|
content = imploded_file.read_text()
|
|
|
|
# Should preserve all content types
|
|
assert "```python" in content
|
|
assert "| Feature | Description |" in content
|
|
assert "" in content
|
|
assert "1. First step" in content
|
|
|
|
def _create_book_structure(self):
|
|
"""Create a realistic book directory structure."""
|
|
# Part 1
|
|
part1_dir = self.temp_dir / "part_1_introduction"
|
|
part1_dir.mkdir()
|
|
(part1_dir / "index.md").write_text("# Part 1: Introduction\nIntroduction to the book.")
|
|
|
|
# Chapter 1
|
|
ch1_dir = part1_dir / "chapter_1_getting_started"
|
|
ch1_dir.mkdir()
|
|
(ch1_dir / "index.md").write_text("## Chapter 1: Getting Started\nGetting started content.")
|
|
(ch1_dir / "section_11_installation.md").write_text("### Section 1.1: Installation\nInstallation instructions.")
|
|
(ch1_dir / "section_12_setup.md").write_text("### Section 1.2: Setup\nSetup procedures.")
|
|
|
|
# Chapter 2
|
|
ch2_dir = part1_dir / "chapter_2_basic_concepts"
|
|
ch2_dir.mkdir()
|
|
(ch2_dir / "index.md").write_text("## Chapter 2: Basic Concepts\nBasic concepts explanation.")
|
|
|
|
# Part 2
|
|
part2_dir = self.temp_dir / "part_2_advanced_topics"
|
|
part2_dir.mkdir()
|
|
(part2_dir / "index.md").write_text("# Part 2: Advanced Topics\nAdvanced topics introduction.")
|
|
(part2_dir / "chapter_3_expert_techniques.md").write_text("## Chapter 3: Expert Techniques\nExpert level content.")
|
|
|
|
def _create_mixed_content_book_structure(self):
|
|
"""Create book structure with mixed content types."""
|
|
tech_dir = self.temp_dir / "technical_guide"
|
|
tech_dir.mkdir()
|
|
(tech_dir / "index.md").write_text("# Technical Guide\nGuide introduction.")
|
|
|
|
# Code examples chapter
|
|
code_dir = tech_dir / "chapter_1_code_examples"
|
|
code_dir.mkdir()
|
|
code_content = """## Chapter 1: Code Examples
|
|
|
|
Example implementation:
|
|
|
|
```python
|
|
def process_data(data):
|
|
return data.strip().lower()
|
|
```
|
|
|
|
And configuration:
|
|
|
|
```yaml
|
|
settings:
|
|
debug: true
|
|
port: 8080
|
|
```
|
|
"""
|
|
(code_dir / "index.md").write_text(code_content)
|
|
|
|
# Tables and data chapter
|
|
data_dir = tech_dir / "chapter_2_data_reference"
|
|
data_dir.mkdir()
|
|
data_content = """## Chapter 2: Data Reference
|
|
|
|
| Feature | Description | Available |
|
|
|---------|-------------|-----------|
|
|
| API | REST API | Yes |
|
|
| CLI | Command Line| Yes |
|
|
| Web UI | Web Interface| No |
|
|
|
|
### Steps to follow:
|
|
1. First step
|
|
2. Second step
|
|
- Sub-step A
|
|
- Sub-step B
|
|
"""
|
|
(data_dir / "index.md").write_text(data_content)
|
|
|
|
# Images and media chapter
|
|
media_content = """## Chapter 3: Architecture
|
|
|
|
System overview:
|
|
|
|

|
|
|
|
> Note: The diagram shows the complete system architecture.
|
|
|
|
For more details, see [documentation](https://example.com).
|
|
"""
|
|
(tech_dir / "chapter_3_architecture.md").write_text(media_content)
|
|
|
|
|
|
class TestTechnicalDocumentationProcessing:
|
|
"""Test processing technical documentation with deep nesting."""
|
|
|
|
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_process_api_documentation_structure(self):
|
|
"""Test processing API documentation with deep hierarchical structure."""
|
|
# This should fail initially (RED phase)
|
|
|
|
self._create_api_documentation_structure()
|
|
|
|
imploded_file = self.temp_dir / "api_docs.md"
|
|
result = cli_implode_directory(self.temp_dir, imploded_file)
|
|
|
|
assert result.success == True
|
|
|
|
content = imploded_file.read_text()
|
|
|
|
# Should maintain API documentation structure
|
|
assert "# API Documentation" in content
|
|
assert "## Authentication" in content
|
|
assert "### OAuth2 Flow" in content
|
|
assert "#### Token Validation" in content
|
|
assert "## Endpoints" in content
|
|
assert "### Users API" in content
|
|
|
|
def test_handle_very_deep_nesting(self):
|
|
"""Test handling documentation with very deep nesting levels."""
|
|
# This should fail initially (RED phase)
|
|
|
|
self._create_deep_nested_structure()
|
|
|
|
imploded_file = self.temp_dir / "deep_nested.md"
|
|
result = cli_implode_directory(self.temp_dir, imploded_file)
|
|
|
|
assert result.success == True
|
|
|
|
content = imploded_file.read_text()
|
|
|
|
# Should handle deep nesting appropriately
|
|
assert "# Level 1" in content
|
|
assert "## Level 2" in content
|
|
assert "### Level 3" in content
|
|
assert "#### Level 4" in content
|
|
assert "##### Level 5" in content
|
|
|
|
def _create_api_documentation_structure(self):
|
|
"""Create realistic API documentation structure."""
|
|
api_dir = self.temp_dir / "api_documentation"
|
|
api_dir.mkdir()
|
|
(api_dir / "index.md").write_text("# API Documentation\nComplete API reference.")
|
|
|
|
# Authentication section
|
|
auth_dir = api_dir / "authentication"
|
|
auth_dir.mkdir()
|
|
(auth_dir / "index.md").write_text("## Authentication\nAuthentication overview.")
|
|
|
|
oauth_dir = auth_dir / "oauth2_flow"
|
|
oauth_dir.mkdir()
|
|
(oauth_dir / "index.md").write_text("### OAuth2 Flow\nOAuth2 implementation details.")
|
|
(oauth_dir / "token_validation.md").write_text("#### Token Validation\nHow to validate tokens.")
|
|
|
|
# Endpoints section
|
|
endpoints_dir = api_dir / "endpoints"
|
|
endpoints_dir.mkdir()
|
|
(endpoints_dir / "index.md").write_text("## Endpoints\nAPI endpoints reference.")
|
|
(endpoints_dir / "users_api.md").write_text("### Users API\nUser management endpoints.")
|
|
|
|
def _create_deep_nested_structure(self):
|
|
"""Create structure with very deep nesting."""
|
|
current_dir = self.temp_dir
|
|
content_parts = []
|
|
|
|
for level in range(1, 6):
|
|
dir_name = f"level_{level}"
|
|
current_dir = current_dir / dir_name
|
|
current_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
heading = "#" * level
|
|
content = f"{heading} Level {level}\nContent for level {level}."
|
|
(current_dir / "index.md").write_text(content)
|
|
|
|
|
|
class TestValidationAndErrorScenarios:
|
|
"""Test validation scenarios and error handling in end-to-end workflows."""
|
|
|
|
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_validate_against_md_explode_output(self):
|
|
"""Test that implode works correctly with actual md-explode output."""
|
|
# This should fail initially (RED phase)
|
|
|
|
# Create original document
|
|
original_content = """# User Guide
|
|
|
|
## Getting Started
|
|
Start here.
|
|
|
|
### Installation
|
|
Install steps.
|
|
|
|
## Advanced Usage
|
|
Advanced topics.
|
|
"""
|
|
|
|
original_file = self.temp_dir / "user_guide.md"
|
|
original_file.write_text(original_content)
|
|
|
|
# Use actual md-explode command
|
|
exploded_dir = self.temp_dir / "user_guide_exploded"
|
|
|
|
explode_result = cli_explode_markdown(original_file, exploded_dir)
|
|
assert explode_result.success == True
|
|
|
|
# Verify exploded structure exists
|
|
assert exploded_dir.exists()
|
|
assert (exploded_dir / "user_guide" / "getting_started").exists()
|
|
|
|
# Now implode it back
|
|
imploded_file = self.temp_dir / "reconstructed.md"
|
|
implode_result = cli_implode_directory(exploded_dir, imploded_file)
|
|
|
|
assert implode_result.success == True
|
|
|
|
# Validate result
|
|
reconstructed = imploded_file.read_text()
|
|
assert "# User Guide" in reconstructed
|
|
assert "## Getting Started" in reconstructed
|
|
assert "### Installation" in reconstructed
|
|
|
|
def test_handle_malformed_directory_structures(self):
|
|
"""Test handling malformed or incomplete directory structures."""
|
|
# This should fail initially (RED phase)
|
|
|
|
# Create malformed structure (missing index files, irregular naming)
|
|
malformed_dir = self.temp_dir / "malformed"
|
|
malformed_dir.mkdir()
|
|
|
|
# Regular file at root
|
|
(malformed_dir / "introduction.md").write_text("# Introduction\nIntro content")
|
|
|
|
# Directory with no index file
|
|
orphan_dir = malformed_dir / "orphan_section"
|
|
orphan_dir.mkdir()
|
|
(orphan_dir / "content.md").write_text("Content without proper heading structure")
|
|
|
|
# Directory with mixed conventions
|
|
mixed_dir = malformed_dir / "mixed_conventions"
|
|
mixed_dir.mkdir()
|
|
(mixed_dir / "index.md").write_text("## Mixed Section\nSection content")
|
|
(mixed_dir / "irregular_file_name.md").write_text("Some content")
|
|
|
|
# Should handle gracefully
|
|
imploded_file = self.temp_dir / "malformed_result.md"
|
|
result = cli_implode_directory(malformed_dir, imploded_file)
|
|
|
|
# Should either succeed with best-effort result or fail gracefully
|
|
if result.success:
|
|
content = imploded_file.read_text()
|
|
assert len(content) > 0
|
|
else:
|
|
assert result.error_message is not None
|
|
|
|
def test_handle_empty_and_edge_case_directories(self):
|
|
"""Test handling empty directories and edge cases."""
|
|
# This should fail initially (RED phase)
|
|
|
|
# Completely empty directory
|
|
empty_dir = self.temp_dir / "empty"
|
|
empty_dir.mkdir()
|
|
|
|
result = cli_implode_directory(empty_dir, self.temp_dir / "empty_result.md")
|
|
|
|
# Should handle empty directory appropriately
|
|
assert result.success == False or (result.success == True and result.warning is not None)
|
|
|
|
# Directory with only non-markdown files
|
|
non_md_dir = self.temp_dir / "non_markdown"
|
|
non_md_dir.mkdir()
|
|
(non_md_dir / "readme.txt").write_text("Not markdown")
|
|
(non_md_dir / "data.json").write_text("{}")
|
|
|
|
result = cli_implode_directory(non_md_dir, self.temp_dir / "non_md_result.md")
|
|
|
|
# Should handle appropriately
|
|
assert result.success == False or result.warning is not None |