re-uploading work
This commit is contained in:
374
tests/e2e/tier3/test_t3_14_execution_notifications.py
Normal file
374
tests/e2e/tier3/test_t3_14_execution_notifications.py
Normal file
@@ -0,0 +1,374 @@
|
||||
"""
|
||||
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")
|
||||
Reference in New Issue
Block a user