""" Unit tests for schema metaschema validation. Tests that schemas validate correctly against the schema-for-schemas metaschema. """ import pytest import json from pathlib import Path try: from jsonschema import Draft7Validator JSONSCHEMA_AVAILABLE = True except ImportError: JSONSCHEMA_AVAILABLE = False from markitect.schema_loader import MarkdownSchemaLoader @pytest.fixture def loader(): """Create a schema loader instance.""" return MarkdownSchemaLoader() @pytest.fixture def metaschema(loader): """Load the metaschema.""" metaschema_path = Path(__file__).parent.parent / 'markitect' / 'schemas' / 'schema-schema-v1.0.md' metaschema_data = loader.load_schema(metaschema_path) return metaschema_data['schema'] @pytest.mark.skipif(not JSONSCHEMA_AVAILABLE, reason="jsonschema not installed") class TestMetaschemaValidation: """Tests for validating schemas against the metaschema.""" def test_metaschema_self_validation(self, loader, metaschema): """Test that metaschema validates itself.""" validator = Draft7Validator(metaschema) errors = list(validator.iter_errors(metaschema)) assert len(errors) == 0, f"Metaschema should validate itself, but got errors: {errors}" def test_manpage_schema_validation(self, loader, metaschema): """Test that manpage schema validates against metaschema.""" manpage_path = Path(__file__).parent.parent / 'markitect' / 'schemas' / 'manpage-schema-v1.0.md' manpage_data = loader.load_schema(manpage_path) manpage_schema = manpage_data['schema'] validator = Draft7Validator(metaschema) errors = list(validator.iter_errors(manpage_schema)) assert len(errors) == 0, f"Manpage schema should be valid, but got errors: {[e.message for e in errors]}" def test_required_fields_enforced(self, metaschema): """Test that metaschema enforces required fields.""" # Schema missing required fields invalid_schema = { "$schema": "http://json-schema.org/draft-07/schema#", # Missing: $id, title, description, version } validator = Draft7Validator(metaschema) errors = list(validator.iter_errors(invalid_schema)) # Should have errors for missing required fields assert len(errors) > 0 error_messages = [e.message for e in errors] # Check that required fields are mentioned required_fields = ['$id', 'title', 'description', 'version'] for field in required_fields: assert any(field in msg for msg in error_messages), \ f"Should report missing required field: {field}" def test_version_format_validation(self, metaschema): """Test that metaschema validates version format.""" # Invalid version formats invalid_versions = [ "1.0", # Missing patch "v1.0.0", # Has v prefix "1", # Only major "1.0.0.0", # Too many parts ] for invalid_version in invalid_versions: schema = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://markitect.dev/schemas/test/v1.0", "title": "Test Schema", "description": "Test schema for validation", "version": invalid_version } validator = Draft7Validator(metaschema) errors = list(validator.iter_errors(schema)) assert len(errors) > 0, f"Should reject invalid version: {invalid_version}" assert any('pattern' in str(e.schema_path) for e in errors), \ f"Should be a pattern error for version: {invalid_version}" def test_valid_version_format(self, metaschema): """Test that valid version formats are accepted.""" valid_versions = [ "1.0.0", "2.5.3", "10.25.99", "0.0.1", ] for valid_version in valid_versions: schema = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://markitect.dev/schemas/test/v1.0", "title": "Test Schema", "description": "Test schema for validation", "version": valid_version } validator = Draft7Validator(metaschema) errors = list(validator.iter_errors(schema)) # Filter out errors not related to version version_errors = [e for e in errors if 'version' in str(e.path)] assert len(version_errors) == 0, \ f"Should accept valid version: {valid_version}, but got errors: {version_errors}" def test_id_format_validation(self, metaschema): """Test that metaschema validates $id format.""" invalid_ids = [ "http://example.com/schema", # Not HTTPS "https://example.com/schema", # No version "schema/v1.0", # Not a URL "https://example.com/schemas/test/1.0", # No 'v' prefix ] for invalid_id in invalid_ids: schema = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": invalid_id, "title": "Test Schema", "description": "Test schema for validation", "version": "1.0.0" } validator = Draft7Validator(metaschema) errors = list(validator.iter_errors(schema)) assert len(errors) > 0, f"Should reject invalid $id: {invalid_id}" def test_valid_id_format(self, metaschema): """Test that valid $id formats are accepted.""" valid_ids = [ "https://markitect.dev/schemas/test/v1.0", "https://example.com/schemas/my-schema/v2.5", "https://api.example.com/schemas/domain/v10.25", ] for valid_id in valid_ids: schema = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": valid_id, "title": "Test Schema", "description": "Test schema for validation", "version": "1.0.0" } validator = Draft7Validator(metaschema) errors = list(validator.iter_errors(schema)) # Filter out errors not related to $id id_errors = [e for e in errors if '$id' in str(e.path)] assert len(id_errors) == 0, \ f"Should accept valid $id: {valid_id}, but got errors: {id_errors}" def test_section_classification_validation(self, metaschema): """Test that section classifications are validated.""" # Invalid classification schema = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://markitect.dev/schemas/test/v1.0", "title": "Test Schema", "description": "Test schema for validation", "version": "1.0.0", "x-markitect-sections": { "SYNOPSIS": { "classification": "mandatory", # Invalid, should be 'required' "heading_level": 2 } } } validator = Draft7Validator(metaschema) errors = list(validator.iter_errors(schema)) assert len(errors) > 0 # Should have error about invalid enum value assert any('enum' in str(e.schema_path) or 'mandatory' in e.message for e in errors) def test_valid_section_classifications(self, metaschema): """Test that valid section classifications are accepted.""" classifications = ['required', 'recommended', 'optional', 'discouraged', 'improper'] for classification in classifications: schema = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://markitect.dev/schemas/test/v1.0", "title": "Test Schema", "description": "Test schema for validation", "version": "1.0.0", "x-markitect-sections": { "TEST_SECTION": { "classification": classification, "heading_level": 2 } } } validator = Draft7Validator(metaschema) errors = list(validator.iter_errors(schema)) # Filter errors related to classification classification_errors = [e for e in errors if 'classification' in str(e.path)] assert len(classification_errors) == 0, \ f"Should accept valid classification: {classification}" def test_schema_with_all_extensions(self, metaschema): """Test schema with all MarkiTect extensions.""" schema = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://markitect.dev/schemas/test/v1.0", "title": "Complete Test Schema", "description": "Schema with all MarkiTect extensions", "version": "1.0.0", "type": "object", "x-markitect-sections": { "SYNOPSIS": { "classification": "required", "heading_level": 2, "content_instruction": "Brief overview", "min_paragraphs": 1, "max_paragraphs": 3 } }, "x-markitect-content-control": { "synopsis": { "required_patterns": ["\\*\\*.*\\*\\*"], "content_quality": { "min_words": 10, "max_words": 100, "readability_target": "technical" } } }, "x-markitect-metadata": { "status": "stable", "authors": ["Test Author "], "tags": ["test", "example"] } } validator = Draft7Validator(metaschema) errors = list(validator.iter_errors(schema)) assert len(errors) == 0, f"Complete schema should be valid, but got errors: {[e.message for e in errors]}" class TestSchemaLoaderIntegration: """Integration tests for schema loader with metaschema.""" def test_load_and_validate_manpage_schema(self, loader, metaschema): """Test loading and validating manpage schema.""" manpage_path = Path(__file__).parent.parent / 'markitect' / 'schemas' / 'manpage-schema-v1.0.md' # Load schema schema_data = loader.load_schema(manpage_path) schema = schema_data['schema'] # Check metadata was merged assert 'x-markitect-source' in schema assert schema['x-markitect-source']['format'] == 'markdown' assert schema['x-markitect-source']['filename'] == 'manpage-schema-v1.0.md' # Validate structure issues = loader.validate_schema_structure(schema) # Should have no critical issues assert all('Missing' not in issue or 'recommended' in issue.lower() for issue in issues) def test_metaschema_structure_validation(self, loader): """Test metaschema structure with loader's validator.""" metaschema_path = Path(__file__).parent.parent / 'markitect' / 'schemas' / 'schema-schema-v1.0.md' metaschema_data = loader.load_schema(metaschema_path) metaschema = metaschema_data['schema'] # Validate structure issues = loader.validate_schema_structure(metaschema) # Metaschema should have minimal issues critical_issues = [i for i in issues if 'Missing required field' in i] assert len(critical_issues) == 0, f"Metaschema has critical issues: {critical_issues}"