## 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>
504 lines
16 KiB
Python
504 lines
16 KiB
Python
"""
|
|
Test for Issue #65: Template Engine Foundation - Integration Tests
|
|
|
|
This test module validates complete template engine integration scenarios
|
|
for business document generation, implementing TDD8 Cycle 3.
|
|
|
|
Tests focus on:
|
|
- Real business document template rendering (invoices, reports)
|
|
- End-to-end template processing workflows
|
|
- Performance with realistic data volumes
|
|
- Integration with MarkdownMatters metadata structure
|
|
"""
|
|
|
|
import pytest
|
|
from typing import Dict, Any
|
|
|
|
|
|
class TestTemplateEngineIntegration:
|
|
"""Test suite for template engine integration scenarios."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment for each test."""
|
|
try:
|
|
from markitect.template.engine import TemplateEngine
|
|
self.engine = TemplateEngine()
|
|
except ImportError:
|
|
self.engine = None
|
|
|
|
def test_render_complete_invoice_template(self):
|
|
"""Test rendering a complete business invoice template.
|
|
|
|
Reference: Issue #65 - Template Engine Foundation
|
|
TDD Phase: Integration test for business use case
|
|
"""
|
|
# Arrange - Complete invoice template from examples/invoice_template.md
|
|
invoice_template = """---
|
|
title: "Invoice {{invoice_number}}"
|
|
date: "{{date}}"
|
|
due_date: "{{due_date}}"
|
|
customer_id: "{{customer.id}}"
|
|
---
|
|
|
|
{{company.name}}
|
|
{{company.address}}
|
|
{{company.city}}, {{company.state}} {{company.zip}}
|
|
{{company.email}} | {{company.phone}}
|
|
|
|
# Invoice {{invoice_number}}
|
|
|
|
**Bill To:**
|
|
{{customer.name}}
|
|
{{customer.address}}
|
|
{{customer.city}}, {{customer.state}} {{customer.zip}}
|
|
|
|
**Invoice Date:** {{date}}
|
|
**Due Date:** {{due_date}}
|
|
**Customer ID:** {{customer.id}}
|
|
|
|
## Summary
|
|
|
|
**Subtotal:** {{subtotal}}
|
|
**Tax ({{tax_rate}}%):** {{tax_amount}}
|
|
**Total:** {{total}} {{currency}}
|
|
|
|
## Payment Information
|
|
Please remit payment to {{company.name}} within {{payment_terms}} days.
|
|
|
|
---
|
|
{{!contentmatter}}
|
|
invoice_number: "{{invoice_number}}"
|
|
customer: "{{customer.name}}"
|
|
total_amount: {{total}}
|
|
currency: "{{currency}}"
|
|
status: "generated"
|
|
{{!/contentmatter}}
|
|
"""
|
|
|
|
# Test data representing realistic invoice data
|
|
invoice_data = {
|
|
"invoice_number": "INV-2025-001",
|
|
"date": "2025-01-15",
|
|
"due_date": "2025-02-14",
|
|
"company": {
|
|
"name": "MarkiTect Solutions",
|
|
"address": "123 Business Park",
|
|
"city": "Tech City",
|
|
"state": "CA",
|
|
"zip": "90210",
|
|
"email": "billing@markitect.com",
|
|
"phone": "(555) 123-4567"
|
|
},
|
|
"customer": {
|
|
"id": "CUST-001",
|
|
"name": "Acme Corporation",
|
|
"address": "456 Industry Blvd",
|
|
"city": "Enterprise",
|
|
"state": "NY",
|
|
"zip": "10001"
|
|
},
|
|
"subtotal": 1500.00,
|
|
"tax_rate": 8.5,
|
|
"tax_amount": 127.50,
|
|
"total": 1627.50,
|
|
"currency": "USD",
|
|
"payment_terms": "30"
|
|
}
|
|
|
|
# Act & Assert
|
|
if self.engine is None:
|
|
pytest.skip("TemplateEngine not implemented yet - TDD integration phase")
|
|
|
|
result = self.engine.render(invoice_template, invoice_data)
|
|
|
|
# Verify critical invoice elements are rendered correctly
|
|
assert "Invoice INV-2025-001" in result
|
|
assert "MarkiTect Solutions" in result
|
|
assert "Acme Corporation" in result
|
|
assert "123 Business Park" in result
|
|
assert "Tech City, CA 90210" in result
|
|
assert "Invoice Date:** 2025-01-15" in result
|
|
assert "Due Date:** 2025-02-14" in result
|
|
assert "Customer ID:** CUST-001" in result
|
|
assert "**Total:** 1627.5 USD" in result
|
|
assert "Tax (8.5%):** 127.5" in result
|
|
assert "within 30 days" in result
|
|
|
|
# Verify frontmatter is rendered
|
|
assert 'title: "Invoice INV-2025-001"' in result
|
|
assert 'customer_id: "CUST-001"' in result
|
|
|
|
# Verify contentmatter placeholders are rendered
|
|
assert 'invoice_number: "INV-2025-001"' in result
|
|
assert 'customer: "Acme Corporation"' in result
|
|
assert 'total_amount: 1627.5' in result
|
|
|
|
def test_render_business_report_template(self):
|
|
"""Test rendering a business report template with calculations.
|
|
|
|
Reference: Issue #65 - Template Engine Foundation
|
|
"""
|
|
# Arrange
|
|
report_template = """---
|
|
title: "{{report_type}} Report - {{period}}"
|
|
generated: "{{generated_date}}"
|
|
department: "{{department.name}}"
|
|
---
|
|
|
|
# {{report_type}} Report
|
|
**Period:** {{period}}
|
|
**Department:** {{department.name}}
|
|
**Generated:** {{generated_date}}
|
|
|
|
## Summary
|
|
- Total Revenue: {{metrics.revenue}} {{currency}}
|
|
- Total Expenses: {{metrics.expenses}} {{currency}}
|
|
- Net Profit: {{metrics.profit}} {{currency}}
|
|
- Profit Margin: {{metrics.profit_margin}}%
|
|
|
|
## Department Performance
|
|
**Manager:** {{department.manager}}
|
|
**Team Size:** {{department.team_size}}
|
|
**Budget Utilization:** {{department.budget_utilization}}%
|
|
|
|
Contact: {{department.contact.email}}
|
|
"""
|
|
|
|
report_data = {
|
|
"report_type": "Monthly Financial",
|
|
"period": "January 2025",
|
|
"generated_date": "2025-02-01",
|
|
"currency": "USD",
|
|
"department": {
|
|
"name": "Sales",
|
|
"manager": "Sarah Johnson",
|
|
"team_size": 12,
|
|
"budget_utilization": 85.5,
|
|
"contact": {
|
|
"email": "sales@company.com"
|
|
}
|
|
},
|
|
"metrics": {
|
|
"revenue": 125000.00,
|
|
"expenses": 87500.00,
|
|
"profit": 37500.00,
|
|
"profit_margin": 30.0
|
|
}
|
|
}
|
|
|
|
# Act & Assert
|
|
if self.engine is None:
|
|
pytest.skip("TemplateEngine not implemented yet")
|
|
|
|
result = self.engine.render(report_template, report_data)
|
|
|
|
# Verify report structure and data
|
|
assert "Monthly Financial Report" in result
|
|
assert "Period:** January 2025" in result
|
|
assert "Department:** Sales" in result
|
|
assert "Total Revenue: 125000.0 USD" in result
|
|
assert "Net Profit: 37500.0 USD" in result
|
|
assert "Profit Margin: 30.0%" in result
|
|
assert "Manager:** Sarah Johnson" in result
|
|
assert "Budget Utilization:** 85.5%" in result
|
|
assert "sales@company.com" in result
|
|
|
|
def test_error_handling_missing_nested_data(self):
|
|
"""Test comprehensive error handling with detailed context.
|
|
|
|
Reference: Issue #65 - Template Engine Foundation
|
|
"""
|
|
# Arrange
|
|
template = "Customer: {{customer.profile.details.name}}, Order: {{order.items.first.description}}"
|
|
incomplete_data = {
|
|
"customer": {
|
|
"profile": {
|
|
# Missing 'details' key
|
|
}
|
|
},
|
|
"order": {
|
|
# Missing 'items' key
|
|
"id": "ORD-001"
|
|
}
|
|
}
|
|
|
|
# Act & Assert
|
|
if self.engine is None:
|
|
pytest.skip("TemplateEngine not implemented yet")
|
|
|
|
# Test strict mode error with context
|
|
with pytest.raises(Exception) as exc_info:
|
|
self.engine.render(template, incomplete_data, strict=True)
|
|
|
|
error_message = str(exc_info.value)
|
|
# Should provide helpful context about what was available
|
|
assert ("details" in error_message.lower() or
|
|
"customer.profile.details.name" in error_message)
|
|
|
|
# Test lenient mode preserves placeholders
|
|
result = self.engine.render(template, incomplete_data, strict=False)
|
|
assert "{{customer.profile.details.name}}" in result
|
|
assert "{{order.items.first.description}}" in result
|
|
|
|
def test_performance_large_business_document(self):
|
|
"""Test performance with realistic large business document.
|
|
|
|
Reference: Issue #65 - Performance Requirements
|
|
"""
|
|
# Arrange - Large template with many variables
|
|
large_template = """# Annual Report {{year}}
|
|
|
|
## Executive Summary
|
|
Company: {{company.name}}
|
|
CEO: {{company.ceo}}
|
|
Revenue: {{financials.revenue}} {{currency}}
|
|
|
|
## Department Reports
|
|
"""
|
|
|
|
# Add many department sections
|
|
for i in range(50):
|
|
dept_prefix = f"departments.dept_{i}"
|
|
large_template += f"""
|
|
### Department {{{{{dept_prefix}.name}}}}
|
|
Manager: {{{{{dept_prefix}.manager}}}}
|
|
Budget: {{{{{dept_prefix}.budget}}}} {{{{currency}}}}
|
|
Team Size: {{{{{dept_prefix}.team_size}}}}
|
|
"""
|
|
|
|
# Generate corresponding data
|
|
departments_data = {}
|
|
for i in range(50):
|
|
departments_data[f"dept_{i}"] = {
|
|
"name": f"Department {i+1}",
|
|
"manager": f"Manager {i+1}",
|
|
"budget": (i+1) * 10000,
|
|
"team_size": (i % 20) + 5
|
|
}
|
|
|
|
large_data = {
|
|
"year": "2025",
|
|
"currency": "USD",
|
|
"company": {
|
|
"name": "Enterprise Corp",
|
|
"ceo": "John CEO"
|
|
},
|
|
"financials": {
|
|
"revenue": 50000000
|
|
},
|
|
"departments": departments_data
|
|
}
|
|
|
|
# Act & Assert
|
|
if self.engine is None:
|
|
pytest.skip("TemplateEngine not implemented yet")
|
|
|
|
import time
|
|
start_time = time.time()
|
|
result = self.engine.render(large_template, large_data)
|
|
render_time = time.time() - start_time
|
|
|
|
# Performance requirement: <100ms for large documents
|
|
assert render_time < 0.1
|
|
|
|
# Verify content was rendered
|
|
assert "Annual Report 2025" in result
|
|
assert "Enterprise Corp" in result
|
|
assert "50000000 USD" in result
|
|
assert "Department 1" in result
|
|
assert "Department 50" in result
|
|
|
|
def test_markdown_structure_preservation(self):
|
|
"""Test that complex markdown structure is preserved during rendering.
|
|
|
|
Reference: Issue #65 - Template Engine Foundation
|
|
"""
|
|
# Arrange - Complex markdown with various elements
|
|
complex_template = """---
|
|
title: "{{document.title}}"
|
|
author: "{{document.author}}"
|
|
---
|
|
|
|
# {{document.title}}
|
|
|
|
## Table of Contents
|
|
- [Introduction](#introduction)
|
|
- [Analysis](#analysis-{{section.id}})
|
|
- [Conclusion](#conclusion)
|
|
|
|
## Introduction
|
|
|
|
Welcome to **{{document.title}}** by *{{document.author}}*.
|
|
|
|
> This document provides {{description.type}} analysis for {{client.name}}.
|
|
|
|
### Code Example
|
|
```python
|
|
def process_{{operation.name}}():
|
|
return "{{operation.result}}"
|
|
```
|
|
|
|
## Analysis {{section.id}}
|
|
|
|
| Metric | Value | Target |
|
|
|--------|-------|--------|
|
|
| {{metrics.primary.name}} | {{metrics.primary.value}} | {{metrics.primary.target}} |
|
|
| {{metrics.secondary.name}} | {{metrics.secondary.value}} | {{metrics.secondary.target}} |
|
|
|
|
### Subsection
|
|
1. First point about {{analysis.point1}}
|
|
2. Second point about {{analysis.point2}}
|
|
3. Third point with [link]({{external.url}})
|
|
|
|
---
|
|
|
|
*Generated on {{generation.date}} by {{generation.system}}*
|
|
"""
|
|
|
|
template_data = {
|
|
"document": {
|
|
"title": "Business Analysis Report",
|
|
"author": "Analytics Team"
|
|
},
|
|
"description": {
|
|
"type": "comprehensive"
|
|
},
|
|
"client": {
|
|
"name": "Global Enterprises"
|
|
},
|
|
"section": {
|
|
"id": "Q1-2025"
|
|
},
|
|
"operation": {
|
|
"name": "quarterly_analysis",
|
|
"result": "success"
|
|
},
|
|
"metrics": {
|
|
"primary": {
|
|
"name": "Revenue",
|
|
"value": "$125K",
|
|
"target": "$120K"
|
|
},
|
|
"secondary": {
|
|
"name": "Growth",
|
|
"value": "12%",
|
|
"target": "10%"
|
|
}
|
|
},
|
|
"analysis": {
|
|
"point1": "market expansion",
|
|
"point2": "customer acquisition"
|
|
},
|
|
"external": {
|
|
"url": "https://example.com/data"
|
|
},
|
|
"generation": {
|
|
"date": "2025-01-15",
|
|
"system": "MarkiTect"
|
|
}
|
|
}
|
|
|
|
# Act & Assert
|
|
if self.engine is None:
|
|
pytest.skip("TemplateEngine not implemented yet")
|
|
|
|
result = self.engine.render(complex_template, template_data)
|
|
|
|
# Verify markdown structure preservation
|
|
assert "---" in result # Frontmatter
|
|
assert "# Business Analysis Report" in result # H1
|
|
assert "## Table of Contents" in result # H2
|
|
assert "- [Introduction](#introduction)" in result # List
|
|
assert "> This document provides" in result # Blockquote
|
|
assert "```python" in result # Code block
|
|
assert "def process_quarterly_analysis():" in result # Rendered in code
|
|
assert "| Revenue | $125K | $120K |" in result # Table
|
|
assert "1. First point about market expansion" in result # Numbered list
|
|
assert "[link](https://example.com/data)" in result # Link
|
|
assert "*Generated on 2025-01-15 by MarkiTect*" in result # Emphasis
|
|
|
|
# Verify frontmatter variables were rendered
|
|
assert 'title: "Business Analysis Report"' in result
|
|
assert 'author: "Analytics Team"' in result
|
|
|
|
|
|
class TestTemplateEngineWorkflows:
|
|
"""Test complete template processing workflows."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment."""
|
|
try:
|
|
from markitect.template.engine import TemplateEngine
|
|
from markitect.template.parser import TemplateParser
|
|
self.engine = TemplateEngine()
|
|
self.parser = TemplateParser()
|
|
except ImportError:
|
|
self.engine = None
|
|
self.parser = None
|
|
|
|
def test_template_validation_workflow(self):
|
|
"""Test complete template validation before rendering workflow.
|
|
|
|
Reference: Issue #65 - Template Engine Foundation
|
|
"""
|
|
# Arrange
|
|
template_with_errors = "Valid: {{name}}, Invalid: {{broken, Incomplete: {missing}"
|
|
valid_template = "Hello {{name}}, welcome to {{company}}!"
|
|
test_data = {"name": "Alice", "company": "MarkiTect"}
|
|
|
|
# Act & Assert
|
|
if self.engine is None or self.parser is None:
|
|
pytest.skip("Template components not implemented yet")
|
|
|
|
# Test validation of problematic template
|
|
errors = self.engine.validate_template(template_with_errors)
|
|
assert len(errors) > 0
|
|
|
|
# Test validation of good template
|
|
errors = self.engine.validate_template(valid_template)
|
|
assert len(errors) == 0
|
|
|
|
# Test rendering after validation
|
|
result = self.engine.render(valid_template, test_data)
|
|
assert result == "Hello Alice, welcome to MarkiTect!"
|
|
|
|
def test_data_completeness_analysis_workflow(self):
|
|
"""Test data completeness analysis before rendering.
|
|
|
|
Reference: Issue #65 - Template Engine Foundation
|
|
"""
|
|
# Arrange
|
|
template = "Invoice {{invoice_number}} for {{customer.name}} - Total: {{total}} {{currency}}"
|
|
complete_data = {
|
|
"invoice_number": "INV-001",
|
|
"customer": {"name": "Acme Corp"},
|
|
"total": 1500.00,
|
|
"currency": "USD"
|
|
}
|
|
incomplete_data = {
|
|
"invoice_number": "INV-001",
|
|
"customer": {"name": "Acme Corp"}
|
|
# Missing 'total' and 'currency'
|
|
}
|
|
|
|
# Act & Assert
|
|
if self.engine is None:
|
|
pytest.skip("TemplateEngine not implemented yet")
|
|
|
|
# Test with complete data
|
|
completeness = self.engine.check_data_completeness(template, complete_data)
|
|
assert completeness['completeness'] == 1.0
|
|
assert len(completeness['missing']) == 0
|
|
assert len(completeness['available']) == 4
|
|
|
|
# Test with incomplete data
|
|
completeness = self.engine.check_data_completeness(template, incomplete_data)
|
|
assert completeness['completeness'] < 1.0
|
|
assert 'total' in completeness['missing']
|
|
assert 'currency' in completeness['missing']
|
|
assert 'invoice_number' in completeness['available']
|
|
assert 'customer.name' in completeness['available']
|
|
|
|
|
|
if __name__ == '__main__':
|
|
pytest.main([__file__, '-v']) |