#!/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")