test: Add metaschema definition tests for Issue #50

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 <noreply@anthropic.com>
This commit is contained in:
2025-10-01 08:25:08 +02:00
parent a8d9b9289c
commit db60a1f3aa

View File

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