""" Unit Tests: End Node Template Engine (Phase 4) Tests execute_end_node() mit AUTO und TEMPLATE modes. """ import pytest from datetime import datetime from workflow_executor import execute_end_node from workflow_models import ( WorkflowNode, EndNodeOutputMode, NodeStatus, NodeExecutionState ) class TestEndNodeTemplateEngine: """Tests für End Node Template Rendering""" def test_auto_mode_concatenates_all_analyses(self): """AUTO mode: Concatenates all analysis_core values""" # Setup: Node mit AUTO mode end_node = WorkflowNode( id="end", type="end", output_mode=EndNodeOutputMode.AUTO ) # Setup: Context mit 2 executed nodes context = { "node_results": { "analysis1": NodeExecutionState( node_id="analysis1", status=NodeStatus.EXECUTED, analysis_core="Body analysis result", decision_signals={"relevanz": "hoch"} ), "analysis2": NodeExecutionState( node_id="analysis2", status=NodeStatus.EXECUTED, analysis_core="Training recommendation", decision_signals={"prioritaet": "mittel"} ) } } # Execute result = execute_end_node(end_node, context) # Assert assert result.status == NodeStatus.EXECUTED assert "## analysis1" in result.analysis_core assert "Body analysis result" in result.analysis_core assert "## analysis2" in result.analysis_core assert "Training recommendation" in result.analysis_core print("✓ AUTO mode concatenation works") def test_auto_mode_skips_skipped_nodes(self): """AUTO mode: Skips nodes with status SKIPPED""" end_node = WorkflowNode( id="end", type="end", output_mode=EndNodeOutputMode.AUTO ) context = { "node_results": { "analysis1": NodeExecutionState( node_id="analysis1", status=NodeStatus.EXECUTED, analysis_core="Executed analysis" ), "analysis2": NodeExecutionState( node_id="analysis2", status=NodeStatus.SKIPPED, analysis_core="This should not appear" ) } } result = execute_end_node(end_node, context) assert "Executed analysis" in result.analysis_core assert "This should not appear" not in result.analysis_core print("✓ AUTO mode skips SKIPPED nodes") def test_template_mode_basic_rendering(self): """TEMPLATE mode: Renders Jinja2 template with node placeholders""" end_node = WorkflowNode( id="end", type="end", output_mode=EndNodeOutputMode.TEMPLATE, template=( "# Final Result\n\n" "## Analysis\n" "{{analysis1.analysis_core}}\n\n" "## Signals\n" "Relevanz: {{analysis1.relevanz}}" ) ) context = { "node_results": { "analysis1": NodeExecutionState( node_id="analysis1", status=NodeStatus.EXECUTED, analysis_core="Test analysis content", decision_signals={"relevanz": "hoch"} ) } } result = execute_end_node(end_node, context) assert result.status == NodeStatus.EXECUTED assert "# Final Result" in result.analysis_core assert "Test analysis content" in result.analysis_core assert "Relevanz: hoch" in result.analysis_core print("✓ TEMPLATE mode basic rendering works") def test_template_conditional_rendering(self): """TEMPLATE mode: Conditional rendering with {% if node_id %}""" end_node = WorkflowNode( id="end", type="end", output_mode=EndNodeOutputMode.TEMPLATE, template=( "# Result\n\n" "{% if analysis1 %}" "Analysis 1 executed\n" "{% endif %}" "{% if analysis2 %}" "Analysis 2 executed\n" "{% endif %}" ) ) context = { "node_results": { "analysis1": NodeExecutionState( node_id="analysis1", status=NodeStatus.EXECUTED, analysis_core="Content 1" ) # analysis2 not in results (optional path not taken) } } result = execute_end_node(end_node, context) assert "Analysis 1 executed" in result.analysis_core assert "Analysis 2 executed" not in result.analysis_core print("✓ TEMPLATE mode conditional rendering works") def test_template_default_values(self): """TEMPLATE mode: Default values for missing nodes""" end_node = WorkflowNode( id="end", type="end", output_mode=EndNodeOutputMode.TEMPLATE, template=( "Result: {{missing_node.analysis_core|default('Nicht verfügbar')}}" ) ) context = {"node_results": {}} result = execute_end_node(end_node, context) assert "Nicht verfügbar" in result.analysis_core print("✓ TEMPLATE mode default values work") def test_template_missing_template_fails(self): """TEMPLATE mode without template raises error""" end_node = WorkflowNode( id="end", type="end", output_mode=EndNodeOutputMode.TEMPLATE, template=None # Missing template ) context = {"node_results": {}} result = execute_end_node(end_node, context) assert result.status == NodeStatus.FAILED assert "no template defined" in result.error.lower() print("✓ TEMPLATE mode fails without template") def test_template_syntax_error_fails(self): """TEMPLATE mode with invalid syntax fails gracefully""" end_node = WorkflowNode( id="end", type="end", output_mode=EndNodeOutputMode.TEMPLATE, template="{% invalid syntax %}" ) context = {"node_results": {}} result = execute_end_node(end_node, context) assert result.status == NodeStatus.FAILED assert "template rendering failed" in result.error.lower() print("✓ TEMPLATE mode handles syntax errors") def test_auto_mode_empty_results(self): """AUTO mode with no executed nodes returns placeholder""" end_node = WorkflowNode( id="end", type="end", output_mode=EndNodeOutputMode.AUTO ) context = {"node_results": {}} result = execute_end_node(end_node, context) assert result.status == NodeStatus.EXECUTED assert "[No analysis generated]" in result.analysis_core print("✓ AUTO mode handles empty results") if __name__ == "__main__": # Run tests test = TestEndNodeTemplateEngine() print("\n=== Testing End Node Template Engine ===\n") test.test_auto_mode_concatenates_all_analyses() test.test_auto_mode_skips_skipped_nodes() test.test_template_mode_basic_rendering() test.test_template_conditional_rendering() test.test_template_default_values() test.test_template_missing_template_fails() test.test_template_syntax_error_fails() test.test_auto_mode_empty_results() print("\n✅ All tests passed!\n")