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>
This commit is contained in:
504
tests/test_issue_65_template_integration.py
Normal file
504
tests/test_issue_65_template_integration.py
Normal file
@@ -0,0 +1,504 @@
|
||||
"""
|
||||
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'])
|
||||
Reference in New Issue
Block a user