""" Tests for Issue #50: Define metaschema for JSON schema structure This test module defines comprehensive tests for the MarkiTect metaschema that extends standard JSON Schema with MarkiTect-specific features like heading text capture, content field instructions, and outline structure representation. Following TDD8 methodology - these tests are written before implementation. """ import json import pytest from pathlib import Path from typing import Dict, Any from markitect.metaschema import MetaschemaValidator, MARKITECT_METASCHEMA_PATH class TestIssue50MetaschemaDefinition: """Test suite for MarkiTect metaschema definition and validation.""" def setup_method(self): """Set up test fixtures.""" self.metaschema_validator = MetaschemaValidator() def test_metaschema_file_exists_and_is_valid_json(self): """Test that the metaschema JSON file exists and contains valid JSON.""" # Arrange & Act metaschema_path = Path(MARKITECT_METASCHEMA_PATH) # Assert assert metaschema_path.exists(), f"Metaschema file should exist at {MARKITECT_METASCHEMA_PATH}" with open(metaschema_path) as f: metaschema = json.load(f) assert isinstance(metaschema, dict), "Metaschema should be a valid JSON object" assert "$schema" in metaschema, "Metaschema should have $schema property" assert "type" in metaschema, "Metaschema should have type property" def test_metaschema_extends_json_schema_draft_07(self): """Test that metaschema properly extends JSON Schema Draft 07.""" # Arrange & Act metaschema = self.metaschema_validator.get_metaschema() # Assert assert metaschema["$schema"] == "http://json-schema.org/draft-07/schema#" assert metaschema["type"] == "object" assert "allOf" in metaschema, "Should extend base JSON Schema using allOf" # Should reference standard JSON Schema found_json_schema_ref = False for schema_ref in metaschema["allOf"]: if "$ref" in schema_ref and "json-schema.org" in schema_ref["$ref"]: found_json_schema_ref = True break assert found_json_schema_ref, "Should reference standard JSON Schema Draft 07" def test_metaschema_supports_heading_text_capture(self): """Test that metaschema supports heading text capture extensions.""" # Arrange & Act metaschema = self.metaschema_validator.get_metaschema() # Assert - Check for MarkiTect-specific heading text properties markitect_properties = self._get_markitect_extensions(metaschema) assert "x-markitect-heading-text" in markitect_properties, \ "Should support x-markitect-heading-text property" heading_text_schema = markitect_properties["x-markitect-heading-text"] assert heading_text_schema["type"] == "string" assert "description" in heading_text_schema assert "preserve actual heading text" in heading_text_schema["description"].lower() def test_metaschema_supports_content_field_instructions(self): """Test that metaschema supports content field instruction capabilities.""" # Arrange & Act metaschema = self.metaschema_validator.get_metaschema() # Assert - Check for content instruction properties markitect_properties = self._get_markitect_extensions(metaschema) assert "x-markitect-content-instructions" in markitect_properties, \ "Should support x-markitect-content-instructions property" instructions_schema = markitect_properties["x-markitect-content-instructions"] assert instructions_schema["type"] == "string" assert "description" in instructions_schema assert "content author" in instructions_schema["description"].lower() def test_metaschema_supports_outline_structure_representation(self): """Test that metaschema supports outline structure representation.""" # Arrange & Act metaschema = self.metaschema_validator.get_metaschema() # Assert - Check for outline structure properties markitect_properties = self._get_markitect_extensions(metaschema) assert "x-markitect-outline-mode" in markitect_properties, \ "Should support x-markitect-outline-mode property" outline_schema = markitect_properties["x-markitect-outline-mode"] assert outline_schema["type"] == "boolean" assert "description" in outline_schema assert "x-markitect-outline-depth" in markitect_properties, \ "Should support x-markitect-outline-depth property" depth_schema = markitect_properties["x-markitect-outline-depth"] assert depth_schema["type"] == "integer" assert depth_schema["minimum"] == 1 def test_metaschema_validates_standard_json_schema(self): """Test that metaschema accepts standard JSON Schema documents (backward compatibility).""" # Arrange standard_schema = { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "Standard Schema", "properties": { "name": {"type": "string"}, "age": {"type": "integer", "minimum": 0} }, "required": ["name"] } # Act & Assert is_valid = self.metaschema_validator.validate_schema(standard_schema) assert is_valid, "Standard JSON Schema should be valid against metaschema" def test_metaschema_validates_markitect_extended_schema(self): """Test that metaschema accepts MarkiTect extended schemas.""" # Arrange markitect_schema = { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "MarkiTect Extended Schema", "x-markitect-outline-mode": True, "x-markitect-outline-depth": 3, "properties": { "headings": { "type": "object", "properties": { "level_1": { "type": "array", "items": { "type": "object", "properties": { "content": {"type": "string"}, "x-markitect-heading-text": "Introduction", "x-markitect-content-instructions": "Provide overview of the document" } } } } } } } # Act & Assert is_valid = self.metaschema_validator.validate_schema(markitect_schema) assert is_valid, "MarkiTect extended schema should be valid against metaschema" def test_metaschema_rejects_invalid_markitect_extensions(self): """Test that metaschema rejects invalid MarkiTect extension values.""" # Arrange invalid_schemas = [ # Invalid outline depth (negative) { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "x-markitect-outline-depth": -1 }, # Invalid outline mode (not boolean) { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "x-markitect-outline-mode": "true" }, # Invalid heading text (not string) { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "heading": { "x-markitect-heading-text": 123 } } } ] # Act & Assert for invalid_schema in invalid_schemas: is_valid = self.metaschema_validator.validate_schema(invalid_schema) assert not is_valid, f"Invalid schema should be rejected: {invalid_schema}" def test_metaschema_validation_provides_detailed_errors(self): """Test that metaschema validation provides detailed error messages.""" # Arrange invalid_schema = { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "x-markitect-outline-depth": "not-a-number" } # Act validation_result = self.metaschema_validator.validate_schema_with_errors(invalid_schema) # Assert assert not validation_result.is_valid assert len(validation_result.errors) > 0 error_messages = [error.message for error in validation_result.errors] assert any("x-markitect-outline-depth" in msg for msg in error_messages), \ "Error should mention the problematic MarkiTect extension" def test_metaschema_supports_content_instruction_types(self): """Test that metaschema supports different types of content instructions.""" # Arrange & Act metaschema = self.metaschema_validator.get_metaschema() markitect_properties = self._get_markitect_extensions(metaschema) # Assert - Check for instruction type support assert "x-markitect-instruction-type" in markitect_properties, \ "Should support x-markitect-instruction-type property" instruction_type_schema = markitect_properties["x-markitect-instruction-type"] assert instruction_type_schema["type"] == "string" assert "enum" in instruction_type_schema expected_types = ["description", "example", "constraint", "template"] for instruction_type in expected_types: assert instruction_type in instruction_type_schema["enum"], \ f"Should support {instruction_type} instruction type" def test_metaschema_supports_schema_metadata(self): """Test that metaschema supports MarkiTect-specific schema metadata.""" # Arrange & Act metaschema = self.metaschema_validator.get_metaschema() markitect_properties = self._get_markitect_extensions(metaschema) # Assert - Check for metadata properties assert "x-markitect-generated-from" in markitect_properties, \ "Should support x-markitect-generated-from property" assert "x-markitect-generation-mode" in markitect_properties, \ "Should support x-markitect-generation-mode property" generation_mode = markitect_properties["x-markitect-generation-mode"] assert "enum" in generation_mode assert "outline" in generation_mode["enum"] assert "full" in generation_mode["enum"] def test_existing_schemas_validate_against_metaschema(self): """Test that all existing MarkiTect schemas validate against the new metaschema.""" # Arrange - Get sample existing schema structure existing_schema = { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "Schema for example.md", "description": "JSON schema describing the structure of example.md", "properties": { "headings": { "type": "object", "properties": { "level_1": { "type": "array", "items": { "type": "object", "properties": { "content": {"type": "string"}, "level": {"type": "integer"}, "position": {"type": "integer"} }, "required": ["content", "level"] } } } }, "metadata": { "type": "object", "properties": { "total_elements": {"type": "integer"}, "structure_types": { "type": "array", "items": {"type": "string"} } } } } } # Act & Assert is_valid = self.metaschema_validator.validate_schema(existing_schema) assert is_valid, "Existing MarkiTect schemas should remain valid (backward compatibility)" def _get_markitect_extensions(self, metaschema: Dict[str, Any]) -> Dict[str, Any]: """Helper to extract MarkiTect extension properties from metaschema.""" # Look for MarkiTect extensions in the allOf extension for extension in metaschema.get("allOf", []): if "properties" in extension: markitect_props = {} for prop_name, prop_schema in extension["properties"].items(): if prop_name.startswith("x-markitect-"): markitect_props[prop_name] = prop_schema if markitect_props: return markitect_props # Fallback: look in patternProperties for x-markitect- patterns pattern_props = metaschema.get("patternProperties", {}) for pattern, schema in pattern_props.items(): if "x-markitect" in pattern: return {pattern: schema} return {} class TestMetaschemaValidator: """Test the MetaschemaValidator utility class.""" def test_metaschema_validator_can_be_created(self): """Test that MetaschemaValidator can be instantiated.""" # Act & Assert validator = MetaschemaValidator() assert validator is not None def test_metaschema_validator_loads_metaschema(self): """Test that MetaschemaValidator properly loads the metaschema.""" # Arrange & Act validator = MetaschemaValidator() metaschema = validator.get_metaschema() # Assert assert isinstance(metaschema, dict) assert "$schema" in metaschema assert metaschema["$schema"] == "http://json-schema.org/draft-07/schema#" def test_metaschema_validator_caches_metaschema(self): """Test that MetaschemaValidator caches the loaded metaschema.""" # Arrange & Act validator = MetaschemaValidator() metaschema1 = validator.get_metaschema() metaschema2 = validator.get_metaschema() # Assert assert metaschema1 is metaschema2, "Should cache metaschema instance"