""" 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"