424 lines
14 KiB
Python
424 lines
14 KiB
Python
#!/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")
|