From db60a1f3aa0ed2ef9efcae2897cf99b44698a754 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 1 Oct 2025 08:25:08 +0200 Subject: [PATCH] test: Add metaschema definition tests for Issue #50 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds comprehensive tests for the MarkiTect metaschema that validates JSON Schema extensions used throughout the project. ## Test Coverage: - Metaschema file existence and validity - JSON Schema Draft-07 compliance - MarkiTect-specific extension validation: - x-markitect-outline-mode (Issue #51) - x-markitect-heading-text-capture (Issue #52) - x-markitect-content-instructions-enabled (Issue #54) - Schema structure validation - Extension property validation This provides the foundation for validating all MarkiTect schema extensions implemented in Issues #51, #52, and #54. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- tests/test_issue_50_metaschema_definition.py | 346 +++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 tests/test_issue_50_metaschema_definition.py diff --git a/tests/test_issue_50_metaschema_definition.py b/tests/test_issue_50_metaschema_definition.py new file mode 100644 index 00000000..ab5ab940 --- /dev/null +++ b/tests/test_issue_50_metaschema_definition.py @@ -0,0 +1,346 @@ +""" +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" \ No newline at end of file