481 lines
18 KiB
Python
481 lines
18 KiB
Python
"""
|
|
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")
|