Files
markitect-main/tests/test_issue_65_template_substitution.py
tegwick bcbe78d04f feat: Complete Issue #65 Template Engine Foundation + Fix CLI Regression
## Issue #65 - Template Engine Foundation (COMPLETED)
- Implement complete TDD8 methodology with 30 comprehensive tests (100% passing)
- Add template variable parser with Unicode and dot notation support
- Add template rendering engine with strict/lenient modes
- Add business document generation (invoices, reports)
- Add CLI integration with `markitect template-render` command
- Add performance optimization (1000+ variables in <0.1s)

## Critical CLI Regression Fix
- Fix broken `markitect --help` due to import path issues in markitect/issues/base.py
- Add proper path resolution for domain module accessibility
- Add 12 comprehensive CLI integration tests to prevent future regressions
- Restore full CLI functionality with 35+ working commands

## Template Engine Architecture
- markitect/template/parser.py - Variable parsing with comprehensive validation
- markitect/template/engine.py - Template rendering with business logic
- markitect/template/__init__.py - Structured package exports
- Comprehensive exception hierarchy for robust error handling

## Test Coverage Excellence
- 30 Issue #65 tests: parser (9), substitution (14), integration (7)
- 12 CLI integration tests for regression prevention
- Business scenario validation with real invoice/report generation
- Performance benchmarking and error handling validation

## CLI Professional Enhancement
- Add template-render command with comprehensive options
- Fix import path issues preventing CLI access
- Add validation, data checking, output options
- Support JSON/YAML data formats with auto-detection

## Business Impact
- Transform MarkiTect from document analysis to business automation platform
- Enable professional invoice and report generation
- Provide robust CLI interface for document workflows
- Establish foundation for Epic #64 advanced template features

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-02 15:33:32 +02:00

346 lines
11 KiB
Python

"""
Test for Issue #65: Template Engine Foundation - Variable Substitution
This test module validates the template variable substitution functionality
for the MarkiTect template engine, implementing TDD8 Cycle 2.
Tests focus on:
- Basic variable substitution with data
- Nested object access with dot notation
- Missing variable handling (strict vs lenient modes)
- Error handling and validation
"""
import pytest
from typing import Dict, Any
class TestTemplateVariableSubstitution:
"""Test suite for template variable substitution functionality."""
def setup_method(self):
"""Set up test environment for each test."""
# Import the template engine (will be implemented)
try:
from markitect.template.engine import TemplateEngine
self.engine = TemplateEngine()
except ImportError:
self.engine = None
def test_substitute_simple_variables(self):
"""Test basic variable substitution from template strings.
Reference: Issue #65 - Template Engine Foundation
TDD Phase: RED (test should fail initially)
"""
# Arrange
template_text = "Hello {{name}}!"
data = {"name": "Alice"}
expected_result = "Hello Alice!"
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
result = self.engine.render(template_text, data)
assert result == expected_result
def test_substitute_nested_variables(self):
"""Test nested object variable substitution with dot notation.
Reference: Issue #65 - Template Engine Foundation
TDD Phase: RED (test should fail initially)
"""
# Arrange
template_text = "Customer: {{customer.name}}, Email: {{customer.contact.email}}"
data = {
"customer": {
"name": "Acme Corp",
"contact": {
"email": "info@acme.example"
}
}
}
expected_result = "Customer: Acme Corp, Email: info@acme.example"
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
result = self.engine.render(template_text, data)
assert result == expected_result
def test_substitute_multiple_variables(self):
"""Test substitution of multiple variables in same template.
Reference: Issue #65 - Template Engine Foundation
"""
# Arrange
template_text = "Invoice {{invoice_number}} for {{customer.name}} - Total: {{total}} {{currency}}"
data = {
"invoice_number": "INV-2025-001",
"customer": {"name": "Acme Corp"},
"total": 1500.00,
"currency": "EUR"
}
expected_result = "Invoice INV-2025-001 for Acme Corp - Total: 1500.0 EUR"
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
result = self.engine.render(template_text, data)
assert result == expected_result
def test_substitute_missing_variable_strict_mode(self):
"""Test handling of missing variables in strict mode (should raise error).
Reference: Issue #65 - Template Engine Foundation
"""
# Arrange
template_text = "Hello {{name}}, welcome to {{missing}}!"
data = {"name": "Alice"}
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
# Strict mode should raise an exception for missing variables
with pytest.raises(Exception) as exc_info:
self.engine.render(template_text, data, strict=True)
assert "missing" in str(exc_info.value).lower()
def test_substitute_missing_variable_lenient_mode(self):
"""Test handling of missing variables in lenient mode (preserve placeholder).
Reference: Issue #65 - Template Engine Foundation
"""
# Arrange
template_text = "Hello {{name}}, welcome to {{missing}}!"
data = {"name": "Alice"}
expected_result = "Hello Alice, welcome to {{missing}}!"
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
result = self.engine.render(template_text, data, strict=False)
assert result == expected_result
def test_substitute_empty_template(self):
"""Test substitution with template containing no variables.
Reference: Issue #65 - Template Engine Foundation
"""
# Arrange
template_text = "This is a regular markdown document with no variables."
data = {"name": "Alice"}
expected_result = template_text # Should remain unchanged
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
result = self.engine.render(template_text, data)
assert result == expected_result
def test_substitute_with_markdown_formatting(self):
"""Test that markdown formatting is preserved during substitution.
Reference: Issue #65 - Template Engine Foundation
"""
# Arrange
template_text = """---
title: "Invoice {{invoice_number}}"
---
# Invoice {{invoice_number}}
**Bill To**: {{customer.name}}
*Email*: {{customer.email}}
## Summary
- Total: {{total}}
- Currency: {{currency}}
"""
data = {
"invoice_number": "INV-2025-001",
"customer": {
"name": "Acme Corp",
"email": "billing@acme.example"
},
"total": 1500.00,
"currency": "EUR"
}
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
result = self.engine.render(template_text, data)
# Check that markdown structure is preserved
assert "---" in result # Frontmatter delimiters
assert "# Invoice INV-2025-001" in result # Header with substituted value
assert "**Bill To**: Acme Corp" in result # Bold formatting preserved
assert "*Email*: billing@acme.example" in result # Italic formatting preserved
assert "- Total: 1500.0" in result # List formatting preserved
def test_substitute_duplicate_variables(self):
"""Test that duplicate variables are all substituted correctly.
Reference: Issue #65 - Template Engine Foundation
"""
# Arrange
template_text = "{{name}} says hello to {{name}} and {{company}}"
data = {"name": "Alice", "company": "Acme Corp"}
expected_result = "Alice says hello to Alice and Acme Corp"
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
result = self.engine.render(template_text, data)
assert result == expected_result
def test_substitute_with_special_characters(self):
"""Test substitution with special characters and unicode.
Reference: Issue #65 - Template Engine Foundation
"""
# Arrange
template_text = "Grüße {{name}}, Café {{café.price}} €"
data = {
"name": "München",
"café": {"price": "3.50"}
}
expected_result = "Grüße München, Café 3.50 €"
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
result = self.engine.render(template_text, data)
assert result == expected_result
class TestTemplateSubstitutionEdgeCases:
"""Test edge cases and error conditions for template substitution."""
def setup_method(self):
"""Set up test environment."""
try:
from markitect.template.engine import TemplateEngine
self.engine = TemplateEngine()
except ImportError:
self.engine = None
def test_substitute_deeply_nested_objects(self):
"""Test substitution with deeply nested object access.
Reference: Issue #65 - Template Engine Foundation
"""
# Arrange
template_text = "{{level1.level2.level3.level4.value}}"
data = {
"level1": {
"level2": {
"level3": {
"level4": {
"value": "deep_value"
}
}
}
}
}
expected_result = "deep_value"
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
result = self.engine.render(template_text, data)
assert result == expected_result
def test_substitute_with_none_values(self):
"""Test substitution when data contains None values.
Reference: Issue #65 - Template Engine Foundation
"""
# Arrange
template_text = "Value: {{value}}, None: {{none_value}}"
data = {"value": "exists", "none_value": None}
expected_result = "Value: exists, None: None"
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
result = self.engine.render(template_text, data)
assert result == expected_result
def test_substitute_with_numeric_types(self):
"""Test substitution with various numeric data types.
Reference: Issue #65 - Template Engine Foundation
"""
# Arrange
template_text = "Int: {{int_val}}, Float: {{float_val}}, Bool: {{bool_val}}"
data = {
"int_val": 42,
"float_val": 3.14159,
"bool_val": True
}
expected_result = "Int: 42, Float: 3.14159, Bool: True"
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
result = self.engine.render(template_text, data)
assert result == expected_result
def test_substitute_performance_large_template(self):
"""Test substitution performance with large templates.
Reference: Issue #65 - Performance Requirements
"""
# Arrange
variables = [f"{{{{field_{i}}}}}" for i in range(100)]
template_text = " ".join(variables)
data = {f"field_{i}": f"value_{i}" for i in range(100)}
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
import time
start_time = time.time()
result = self.engine.render(template_text, data)
render_time = time.time() - start_time
# Performance requirement: <50ms for 100+ variables
assert render_time < 0.05
assert "value_0" in result
assert "value_99" in result
def test_substitute_invalid_data_type(self):
"""Test error handling when data is not a dictionary.
Reference: Issue #65 - Template Engine Foundation
"""
# Arrange
template_text = "Hello {{name}}!"
invalid_data = "not a dictionary"
# Act & Assert
if self.engine is None:
pytest.skip("TemplateEngine not implemented yet - TDD RED phase")
with pytest.raises(TypeError):
self.engine.render(template_text, invalid_data)
if __name__ == '__main__':
pytest.main([__file__, '-v'])