""" T2.1: Nested Workflow Execution Tests that parent workflows can call child workflows, creating a proper execution hierarchy with correct parent-child relationships. Test validates: - Multi-level execution hierarchy (parent → child → grandchildren) - parent_execution_id chains are correct - Execution tree structure is maintained - Results propagate up from children to parent - Parent waits for all descendants to complete """ import time import pytest from helpers.client import AttuneClient from helpers.fixtures import create_echo_action, unique_ref from helpers.polling import ( wait_for_execution_count, wait_for_execution_status, ) def test_nested_workflow_execution(client: AttuneClient, test_pack): """ Test that workflows can call child workflows, creating proper execution hierarchy. Execution tree: Parent Workflow (execution_id=1) └─ Child Workflow (execution_id=2, parent=1) ├─ Task 1 (execution_id=3, parent=2) └─ Task 2 (execution_id=4, parent=2) """ print("\n" + "=" * 80) print("TEST: Nested Workflow Execution (T2.1)") print("=" * 80) pack_ref = test_pack["ref"] # ======================================================================== # STEP 1: Create child actions that will be called by child workflow # ======================================================================== print("\n[STEP 1] Creating child actions...") task1_action = create_echo_action( client=client, pack_ref=pack_ref, action_name=f"task1_{unique_ref()}", echo_message="Task 1 executed", ) print(f"✓ Created task1 action: {task1_action['ref']}") task2_action = create_echo_action( client=client, pack_ref=pack_ref, action_name=f"task2_{unique_ref()}", echo_message="Task 2 executed", ) print(f"✓ Created task2 action: {task2_action['ref']}") # ======================================================================== # STEP 2: Create child workflow action (calls task1 and task2) # ======================================================================== print("\n[STEP 2] Creating child workflow action...") child_workflow_action = client.create_action( pack_ref=pack_ref, data={ "name": f"child_workflow_{unique_ref()}", "description": "Child workflow with 2 tasks", "runner_type": "workflow", "entry_point": "", "enabled": True, "parameters": {}, "workflow_definition": { "tasks": [ { "name": "child_task_1", "action": task1_action["ref"], "parameters": {}, }, { "name": "child_task_2", "action": task2_action["ref"], "parameters": {}, }, ] }, }, ) child_workflow_ref = child_workflow_action["ref"] print(f"✓ Created child workflow: {child_workflow_ref}") print(f" - Tasks: child_task_1, child_task_2") # ======================================================================== # STEP 3: Create parent workflow action (calls child workflow) # ======================================================================== print("\n[STEP 3] Creating parent workflow action...") parent_workflow_action = client.create_action( pack_ref=pack_ref, data={ "name": f"parent_workflow_{unique_ref()}", "description": "Parent workflow that calls child workflow", "runner_type": "workflow", "entry_point": "", "enabled": True, "parameters": {}, "workflow_definition": { "tasks": [ { "name": "call_child_workflow", "action": child_workflow_ref, "parameters": {}, } ] }, }, ) parent_workflow_ref = parent_workflow_action["ref"] print(f"✓ Created parent workflow: {parent_workflow_ref}") print(f" - Calls: {child_workflow_ref}") # ======================================================================== # STEP 4: Execute parent workflow # ======================================================================== print("\n[STEP 4] Executing parent workflow...") parent_execution = client.create_execution( action_ref=parent_workflow_ref, parameters={} ) parent_execution_id = parent_execution["id"] print(f"✓ Parent execution created: ID={parent_execution_id}") # ======================================================================== # STEP 5: Wait for parent to complete # ======================================================================== print("\n[STEP 5] Waiting for parent workflow to complete...") parent_result = wait_for_execution_status( client=client, execution_id=parent_execution_id, expected_status="succeeded", timeout=30, ) print(f"✓ Parent workflow completed: status={parent_result['status']}") # ======================================================================== # STEP 6: Verify execution hierarchy # ======================================================================== print("\n[STEP 6] Verifying execution hierarchy...") # Get all executions for this test all_executions = client.list_executions(limit=100) # Filter to our executions (parent and children) our_executions = [ ex for ex in all_executions if ex["id"] == parent_execution_id or ex.get("parent_execution_id") == parent_execution_id ] print(f" Found {len(our_executions)} total executions") # Build execution tree parent_exec = None child_workflow_exec = None grandchild_execs = [] for ex in our_executions: if ex["id"] == parent_execution_id: parent_exec = ex elif ex.get("parent_execution_id") == parent_execution_id: # This is the child workflow execution child_workflow_exec = ex assert parent_exec is not None, "Parent execution not found" assert child_workflow_exec is not None, "Child workflow execution not found" print(f"\n Execution Tree:") print(f" └─ Parent (ID={parent_exec['id']}, status={parent_exec['status']})") print( f" └─ Child Workflow (ID={child_workflow_exec['id']}, parent={child_workflow_exec.get('parent_execution_id')}, status={child_workflow_exec['status']})" ) # Find grandchildren (task executions under child workflow) child_workflow_id = child_workflow_exec["id"] grandchild_execs = [ ex for ex in all_executions if ex.get("parent_execution_id") == child_workflow_id ] print(f" Found {len(grandchild_execs)} grandchild executions:") for gc in grandchild_execs: print( f" └─ Task (ID={gc['id']}, parent={gc.get('parent_execution_id')}, action={gc['action_ref']}, status={gc['status']})" ) # ======================================================================== # STEP 7: Validate success criteria # ======================================================================== print("\n[STEP 7] Validating success criteria...") # Criterion 1: At least 3 execution levels exist assert parent_exec is not None, "❌ Parent execution missing" assert child_workflow_exec is not None, "❌ Child workflow execution missing" assert len(grandchild_execs) >= 2, ( f"❌ Expected at least 2 grandchild executions, got {len(grandchild_execs)}" ) print(" ✓ 3 execution levels exist: parent → child → grandchildren") # Criterion 2: parent_execution_id chain is correct assert child_workflow_exec["parent_execution_id"] == parent_execution_id, ( f"❌ Child workflow parent_id incorrect: expected {parent_execution_id}, got {child_workflow_exec['parent_execution_id']}" ) print(f" ✓ Child workflow parent_execution_id = {parent_execution_id}") for gc in grandchild_execs: assert gc["parent_execution_id"] == child_workflow_id, ( f"❌ Grandchild parent_id incorrect: expected {child_workflow_id}, got {gc['parent_execution_id']}" ) print(f" ✓ All grandchildren have parent_execution_id = {child_workflow_id}") # Criterion 3: All executions completed successfully assert parent_exec["status"] == "succeeded", ( f"❌ Parent status not succeeded: {parent_exec['status']}" ) assert child_workflow_exec["status"] == "succeeded", ( f"❌ Child workflow status not succeeded: {child_workflow_exec['status']}" ) for gc in grandchild_execs: assert gc["status"] == "succeeded", ( f"❌ Grandchild {gc['id']} status not succeeded: {gc['status']}" ) print(" ✓ All executions completed successfully") # Criterion 4: Verify execution tree structure # Parent should have started first, then child, then grandchildren parent_start = parent_exec.get("start_timestamp") child_start = child_workflow_exec.get("start_timestamp") if parent_start and child_start: assert child_start >= parent_start, "❌ Child started before parent" print(f" ✓ Execution order correct: parent started before child") # Criterion 5: Verify all task executions reference correct actions task_refs = {gc["action_ref"] for gc in grandchild_execs} expected_refs = {task1_action["ref"], task2_action["ref"]} assert task_refs == expected_refs, ( f"❌ Task action refs don't match: expected {expected_refs}, got {task_refs}" ) print(f" ✓ All task actions executed correctly") # ======================================================================== # FINAL SUMMARY # ======================================================================== print("\n" + "=" * 80) print("TEST SUMMARY: Nested Workflow Execution") print("=" * 80) print(f"✓ Parent workflow executed: {parent_workflow_ref}") print(f"✓ Child workflow executed: {child_workflow_ref}") print(f"✓ Execution hierarchy validated:") print(f" - Parent execution ID: {parent_execution_id}") print(f" - Child workflow execution ID: {child_workflow_id}") print(f" - Grandchild executions: {len(grandchild_execs)}") print(f"✓ All {1 + 1 + len(grandchild_execs)} executions succeeded") print(f"✓ parent_execution_id chains correct") print(f"✓ Execution tree structure maintained") print("\n✅ TEST PASSED: Nested workflow execution works correctly!") print("=" * 80 + "\n") def test_deeply_nested_workflow(client: AttuneClient, test_pack): """ Test deeper nesting: 3 levels of workflows (great-grandchildren). Execution tree: Level 0: Root Workflow └─ Level 1: Child Workflow └─ Level 2: Grandchild Workflow └─ Level 3: Task Action """ print("\n" + "=" * 80) print("TEST: Deeply Nested Workflow (3 Levels)") print("=" * 80) pack_ref = test_pack["ref"] # ======================================================================== # STEP 1: Create leaf action (level 3) # ======================================================================== print("\n[STEP 1] Creating leaf action...") leaf_action = create_echo_action( client=client, pack_ref=pack_ref, action_name=f"leaf_{unique_ref()}", echo_message="Leaf action at level 3", ) print(f"✓ Created leaf action: {leaf_action['ref']}") # ======================================================================== # STEP 2: Create grandchild workflow (level 2) # ======================================================================== print("\n[STEP 2] Creating grandchild workflow (level 2)...") grandchild_workflow = client.create_action( pack_ref=pack_ref, data={ "name": f"grandchild_wf_{unique_ref()}", "description": "Grandchild workflow (level 2)", "runner_type": "workflow", "entry_point": "", "enabled": True, "parameters": {}, "workflow_definition": { "tasks": [ { "name": "call_leaf", "action": leaf_action["ref"], "parameters": {}, } ] }, }, ) print(f"✓ Created grandchild workflow: {grandchild_workflow['ref']}") # ======================================================================== # STEP 3: Create child workflow (level 1) # ======================================================================== print("\n[STEP 3] Creating child workflow (level 1)...") child_workflow = client.create_action( pack_ref=pack_ref, data={ "name": f"child_wf_{unique_ref()}", "description": "Child workflow (level 1)", "runner_type": "workflow", "entry_point": "", "enabled": True, "parameters": {}, "workflow_definition": { "tasks": [ { "name": "call_grandchild", "action": grandchild_workflow["ref"], "parameters": {}, } ] }, }, ) print(f"✓ Created child workflow: {child_workflow['ref']}") # ======================================================================== # STEP 4: Create root workflow (level 0) # ======================================================================== print("\n[STEP 4] Creating root workflow (level 0)...") root_workflow = client.create_action( pack_ref=pack_ref, data={ "name": f"root_wf_{unique_ref()}", "description": "Root workflow (level 0)", "runner_type": "workflow", "entry_point": "", "enabled": True, "parameters": {}, "workflow_definition": { "tasks": [ { "name": "call_child", "action": child_workflow["ref"], "parameters": {}, } ] }, }, ) print(f"✓ Created root workflow: {root_workflow['ref']}") # ======================================================================== # STEP 5: Execute root workflow # ======================================================================== print("\n[STEP 5] Executing root workflow...") root_execution = client.create_execution( action_ref=root_workflow["ref"], parameters={} ) root_execution_id = root_execution["id"] print(f"✓ Root execution created: ID={root_execution_id}") # ======================================================================== # STEP 6: Wait for completion # ======================================================================== print("\n[STEP 6] Waiting for all nested workflows to complete...") root_result = wait_for_execution_status( client=client, execution_id=root_execution_id, expected_status="succeeded", timeout=40, ) print(f"✓ Root workflow completed: status={root_result['status']}") # ======================================================================== # STEP 7: Verify 4-level hierarchy # ======================================================================== print("\n[STEP 7] Verifying 4-level execution hierarchy...") all_executions = client.list_executions(limit=100) # Build hierarchy by following parent_execution_id chain def find_children(parent_id): return [ ex for ex in all_executions if ex.get("parent_execution_id") == parent_id ] level0 = [ex for ex in all_executions if ex["id"] == root_execution_id][0] level1 = find_children(level0["id"]) level2 = [] for l1 in level1: level2.extend(find_children(l1["id"])) level3 = [] for l2 in level2: level3.extend(find_children(l2["id"])) print(f"\n Execution Hierarchy:") print(f" Level 0 (Root): {len([level0])} execution") print(f" Level 1 (Child): {len(level1)} execution(s)") print(f" Level 2 (Grandchild): {len(level2)} execution(s)") print(f" Level 3 (Leaf): {len(level3)} execution(s)") # ======================================================================== # STEP 8: Validate success criteria # ======================================================================== print("\n[STEP 8] Validating success criteria...") assert len(level1) >= 1, ( f"❌ Expected at least 1 level 1 execution, got {len(level1)}" ) assert len(level2) >= 1, ( f"❌ Expected at least 1 level 2 execution, got {len(level2)}" ) assert len(level3) >= 1, ( f"❌ Expected at least 1 level 3 execution, got {len(level3)}" ) print(" ✓ All 4 execution levels present") # Verify all succeeded all_execs = [level0] + level1 + level2 + level3 for ex in all_execs: assert ex["status"] == "succeeded", ( f"❌ Execution {ex['id']} failed: {ex['status']}" ) print(f" ✓ All {len(all_execs)} executions succeeded") # ======================================================================== # FINAL SUMMARY # ======================================================================== print("\n" + "=" * 80) print("TEST SUMMARY: Deeply Nested Workflow (3 Levels)") print("=" * 80) print(f"✓ 4-level execution hierarchy created:") print(f" - Root workflow (level 0)") print(f" - Child workflow (level 1)") print(f" - Grandchild workflow (level 2)") print(f" - Leaf action (level 3)") print(f"✓ Total executions: {len(all_execs)}") print(f"✓ All executions succeeded") print(f"✓ parent_execution_id chain validated") print("\n✅ TEST PASSED: Deep nesting works correctly!") print("=" * 80 + "\n")