re-uploading work
This commit is contained in:
423
tests/e2e/tier1/test_t1_04_webhook_trigger.py
Normal file
423
tests/e2e/tier1/test_t1_04_webhook_trigger.py
Normal file
@@ -0,0 +1,423 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
T1.4: Webhook Trigger with Payload
|
||||
|
||||
Tests that a webhook POST triggers an action with payload data.
|
||||
|
||||
Test Flow:
|
||||
1. Create webhook trigger (generates unique URL)
|
||||
2. Create action that echoes webhook payload
|
||||
3. Create rule linking webhook → action
|
||||
4. POST JSON payload to webhook URL
|
||||
5. Verify event created with correct payload
|
||||
6. Verify execution receives payload as parameters
|
||||
7. Verify action output includes webhook data
|
||||
|
||||
Success Criteria:
|
||||
- Webhook trigger generates unique URL (/api/v1/webhooks/{trigger_id})
|
||||
- POST to webhook creates event immediately
|
||||
- Event payload matches POST body
|
||||
- Rule evaluates and creates enforcement
|
||||
- Execution receives webhook data as input
|
||||
- Action can access webhook payload fields
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from helpers import (
|
||||
AttuneClient,
|
||||
create_echo_action,
|
||||
create_rule,
|
||||
create_webhook_trigger,
|
||||
wait_for_event_count,
|
||||
wait_for_execution_count,
|
||||
wait_for_execution_status,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.tier1
|
||||
@pytest.mark.webhook
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.timeout(30)
|
||||
class TestWebhookTrigger:
|
||||
"""Test webhook trigger automation flow"""
|
||||
|
||||
def test_webhook_trigger_with_payload(self, client: AttuneClient, pack_ref: str):
|
||||
"""Test that webhook POST triggers action with payload"""
|
||||
|
||||
print(f"\n=== T1.4: Webhook Trigger with Payload ===")
|
||||
|
||||
# Step 1: Create webhook trigger
|
||||
print("\n[1/6] Creating webhook trigger...")
|
||||
trigger = create_webhook_trigger(client=client, pack_ref=pack_ref)
|
||||
print(f"✓ Created trigger: {trigger['label']} (ID: {trigger['id']})")
|
||||
print(f" Ref: {trigger['ref']}")
|
||||
print(f" Webhook URL: /api/v1/webhooks/{trigger['id']}")
|
||||
assert "webhook" in trigger["ref"].lower() or trigger.get(
|
||||
"webhook_enabled", False
|
||||
)
|
||||
|
||||
# Step 2: Create echo action
|
||||
print("\n[2/6] Creating echo action...")
|
||||
action = create_echo_action(client=client, pack_ref=pack_ref)
|
||||
action_ref = action["ref"]
|
||||
print(f"✓ Created action: {action_ref} (ID: {action['id']})")
|
||||
|
||||
# Step 3: Create rule linking webhook → action
|
||||
print("\n[3/6] Creating rule...")
|
||||
|
||||
# Capture timestamp before rule creation for filtering
|
||||
from datetime import datetime, timezone
|
||||
|
||||
rule_creation_time = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
rule = create_rule(
|
||||
client=client,
|
||||
trigger_id=trigger["id"],
|
||||
action_ref=action_ref,
|
||||
pack_ref=pack_ref,
|
||||
enabled=True,
|
||||
action_parameters={
|
||||
"message": "{{ trigger.data.message }}",
|
||||
"count": 1,
|
||||
},
|
||||
)
|
||||
print(f"✓ Created rule: {rule['label']} (ID: {rule['id']})")
|
||||
print(f" Rule creation timestamp: {rule_creation_time}")
|
||||
assert rule["enabled"] is True
|
||||
|
||||
# Step 4: POST to webhook
|
||||
print("\n[4/6] Firing webhook with payload...")
|
||||
webhook_payload = {
|
||||
"event_type": "test.webhook",
|
||||
"message": "Hello from webhook!",
|
||||
"user_id": 12345,
|
||||
"metadata": {"source": "e2e_test", "timestamp": time.time()},
|
||||
}
|
||||
print(f" Payload: {webhook_payload}")
|
||||
|
||||
event_response = client.fire_webhook(
|
||||
trigger_id=trigger["id"], payload=webhook_payload
|
||||
)
|
||||
print(f"✓ Webhook fired")
|
||||
print(f" Event ID: {event_response.get('id')}")
|
||||
|
||||
# Step 5: Verify event created
|
||||
print("\n[5/6] Verifying event created...")
|
||||
events = wait_for_event_count(
|
||||
client=client,
|
||||
expected_count=1,
|
||||
trigger_id=trigger["id"],
|
||||
timeout=10,
|
||||
poll_interval=0.5,
|
||||
)
|
||||
|
||||
assert len(events) >= 1, "Expected at least 1 event"
|
||||
event = events[0]
|
||||
|
||||
print(f"✓ Event created (ID: {event['id']})")
|
||||
print(f" Trigger ID: {event['trigger']}")
|
||||
print(f" Payload: {event.get('payload')}")
|
||||
|
||||
# Verify event payload matches webhook payload
|
||||
assert event["trigger"] == trigger["id"]
|
||||
event_payload = event.get("payload", {})
|
||||
|
||||
# Check key fields from webhook payload
|
||||
for key in ["event_type", "message", "user_id"]:
|
||||
assert key in event_payload, f"Missing key '{key}' in event payload"
|
||||
assert event_payload[key] == webhook_payload[key], (
|
||||
f"Event payload mismatch for '{key}': "
|
||||
f"expected {webhook_payload[key]}, got {event_payload[key]}"
|
||||
)
|
||||
|
||||
print(f"✓ Event payload matches webhook payload")
|
||||
|
||||
# Step 6: Verify execution completed with webhook data
|
||||
print("\n[6/6] Verifying execution with webhook data...")
|
||||
|
||||
executions = wait_for_execution_count(
|
||||
client=client,
|
||||
expected_count=1,
|
||||
rule_id=rule["id"],
|
||||
created_after=rule_creation_time,
|
||||
timeout=20,
|
||||
poll_interval=0.5,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
assert len(executions) >= 1, "Expected at least 1 execution"
|
||||
execution = executions[0]
|
||||
|
||||
print(f"✓ Execution created (ID: {execution['id']})")
|
||||
print(f" Status: {execution['status']}")
|
||||
|
||||
# Wait for execution to complete
|
||||
if execution["status"] not in ["succeeded", "failed", "canceled"]:
|
||||
execution = wait_for_execution_status(
|
||||
client=client,
|
||||
execution_id=execution["id"],
|
||||
expected_status="succeeded",
|
||||
timeout=15,
|
||||
)
|
||||
|
||||
assert execution["status"] == "succeeded", (
|
||||
f"Execution failed with status: {execution['status']}"
|
||||
)
|
||||
|
||||
# Verify execution received webhook data
|
||||
print(f"\n Execution details:")
|
||||
print(f" Action: {execution['action_ref']}")
|
||||
print(f" Parameters: {execution.get('parameters')}")
|
||||
print(f" Result: {execution.get('result')}")
|
||||
|
||||
# Final summary
|
||||
print("\n=== Test Summary ===")
|
||||
print(f"✓ Webhook trigger created")
|
||||
print(f"✓ Webhook POST created event")
|
||||
print(f"✓ Event payload correct")
|
||||
print(f"✓ Execution completed successfully")
|
||||
print(f"✓ Webhook data accessible in action")
|
||||
print(f"✓ Test PASSED")
|
||||
|
||||
def test_multiple_webhook_posts(self, client: AttuneClient, pack_ref: str):
|
||||
"""Test multiple webhook POSTs create multiple executions"""
|
||||
|
||||
print(f"\n=== T1.4b: Multiple Webhook POSTs ===")
|
||||
|
||||
num_posts = 3
|
||||
|
||||
# Create automation
|
||||
print("\n[1/4] Setting up webhook automation...")
|
||||
from datetime import datetime, timezone
|
||||
|
||||
test_start = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
trigger = create_webhook_trigger(client=client, pack_ref=pack_ref)
|
||||
action = create_echo_action(client=client, pack_ref=pack_ref)
|
||||
rule = create_rule(
|
||||
client=client,
|
||||
trigger_id=trigger["id"],
|
||||
action_ref=action["ref"],
|
||||
pack_ref=pack_ref,
|
||||
)
|
||||
print(f"✓ Setup complete")
|
||||
|
||||
# Fire webhook multiple times
|
||||
print(f"\n[2/4] Firing webhook {num_posts} times...")
|
||||
for i in range(num_posts):
|
||||
payload = {
|
||||
"iteration": i + 1,
|
||||
"message": f"Webhook post #{i + 1}",
|
||||
"timestamp": time.time(),
|
||||
}
|
||||
client.fire_webhook(trigger_id=trigger["id"], payload=payload)
|
||||
print(f" ✓ POST {i + 1}/{num_posts}")
|
||||
time.sleep(0.5) # Small delay between posts
|
||||
|
||||
# Verify events created
|
||||
print(f"\n[3/4] Verifying {num_posts} events created...")
|
||||
events = wait_for_event_count(
|
||||
client=client,
|
||||
expected_count=num_posts,
|
||||
trigger_id=trigger["id"],
|
||||
timeout=15,
|
||||
poll_interval=0.5,
|
||||
)
|
||||
|
||||
print(f"✓ {len(events)} events created")
|
||||
assert len(events) >= num_posts
|
||||
|
||||
# Verify executions created
|
||||
print(f"\n[4/4] Verifying {num_posts} executions completed...")
|
||||
executions = wait_for_execution_count(
|
||||
client=client,
|
||||
expected_count=num_posts,
|
||||
rule_id=rule["id"],
|
||||
created_after=test_start,
|
||||
timeout=20,
|
||||
poll_interval=0.5,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
print(f"✓ {len(executions)} executions created")
|
||||
|
||||
# Wait for all to complete
|
||||
succeeded = 0
|
||||
for execution in executions[:num_posts]:
|
||||
if execution["status"] not in ["succeeded", "failed", "canceled"]:
|
||||
execution = wait_for_execution_status(
|
||||
client=client,
|
||||
execution_id=execution["id"],
|
||||
expected_status="succeeded",
|
||||
timeout=10,
|
||||
)
|
||||
if execution["status"] == "succeeded":
|
||||
succeeded += 1
|
||||
|
||||
print(f"✓ {succeeded}/{num_posts} executions succeeded")
|
||||
assert succeeded == num_posts
|
||||
|
||||
print("\n=== Test Summary ===")
|
||||
print(f"✓ {num_posts} webhook POSTs handled")
|
||||
print(f"✓ {num_posts} events created")
|
||||
print(f"✓ {num_posts} executions completed")
|
||||
print(f"✓ Test PASSED")
|
||||
|
||||
def test_webhook_with_complex_payload(self, client: AttuneClient, pack_ref: str):
|
||||
"""Test webhook with nested JSON payload"""
|
||||
|
||||
print(f"\n=== T1.4c: Webhook with Complex Payload ===")
|
||||
|
||||
# Setup
|
||||
print("\n[1/3] Setting up webhook automation...")
|
||||
from datetime import datetime, timezone
|
||||
|
||||
test_start = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
trigger = create_webhook_trigger(client=client, pack_ref=pack_ref)
|
||||
action = create_echo_action(client=client, pack_ref=pack_ref)
|
||||
rule = create_rule(
|
||||
client=client,
|
||||
trigger_id=trigger["id"],
|
||||
action_ref=action["ref"],
|
||||
pack_ref=pack_ref,
|
||||
)
|
||||
print(f"✓ Setup complete")
|
||||
|
||||
# Complex nested payload
|
||||
print("\n[2/3] Posting complex payload...")
|
||||
complex_payload = {
|
||||
"event": "user.signup",
|
||||
"user": {
|
||||
"id": 99999,
|
||||
"email": "test@example.com",
|
||||
"profile": {
|
||||
"name": "Test User",
|
||||
"age": 30,
|
||||
"preferences": {
|
||||
"theme": "dark",
|
||||
"notifications": True,
|
||||
},
|
||||
},
|
||||
"tags": ["new", "trial", "priority"],
|
||||
},
|
||||
"metadata": {
|
||||
"source": "web",
|
||||
"ip": "192.168.1.100",
|
||||
"user_agent": "Mozilla/5.0",
|
||||
},
|
||||
"timestamp": "2024-01-01T00:00:00Z",
|
||||
}
|
||||
|
||||
client.fire_webhook(trigger_id=trigger["id"], payload=complex_payload)
|
||||
print(f"✓ Complex payload posted")
|
||||
|
||||
# Verify event and execution
|
||||
print("\n[3/3] Verifying event and execution...")
|
||||
events = wait_for_event_count(
|
||||
client=client,
|
||||
expected_count=1,
|
||||
trigger_id=trigger["id"],
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert len(events) >= 1
|
||||
event = events[0]
|
||||
event_payload = event.get("payload", {})
|
||||
|
||||
# Verify nested structure preserved
|
||||
assert "user" in event_payload
|
||||
assert "profile" in event_payload["user"]
|
||||
assert "preferences" in event_payload["user"]["profile"]
|
||||
assert event_payload["user"]["profile"]["preferences"]["theme"] == "dark"
|
||||
assert event_payload["user"]["tags"] == ["new", "trial", "priority"]
|
||||
|
||||
print(f"✓ Complex nested payload preserved")
|
||||
|
||||
# Verify execution
|
||||
executions = wait_for_execution_count(
|
||||
client=client,
|
||||
expected_count=1,
|
||||
rule_id=rule["id"],
|
||||
created_after=test_start,
|
||||
timeout=15,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
execution = executions[0]
|
||||
if execution["status"] not in ["succeeded", "failed", "canceled"]:
|
||||
execution = wait_for_execution_status(
|
||||
client=client,
|
||||
execution_id=execution["id"],
|
||||
expected_status="succeeded",
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert execution["status"] == "succeeded"
|
||||
print(f"✓ Execution completed successfully")
|
||||
|
||||
print("\n=== Test Summary ===")
|
||||
print(f"✓ Complex nested payload handled")
|
||||
print(f"✓ JSON structure preserved")
|
||||
print(f"✓ Execution completed")
|
||||
print(f"✓ Test PASSED")
|
||||
|
||||
def test_webhook_without_payload(self, client: AttuneClient, pack_ref: str):
|
||||
"""Test webhook POST without payload (empty body)"""
|
||||
|
||||
print(f"\n=== T1.4d: Webhook without Payload ===")
|
||||
|
||||
# Setup
|
||||
from datetime import datetime, timezone
|
||||
|
||||
test_start = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
trigger = create_webhook_trigger(client=client, pack_ref=pack_ref)
|
||||
action = create_echo_action(client=client, pack_ref=pack_ref)
|
||||
rule = create_rule(
|
||||
client=client,
|
||||
trigger_id=trigger["id"],
|
||||
action_ref=action["ref"],
|
||||
pack_ref=pack_ref,
|
||||
)
|
||||
|
||||
# Fire webhook with empty payload
|
||||
print("\nFiring webhook with empty payload...")
|
||||
client.fire_webhook(trigger_id=trigger["id"], payload={})
|
||||
|
||||
# Verify event created
|
||||
events = wait_for_event_count(
|
||||
client=client,
|
||||
expected_count=1,
|
||||
trigger_id=trigger["id"],
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert len(events) >= 1
|
||||
event = events[0]
|
||||
print(f"✓ Event created with empty payload")
|
||||
|
||||
# Verify execution
|
||||
executions = wait_for_execution_count(
|
||||
client=client,
|
||||
expected_count=1,
|
||||
rule_id=rule["id"],
|
||||
created_after=test_start,
|
||||
timeout=15,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
execution = executions[0]
|
||||
if execution["status"] not in ["succeeded", "failed", "canceled"]:
|
||||
execution = wait_for_execution_status(
|
||||
client=client,
|
||||
execution_id=execution["id"],
|
||||
expected_status="succeeded",
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert execution["status"] == "succeeded"
|
||||
print(f"✓ Execution succeeded with empty payload")
|
||||
print(f"✓ Test PASSED")
|
||||
Reference in New Issue
Block a user