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
Clean up test infrastructure by removing problematic tests that create circular dependencies and execute the test suite from within tests. Key removals: - Delete test_issue_57_test_efficiency_improvements.py entirely (12 tests) - Contained tests that ran `make test-tdd`, `make test-status` etc. - Created circular dependencies where tests execute the entire test suite - Violated separation of concerns between testing and test infrastructure - Remove self-execution blocks from 11 test files - Eliminated `if __name__ == '__main__': pytest.main([__file__, '-v'])` patterns - Prevents confusion and potential circular execution paths - Test files should be run via pytest, not as standalone scripts Test Infrastructure Improvements: - Reduced test count from 701 to 689 tests (removed 12 problematic tests) - Eliminated subprocess calls to `make test-*` commands from within tests - Removed `pytest.main()` calls that could cause circular execution - Maintained clean separation between test infrastructure and actual tests Impact: - No more tests testing tests (circular dependency elimination) - Cleaner test execution without subprocess complexity - Proper test isolation and independence - Faster and more reliable test runs The proper way to test infrastructure is to test the underlying functions directly, not to execute the entire test suite from within a test. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
508 lines
17 KiB
Python
508 lines
17 KiB
Python
"""
|
|
Tests for Issue #8: Get Validation Errors.
|
|
|
|
Tests the detailed error reporting functionality that provides specific
|
|
feedback about how markdown documents deviate from JSON schemas.
|
|
"""
|
|
|
|
import json
|
|
import pytest
|
|
from pathlib import Path
|
|
from tempfile import NamedTemporaryFile
|
|
from textwrap import dedent
|
|
|
|
import sys
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
|
|
|
from markitect.schema_validator import SchemaValidator
|
|
from markitect.validation_error import ValidationErrorType, ValidationErrorCollector
|
|
from markitect.exceptions import FileNotFoundError, InvalidSchemaError
|
|
|
|
|
|
class TestIssue8ValidationErrors:
|
|
"""Test suite for detailed validation error reporting."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment."""
|
|
self.validator = SchemaValidator()
|
|
|
|
def test_validate_file_with_errors_returns_collector(self):
|
|
"""
|
|
ISSUE #8: Test that validation with errors returns ValidationErrorCollector.
|
|
"""
|
|
# Arrange - Create simple markdown
|
|
markdown_content = "# Test\n\nParagraph content."
|
|
|
|
with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
|
|
f.write(markdown_content)
|
|
temp_file = Path(f.name)
|
|
|
|
# Simple schema that should match
|
|
schema = {
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"properties": {
|
|
"headings": {
|
|
"type": "object",
|
|
"properties": {
|
|
"level_1": {
|
|
"type": "array",
|
|
"minItems": 1,
|
|
"maxItems": 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
try:
|
|
# Act - Validate with error collection
|
|
error_collector = self.validator.validate_file_with_errors(temp_file, schema)
|
|
|
|
# Assert - Returns ValidationErrorCollector
|
|
assert isinstance(error_collector, ValidationErrorCollector)
|
|
assert not error_collector.has_errors() # Should be valid
|
|
|
|
finally:
|
|
temp_file.unlink()
|
|
|
|
def test_validation_errors_for_missing_required_headings(self):
|
|
"""
|
|
ISSUE #8: Test specific error for missing required headings.
|
|
"""
|
|
# Arrange - Markdown with only level 1 heading
|
|
markdown_content = "# Main Title\n\nSome content."
|
|
|
|
with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
|
|
f.write(markdown_content)
|
|
temp_file = Path(f.name)
|
|
|
|
# Schema requiring level 2 headings
|
|
schema = {
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"properties": {
|
|
"headings": {
|
|
"type": "object",
|
|
"properties": {
|
|
"level_1": {
|
|
"type": "array",
|
|
"minItems": 1
|
|
},
|
|
"level_2": {
|
|
"type": "array",
|
|
"minItems": 2
|
|
}
|
|
},
|
|
"required": ["level_1", "level_2"]
|
|
}
|
|
},
|
|
"required": ["headings"]
|
|
}
|
|
|
|
try:
|
|
# Act - Validate and collect errors
|
|
error_collector = self.validator.validate_file_with_errors(temp_file, schema)
|
|
|
|
# Assert - Should have missing heading error
|
|
assert error_collector.has_errors()
|
|
assert error_collector.get_error_count() == 1
|
|
|
|
errors = error_collector.get_errors_by_type(ValidationErrorType.MISSING_REQUIRED_HEADING)
|
|
assert len(errors) == 1
|
|
|
|
error = errors[0]
|
|
assert "Missing required heading level 2" in error.message
|
|
assert "headings.level_2" in error.path
|
|
assert "## Heading" in error.suggestion
|
|
|
|
finally:
|
|
temp_file.unlink()
|
|
|
|
def test_validation_errors_for_insufficient_content(self):
|
|
"""
|
|
ISSUE #8: Test specific error for insufficient content.
|
|
"""
|
|
# Arrange - Markdown with only 1 paragraph
|
|
markdown_content = "# Title\n\nOnly one paragraph."
|
|
|
|
with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
|
|
f.write(markdown_content)
|
|
temp_file = Path(f.name)
|
|
|
|
# Schema requiring 3 paragraphs
|
|
schema = {
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"properties": {
|
|
"paragraphs": {
|
|
"type": "array",
|
|
"minItems": 3
|
|
}
|
|
},
|
|
"required": ["paragraphs"]
|
|
}
|
|
|
|
try:
|
|
# Act - Validate and collect errors
|
|
error_collector = self.validator.validate_file_with_errors(temp_file, schema)
|
|
|
|
# Assert - Should have insufficient content error
|
|
assert error_collector.has_errors()
|
|
|
|
errors = error_collector.get_errors_by_type(ValidationErrorType.INSUFFICIENT_CONTENT)
|
|
assert len(errors) == 1
|
|
|
|
error = errors[0]
|
|
assert "Insufficient paragraphs" in error.message
|
|
# Check the actual vs expected counts (actual count may vary)
|
|
assert "required at least 3" in error.message
|
|
assert "found" in error.message
|
|
assert "Add" in error.suggestion and "more paragraphs" in error.suggestion
|
|
|
|
finally:
|
|
temp_file.unlink()
|
|
|
|
def test_validation_errors_for_missing_required_sections(self):
|
|
"""
|
|
ISSUE #8: Test specific error for missing required sections.
|
|
"""
|
|
# Arrange - Simple markdown without lists
|
|
markdown_content = "# Title\n\nJust text content."
|
|
|
|
with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
|
|
f.write(markdown_content)
|
|
temp_file = Path(f.name)
|
|
|
|
# Schema requiring lists
|
|
schema = {
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"properties": {
|
|
"lists": {
|
|
"type": "array",
|
|
"minItems": 1
|
|
}
|
|
},
|
|
"required": ["lists"]
|
|
}
|
|
|
|
try:
|
|
# Act - Validate and collect errors
|
|
error_collector = self.validator.validate_file_with_errors(temp_file, schema)
|
|
|
|
# Assert - Should have missing section error
|
|
assert error_collector.has_errors()
|
|
|
|
errors = error_collector.get_errors_by_type(ValidationErrorType.MISSING_REQUIRED_SECTION)
|
|
assert len(errors) >= 1
|
|
|
|
# Find the specific error about lists
|
|
list_errors = [e for e in errors if "lists" in e.message]
|
|
assert len(list_errors) >= 1
|
|
|
|
error = list_errors[0]
|
|
assert "lists" in error.message
|
|
assert "Add" in error.suggestion
|
|
|
|
finally:
|
|
temp_file.unlink()
|
|
|
|
def test_validation_errors_multiple_types(self):
|
|
"""
|
|
ISSUE #8: Test that multiple error types are collected correctly.
|
|
"""
|
|
# Arrange - Incomplete markdown
|
|
markdown_content = dedent("""
|
|
# Main Title
|
|
|
|
Single paragraph of content.
|
|
""").strip()
|
|
|
|
with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
|
|
f.write(markdown_content)
|
|
temp_file = Path(f.name)
|
|
|
|
# Complex schema with multiple requirements
|
|
schema = {
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"properties": {
|
|
"headings": {
|
|
"type": "object",
|
|
"properties": {
|
|
"level_1": {
|
|
"type": "array",
|
|
"minItems": 1
|
|
},
|
|
"level_2": {
|
|
"type": "array",
|
|
"minItems": 2 # Missing
|
|
}
|
|
},
|
|
"required": ["level_1", "level_2"]
|
|
},
|
|
"paragraphs": {
|
|
"type": "array",
|
|
"minItems": 4 # Insufficient
|
|
},
|
|
"lists": {
|
|
"type": "array",
|
|
"minItems": 1 # Missing
|
|
}
|
|
},
|
|
"required": ["headings", "paragraphs", "lists"]
|
|
}
|
|
|
|
try:
|
|
# Act - Validate and collect errors
|
|
error_collector = self.validator.validate_file_with_errors(temp_file, schema)
|
|
|
|
# Assert - Should have multiple error types
|
|
assert error_collector.has_errors()
|
|
assert error_collector.get_error_count() >= 3
|
|
|
|
# Check for different error types
|
|
heading_errors = error_collector.get_errors_by_type(ValidationErrorType.MISSING_REQUIRED_HEADING)
|
|
content_errors = error_collector.get_errors_by_type(ValidationErrorType.INSUFFICIENT_CONTENT)
|
|
section_errors = error_collector.get_errors_by_type(ValidationErrorType.MISSING_REQUIRED_SECTION)
|
|
|
|
assert len(heading_errors) >= 1
|
|
assert len(content_errors) >= 1
|
|
assert len(section_errors) >= 1
|
|
|
|
# Check error summary
|
|
summary = error_collector.get_summary()
|
|
assert summary['total_errors'] >= 3
|
|
assert 'error' in summary['by_severity']
|
|
|
|
finally:
|
|
temp_file.unlink()
|
|
|
|
def test_error_formatting_text_format(self):
|
|
"""
|
|
ISSUE #8: Test text format error output.
|
|
"""
|
|
# Create error collector with sample errors
|
|
error_collector = ValidationErrorCollector()
|
|
error_collector.add_error(
|
|
ValidationErrorType.MISSING_REQUIRED_HEADING,
|
|
"Missing required heading level 2",
|
|
"headings.level_2",
|
|
expected="At least one heading at level 2",
|
|
actual="No headings found",
|
|
suggestion="Add heading(s) at level 2"
|
|
)
|
|
|
|
# Test text formatting
|
|
text_output = error_collector.format_errors("text")
|
|
|
|
assert "Validation failed with 1 error(s):" in text_output
|
|
assert "Missing required heading level 2" in text_output
|
|
assert "headings.level_2" in text_output
|
|
assert "💡 Add heading(s) at level 2" in text_output
|
|
|
|
def test_error_formatting_json_format(self):
|
|
"""
|
|
ISSUE #8: Test JSON format error output.
|
|
"""
|
|
# Create error collector with sample error
|
|
error_collector = ValidationErrorCollector()
|
|
error_collector.add_error(
|
|
ValidationErrorType.INSUFFICIENT_CONTENT,
|
|
"Insufficient paragraphs",
|
|
"content.paragraphs",
|
|
expected="At least 3",
|
|
actual="1",
|
|
suggestion="Add more paragraphs"
|
|
)
|
|
|
|
# Test JSON formatting
|
|
json_output = error_collector.format_errors("json")
|
|
parsed_json = json.loads(json_output)
|
|
|
|
assert len(parsed_json) == 1
|
|
error_data = parsed_json[0]
|
|
|
|
assert error_data["type"] == "insufficient_content"
|
|
assert error_data["message"] == "Insufficient paragraphs"
|
|
assert error_data["path"] == "content.paragraphs"
|
|
assert error_data["expected"] == "At least 3"
|
|
assert error_data["actual"] == "1"
|
|
assert error_data["suggestion"] == "Add more paragraphs"
|
|
assert error_data["severity"] == "error"
|
|
|
|
def test_error_formatting_markdown_format(self):
|
|
"""
|
|
ISSUE #8: Test Markdown format error output.
|
|
"""
|
|
# Create error collector with sample error
|
|
error_collector = ValidationErrorCollector()
|
|
error_collector.add_error(
|
|
ValidationErrorType.MISSING_REQUIRED_SECTION,
|
|
"Missing required lists section",
|
|
"document.lists",
|
|
suggestion="Add lists to document"
|
|
)
|
|
|
|
# Test Markdown formatting
|
|
md_output = error_collector.format_errors("markdown")
|
|
|
|
assert "# Validation Errors" in md_output
|
|
assert "## Errors" in md_output
|
|
assert "- **document.lists**:" in md_output
|
|
assert "Missing required lists section" in md_output
|
|
assert "- Suggestion: Add lists to document" in md_output
|
|
|
|
def test_validate_file_with_errors_string(self):
|
|
"""
|
|
ISSUE #8: Test validation with schema provided as JSON string.
|
|
"""
|
|
# Arrange
|
|
markdown_content = "# Test\n\nContent."
|
|
|
|
with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
|
|
f.write(markdown_content)
|
|
temp_file = Path(f.name)
|
|
|
|
schema_json = json.dumps({
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"properties": {
|
|
"lists": {
|
|
"type": "array",
|
|
"minItems": 1
|
|
}
|
|
},
|
|
"required": ["lists"]
|
|
})
|
|
|
|
try:
|
|
# Act - Validate using JSON string
|
|
error_collector = self.validator.validate_file_with_errors_string(temp_file, schema_json)
|
|
|
|
# Assert - Should detect missing lists
|
|
assert error_collector.has_errors()
|
|
errors = error_collector.get_errors_by_type(ValidationErrorType.MISSING_REQUIRED_SECTION)
|
|
assert len(errors) >= 1
|
|
|
|
finally:
|
|
temp_file.unlink()
|
|
|
|
def test_validate_file_with_errors_file(self):
|
|
"""
|
|
ISSUE #8: Test validation with schema from file.
|
|
"""
|
|
# Arrange
|
|
markdown_content = "# Test\n\nContent."
|
|
schema_content = {
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"properties": {
|
|
"code_blocks": {
|
|
"type": "array",
|
|
"minItems": 1
|
|
}
|
|
},
|
|
"required": ["code_blocks"]
|
|
}
|
|
|
|
with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as md_file:
|
|
md_file.write(markdown_content)
|
|
md_path = Path(md_file.name)
|
|
|
|
with NamedTemporaryFile(mode='w', suffix='.json', delete=False) as schema_file:
|
|
json.dump(schema_content, schema_file)
|
|
schema_path = Path(schema_file.name)
|
|
|
|
try:
|
|
# Act - Validate using schema file
|
|
error_collector = self.validator.validate_file_with_errors_file(md_path, schema_path)
|
|
|
|
# Assert - Should detect missing code blocks
|
|
assert error_collector.has_errors()
|
|
errors = error_collector.get_errors_by_type(ValidationErrorType.MISSING_REQUIRED_SECTION)
|
|
assert len(errors) >= 1
|
|
|
|
code_block_errors = [e for e in errors if "code_blocks" in e.message]
|
|
assert len(code_block_errors) >= 1
|
|
|
|
finally:
|
|
md_path.unlink()
|
|
schema_path.unlink()
|
|
|
|
def test_error_handling_file_not_found(self):
|
|
"""
|
|
ISSUE #8: Test error handling for non-existent files.
|
|
"""
|
|
non_existent_file = Path("/tmp/non_existent_file.md")
|
|
valid_schema = {
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"type": "object"
|
|
}
|
|
|
|
# Should raise FileNotFoundError
|
|
with pytest.raises(FileNotFoundError):
|
|
self.validator.validate_file_with_errors(non_existent_file, valid_schema)
|
|
|
|
def test_error_handling_invalid_schema(self):
|
|
"""
|
|
ISSUE #8: Test error handling for invalid schemas.
|
|
"""
|
|
# Arrange - Valid file, invalid schema
|
|
markdown_content = "# Test\n\nContent."
|
|
|
|
with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
|
|
f.write(markdown_content)
|
|
temp_file = Path(f.name)
|
|
|
|
invalid_schema = {
|
|
# Missing required $schema and type fields
|
|
"properties": {}
|
|
}
|
|
|
|
try:
|
|
# Should raise InvalidSchemaError
|
|
with pytest.raises(InvalidSchemaError):
|
|
self.validator.validate_file_with_errors(temp_file, invalid_schema)
|
|
|
|
finally:
|
|
temp_file.unlink()
|
|
|
|
def test_validation_error_collector_summary(self):
|
|
"""
|
|
ISSUE #8: Test error collector summary functionality.
|
|
"""
|
|
# Create collector with multiple error types and severities
|
|
collector = ValidationErrorCollector()
|
|
|
|
# Add different types of errors
|
|
collector.add_error(
|
|
ValidationErrorType.MISSING_REQUIRED_HEADING,
|
|
"Missing heading",
|
|
"headings.level_1"
|
|
)
|
|
collector.add_error(
|
|
ValidationErrorType.INSUFFICIENT_CONTENT,
|
|
"Not enough paragraphs",
|
|
"content.paragraphs",
|
|
severity="warning"
|
|
)
|
|
collector.add_error(
|
|
ValidationErrorType.MISSING_REQUIRED_SECTION,
|
|
"Missing lists",
|
|
"document.lists"
|
|
)
|
|
|
|
# Test summary
|
|
summary = collector.get_summary()
|
|
|
|
assert summary["total_errors"] == 3
|
|
assert summary["by_severity"]["error"] == 2
|
|
assert summary["by_severity"]["warning"] == 1
|
|
assert summary["by_type"]["missing_required_heading"] == 1
|
|
assert summary["by_type"]["insufficient_content"] == 1
|
|
assert summary["by_type"]["missing_required_section"] == 1
|
|
|
|
|