Files
markitect-main/tests/test_frontmatter_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

428 lines
15 KiB
Python

"""
TDD8 Cycle 2: Frontmatter Commands Tests (RED Phase)
Issue #38 - MarkdownMatters CLI Implementation
This test file implements the RED phase tests for frontmatter command family:
- markitect frontmatter-get [key] [path] - Get specific frontmatter value
- markitect frontmatter-set key=value [path] - Set frontmatter value
- markitect frontmatter-keys [path] - List all frontmatter keys
- markitect frontmatter-stats [path] - Frontmatter 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_frontmatter.parser import FrontmatterParser
from markitect.matter_frontmatter.stats import FrontmatterStats
from markitect.matter_frontmatter.commands import frontmatter_get, frontmatter_set, frontmatter_keys, frontmatter_stats
class TestFrontmatterExtraction:
"""Test frontmatter extraction and parsing."""
@pytest.fixture
def test_files_dir(self):
"""Path to frontmatter test fixture files."""
return Path(__file__).parent / "fixtures" / "frontmatter_test_files"
@pytest.fixture
def frontmatter_parser(self):
"""Frontmatter parser instance."""
return FrontmatterParser()
def test_frontmatter_parser_extracts_yaml_frontmatter(self, frontmatter_parser, test_files_dir):
"""Test that parser extracts YAML frontmatter correctly."""
file_path = test_files_dir / "yaml_frontmatter.md"
with open(file_path, 'r') as f:
text = f.read()
frontmatter = frontmatter_parser.extract_frontmatter(text)
# Should extract all YAML frontmatter fields
assert frontmatter["title"] == "YAML Frontmatter Test Document"
assert frontmatter["author"] == "Test Author"
assert str(frontmatter["date"]) == "2025-10-02"
assert frontmatter["tags"] == ["yaml", "frontmatter", "testing"]
assert frontmatter["version"] == 1.2
assert frontmatter["published"] is True
# Should handle nested objects
assert frontmatter["nested"]["category"] == "documentation"
assert frontmatter["nested"]["priority"] == "high"
assert frontmatter["nested"]["metadata"]["creation_date"] == "2025-10-02"
def test_frontmatter_parser_extracts_json_frontmatter(self, frontmatter_parser, test_files_dir):
"""Test that parser extracts JSON frontmatter correctly."""
file_path = test_files_dir / "json_frontmatter.md"
with open(file_path, 'r') as f:
text = f.read()
frontmatter = frontmatter_parser.extract_frontmatter(text)
# Should extract all JSON frontmatter fields
assert frontmatter["title"] == "JSON Frontmatter Test Document"
assert frontmatter["author"] == "Test Author"
assert frontmatter["tags"] == ["json", "frontmatter", "testing"]
assert frontmatter["version"] == 2.1
assert frontmatter["published"] is False
# Should handle nested objects
assert frontmatter["config"]["theme"] == "dark"
assert frontmatter["config"]["language"] == "en"
assert frontmatter["config"]["features"] == ["toc", "search", "navigation"]
def test_frontmatter_parser_handles_no_frontmatter(self, frontmatter_parser, test_files_dir):
"""Test that parser handles documents without frontmatter."""
file_path = test_files_dir / "no_frontmatter.md"
with open(file_path, 'r') as f:
text = f.read()
frontmatter = frontmatter_parser.extract_frontmatter(text)
# Should return empty dict for no frontmatter
assert frontmatter == {}
def test_frontmatter_parser_handles_empty_frontmatter(self, frontmatter_parser, test_files_dir):
"""Test that parser handles empty frontmatter blocks."""
file_path = test_files_dir / "empty_frontmatter.md"
with open(file_path, 'r') as f:
text = f.read()
frontmatter = frontmatter_parser.extract_frontmatter(text)
# Should return empty dict for empty frontmatter
assert frontmatter == {}
def test_frontmatter_parser_get_nested_value(self, frontmatter_parser, test_files_dir):
"""Test getting nested values using dot notation."""
file_path = test_files_dir / "yaml_frontmatter.md"
with open(file_path, 'r') as f:
text = f.read()
frontmatter = frontmatter_parser.extract_frontmatter(text)
# Should support dot notation for nested access
value = frontmatter_parser.get_nested_value(frontmatter, "nested.category")
assert value == "documentation"
value = frontmatter_parser.get_nested_value(frontmatter, "nested.metadata.creation_date")
assert value == "2025-10-02"
# Should return None for non-existent keys
value = frontmatter_parser.get_nested_value(frontmatter, "non.existent.key")
assert value is None
class TestFrontmatterModification:
"""Test frontmatter modification operations."""
@pytest.fixture
def frontmatter_parser(self):
"""Frontmatter parser instance."""
return FrontmatterParser()
def test_frontmatter_set_simple_value(self, frontmatter_parser):
"""Test setting simple frontmatter values."""
text = """---
title: "Original Title"
author: "Original Author"
---
# Content here"""
new_text = frontmatter_parser.set_frontmatter_value(text, "title", "New Title")
# Should update the title value
frontmatter = frontmatter_parser.extract_frontmatter(new_text)
assert frontmatter["title"] == "New Title"
assert frontmatter["author"] == "Original Author"
def test_frontmatter_set_new_value(self, frontmatter_parser):
"""Test adding new frontmatter values."""
text = """---
title: "Original Title"
---
# Content here"""
new_text = frontmatter_parser.set_frontmatter_value(text, "author", "New Author")
# Should add the new field
frontmatter = frontmatter_parser.extract_frontmatter(new_text)
assert frontmatter["title"] == "Original Title"
assert frontmatter["author"] == "New Author"
def test_frontmatter_set_nested_value(self, frontmatter_parser):
"""Test setting nested frontmatter values using dot notation."""
text = """---
title: "Test"
config:
theme: "light"
---
# Content here"""
new_text = frontmatter_parser.set_frontmatter_value(text, "config.theme", "dark")
# Should update nested value
frontmatter = frontmatter_parser.extract_frontmatter(new_text)
assert frontmatter["config"]["theme"] == "dark"
def test_frontmatter_add_to_empty_document(self, frontmatter_parser):
"""Test adding frontmatter to document without any."""
text = """# Content Without Frontmatter
Just some content here."""
new_text = frontmatter_parser.set_frontmatter_value(text, "title", "New Title")
# Should add frontmatter block
frontmatter = frontmatter_parser.extract_frontmatter(new_text)
assert frontmatter["title"] == "New Title"
# Should preserve content
assert "# Content Without Frontmatter" in new_text
class TestFrontmatterKeys:
"""Test frontmatter key listing functionality."""
@pytest.fixture
def test_files_dir(self):
"""Path to frontmatter test fixture files."""
return Path(__file__).parent / "fixtures" / "frontmatter_test_files"
@pytest.fixture
def frontmatter_parser(self):
"""Frontmatter parser instance."""
return FrontmatterParser()
def test_frontmatter_keys_yaml_document(self, frontmatter_parser, test_files_dir):
"""Test listing keys from YAML frontmatter."""
file_path = test_files_dir / "yaml_frontmatter.md"
with open(file_path, 'r') as f:
text = f.read()
keys = frontmatter_parser.get_frontmatter_keys(text)
# Should return all top-level keys
expected_keys = ["title", "author", "date", "tags", "version", "published", "description", "nested"]
assert set(keys) == set(expected_keys)
def test_frontmatter_keys_with_nested_option(self, frontmatter_parser, test_files_dir):
"""Test listing keys including nested keys with dot notation."""
file_path = test_files_dir / "yaml_frontmatter.md"
with open(file_path, 'r') as f:
text = f.read()
keys = frontmatter_parser.get_frontmatter_keys(text, include_nested=True)
# Should include nested keys with dot notation
assert "nested.category" in keys
assert "nested.priority" in keys
assert "nested.metadata.creation_date" in keys
assert "nested.metadata.last_modified" in keys
def test_frontmatter_keys_empty_document(self, frontmatter_parser, test_files_dir):
"""Test listing keys from document without frontmatter."""
file_path = test_files_dir / "no_frontmatter.md"
with open(file_path, 'r') as f:
text = f.read()
keys = frontmatter_parser.get_frontmatter_keys(text)
# Should return empty list
assert keys == []
class TestFrontmatterStatistics:
"""Test frontmatter statistics calculation."""
@pytest.fixture
def test_files_dir(self):
"""Path to frontmatter test fixture files."""
return Path(__file__).parent / "fixtures" / "frontmatter_test_files"
@pytest.fixture
def frontmatter_parser(self):
"""Frontmatter parser instance."""
return FrontmatterParser()
def test_frontmatter_stats_yaml_document(self, frontmatter_parser, test_files_dir):
"""Test statistics calculation for YAML frontmatter."""
file_path = test_files_dir / "yaml_frontmatter.md"
with open(file_path, 'r') as f:
text = f.read()
stats = frontmatter_parser.calculate_frontmatter_stats(text)
# Should count fields correctly
assert stats.total_fields == 8 # Top-level fields
assert stats.nested_fields == 5 # Nested fields (category, priority, creation_date, last_modified, metadata object)
assert stats.format == "yaml"
assert stats.has_frontmatter is True
# Should categorize field types
assert "string" in stats.field_types
assert "array" in stats.field_types
assert "number" in stats.field_types
assert "boolean" in stats.field_types
assert "object" in stats.field_types
def test_frontmatter_stats_json_document(self, frontmatter_parser, test_files_dir):
"""Test statistics calculation for JSON frontmatter."""
file_path = test_files_dir / "json_frontmatter.md"
with open(file_path, 'r') as f:
text = f.read()
stats = frontmatter_parser.calculate_frontmatter_stats(text)
# Should identify JSON format
assert stats.format == "json"
assert stats.has_frontmatter is True
assert stats.total_fields > 0
def test_frontmatter_stats_no_frontmatter(self, frontmatter_parser, test_files_dir):
"""Test statistics for document without frontmatter."""
file_path = test_files_dir / "no_frontmatter.md"
with open(file_path, 'r') as f:
text = f.read()
stats = frontmatter_parser.calculate_frontmatter_stats(text)
# Should indicate no frontmatter
assert stats.has_frontmatter is False
assert stats.total_fields == 0
assert stats.nested_fields == 0
assert stats.format is None
class TestFrontmatterCLICommands:
"""Test CLI command integration."""
@pytest.fixture
def runner(self):
"""CLI test runner."""
return CliRunner()
@pytest.fixture
def test_files_dir(self):
"""Path to frontmatter test fixture files."""
return Path(__file__).parent / "fixtures" / "frontmatter_test_files"
def test_frontmatter_get_command(self, runner, test_files_dir):
"""Test frontmatter-get CLI command."""
file_path = test_files_dir / "yaml_frontmatter.md"
# Test getting simple value
result = runner.invoke(frontmatter_get, ['title', '--file', str(file_path)])
assert result.exit_code == 0
assert "YAML Frontmatter Test Document" in result.output
# Test getting nested value
result = runner.invoke(frontmatter_get, ['nested.category', '--file', str(file_path)])
assert result.exit_code == 0
assert "documentation" in result.output
def test_frontmatter_keys_command(self, runner, test_files_dir):
"""Test frontmatter-keys CLI command."""
file_path = test_files_dir / "yaml_frontmatter.md"
result = runner.invoke(frontmatter_keys, ['--file', str(file_path)])
assert result.exit_code == 0
assert "title" in result.output
assert "author" in result.output
assert "tags" in result.output
def test_frontmatter_stats_command(self, runner, test_files_dir):
"""Test frontmatter-stats CLI command."""
file_path = test_files_dir / "yaml_frontmatter.md"
result = runner.invoke(frontmatter_stats, ['--file', str(file_path)])
assert result.exit_code == 0
assert "total_fields" in result.output
assert "format" in result.output
def test_frontmatter_set_command(self, runner, test_files_dir):
"""Test frontmatter-set CLI command."""
# Create temporary file for testing
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
f.write("""---
title: "Original Title"
---
# Test Content""")
temp_file = f.name
try:
result = runner.invoke(frontmatter_set, ['title=New Title', '--file', temp_file])
assert result.exit_code == 0
# Verify the change was made
with open(temp_file, 'r') as f:
content = f.read()
assert "New Title" in content
finally:
os.unlink(temp_file)
def test_frontmatter_commands_help_text(self, runner):
"""Test that help text is available for all frontmatter commands."""
commands = [frontmatter_get, frontmatter_keys, frontmatter_stats, frontmatter_set]
for command in commands:
result = runner.invoke(command, ['--help'])
assert result.exit_code == 0
assert "frontmatter" in result.output.lower()
class TestFrontmatterStats:
"""Test FrontmatterStats data class."""
def test_frontmatter_stats_creation(self):
"""Test FrontmatterStats object creation."""
stats = FrontmatterStats(
has_frontmatter=True,
total_fields=5,
nested_fields=2,
format="yaml",
field_types={"string": 3, "number": 1, "boolean": 1}
)
assert stats.has_frontmatter is True
assert stats.total_fields == 5
assert stats.nested_fields == 2
assert stats.format == "yaml"
assert stats.field_types["string"] == 3
def test_frontmatter_stats_to_dict(self):
"""Test FrontmatterStats conversion to dictionary."""
stats = FrontmatterStats(
has_frontmatter=True,
total_fields=5,
nested_fields=2,
format="yaml",
field_types={"string": 3}
)
stats_dict = stats.to_dict()
assert stats_dict["has_frontmatter"] is True
assert stats_dict["total_fields"] == 5
assert stats_dict["format"] == "yaml"