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:
294
tests/test_cli_integration.py
Normal file
294
tests/test_cli_integration.py
Normal file
@@ -0,0 +1,294 @@
|
||||
"""
|
||||
CLI Integration Tests - Prevent CLI Entry Point Regressions
|
||||
|
||||
This test module validates that the CLI entry point is properly accessible
|
||||
and core commands work as expected. It prevents regressions like broken
|
||||
imports or missing entry points that would break user accessibility.
|
||||
|
||||
Tests focus on:
|
||||
- CLI entry point accessibility (markitect --help)
|
||||
- Core command availability and help text
|
||||
- Template rendering CLI functionality
|
||||
- Error handling in CLI commands
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import tempfile
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class TestCLIEntryPoint:
|
||||
"""Test CLI entry point accessibility."""
|
||||
|
||||
def test_markitect_help_accessible(self):
|
||||
"""Test that markitect --help works and shows expected content.
|
||||
|
||||
This prevents regressions where import errors break CLI accessibility.
|
||||
"""
|
||||
# Run markitect --help
|
||||
result = subprocess.run(
|
||||
['markitect', '--help'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
# Should exit successfully
|
||||
assert result.returncode == 0, f"CLI help failed with error: {result.stderr}"
|
||||
|
||||
# Should contain core CLI information
|
||||
output = result.stdout
|
||||
assert "MarkiTect - Advanced Markdown engine" in output
|
||||
assert "Commands:" in output
|
||||
assert "--help" in output
|
||||
|
||||
# Should not have import errors
|
||||
assert "ModuleNotFoundError" not in result.stderr
|
||||
assert "ImportError" not in result.stderr
|
||||
|
||||
def test_core_commands_available(self):
|
||||
"""Test that core commands are listed in help output."""
|
||||
result = subprocess.run(
|
||||
['markitect', '--help'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
output = result.stdout
|
||||
|
||||
# Core functionality commands
|
||||
assert "ingest" in output
|
||||
assert "list" in output
|
||||
assert "status" in output or "stats" in output
|
||||
|
||||
# Template engine command (Issue #65)
|
||||
assert "template-render" in output
|
||||
|
||||
# Schema commands
|
||||
assert "schema-generate" in output
|
||||
assert "validate" in output
|
||||
|
||||
def test_template_render_command_help(self):
|
||||
"""Test that template-render command help is accessible."""
|
||||
result = subprocess.run(
|
||||
['markitect', 'template-render', '--help'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert result.returncode == 0
|
||||
output = result.stdout
|
||||
|
||||
assert "Render a template with data" in output
|
||||
assert "TEMPLATE_FILE" in output
|
||||
assert "DATA_FILE" in output
|
||||
assert "--output" in output
|
||||
assert "--strict" in output
|
||||
assert "--lenient" in output
|
||||
|
||||
|
||||
class TestTemplateRenderCLI:
|
||||
"""Test template-render CLI functionality end-to-end."""
|
||||
|
||||
def test_template_render_basic_functionality(self):
|
||||
"""Test basic template rendering via CLI."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Create test template
|
||||
template_file = temp_path / "test.md"
|
||||
template_file.write_text("# {{title}}\n\nHello {{name}}!")
|
||||
|
||||
# Create test data
|
||||
data_file = temp_path / "data.json"
|
||||
data = {"title": "Test Document", "name": "World"}
|
||||
data_file.write_text(json.dumps(data))
|
||||
|
||||
# Run template rendering
|
||||
result = subprocess.run(
|
||||
['markitect', 'template-render', str(template_file), str(data_file)],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert result.returncode == 0, f"Template rendering failed: {result.stderr}"
|
||||
|
||||
output = result.stdout
|
||||
assert "# Test Document" in output
|
||||
assert "Hello World!" in output
|
||||
|
||||
def test_template_render_with_output_file(self):
|
||||
"""Test template rendering with output file option."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Create test files
|
||||
template_file = temp_path / "template.md"
|
||||
template_file.write_text("Result: {{value}}")
|
||||
|
||||
data_file = temp_path / "data.json"
|
||||
data_file.write_text(json.dumps({"value": "SUCCESS"}))
|
||||
|
||||
output_file = temp_path / "output.md"
|
||||
|
||||
# Run with output option
|
||||
result = subprocess.run(
|
||||
['markitect', 'template-render',
|
||||
str(template_file), str(data_file),
|
||||
'--output', str(output_file)],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert result.returncode == 0
|
||||
assert "Template rendered successfully" in result.stdout
|
||||
|
||||
# Check output file was created
|
||||
assert output_file.exists()
|
||||
content = output_file.read_text()
|
||||
assert "Result: SUCCESS" in content
|
||||
|
||||
def test_template_render_validation_mode(self):
|
||||
"""Test template rendering with validation options."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Create valid template
|
||||
template_file = temp_path / "valid.md"
|
||||
template_file.write_text("Valid: {{name}}")
|
||||
|
||||
data_file = temp_path / "data.json"
|
||||
data_file.write_text(json.dumps({"name": "test"}))
|
||||
|
||||
# Run with validation
|
||||
result = subprocess.run(
|
||||
['markitect', 'template-render',
|
||||
str(template_file), str(data_file),
|
||||
'--validate', '--check-data'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert result.returncode == 0
|
||||
assert "Valid: test" in result.stdout
|
||||
|
||||
def test_template_render_error_handling(self):
|
||||
"""Test CLI error handling for invalid inputs."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Test with non-existent template file
|
||||
result = subprocess.run(
|
||||
['markitect', 'template-render', 'nonexistent.md', 'data.json'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert result.returncode != 0
|
||||
assert "does not exist" in result.stderr.lower() or "not found" in result.stderr.lower()
|
||||
|
||||
def test_template_render_strict_vs_lenient_mode(self):
|
||||
"""Test strict vs lenient mode behavior."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Template with missing variable
|
||||
template_file = temp_path / "template.md"
|
||||
template_file.write_text("Hello {{name}}, missing: {{missing}}")
|
||||
|
||||
# Data missing the 'missing' variable
|
||||
data_file = temp_path / "data.json"
|
||||
data_file.write_text(json.dumps({"name": "Alice"}))
|
||||
|
||||
# Test strict mode (should fail)
|
||||
result_strict = subprocess.run(
|
||||
['markitect', 'template-render', str(template_file), str(data_file), '--strict'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert result_strict.returncode != 0
|
||||
|
||||
# Test lenient mode (should succeed)
|
||||
result_lenient = subprocess.run(
|
||||
['markitect', 'template-render', str(template_file), str(data_file), '--lenient'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert result_lenient.returncode == 0
|
||||
output = result_lenient.stdout
|
||||
assert "Hello Alice" in output
|
||||
assert "{{missing}}" in output # Placeholder preserved
|
||||
|
||||
|
||||
class TestCLIRegressionPrevention:
|
||||
"""Tests specifically designed to catch common CLI regression patterns."""
|
||||
|
||||
def test_import_paths_valid(self):
|
||||
"""Test that all CLI module imports work correctly.
|
||||
|
||||
This catches issues like the domain module import that broke CLI access.
|
||||
"""
|
||||
# Try to import the CLI module directly
|
||||
try:
|
||||
import markitect.cli
|
||||
# Should not raise ImportError or ModuleNotFoundError
|
||||
except (ImportError, ModuleNotFoundError) as e:
|
||||
pytest.fail(f"CLI module import failed: {e}")
|
||||
|
||||
def test_cli_entry_point_configuration(self):
|
||||
"""Test that the CLI entry point is properly configured."""
|
||||
# Check that the entry point script exists and is executable
|
||||
import shutil
|
||||
markitect_path = shutil.which('markitect')
|
||||
|
||||
assert markitect_path is not None, "markitect command not found in PATH"
|
||||
assert os.access(markitect_path, os.X_OK), "markitect command is not executable"
|
||||
|
||||
def test_no_runtime_import_errors(self):
|
||||
"""Test that basic CLI commands don't have runtime import errors."""
|
||||
# Test a few key commands to ensure no import errors at runtime
|
||||
commands_to_test = [
|
||||
['markitect', '--version'], # Should show version or error gracefully
|
||||
['markitect', 'list', '--help'], # Core command help
|
||||
['markitect', 'template-render', '--help'], # New template command help
|
||||
]
|
||||
|
||||
for cmd in commands_to_test:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
# Even if command fails, it shouldn't be due to import errors
|
||||
assert "ModuleNotFoundError" not in result.stderr
|
||||
assert "ImportError" not in result.stderr
|
||||
assert "No module named" not in result.stderr
|
||||
|
||||
def test_template_engine_availability(self):
|
||||
"""Test that template engine is properly available to CLI."""
|
||||
# Create minimal test to ensure template engine can be imported by CLI
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
template_file = temp_path / "minimal.md"
|
||||
template_file.write_text("test")
|
||||
|
||||
data_file = temp_path / "minimal.json"
|
||||
data_file.write_text("{}")
|
||||
|
||||
# This should not fail with import errors
|
||||
result = subprocess.run(
|
||||
['markitect', 'template-render', str(template_file), str(data_file)],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
# Should succeed or fail gracefully, but not with import errors
|
||||
assert "ImportError" not in result.stderr
|
||||
assert "ModuleNotFoundError" not in result.stderr
|
||||
assert "Template engine not available" not in result.stderr
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
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'])
|
||||
222
tests/test_issue_65_template_parser.py
Normal file
222
tests/test_issue_65_template_parser.py
Normal file
@@ -0,0 +1,222 @@
|
||||
"""
|
||||
Test for Issue #65: Template Engine Foundation - Template Variable Parser
|
||||
|
||||
This test module validates the core template variable parsing functionality
|
||||
for the MarkiTect template engine, implementing TDD8 Cycle 1.
|
||||
|
||||
Tests focus on:
|
||||
- Basic variable parsing from template strings
|
||||
- Nested object variable extraction
|
||||
- Markdown structure preservation during parsing
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from typing import List, Set
|
||||
|
||||
|
||||
class TestTemplateVariableParser:
|
||||
"""Test suite for template variable parsing functionality."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Set up test environment for each test."""
|
||||
# Import the template parser (will be implemented)
|
||||
# For now, this will fail - following TDD RED phase
|
||||
try:
|
||||
from markitect.template.parser import TemplateParser
|
||||
self.parser = TemplateParser()
|
||||
except ImportError:
|
||||
# Expected to fail initially - TDD RED phase
|
||||
self.parser = None
|
||||
|
||||
def test_parse_simple_variables(self):
|
||||
"""Test basic variable parsing from template strings.
|
||||
|
||||
Reference: Issue #65 - Template Engine Foundation
|
||||
TDD Phase: RED (test should fail initially)
|
||||
"""
|
||||
# Arrange
|
||||
template_text = "Hello {{name}}, welcome to {{company}}!"
|
||||
expected_variables = {"name", "company"}
|
||||
|
||||
# Act & Assert
|
||||
if self.parser is None:
|
||||
pytest.skip("TemplateParser not implemented yet - TDD RED phase")
|
||||
|
||||
variables = self.parser.extract_variables(template_text)
|
||||
assert isinstance(variables, (list, set))
|
||||
assert set(variables) == expected_variables
|
||||
|
||||
def test_parse_nested_variables(self):
|
||||
"""Test nested object variable parsing 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}}"
|
||||
expected_variables = {"customer.name", "customer.contact.email"}
|
||||
|
||||
# Act & Assert
|
||||
if self.parser is None:
|
||||
pytest.skip("TemplateParser not implemented yet - TDD RED phase")
|
||||
|
||||
variables = self.parser.extract_variables(template_text)
|
||||
assert set(variables) == expected_variables
|
||||
|
||||
def test_parse_markdown_with_variables(self):
|
||||
"""Test variable parsing from markdown content while preserving structure.
|
||||
|
||||
Reference: Issue #65 - Template Engine Foundation
|
||||
TDD Phase: RED (test should fail initially)
|
||||
"""
|
||||
# Arrange
|
||||
template_text = """---
|
||||
title: "Invoice {{invoice_number}}"
|
||||
customer: "{{customer.name}}"
|
||||
---
|
||||
|
||||
# Invoice {{invoice_number}}
|
||||
|
||||
**Bill To**: {{customer.name}}
|
||||
**Email**: {{customer.email}}
|
||||
**Total**: {{total}} {{currency}}
|
||||
|
||||
## Line Items
|
||||
| Description | Amount |
|
||||
|-------------|--------|
|
||||
| Service | {{service.amount}} |
|
||||
"""
|
||||
expected_variables = {
|
||||
"invoice_number",
|
||||
"customer.name",
|
||||
"customer.email",
|
||||
"total",
|
||||
"currency",
|
||||
"service.amount"
|
||||
}
|
||||
|
||||
# Act & Assert
|
||||
if self.parser is None:
|
||||
pytest.skip("TemplateParser not implemented yet - TDD RED phase")
|
||||
|
||||
variables = self.parser.extract_variables(template_text)
|
||||
assert set(variables) == expected_variables
|
||||
|
||||
def test_parse_duplicate_variables(self):
|
||||
"""Test that duplicate variables are handled correctly.
|
||||
|
||||
Reference: Issue #65 - Template Engine Foundation
|
||||
"""
|
||||
# Arrange
|
||||
template_text = "{{name}} says hello to {{name}} and {{company}}"
|
||||
expected_variables = {"name", "company"}
|
||||
|
||||
# Act & Assert
|
||||
if self.parser is None:
|
||||
pytest.skip("TemplateParser not implemented yet - TDD RED phase")
|
||||
|
||||
variables = self.parser.extract_variables(template_text)
|
||||
assert set(variables) == expected_variables
|
||||
|
||||
def test_parse_empty_template(self):
|
||||
"""Test parsing template with no variables.
|
||||
|
||||
Reference: Issue #65 - Template Engine Foundation
|
||||
"""
|
||||
# Arrange
|
||||
template_text = "This is a regular markdown document with no variables."
|
||||
expected_variables = set()
|
||||
|
||||
# Act & Assert
|
||||
if self.parser is None:
|
||||
pytest.skip("TemplateParser not implemented yet - TDD RED phase")
|
||||
|
||||
variables = self.parser.extract_variables(template_text)
|
||||
assert set(variables) == expected_variables
|
||||
|
||||
def test_parse_malformed_variables(self):
|
||||
"""Test handling of malformed variable syntax.
|
||||
|
||||
Reference: Issue #65 - Template Engine Foundation
|
||||
"""
|
||||
# Arrange
|
||||
template_text = "Valid: {{name}}, Invalid: {{broken, Incomplete: {missing}"
|
||||
expected_variables = {"name"} # Only valid variables should be extracted
|
||||
|
||||
# Act & Assert
|
||||
if self.parser is None:
|
||||
pytest.skip("TemplateParser not implemented yet - TDD RED phase")
|
||||
|
||||
variables = self.parser.extract_variables(template_text)
|
||||
assert set(variables) == expected_variables
|
||||
|
||||
def test_parse_nested_braces(self):
|
||||
"""Test handling of nested braces and complex syntax.
|
||||
|
||||
Reference: Issue #65 - Template Engine Foundation
|
||||
"""
|
||||
# Arrange
|
||||
template_text = "Code: {{code.value}} and JSON: {\"key\": \"{{data.field}}\"}"
|
||||
expected_variables = {"code.value", "data.field"}
|
||||
|
||||
# Act & Assert
|
||||
if self.parser is None:
|
||||
pytest.skip("TemplateParser not implemented yet - TDD RED phase")
|
||||
|
||||
variables = self.parser.extract_variables(template_text)
|
||||
assert set(variables) == expected_variables
|
||||
|
||||
|
||||
class TestTemplateParserEdgeCases:
|
||||
"""Test edge cases and error conditions for template parser."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Set up test environment."""
|
||||
try:
|
||||
from markitect.template.parser import TemplateParser
|
||||
self.parser = TemplateParser()
|
||||
except ImportError:
|
||||
self.parser = None
|
||||
|
||||
def test_parse_extremely_long_template(self):
|
||||
"""Test parsing performance with large templates.
|
||||
|
||||
Reference: Issue #65 - Performance Requirements
|
||||
"""
|
||||
# Arrange
|
||||
# Create a large template with many variables
|
||||
variables = [f"{{{{field_{i}}}}}" for i in range(1000)]
|
||||
template_text = " ".join(variables)
|
||||
expected_count = 1000
|
||||
|
||||
# Act & Assert
|
||||
if self.parser is None:
|
||||
pytest.skip("TemplateParser not implemented yet - TDD RED phase")
|
||||
|
||||
import time
|
||||
start_time = time.time()
|
||||
variables = self.parser.extract_variables(template_text)
|
||||
parse_time = time.time() - start_time
|
||||
|
||||
assert len(variables) == expected_count
|
||||
assert parse_time < 0.1 # Should parse large templates quickly
|
||||
|
||||
def test_parse_unicode_variables(self):
|
||||
"""Test parsing templates with unicode content.
|
||||
|
||||
Reference: Issue #65 - Template Engine Foundation
|
||||
"""
|
||||
# Arrange
|
||||
template_text = "Grüße {{name}}, café {{café.price}} €"
|
||||
expected_variables = {"name", "café.price"}
|
||||
|
||||
# Act & Assert
|
||||
if self.parser is None:
|
||||
pytest.skip("TemplateParser not implemented yet - TDD RED phase")
|
||||
|
||||
variables = self.parser.extract_variables(template_text)
|
||||
assert set(variables) == expected_variables
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
346
tests/test_issue_65_template_substitution.py
Normal file
346
tests/test_issue_65_template_substitution.py
Normal file
@@ -0,0 +1,346 @@
|
||||
"""
|
||||
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'])
|
||||
Reference in New Issue
Block a user