Files
markitect-main/tests/test_contentmatter_commands.py
tegwick 494e1b7128 feat: Complete Issue #38 - Full MarkdownMatters CLI implementation with TDD8 methodology
Implemented comprehensive MarkdownMatters CLI following complete TDD8 seven-cycle methodology with full three-zone separation and extensive testing validation.

## Complete Implementation Summary

### TDD8 Cycles Completed (7/7)
-  Cycle 1: Content command family
-  Cycle 2: Frontmatter command family
-  Cycle 3: Contentmatter command family
-  Cycle 4: Tailmatter foundation
-  Cycle 5: Tailmatter advanced features (QA, editorial, agent config)
-  Cycle 6: Integration and performance optimization
-  Cycle 7: Documentation and comprehensive testing

### Command Families Implemented (4/4)

#### Content Commands
- `content-get` - Extract main content without matter zones
- `content-stats` - Content statistics (words, lines, paragraphs, characters)

#### Frontmatter Commands
- `frontmatter-get [key]` - Get YAML/JSON frontmatter values (dot notation support)
- `frontmatter-set key=value` - Set frontmatter values with type detection
- `frontmatter-keys` - List all frontmatter keys (nested support)
- `frontmatter-stats` - Frontmatter analysis and statistics

#### Contentmatter Commands
- `contentmatter-get [key]` - Get MultiMarkdown key-value pairs from content
- `contentmatter-set key=value` - Set MMD key-value pairs within content
- `contentmatter-keys` - List all contentmatter keys
- `contentmatter-stats` - Contentmatter analysis (URLs, emails, dates)

#### Tailmatter Commands
- `tailmatter-get [key]` - Get tailmatter values (dot notation for nested)
- `tailmatter-set key=value` - Set tailmatter values in YAML/JSON blocks
- `tailmatter-keys` - List all tailmatter keys
- `tailmatter-stats` - Tailmatter analysis with QA/editorial status
- `tailmatter-check` - QA checklist validation with progress tracking

### MarkdownMatters Specification Compliance
- **Three-zone separation**: Frontmatter (Publisher), Contentmatter (Author), Tailmatter (Editor/QA)
- **Format support**: YAML/JSON frontmatter, MMD key-value contentmatter, YAML/JSON tailmatter
- **Reserved namespaces**: qa_checklist, editorial, agent_config in tailmatter
- **Proper delimitation**: `---` frontmatter, inline contentmatter, `yaml tailmatter`/`json tailmatter` blocks

### Technical Architecture

#### Module Structure
```
markitect/
├── content/              # Content extraction (Cycle 1)
├── matter_frontmatter/   # YAML/JSON frontmatter (Cycle 2)
├── matter_contentmatter/ # MultiMarkdown key-value (Cycle 3)
└── matter_tailmatter/    # QA, editorial, agent config (Cycles 4-5)
```

#### Advanced Features
- **Dot notation**: Nested access (`nested.key.subkey`)
- **Smart typing**: Automatic boolean/number/array detection
- **Performance**: Large document processing <2 seconds
- **Error handling**: Comprehensive validation and recovery
- **Output formats**: Raw, JSON, text with consistent interfaces
- **Backup support**: Safe file modification with backup options

### Testing Results (65/65 tests passing)
- **Content commands**: 16 tests - Parser, statistics, CLI integration
- **Frontmatter commands**: 22 tests - YAML/JSON parsing, nested access, modification
- **Contentmatter commands**: 21 tests - MMD extraction, statistics, content analysis
- **Integration tests**: 6 tests - Cross-command validation, performance, error handling

### Validation Achievements
-  **100% test success rate** (65/65 tests passing)
-  **Perfect zone separation** - Each command family accesses only its designated zone
-  **MarkdownMatters compliance** - Full specification adherence
-  **Performance validated** - Large documents process efficiently
-  **Integration verified** - All command families work together seamlessly
-  **CLI consistency** - Uniform command patterns and error handling

### Usage Examples
```bash
# Extract pure content without matter zones
markitect content-get --file document.md

# Access frontmatter with nested keys
markitect frontmatter-get config.theme --file document.md

# Work with inline MultiMarkdown key-values
markitect contentmatter-get Author --file document.md

# Validate QA checklist in tailmatter
markitect tailmatter-check --file document.md

# Get comprehensive statistics
markitect content-stats --file document.md
markitect frontmatter-stats --file document.md
markitect contentmatter-stats --file document.md
markitect tailmatter-stats --file document.md
```

This implementation provides complete MarkdownMatters CLI functionality with systematic TDD8 development, comprehensive testing, and full specification compliance for professional document metadata management.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-02 09:14:24 +02:00

429 lines
16 KiB
Python

"""
TDD8 Cycle 3: Contentmatter Commands Tests (RED Phase)
Issue #38 - MarkdownMatters CLI Implementation
This test file implements the RED phase tests for contentmatter command family:
- markitect contentmatter-get [key] [path] - Get MMD key-value from content
- markitect contentmatter-set key=value [path] - Set MMD key-value in content
- markitect contentmatter-keys [path] - List all contentmatter keys
- markitect contentmatter-stats [path] - Contentmatter statistics
Following TDD8 methodology, these tests MUST FAIL initially.
"""
import pytest
import tempfile
import os
from pathlib import Path
from click.testing import CliRunner
from markitect.matter_contentmatter.parser import ContentmatterParser
from markitect.matter_contentmatter.stats import ContentmatterStats
from markitect.matter_contentmatter.commands import contentmatter_get, contentmatter_set, contentmatter_keys, contentmatter_stats
class TestContentmatterExtraction:
"""Test contentmatter extraction and parsing."""
@pytest.fixture
def test_files_dir(self):
"""Path to contentmatter test fixture files."""
return Path(__file__).parent / "fixtures" / "contentmatter_test_files"
@pytest.fixture
def contentmatter_parser(self):
"""Contentmatter parser instance."""
return ContentmatterParser()
def test_contentmatter_parser_extracts_mmd_keyvalues(self, contentmatter_parser, test_files_dir):
"""Test that parser extracts MultiMarkdown key-value pairs."""
file_path = test_files_dir / "simple_contentmatter.md"
with open(file_path, 'r') as f:
text = f.read()
contentmatter = contentmatter_parser.extract_contentmatter(text)
# Should extract basic key-value pairs
assert contentmatter["Author"] == "Jane Smith"
assert contentmatter["Project"] == "Contentmatter Testing"
assert contentmatter["Version"] == "2.0"
assert contentmatter["Status"] == "Active"
assert contentmatter["License"] == "MIT"
assert contentmatter["Repository"] == "https://github.com/example/project"
assert contentmatter["Documentation"] == "https://docs.example.com"
assert contentmatter["Updated"] == "2025-10-02"
assert contentmatter["Reviewer"] == "John Doe"
assert contentmatter["Category"] == "Testing"
def test_contentmatter_parser_extracts_complex_content(self, contentmatter_parser, test_files_dir):
"""Test extraction from document with rich contentmatter."""
file_path = test_files_dir / "mmd_rich_content.md"
with open(file_path, 'r') as f:
text = f.read()
contentmatter = contentmatter_parser.extract_contentmatter(text)
# Should extract author information
assert contentmatter["Author"] == "Dr. Sarah Johnson"
assert contentmatter["Institution"] == "MIT Computer Science Department"
assert contentmatter["Email"] == "sarah.johnson@mit.edu"
# Should extract research metadata
assert contentmatter["Keywords"] == "algorithms, distributed systems, optimization, performance"
assert contentmatter["Classification"] == "Computer Science - Distributed Computing"
assert contentmatter["Grant Number"] == "NSF-CS-2025-001"
# Should extract methodology details
assert contentmatter["Research Method"] == "Experimental Analysis"
assert contentmatter["Sample Size"] == "1000 distributed nodes"
assert contentmatter["Performance Improvement"] == "23% average speedup"
def test_contentmatter_parser_handles_no_contentmatter(self, contentmatter_parser, test_files_dir):
"""Test that parser handles documents without contentmatter."""
file_path = test_files_dir / "no_contentmatter.md"
with open(file_path, 'r') as f:
text = f.read()
contentmatter = contentmatter_parser.extract_contentmatter(text)
# Should return empty dict for no contentmatter
assert contentmatter == {}
def test_contentmatter_parser_ignores_frontmatter_and_tailmatter(self, contentmatter_parser, test_files_dir):
"""Test that parser only extracts from content, not matter zones."""
file_path = test_files_dir / "mmd_rich_content.md"
with open(file_path, 'r') as f:
text = f.read()
contentmatter = contentmatter_parser.extract_contentmatter(text)
# Should not include frontmatter values
assert "title" not in contentmatter # This is in frontmatter
assert "qa_checklist" not in contentmatter # This is in tailmatter
# Should only include content key-values
assert "Author" in contentmatter # This is in content
def test_contentmatter_get_specific_value(self, contentmatter_parser, test_files_dir):
"""Test getting specific contentmatter values."""
file_path = test_files_dir / "simple_contentmatter.md"
with open(file_path, 'r') as f:
text = f.read()
value = contentmatter_parser.get_contentmatter_value(text, "Author")
assert value == "Jane Smith"
value = contentmatter_parser.get_contentmatter_value(text, "Repository")
assert value == "https://github.com/example/project"
# Should return None for non-existent keys
value = contentmatter_parser.get_contentmatter_value(text, "NonExistent")
assert value is None
class TestContentmatterModification:
"""Test contentmatter modification operations."""
@pytest.fixture
def contentmatter_parser(self):
"""Contentmatter parser instance."""
return ContentmatterParser()
def test_contentmatter_set_new_value(self, contentmatter_parser):
"""Test adding new contentmatter to document."""
text = """# Test Document
Some content here.
## Section
More content."""
new_text = contentmatter_parser.set_contentmatter_value(text, "Author", "New Author")
# Should add the contentmatter
contentmatter = contentmatter_parser.extract_contentmatter(new_text)
assert contentmatter["Author"] == "New Author"
# Should preserve original content
assert "# Test Document" in new_text
assert "Some content here." in new_text
def test_contentmatter_update_existing_value(self, contentmatter_parser):
"""Test updating existing contentmatter value."""
text = """# Test Document
Author: Original Author
Project: Test Project
Some content here."""
new_text = contentmatter_parser.set_contentmatter_value(text, "Author", "Updated Author")
# Should update the existing value
contentmatter = contentmatter_parser.extract_contentmatter(new_text)
assert contentmatter["Author"] == "Updated Author"
assert contentmatter["Project"] == "Test Project" # Should preserve other values
def test_contentmatter_set_multiple_values(self, contentmatter_parser):
"""Test setting multiple contentmatter values."""
text = """# Test Document
Some content here."""
# Add multiple values
text = contentmatter_parser.set_contentmatter_value(text, "Author", "Test Author")
text = contentmatter_parser.set_contentmatter_value(text, "Version", "1.0")
text = contentmatter_parser.set_contentmatter_value(text, "Status", "Active")
contentmatter = contentmatter_parser.extract_contentmatter(text)
assert contentmatter["Author"] == "Test Author"
assert contentmatter["Version"] == "1.0"
assert contentmatter["Status"] == "Active"
class TestContentmatterKeys:
"""Test contentmatter key listing functionality."""
@pytest.fixture
def test_files_dir(self):
"""Path to contentmatter test fixture files."""
return Path(__file__).parent / "fixtures" / "contentmatter_test_files"
@pytest.fixture
def contentmatter_parser(self):
"""Contentmatter parser instance."""
return ContentmatterParser()
def test_contentmatter_keys_simple_document(self, contentmatter_parser, test_files_dir):
"""Test listing keys from simple contentmatter document."""
file_path = test_files_dir / "simple_contentmatter.md"
with open(file_path, 'r') as f:
text = f.read()
keys = contentmatter_parser.get_contentmatter_keys(text)
# Should return all contentmatter keys
expected_keys = ["Author", "Project", "Version", "Status", "License", "Repository", "Documentation", "Updated", "Reviewer", "Category"]
assert set(keys) == set(expected_keys)
def test_contentmatter_keys_complex_document(self, contentmatter_parser, test_files_dir):
"""Test listing keys from complex contentmatter document."""
file_path = test_files_dir / "mmd_rich_content.md"
with open(file_path, 'r') as f:
text = f.read()
keys = contentmatter_parser.get_contentmatter_keys(text)
# Should include research paper metadata keys
assert "Author" in keys
assert "Institution" in keys
assert "Keywords" in keys
assert "Research Method" in keys
assert "Performance Improvement" in keys
# Should not include frontmatter or tailmatter keys
assert "title" not in keys # frontmatter
assert "qa_checklist" not in keys # tailmatter
def test_contentmatter_keys_empty_document(self, contentmatter_parser, test_files_dir):
"""Test listing keys from document without contentmatter."""
file_path = test_files_dir / "no_contentmatter.md"
with open(file_path, 'r') as f:
text = f.read()
keys = contentmatter_parser.get_contentmatter_keys(text)
# Should return empty list
assert keys == []
class TestContentmatterStatistics:
"""Test contentmatter statistics calculation."""
@pytest.fixture
def test_files_dir(self):
"""Path to contentmatter test fixture files."""
return Path(__file__).parent / "fixtures" / "contentmatter_test_files"
@pytest.fixture
def contentmatter_parser(self):
"""Contentmatter parser instance."""
return ContentmatterParser()
def test_contentmatter_stats_simple_document(self, contentmatter_parser, test_files_dir):
"""Test statistics calculation for simple contentmatter."""
file_path = test_files_dir / "simple_contentmatter.md"
with open(file_path, 'r') as f:
text = f.read()
stats = contentmatter_parser.calculate_contentmatter_stats(text)
# Should count contentmatter correctly
assert stats.total_pairs == 10 # Number of key-value pairs
assert stats.has_contentmatter is True
assert stats.average_key_length > 0
assert stats.average_value_length > 0
# Should categorize value types
assert stats.url_values > 0 # Repository and Documentation URLs
assert stats.date_values > 0 # Updated field
assert stats.email_values == 0 # No email in simple document
def test_contentmatter_stats_complex_document(self, contentmatter_parser, test_files_dir):
"""Test statistics calculation for complex contentmatter."""
file_path = test_files_dir / "mmd_rich_content.md"
with open(file_path, 'r') as f:
text = f.read()
stats = contentmatter_parser.calculate_contentmatter_stats(text)
# Should count rich contentmatter
assert stats.total_pairs > 15 # Many key-value pairs in research paper
assert stats.has_contentmatter is True
# Should detect email values
assert stats.email_values > 0 # Email field in author info
def test_contentmatter_stats_no_contentmatter(self, contentmatter_parser, test_files_dir):
"""Test statistics for document without contentmatter."""
file_path = test_files_dir / "no_contentmatter.md"
with open(file_path, 'r') as f:
text = f.read()
stats = contentmatter_parser.calculate_contentmatter_stats(text)
# Should indicate no contentmatter
assert stats.has_contentmatter is False
assert stats.total_pairs == 0
assert stats.url_values == 0
assert stats.email_values == 0
class TestContentmatterCLICommands:
"""Test CLI command integration."""
@pytest.fixture
def runner(self):
"""CLI test runner."""
return CliRunner()
@pytest.fixture
def test_files_dir(self):
"""Path to contentmatter test fixture files."""
return Path(__file__).parent / "fixtures" / "contentmatter_test_files"
def test_contentmatter_get_command(self, runner, test_files_dir):
"""Test contentmatter-get CLI command."""
file_path = test_files_dir / "simple_contentmatter.md"
# Test getting simple value
result = runner.invoke(contentmatter_get, ['Author', '--file', str(file_path)])
assert result.exit_code == 0
assert "Jane Smith" in result.output
# Test getting URL value
result = runner.invoke(contentmatter_get, ['Repository', '--file', str(file_path)])
assert result.exit_code == 0
assert "https://github.com/example/project" in result.output
def test_contentmatter_keys_command(self, runner, test_files_dir):
"""Test contentmatter-keys CLI command."""
file_path = test_files_dir / "simple_contentmatter.md"
result = runner.invoke(contentmatter_keys, ['--file', str(file_path)])
assert result.exit_code == 0
assert "Author" in result.output
assert "Project" in result.output
assert "Repository" in result.output
def test_contentmatter_stats_command(self, runner, test_files_dir):
"""Test contentmatter-stats CLI command."""
file_path = test_files_dir / "simple_contentmatter.md"
result = runner.invoke(contentmatter_stats, ['--file', str(file_path)])
assert result.exit_code == 0
assert "total_pairs" in result.output
assert "has_contentmatter" in result.output
def test_contentmatter_set_command(self, runner, test_files_dir):
"""Test contentmatter-set CLI command."""
# Create temporary file for testing
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
f.write("""# Test Document
Author: Original Author
Some content here.""")
temp_file = f.name
try:
result = runner.invoke(contentmatter_set, ['Author=New Author', '--file', temp_file])
assert result.exit_code == 0
# Verify the change was made
with open(temp_file, 'r') as f:
content = f.read()
assert "Author: New Author" in content
finally:
os.unlink(temp_file)
def test_contentmatter_commands_help_text(self, runner):
"""Test that help text is available for all contentmatter commands."""
commands = [contentmatter_get, contentmatter_keys, contentmatter_stats, contentmatter_set]
for command in commands:
result = runner.invoke(command, ['--help'])
assert result.exit_code == 0
assert "contentmatter" in result.output.lower()
class TestContentmatterStats:
"""Test ContentmatterStats data class."""
def test_contentmatter_stats_creation(self):
"""Test ContentmatterStats object creation."""
stats = ContentmatterStats(
has_contentmatter=True,
total_pairs=10,
average_key_length=8.5,
average_value_length=15.2,
url_values=2,
email_values=1,
date_values=1
)
assert stats.has_contentmatter is True
assert stats.total_pairs == 10
assert stats.average_key_length == 8.5
assert stats.url_values == 2
def test_contentmatter_stats_to_dict(self):
"""Test ContentmatterStats conversion to dictionary."""
stats = ContentmatterStats(
has_contentmatter=True,
total_pairs=5,
average_key_length=8.0,
average_value_length=12.0,
url_values=1,
email_values=0,
date_values=1
)
stats_dict = stats.to_dict()
assert stats_dict["has_contentmatter"] is True
assert stats_dict["total_pairs"] == 5
assert stats_dict["url_values"] == 1