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>
346 lines
14 KiB
Python
346 lines
14 KiB
Python
"""
|
|
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" |