Files
markitect-main/tests/test_schema_metaschema.py
tegwick f3aaec99bb
Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
feat: implement Phase 3 - Schema-for-Schemas Metaschema
Completed Phase 3 of the schema-of-schemas implementation with a
comprehensive metaschema that validates all MarkiTect schema files
against conventions and standards.

Metaschema Implementation (schema-schema-v1.0.md - 650+ lines):
- Validates core JSON Schema fields ($schema, $id, title, description)
- Validates MarkiTect version field (SemVer: major.minor.patch)
- Validates $id URL format (HTTPS with version path)
- Validates MarkiTect extensions:
  - x-markitect-sections: section classifications and content rules
  - x-markitect-content-control: pattern and quality validation
  - x-markitect-metadata: status, authors, tags
  - x-markitect-source: loader metadata (auto-added)
- Section classification validation (required, recommended, optional,
  discouraged, improper)
- Content control pattern validation
- Comprehensive documentation with examples and usage guides

CLI Command (markitect schema-validate):
- Validates schema files against metaschema
- Supports both markdown and JSON schema files
- Detailed error reporting with schema paths
- Structure validation recommendations
- Exit codes for CI/CD integration

Test Coverage (tests/test_schema_metaschema.py - 12 tests, 100% passing):
- Metaschema self-validation
- Manpage schema validation
- Required fields enforcement
- Version format validation (valid and invalid cases)
- $id format validation (valid and invalid cases)
- Section classification validation
- Complete schema with all extensions

Validation Results:
-  Metaschema validates itself successfully
-  Manpage schema (v1.0.md) validates successfully
- ⚠️  Terminology schema needs migration (missing version, incorrect $id)

Progress Tracking:
- Updated TODO.md with Phase 3 completion
- Updated CHANGELOG.md with implementation details
- Next: Phase 4 - Schema Migration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-05 03:10:49 +01:00

301 lines
12 KiB
Python

"""
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 <test@example.com>"],
"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}"