""" 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'])