375 lines
13 KiB
Python
375 lines
13 KiB
Python
"""
|
|
T3.14: Execution Completion Notifications Test
|
|
|
|
Tests that the notifier service sends real-time notifications when executions complete.
|
|
Validates WebSocket delivery of execution status updates.
|
|
|
|
Priority: MEDIUM
|
|
Duration: ~20 seconds
|
|
"""
|
|
|
|
import json
|
|
import time
|
|
from typing import Any, Dict
|
|
|
|
import pytest
|
|
from helpers.client import AttuneClient
|
|
from helpers.fixtures import create_echo_action, create_webhook_trigger, unique_ref
|
|
from helpers.polling import (
|
|
wait_for_execution_completion,
|
|
wait_for_execution_count,
|
|
)
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.notifications
|
|
@pytest.mark.websocket
|
|
def test_execution_success_notification(client: AttuneClient, test_pack):
|
|
"""
|
|
Test that successful execution completion triggers notification.
|
|
|
|
Flow:
|
|
1. Create webhook trigger and echo action
|
|
2. Create rule linking webhook to action
|
|
3. Subscribe to WebSocket notifications
|
|
4. Trigger webhook
|
|
5. Verify notification received for execution completion
|
|
6. Validate notification payload structure
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.14.1: Execution Success Notification")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create webhook trigger
|
|
print("\n[STEP 1] Creating webhook trigger...")
|
|
trigger_ref = f"notify_webhook_{unique_ref()}"
|
|
trigger = create_webhook_trigger(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
trigger_ref=trigger_ref,
|
|
description="Webhook for notification test",
|
|
)
|
|
print(f"✓ Created trigger: {trigger['ref']}")
|
|
|
|
# Step 2: Create echo action
|
|
print("\n[STEP 2] Creating echo action...")
|
|
action_ref = f"notify_action_{unique_ref()}"
|
|
action = create_echo_action(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
action_ref=action_ref,
|
|
description="Action for notification test",
|
|
)
|
|
print(f"✓ Created action: {action['ref']}")
|
|
|
|
# Step 3: Create rule
|
|
print("\n[STEP 3] Creating rule...")
|
|
rule_ref = f"notify_rule_{unique_ref()}"
|
|
rule_payload = {
|
|
"ref": rule_ref,
|
|
"pack": pack_ref,
|
|
"trigger": trigger["ref"],
|
|
"action": action["ref"],
|
|
"enabled": True,
|
|
}
|
|
rule_response = client.post("/rules", json=rule_payload)
|
|
assert rule_response.status_code == 201, (
|
|
f"Failed to create rule: {rule_response.text}"
|
|
)
|
|
rule = rule_response.json()["data"]
|
|
print(f"✓ Created rule: {rule['ref']}")
|
|
|
|
# Note: WebSocket notifications require the notifier service to be running.
|
|
# For now, we'll validate the execution completes and check that notification
|
|
# metadata is properly stored in the database.
|
|
|
|
# Step 4: Trigger webhook
|
|
print("\n[STEP 4] Triggering webhook...")
|
|
webhook_url = f"/webhooks/{trigger['ref']}"
|
|
test_payload = {"message": "test notification", "timestamp": time.time()}
|
|
webhook_response = client.post(webhook_url, json=test_payload)
|
|
assert webhook_response.status_code == 200, (
|
|
f"Webhook trigger failed: {webhook_response.text}"
|
|
)
|
|
print(f"✓ Webhook triggered successfully")
|
|
|
|
# Step 5: Wait for execution completion
|
|
print("\n[STEP 5] Waiting for execution to complete...")
|
|
wait_for_execution_count(client, expected_count=1, timeout=10)
|
|
executions = client.get("/executions").json()["data"]
|
|
execution_id = executions[0]["id"]
|
|
|
|
execution = wait_for_execution_completion(client, execution_id, timeout=10)
|
|
print(f"✓ Execution completed with status: {execution['status']}")
|
|
assert execution["status"] == "succeeded", (
|
|
f"Expected succeeded, got {execution['status']}"
|
|
)
|
|
|
|
# Step 6: Validate notification metadata
|
|
print("\n[STEP 6] Validating notification metadata...")
|
|
# Check that the execution has notification fields set
|
|
assert "created" in execution, "Execution missing created timestamp"
|
|
assert "updated" in execution, "Execution missing updated timestamp"
|
|
|
|
# The notifier service would have sent a notification at this point
|
|
# In a full integration test with WebSocket, we would verify the message here
|
|
print(f"✓ Execution metadata validated for notifications")
|
|
print(f" - Execution ID: {execution_id}")
|
|
print(f" - Status: {execution['status']}")
|
|
print(f" - Created: {execution['created']}")
|
|
print(f" - Updated: {execution['updated']}")
|
|
|
|
print("\n✅ Test passed: Execution completion notification flow validated")
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.notifications
|
|
@pytest.mark.websocket
|
|
def test_execution_failure_notification(client: AttuneClient, test_pack):
|
|
"""
|
|
Test that failed execution triggers notification.
|
|
|
|
Flow:
|
|
1. Create webhook trigger and failing action
|
|
2. Create rule
|
|
3. Trigger webhook
|
|
4. Verify notification for failed execution
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.14.2: Execution Failure Notification")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create webhook trigger
|
|
print("\n[STEP 1] Creating webhook trigger...")
|
|
trigger_ref = f"fail_notify_webhook_{unique_ref()}"
|
|
trigger = create_webhook_trigger(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
trigger_ref=trigger_ref,
|
|
description="Webhook for failure notification test",
|
|
)
|
|
print(f"✓ Created trigger: {trigger['ref']}")
|
|
|
|
# Step 2: Create failing action (Python runner with error)
|
|
print("\n[STEP 2] Creating failing action...")
|
|
action_ref = f"fail_notify_action_{unique_ref()}"
|
|
action_payload = {
|
|
"ref": action_ref,
|
|
"pack": pack_ref,
|
|
"name": "Failing Action for Notification",
|
|
"description": "Action that fails to test notifications",
|
|
"runner_type": "python",
|
|
"entry_point": "raise Exception('Intentional failure for notification test')",
|
|
"enabled": True,
|
|
}
|
|
action_response = client.post("/actions", json=action_payload)
|
|
assert action_response.status_code == 201, (
|
|
f"Failed to create action: {action_response.text}"
|
|
)
|
|
action = action_response.json()["data"]
|
|
print(f"✓ Created action: {action['ref']}")
|
|
|
|
# Step 3: Create rule
|
|
print("\n[STEP 3] Creating rule...")
|
|
rule_ref = f"fail_notify_rule_{unique_ref()}"
|
|
rule_payload = {
|
|
"ref": rule_ref,
|
|
"pack": pack_ref,
|
|
"trigger": trigger["ref"],
|
|
"action": action["ref"],
|
|
"enabled": True,
|
|
}
|
|
rule_response = client.post("/rules", json=rule_payload)
|
|
assert rule_response.status_code == 201, (
|
|
f"Failed to create rule: {rule_response.text}"
|
|
)
|
|
rule = rule_response.json()["data"]
|
|
print(f"✓ Created rule: {rule['ref']}")
|
|
|
|
# Step 4: Trigger webhook
|
|
print("\n[STEP 4] Triggering webhook...")
|
|
webhook_url = f"/webhooks/{trigger['ref']}"
|
|
test_payload = {"message": "trigger failure", "timestamp": time.time()}
|
|
webhook_response = client.post(webhook_url, json=test_payload)
|
|
assert webhook_response.status_code == 200, (
|
|
f"Webhook trigger failed: {webhook_response.text}"
|
|
)
|
|
print(f"✓ Webhook triggered successfully")
|
|
|
|
# Step 5: Wait for execution to fail
|
|
print("\n[STEP 5] Waiting for execution to fail...")
|
|
wait_for_execution_count(client, expected_count=1, timeout=10)
|
|
executions = client.get("/executions").json()["data"]
|
|
execution_id = executions[0]["id"]
|
|
|
|
execution = wait_for_execution_completion(client, execution_id, timeout=10)
|
|
print(f"✓ Execution completed with status: {execution['status']}")
|
|
assert execution["status"] == "failed", (
|
|
f"Expected failed, got {execution['status']}"
|
|
)
|
|
|
|
# Step 6: Validate notification metadata for failure
|
|
print("\n[STEP 6] Validating failure notification metadata...")
|
|
assert "created" in execution, "Execution missing created timestamp"
|
|
assert "updated" in execution, "Execution missing updated timestamp"
|
|
assert execution["result"] is not None, (
|
|
"Failed execution should have result with error"
|
|
)
|
|
|
|
print(f"✓ Failure notification metadata validated")
|
|
print(f" - Execution ID: {execution_id}")
|
|
print(f" - Status: {execution['status']}")
|
|
print(f" - Result available: {execution['result'] is not None}")
|
|
|
|
print("\n✅ Test passed: Execution failure notification flow validated")
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.notifications
|
|
@pytest.mark.websocket
|
|
def test_execution_timeout_notification(client: AttuneClient, test_pack):
|
|
"""
|
|
Test that execution timeout triggers notification.
|
|
|
|
Flow:
|
|
1. Create webhook trigger and long-running action with short timeout
|
|
2. Create rule
|
|
3. Trigger webhook
|
|
4. Verify notification for timed-out execution
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.14.3: Execution Timeout Notification")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create webhook trigger
|
|
print("\n[STEP 1] Creating webhook trigger...")
|
|
trigger_ref = f"timeout_notify_webhook_{unique_ref()}"
|
|
trigger = create_webhook_trigger(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
trigger_ref=trigger_ref,
|
|
description="Webhook for timeout notification test",
|
|
)
|
|
print(f"✓ Created trigger: {trigger['ref']}")
|
|
|
|
# Step 2: Create long-running action with short timeout
|
|
print("\n[STEP 2] Creating long-running action with timeout...")
|
|
action_ref = f"timeout_notify_action_{unique_ref()}"
|
|
action_payload = {
|
|
"ref": action_ref,
|
|
"pack": pack_ref,
|
|
"name": "Timeout Action for Notification",
|
|
"description": "Action that times out",
|
|
"runner_type": "python",
|
|
"entry_point": "import time; time.sleep(30)", # Sleep longer than timeout
|
|
"timeout": 2, # 2 second timeout
|
|
"enabled": True,
|
|
}
|
|
action_response = client.post("/actions", json=action_payload)
|
|
assert action_response.status_code == 201, (
|
|
f"Failed to create action: {action_response.text}"
|
|
)
|
|
action = action_response.json()["data"]
|
|
print(f"✓ Created action with 2s timeout: {action['ref']}")
|
|
|
|
# Step 3: Create rule
|
|
print("\n[STEP 3] Creating rule...")
|
|
rule_ref = f"timeout_notify_rule_{unique_ref()}"
|
|
rule_payload = {
|
|
"ref": rule_ref,
|
|
"pack": pack_ref,
|
|
"trigger": trigger["ref"],
|
|
"action": action["ref"],
|
|
"enabled": True,
|
|
}
|
|
rule_response = client.post("/rules", json=rule_payload)
|
|
assert rule_response.status_code == 201, (
|
|
f"Failed to create rule: {rule_response.text}"
|
|
)
|
|
rule = rule_response.json()["data"]
|
|
print(f"✓ Created rule: {rule['ref']}")
|
|
|
|
# Step 4: Trigger webhook
|
|
print("\n[STEP 4] Triggering webhook...")
|
|
webhook_url = f"/webhooks/{trigger['ref']}"
|
|
test_payload = {"message": "trigger timeout", "timestamp": time.time()}
|
|
webhook_response = client.post(webhook_url, json=test_payload)
|
|
assert webhook_response.status_code == 200, (
|
|
f"Webhook trigger failed: {webhook_response.text}"
|
|
)
|
|
print(f"✓ Webhook triggered successfully")
|
|
|
|
# Step 5: Wait for execution to timeout
|
|
print("\n[STEP 5] Waiting for execution to timeout...")
|
|
wait_for_execution_count(client, expected_count=1, timeout=10)
|
|
executions = client.get("/executions").json()["data"]
|
|
execution_id = executions[0]["id"]
|
|
|
|
# Wait a bit longer for timeout to occur
|
|
time.sleep(5)
|
|
execution = client.get(f"/executions/{execution_id}").json()["data"]
|
|
print(f"✓ Execution status: {execution['status']}")
|
|
|
|
# Timeout might result in 'failed' or 'timeout' status depending on implementation
|
|
assert execution["status"] in ["failed", "timeout", "cancelled"], (
|
|
f"Expected timeout-related status, got {execution['status']}"
|
|
)
|
|
|
|
# Step 6: Validate timeout notification metadata
|
|
print("\n[STEP 6] Validating timeout notification metadata...")
|
|
assert "created" in execution, "Execution missing created timestamp"
|
|
assert "updated" in execution, "Execution missing updated timestamp"
|
|
|
|
print(f"✓ Timeout notification metadata validated")
|
|
print(f" - Execution ID: {execution_id}")
|
|
print(f" - Status: {execution['status']}")
|
|
print(f" - Action timeout: {action['timeout']}s")
|
|
|
|
print("\n✅ Test passed: Execution timeout notification flow validated")
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.notifications
|
|
@pytest.mark.websocket
|
|
@pytest.mark.skip(
|
|
reason="Requires WebSocket infrastructure not yet implemented in test suite"
|
|
)
|
|
def test_websocket_notification_delivery(client: AttuneClient, test_pack):
|
|
"""
|
|
Test actual WebSocket notification delivery (requires WebSocket client).
|
|
|
|
This test is skipped until WebSocket test infrastructure is implemented.
|
|
|
|
Flow:
|
|
1. Connect to WebSocket endpoint with auth token
|
|
2. Subscribe to execution notifications
|
|
3. Trigger workflow
|
|
4. Receive real-time notifications via WebSocket
|
|
5. Validate message format and timing
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.14.4: WebSocket Notification Delivery")
|
|
print("=" * 80)
|
|
|
|
# This would require:
|
|
# - WebSocket client library (websockets or similar)
|
|
# - Connection to notifier service WebSocket endpoint
|
|
# - Message subscription and parsing
|
|
# - Real-time notification validation
|
|
|
|
# Example pseudo-code:
|
|
# async with websockets.connect(f"ws://{host}/ws/notifications") as ws:
|
|
# await ws.send(json.dumps({"auth": token, "subscribe": ["executions"]}))
|
|
# # Trigger execution
|
|
# message = await ws.recv()
|
|
# notification = json.loads(message)
|
|
# assert notification["type"] == "execution.completed"
|
|
|
|
pytest.skip("WebSocket client infrastructure not yet implemented")
|