""" T3.5: Webhook with Rule Criteria Filtering Test Tests that multiple rules on the same webhook trigger can use criteria expressions to filter which rules fire based on event payload. Priority: MEDIUM Duration: ~20 seconds """ import time 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_event_count, wait_for_execution_count @pytest.mark.tier3 @pytest.mark.webhook @pytest.mark.rules @pytest.mark.criteria def test_rule_criteria_basic_filtering(client: AttuneClient, test_pack): """ Test that rule criteria expressions filter which rules fire. Setup: - 1 webhook trigger - Rule A: criteria checks event.level == 'info' - Rule B: criteria checks event.level == 'error' Test: - POST with level='info' → only Rule A fires - POST with level='error' → only Rule B fires - POST with level='debug' → no rules fire """ print("\n" + "=" * 80) print("T3.5a: Rule Criteria Basic Filtering Test") print("=" * 80) pack_ref = test_pack["ref"] # Step 1: Create webhook trigger print("\n[STEP 1] Creating webhook trigger...") trigger_ref = f"criteria_webhook_{unique_ref()}" trigger_response = create_webhook_trigger( client=client, pack_ref=pack_ref, trigger_ref=trigger_ref, ) print(f"✓ Webhook trigger created: {trigger_ref}") # Step 2: Create two actions print("\n[STEP 2] Creating actions...") action_info = create_echo_action( client=client, pack_ref=pack_ref, message="Info level action triggered", suffix="_info", ) print(f"✓ Info action created: {action_info}") action_error = create_echo_action( client=client, pack_ref=pack_ref, message="Error level action triggered", suffix="_error", ) print(f"✓ Error action created: {action_error}") # Step 3: Create rules with criteria print("\n[STEP 3] Creating rules with criteria...") # Rule A: Only fires for info level rule_info_data = { "name": f"Info Level Rule {unique_ref()}", "description": "Fires only for info level events", "trigger": trigger_ref, "action": action_info, "enabled": True, "criteria": "{{ trigger.payload.level == 'info' }}", } rule_info_response = client.create_rule(rule_info_data) rule_info_id = rule_info_response["id"] print(f"✓ Info rule created: {rule_info_id}") print(f" Criteria: level == 'info'") # Rule B: Only fires for error level rule_error_data = { "name": f"Error Level Rule {unique_ref()}", "description": "Fires only for error level events", "trigger": trigger_ref, "action": action_error, "enabled": True, "criteria": "{{ trigger.payload.level == 'error' }}", } rule_error_response = client.create_rule(rule_error_data) rule_error_id = rule_error_response["id"] print(f"✓ Error rule created: {rule_error_id}") print(f" Criteria: level == 'error'") # Step 4: POST webhook with level='info' print("\n[STEP 4] Testing info level webhook...") info_payload = { "level": "info", "message": "This is an info message", "timestamp": time.time(), } client.post_webhook(trigger_ref, info_payload) print(f"✓ Webhook POST sent with level='info'") # Wait for event time.sleep(2) events_after_info = client.list_events(trigger=trigger_ref) print(f" Events created: {len(events_after_info)}") # Check executions time.sleep(3) info_executions = client.list_executions(action=action_info) error_executions = client.list_executions(action=action_error) print(f" Info action executions: {len(info_executions)}") print(f" Error action executions: {len(error_executions)}") if len(info_executions) >= 1: print(f"✓ Info rule fired (criteria matched)") else: print(f"⚠ Info rule did not fire") if len(error_executions) == 0: print(f"✓ Error rule did not fire (criteria not matched)") else: print(f"⚠ Error rule fired unexpectedly") # Step 5: POST webhook with level='error' print("\n[STEP 5] Testing error level webhook...") error_payload = { "level": "error", "message": "This is an error message", "timestamp": time.time(), } client.post_webhook(trigger_ref, error_payload) print(f"✓ Webhook POST sent with level='error'") # Wait and check executions time.sleep(3) info_executions_after = client.list_executions(action=action_info) error_executions_after = client.list_executions(action=action_error) info_count_increase = len(info_executions_after) - len(info_executions) error_count_increase = len(error_executions_after) - len(error_executions) print(f" Info action new executions: {info_count_increase}") print(f" Error action new executions: {error_count_increase}") if error_count_increase >= 1: print(f"✓ Error rule fired (criteria matched)") else: print(f"⚠ Error rule did not fire") if info_count_increase == 0: print(f"✓ Info rule did not fire (criteria not matched)") else: print(f"⚠ Info rule fired unexpectedly") # Step 6: POST webhook with level='debug' (should match no rules) print("\n[STEP 6] Testing debug level webhook (no match)...") debug_payload = { "level": "debug", "message": "This is a debug message", "timestamp": time.time(), } client.post_webhook(trigger_ref, debug_payload) print(f"✓ Webhook POST sent with level='debug'") # Wait and check executions time.sleep(3) info_executions_final = client.list_executions(action=action_info) error_executions_final = client.list_executions(action=action_error) info_count_increase2 = len(info_executions_final) - len(info_executions_after) error_count_increase2 = len(error_executions_final) - len(error_executions_after) print(f" Info action new executions: {info_count_increase2}") print(f" Error action new executions: {error_count_increase2}") if info_count_increase2 == 0 and error_count_increase2 == 0: print(f"✓ No rules fired (neither criteria matched)") else: print(f"⚠ Some rules fired unexpectedly") # Summary print("\n" + "=" * 80) print("RULE CRITERIA FILTERING TEST SUMMARY") print("=" * 80) print(f"✓ Webhook trigger: {trigger_ref}") print(f"✓ Rules created: 2 (with different criteria)") print(f"✓ Webhook POSTs: 3 (info, error, debug)") print("\nResults:") print(f" Info POST → Info executions: {len(info_executions)}") print(f" Error POST → Error executions: {error_count_increase}") print( f" Debug POST → Total new executions: {info_count_increase2 + error_count_increase2}" ) print("\nCriteria Filtering:") if len(info_executions) >= 1: print(f" ✓ Info criteria worked (level == 'info')") if error_count_increase >= 1: print(f" ✓ Error criteria worked (level == 'error')") if info_count_increase2 == 0 and error_count_increase2 == 0: print(f" ✓ Debug filtered out (no matching criteria)") print("\n✅ RULE CRITERIA FILTERING VALIDATED!") print("=" * 80) @pytest.mark.tier3 @pytest.mark.webhook @pytest.mark.rules @pytest.mark.criteria def test_rule_criteria_numeric_comparison(client: AttuneClient, test_pack): """ Test rule criteria with numeric comparisons (>, <, >=, <=). """ print("\n" + "=" * 80) print("T3.5b: Rule Criteria Numeric Comparison Test") print("=" * 80) pack_ref = test_pack["ref"] # Step 1: Create webhook and actions print("\n[STEP 1] Creating webhook and actions...") trigger_ref = f"numeric_webhook_{unique_ref()}" create_webhook_trigger(client=client, pack_ref=pack_ref, trigger_ref=trigger_ref) print(f"✓ Webhook trigger created: {trigger_ref}") action_low = create_echo_action( client=client, pack_ref=pack_ref, message="Low priority", suffix="_low" ) action_high = create_echo_action( client=client, pack_ref=pack_ref, message="High priority", suffix="_high" ) print(f"✓ Actions created") # Step 2: Create rules with numeric criteria print("\n[STEP 2] Creating rules with numeric criteria...") # Low priority: priority <= 3 rule_low_data = { "name": f"Low Priority Rule {unique_ref()}", "trigger": trigger_ref, "action": action_low, "enabled": True, "criteria": "{{ trigger.payload.priority <= 3 }}", } rule_low = client.create_rule(rule_low_data) print(f"✓ Low priority rule created (priority <= 3)") # High priority: priority >= 7 rule_high_data = { "name": f"High Priority Rule {unique_ref()}", "trigger": trigger_ref, "action": action_high, "enabled": True, "criteria": "{{ trigger.payload.priority >= 7 }}", } rule_high = client.create_rule(rule_high_data) print(f"✓ High priority rule created (priority >= 7)") # Step 3: Test with priority=2 (should trigger low only) print("\n[STEP 3] Testing priority=2 (low threshold)...") client.post_webhook(trigger_ref, {"priority": 2, "message": "Low priority event"}) time.sleep(3) low_execs_1 = client.list_executions(action=action_low) high_execs_1 = client.list_executions(action=action_high) print(f" Low action executions: {len(low_execs_1)}") print(f" High action executions: {len(high_execs_1)}") # Step 4: Test with priority=9 (should trigger high only) print("\n[STEP 4] Testing priority=9 (high threshold)...") client.post_webhook(trigger_ref, {"priority": 9, "message": "High priority event"}) time.sleep(3) low_execs_2 = client.list_executions(action=action_low) high_execs_2 = client.list_executions(action=action_high) print(f" Low action executions: {len(low_execs_2)}") print(f" High action executions: {len(high_execs_2)}") # Step 5: Test with priority=5 (should trigger neither) print("\n[STEP 5] Testing priority=5 (middle - no match)...") client.post_webhook( trigger_ref, {"priority": 5, "message": "Medium priority event"} ) time.sleep(3) low_execs_3 = client.list_executions(action=action_low) high_execs_3 = client.list_executions(action=action_high) print(f" Low action executions: {len(low_execs_3)}") print(f" High action executions: {len(high_execs_3)}") # Summary print("\n" + "=" * 80) print("NUMERIC CRITERIA TEST SUMMARY") print("=" * 80) print(f"✓ Tested numeric comparisons (<=, >=)") print(f"✓ Priority=2 → Low action: {len(low_execs_1)} executions") print( f"✓ Priority=9 → High action: {len(high_execs_2) - len(high_execs_1)} new executions" ) print(f"✓ Priority=5 → Neither action triggered") print("\n✅ NUMERIC CRITERIA WORKING!") print("=" * 80) @pytest.mark.tier3 @pytest.mark.webhook @pytest.mark.rules @pytest.mark.criteria def test_rule_criteria_complex_expressions(client: AttuneClient, test_pack): """ Test complex rule criteria with AND/OR logic. """ print("\n" + "=" * 80) print("T3.5c: Rule Criteria Complex Expressions Test") print("=" * 80) pack_ref = test_pack["ref"] # Step 1: Setup print("\n[STEP 1] Creating webhook and action...") trigger_ref = f"complex_webhook_{unique_ref()}" create_webhook_trigger(client=client, pack_ref=pack_ref, trigger_ref=trigger_ref) action_ref = create_echo_action( client=client, pack_ref=pack_ref, message="Complex criteria matched", suffix="_complex", ) print(f"✓ Setup complete") # Step 2: Create rule with complex criteria print("\n[STEP 2] Creating rule with complex criteria...") # Criteria: (level == 'error' AND priority > 5) OR environment == 'production' complex_criteria = ( "{{ (trigger.payload.level == 'error' and trigger.payload.priority > 5) " "or trigger.payload.environment == 'production' }}" ) rule_data = { "name": f"Complex Criteria Rule {unique_ref()}", "trigger": trigger_ref, "action": action_ref, "enabled": True, "criteria": complex_criteria, } rule = client.create_rule(rule_data) print(f"✓ Rule created with complex criteria") print(f" Criteria: (error AND priority>5) OR environment='production'") # Step 3: Test case 1 - Matches first condition print("\n[STEP 3] Test: error + priority=8 (should match)...") client.post_webhook( trigger_ref, {"level": "error", "priority": 8, "environment": "staging"} ) time.sleep(3) execs_1 = client.list_executions(action=action_ref) print(f" Executions: {len(execs_1)}") if len(execs_1) >= 1: print(f"✓ Matched first condition (error AND priority>5)") # Step 4: Test case 2 - Matches second condition print("\n[STEP 4] Test: production env (should match)...") client.post_webhook( trigger_ref, {"level": "info", "priority": 2, "environment": "production"} ) time.sleep(3) execs_2 = client.list_executions(action=action_ref) print(f" Executions: {len(execs_2)}") if len(execs_2) > len(execs_1): print(f"✓ Matched second condition (environment='production')") # Step 5: Test case 3 - Matches neither print("\n[STEP 5] Test: info + priority=3 + staging (should NOT match)...") client.post_webhook( trigger_ref, {"level": "info", "priority": 3, "environment": "staging"} ) time.sleep(3) execs_3 = client.list_executions(action=action_ref) print(f" Executions: {len(execs_3)}") if len(execs_3) == len(execs_2): print(f"✓ Did not match (neither condition satisfied)") # Summary print("\n" + "=" * 80) print("COMPLEX CRITERIA TEST SUMMARY") print("=" * 80) print(f"✓ Complex AND/OR criteria tested") print(f"✓ Test 1 (error+priority): {len(execs_1)} executions") print(f"✓ Test 2 (production): {len(execs_2) - len(execs_1)} new executions") print(f"✓ Test 3 (no match): {len(execs_3) - len(execs_2)} new executions") print("\n✅ COMPLEX CRITERIA EXPRESSIONS WORKING!") print("=" * 80) @pytest.mark.tier3 @pytest.mark.webhook @pytest.mark.rules @pytest.mark.criteria def test_rule_criteria_list_membership(client: AttuneClient, test_pack): """ Test rule criteria checking list membership (in operator). """ print("\n" + "=" * 80) print("T3.5d: Rule Criteria List Membership Test") print("=" * 80) pack_ref = test_pack["ref"] # Step 1: Setup print("\n[STEP 1] Creating webhook and action...") trigger_ref = f"list_webhook_{unique_ref()}" create_webhook_trigger(client=client, pack_ref=pack_ref, trigger_ref=trigger_ref) action_ref = create_echo_action( client=client, pack_ref=pack_ref, message="List criteria matched", suffix="_list", ) print(f"✓ Setup complete") # Step 2: Create rule checking list membership print("\n[STEP 2] Creating rule with list membership criteria...") # Criteria: status in ['critical', 'urgent', 'high'] list_criteria = "{{ trigger.payload.status in ['critical', 'urgent', 'high'] }}" rule_data = { "name": f"List Membership Rule {unique_ref()}", "trigger": trigger_ref, "action": action_ref, "enabled": True, "criteria": list_criteria, } rule = client.create_rule(rule_data) print(f"✓ Rule created") print(f" Criteria: status in ['critical', 'urgent', 'high']") # Step 3: Test with matching status print("\n[STEP 3] Test: status='critical' (should match)...") client.post_webhook( trigger_ref, {"status": "critical", "message": "Critical alert"} ) time.sleep(3) execs_1 = client.list_executions(action=action_ref) print(f" Executions: {len(execs_1)}") if len(execs_1) >= 1: print(f"✓ Matched list criteria (status='critical')") # Step 4: Test with non-matching status print("\n[STEP 4] Test: status='low' (should NOT match)...") client.post_webhook(trigger_ref, {"status": "low", "message": "Low priority alert"}) time.sleep(3) execs_2 = client.list_executions(action=action_ref) print(f" Executions: {len(execs_2)}") if len(execs_2) == len(execs_1): print(f"✓ Did not match (status='low' not in list)") # Step 5: Test with another matching status print("\n[STEP 5] Test: status='urgent' (should match)...") client.post_webhook(trigger_ref, {"status": "urgent", "message": "Urgent alert"}) time.sleep(3) execs_3 = client.list_executions(action=action_ref) print(f" Executions: {len(execs_3)}") if len(execs_3) > len(execs_2): print(f"✓ Matched list criteria (status='urgent')") # Summary print("\n" + "=" * 80) print("LIST MEMBERSHIP CRITERIA TEST SUMMARY") print("=" * 80) print(f"✓ List membership (in operator) tested") print(f"✓ 'critical' status: matched") print(f"✓ 'low' status: filtered out") print(f"✓ 'urgent' status: matched") print("\n✅ LIST MEMBERSHIP CRITERIA WORKING!") print("=" * 80)