429 lines
16 KiB
Python
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
|