## 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>
294 lines
10 KiB
Python
294 lines
10 KiB
Python
"""
|
|
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']) |