""" Test for Issue #7: Validate a Markdown File Against a Schema. Tests the ability to validate markdown documents against JSON schemas for arc42 architecture documentation compliance checking - critical for intelligent document analysis and plan-actual comparison capabilities. """ import json import pytest from pathlib import Path from tempfile import NamedTemporaryFile from markitect.schema_validator import SchemaValidator from markitect.schema_generator import SchemaGenerator from markitect.exceptions import FileNotFoundError, SchemaValidationError, InvalidSchemaError class TestIssue7SchemaValidation: """Test suite for schema validation of markdown files.""" def setup_method(self): """Set up test environment.""" self.schema_validator = SchemaValidator() self.schema_generator = SchemaGenerator() def teardown_method(self): """Clean up after tests.""" pass def test_validate_markdown_against_matching_schema_returns_true(self): """ ISSUE #7: Test markdown validation against matching schema returns True. Verifies that a markdown document that matches its generated schema returns True for compliance - essential for arc42 document validation. """ # Arrange - Create markdown with known structure markdown_content = """# Architecture Document This document describes the system architecture. ## Overview The system consists of several components: - Component A - Component B ## Details More detailed information here. """ with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Generate schema from the same content schema = self.schema_generator.generate_schema_from_file(temp_file) # Act - Validate the document against its own schema is_valid = self.schema_validator.validate_file_against_schema(temp_file, schema) # Assert - Document should match its own schema assert is_valid is True finally: temp_file.unlink() def test_validate_markdown_against_non_matching_schema_returns_false(self): """ ISSUE #7: Test markdown validation against non-matching schema returns False. Verifies that a markdown document that doesn't match the schema structure returns False - critical for detecting arc42 compliance violations. """ # Arrange - Create markdown document markdown_content = """# Single Heading Just one paragraph of content. """ # Create schema that expects different structure (multiple headings) expected_schema = { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "headings": { "type": "object", "properties": { "level_1": { "type": "array", "minItems": 2, # Expect at least 2 level-1 headings "maxItems": 2 }, "level_2": { "type": "array", "minItems": 1 # Expect at least 1 level-2 heading } }, "required": ["level_1", "level_2"] } }, "required": ["headings"] } with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act - Validate against non-matching schema is_valid = self.schema_validator.validate_file_against_schema(temp_file, expected_schema) # Assert - Document should not match the schema assert is_valid is False finally: temp_file.unlink() def test_validate_markdown_with_depth_limited_schema(self): """ ISSUE #7: Test validation with depth-limited schemas for arc42 sections. Ensures validation works correctly with depth-limited schemas, essential for arc42 section-specific compliance checking. """ # Arrange - Markdown with multiple heading levels markdown_content = """# Main Architecture ## System Overview Content here. ### Implementation Details Deep content. #### Technical Notes Very deep content. """ with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Generate schema with depth limit of 2 schema_depth_2 = self.schema_generator.generate_schema_from_file(temp_file, max_depth=2) # Act - Validate against depth-limited schema is_valid = self.schema_validator.validate_file_against_schema(temp_file, schema_depth_2) # Assert - Document should match depth-limited schema assert is_valid is True finally: temp_file.unlink() def test_validate_file_not_found_raises_exception(self): """ ISSUE #7: Test error handling when markdown file doesn't exist. """ # Arrange - Non-existent file and valid schema non_existent_file = Path("/tmp/non_existent_file.md") valid_schema = { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object" } # Act & Assert - Should raise FileNotFoundError with pytest.raises(FileNotFoundError): self.schema_validator.validate_file_against_schema(non_existent_file, valid_schema) def test_validate_with_invalid_schema_raises_exception(self): """ ISSUE #7: Test error handling for invalid JSON schema. """ # Arrange - Valid markdown file and invalid schema markdown_content = "# Test\n\nContent." with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) invalid_schema = { # Missing required $schema and type fields "properties": {} } try: # Act & Assert - Should raise InvalidSchemaError with pytest.raises(InvalidSchemaError): self.schema_validator.validate_file_against_schema(temp_file, invalid_schema) finally: temp_file.unlink() def test_validate_markdown_with_complex_schema_requirements(self): """ ISSUE #7: Test validation against complex schema requirements. Validates comprehensive schema rules including specific content requirements, essential for arc42 template compliance. """ # Arrange - Create markdown that should match complex requirements markdown_content = """# Architecture Decision Record ## Status Accepted ## Context This document describes architectural decisions. ## Decision We decided to use microservices architecture. ## Consequences - Benefit: Better scalability - Cost: Increased complexity """ # Create schema with specific structural requirements adr_schema = { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "Architecture Decision Record Schema", "properties": { "headings": { "type": "object", "properties": { "level_1": { "type": "array", "minItems": 1, "maxItems": 1 # Exactly one main title }, "level_2": { "type": "array", "minItems": 4, # Expect Status, Context, Decision, Consequences "maxItems": 4 } }, "required": ["level_1", "level_2"] }, "paragraphs": { "type": "array", "minItems": 4 # Expect content under each section }, "lists": { "type": "array", "minItems": 1 # Expect consequences list } }, "required": ["headings", "paragraphs", "lists"] } with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act - Validate against ADR schema is_valid = self.schema_validator.validate_file_against_schema(temp_file, adr_schema) # Assert - Document should match ADR schema requirements assert is_valid is True finally: temp_file.unlink() def test_validate_returns_false_for_missing_required_elements(self): """ ISSUE #7: Test validation returns False when required elements are missing. Ensures strict compliance checking for arc42 architectural templates. """ # Arrange - Incomplete markdown missing required elements incomplete_markdown = """# Incomplete Document This document is missing required sections. """ # Schema requiring multiple sections strict_schema = { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "headings": { "type": "object", "properties": { "level_1": { "type": "array", "minItems": 1 }, "level_2": { "type": "array", "minItems": 3 # Require at least 3 level-2 headings } }, "required": ["level_1", "level_2"] }, "lists": { "type": "array", "minItems": 2 # Require at least 2 lists } }, "required": ["headings", "lists"] } with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(incomplete_markdown) temp_file = Path(f.name) try: # Act - Validate incomplete document is_valid = self.schema_validator.validate_file_against_schema(temp_file, strict_schema) # Assert - Should return False due to missing required elements assert is_valid is False finally: temp_file.unlink() def test_validate_with_schema_from_json_string(self): """ ISSUE #7: Test validation using schema provided as JSON string. Supports flexible schema input for CLI and API integration. """ # Arrange - Simple markdown and schema as JSON string markdown_content = """# Test Document Simple test content. """ schema_json = """{ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "headings": { "type": "object", "properties": { "level_1": { "type": "array", "minItems": 1, "maxItems": 1 } } } } }""" with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act - Validate using JSON string schema is_valid = self.schema_validator.validate_file_against_schema_string(temp_file, schema_json) # Assert - Should successfully validate assert is_valid is True finally: temp_file.unlink()