465 lines
16 KiB
Python
465 lines
16 KiB
Python
"""
|
|
T3.16: Rule Trigger Notifications Test
|
|
|
|
Tests that the notifier service sends real-time notifications when rules are
|
|
triggered, including rule evaluation, enforcement creation, and rule state changes.
|
|
|
|
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_enforcement_count,
|
|
wait_for_event_count,
|
|
wait_for_execution_count,
|
|
)
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.notifications
|
|
@pytest.mark.rules
|
|
@pytest.mark.websocket
|
|
def test_rule_trigger_notification(client: AttuneClient, test_pack):
|
|
"""
|
|
Test that rule triggering sends notification.
|
|
|
|
Flow:
|
|
1. Create webhook trigger, action, and rule
|
|
2. Trigger webhook
|
|
3. Verify notification metadata for rule trigger event
|
|
4. Verify enforcement creation tracked
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.16.1: Rule Trigger Notification")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create webhook trigger
|
|
print("\n[STEP 1] Creating webhook trigger...")
|
|
trigger_ref = f"rule_notify_webhook_{unique_ref()}"
|
|
trigger = create_webhook_trigger(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
trigger_ref=trigger_ref,
|
|
description="Webhook for rule notification test",
|
|
)
|
|
print(f"✓ Created trigger: {trigger['ref']}")
|
|
|
|
# Step 2: Create echo action
|
|
print("\n[STEP 2] Creating echo action...")
|
|
action_ref = f"rule_notify_action_{unique_ref()}"
|
|
action = create_echo_action(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
action_ref=action_ref,
|
|
description="Action for rule notification test",
|
|
)
|
|
print(f"✓ Created action: {action['ref']}")
|
|
|
|
# Step 3: Create rule
|
|
print("\n[STEP 3] Creating rule...")
|
|
rule_ref = f"rule_notify_rule_{unique_ref()}"
|
|
rule_payload = {
|
|
"ref": rule_ref,
|
|
"pack": pack_ref,
|
|
"trigger": trigger["ref"],
|
|
"action": action["ref"],
|
|
"enabled": True,
|
|
"parameters": {
|
|
"message": "Rule triggered - notification test",
|
|
},
|
|
}
|
|
rule_response = client.post("/rules", json=rule_payload)
|
|
assert rule_response.status_code == 201, (
|
|
f"Failed to create rule: {rule_response.text}"
|
|
)
|
|
rule = rule_response.json()["data"]
|
|
print(f"✓ Created rule: {rule['ref']}")
|
|
|
|
# Step 4: Trigger webhook
|
|
print("\n[STEP 4] Triggering webhook to fire rule...")
|
|
webhook_url = f"/webhooks/{trigger['ref']}"
|
|
webhook_response = client.post(
|
|
webhook_url, json={"test": "rule_notification", "timestamp": time.time()}
|
|
)
|
|
assert webhook_response.status_code == 200, (
|
|
f"Webhook trigger failed: {webhook_response.text}"
|
|
)
|
|
print(f"✓ Webhook triggered successfully")
|
|
|
|
# Step 5: Wait for event creation
|
|
print("\n[STEP 5] Waiting for event creation...")
|
|
wait_for_event_count(client, expected_count=1, timeout=10)
|
|
events = client.get("/events").json()["data"]
|
|
event = events[0]
|
|
print(f"✓ Event created: {event['id']}")
|
|
|
|
# Step 6: Wait for enforcement creation
|
|
print("\n[STEP 6] Waiting for rule enforcement...")
|
|
wait_for_enforcement_count(client, expected_count=1, timeout=10)
|
|
enforcements = client.get("/enforcements").json()["data"]
|
|
enforcement = enforcements[0]
|
|
print(f"✓ Enforcement created: {enforcement['id']}")
|
|
|
|
# Step 7: Validate notification metadata
|
|
print("\n[STEP 7] Validating rule trigger notification metadata...")
|
|
assert enforcement["rule_id"] == rule["id"], "Enforcement should link to rule"
|
|
assert enforcement["event_id"] == event["id"], "Enforcement should link to event"
|
|
assert "created" in enforcement, "Enforcement missing created timestamp"
|
|
assert "updated" in enforcement, "Enforcement missing updated timestamp"
|
|
|
|
print(f"✓ Rule trigger notification metadata validated")
|
|
print(f" - Rule ID: {rule['id']}")
|
|
print(f" - Event ID: {event['id']}")
|
|
print(f" - Enforcement ID: {enforcement['id']}")
|
|
print(f" - Created: {enforcement['created']}")
|
|
|
|
# The notifier service would send a notification at this point
|
|
print(f"\nNote: Notifier service would send notification with:")
|
|
print(f" - Type: rule.triggered")
|
|
print(f" - Rule ID: {rule['id']}")
|
|
print(f" - Event ID: {event['id']}")
|
|
print(f" - Enforcement ID: {enforcement['id']}")
|
|
|
|
print("\n✅ Test passed: Rule trigger notification flow validated")
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.notifications
|
|
@pytest.mark.rules
|
|
@pytest.mark.websocket
|
|
def test_rule_enable_disable_notification(client: AttuneClient, test_pack):
|
|
"""
|
|
Test that enabling/disabling rules sends notifications.
|
|
|
|
Flow:
|
|
1. Create rule
|
|
2. Disable rule, verify notification metadata
|
|
3. Re-enable rule, verify notification metadata
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.16.2: Rule Enable/Disable Notification")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create webhook trigger
|
|
print("\n[STEP 1] Creating webhook trigger...")
|
|
trigger_ref = f"rule_state_webhook_{unique_ref()}"
|
|
trigger = create_webhook_trigger(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
trigger_ref=trigger_ref,
|
|
description="Webhook for rule state test",
|
|
)
|
|
print(f"✓ Created trigger: {trigger['ref']}")
|
|
|
|
# Step 2: Create action
|
|
print("\n[STEP 2] Creating action...")
|
|
action_ref = f"rule_state_action_{unique_ref()}"
|
|
action = create_echo_action(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
action_ref=action_ref,
|
|
description="Action for rule state test",
|
|
)
|
|
print(f"✓ Created action: {action['ref']}")
|
|
|
|
# Step 3: Create enabled rule
|
|
print("\n[STEP 3] Creating enabled rule...")
|
|
rule_ref = f"rule_state_rule_{unique_ref()}"
|
|
rule_payload = {
|
|
"ref": rule_ref,
|
|
"pack": pack_ref,
|
|
"trigger": trigger["ref"],
|
|
"action": action["ref"],
|
|
"enabled": True,
|
|
}
|
|
rule_response = client.post("/rules", json=rule_payload)
|
|
assert rule_response.status_code == 201
|
|
rule = rule_response.json()["data"]
|
|
rule_id = rule["id"]
|
|
print(f"✓ Created rule: {rule['ref']}")
|
|
print(f" Initial state: enabled={rule['enabled']}")
|
|
|
|
# Step 4: Disable the rule
|
|
print("\n[STEP 4] Disabling rule...")
|
|
disable_payload = {"enabled": False}
|
|
disable_response = client.patch(f"/rules/{rule_id}", json=disable_payload)
|
|
assert disable_response.status_code == 200, (
|
|
f"Failed to disable rule: {disable_response.text}"
|
|
)
|
|
disabled_rule = disable_response.json()["data"]
|
|
print(f"✓ Rule disabled")
|
|
assert disabled_rule["enabled"] is False, "Rule should be disabled"
|
|
|
|
# Verify notification metadata
|
|
print(f" - Rule state changed: enabled=True → enabled=False")
|
|
print(f" - Updated timestamp: {disabled_rule['updated']}")
|
|
|
|
print(f"\nNote: Notifier service would send notification with:")
|
|
print(f" - Type: rule.disabled")
|
|
print(f" - Rule ID: {rule_id}")
|
|
print(f" - Rule ref: {rule['ref']}")
|
|
|
|
# Step 5: Re-enable the rule
|
|
print("\n[STEP 5] Re-enabling rule...")
|
|
enable_payload = {"enabled": True}
|
|
enable_response = client.patch(f"/rules/{rule_id}", json=enable_payload)
|
|
assert enable_response.status_code == 200, (
|
|
f"Failed to enable rule: {enable_response.text}"
|
|
)
|
|
enabled_rule = enable_response.json()["data"]
|
|
print(f"✓ Rule re-enabled")
|
|
assert enabled_rule["enabled"] is True, "Rule should be enabled"
|
|
|
|
# Verify notification metadata
|
|
print(f" - Rule state changed: enabled=False → enabled=True")
|
|
print(f" - Updated timestamp: {enabled_rule['updated']}")
|
|
|
|
print(f"\nNote: Notifier service would send notification with:")
|
|
print(f" - Type: rule.enabled")
|
|
print(f" - Rule ID: {rule_id}")
|
|
print(f" - Rule ref: {rule['ref']}")
|
|
|
|
print("\n✅ Test passed: Rule state change notification flow validated")
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.notifications
|
|
@pytest.mark.rules
|
|
@pytest.mark.websocket
|
|
def test_multiple_rule_triggers_notification(client: AttuneClient, test_pack):
|
|
"""
|
|
Test notifications when single event triggers multiple rules.
|
|
|
|
Flow:
|
|
1. Create 1 webhook trigger
|
|
2. Create 3 rules using same trigger
|
|
3. Trigger webhook once
|
|
4. Verify notification metadata for each rule trigger
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.16.3: Multiple Rule Triggers Notification")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create webhook trigger
|
|
print("\n[STEP 1] Creating webhook trigger...")
|
|
trigger_ref = f"multi_rule_webhook_{unique_ref()}"
|
|
trigger = create_webhook_trigger(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
trigger_ref=trigger_ref,
|
|
description="Webhook for multiple rule test",
|
|
)
|
|
print(f"✓ Created trigger: {trigger['ref']}")
|
|
|
|
# Step 2: Create actions
|
|
print("\n[STEP 2] Creating actions...")
|
|
actions = []
|
|
for i in range(3):
|
|
action_ref = f"multi_rule_action_{i}_{unique_ref()}"
|
|
action = create_echo_action(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
action_ref=action_ref,
|
|
description=f"Action {i} for multi-rule test",
|
|
)
|
|
actions.append(action)
|
|
print(f" ✓ Created action {i}: {action['ref']}")
|
|
|
|
# Step 3: Create multiple rules for same trigger
|
|
print("\n[STEP 3] Creating 3 rules for same trigger...")
|
|
rules = []
|
|
for i, action in enumerate(actions):
|
|
rule_ref = f"multi_rule_{i}_{unique_ref()}"
|
|
rule_payload = {
|
|
"ref": rule_ref,
|
|
"pack": pack_ref,
|
|
"trigger": trigger["ref"],
|
|
"action": action["ref"],
|
|
"enabled": True,
|
|
"parameters": {
|
|
"message": f"Rule {i} triggered",
|
|
},
|
|
}
|
|
rule_response = client.post("/rules", json=rule_payload)
|
|
assert rule_response.status_code == 201
|
|
rule = rule_response.json()["data"]
|
|
rules.append(rule)
|
|
print(f" ✓ Created rule {i}: {rule['ref']}")
|
|
|
|
# Step 4: Trigger webhook once
|
|
print("\n[STEP 4] Triggering webhook (should fire 3 rules)...")
|
|
webhook_url = f"/webhooks/{trigger['ref']}"
|
|
webhook_response = client.post(
|
|
webhook_url, json={"test": "multiple_rules", "timestamp": time.time()}
|
|
)
|
|
assert webhook_response.status_code == 200
|
|
print(f"✓ Webhook triggered")
|
|
|
|
# Step 5: Wait for event
|
|
print("\n[STEP 5] Waiting for event...")
|
|
wait_for_event_count(client, expected_count=1, timeout=10)
|
|
events = client.get("/events").json()["data"]
|
|
event = events[0]
|
|
print(f"✓ Event created: {event['id']}")
|
|
|
|
# Step 6: Wait for enforcements
|
|
print("\n[STEP 6] Waiting for rule enforcements...")
|
|
wait_for_enforcement_count(client, expected_count=3, timeout=10)
|
|
enforcements = client.get("/enforcements").json()["data"]
|
|
print(f"✓ Found {len(enforcements)} enforcements")
|
|
|
|
# Step 7: Validate notification metadata for each rule
|
|
print("\n[STEP 7] Validating notification metadata for each rule...")
|
|
for i, rule in enumerate(rules):
|
|
# Find enforcement for this rule
|
|
rule_enforcements = [e for e in enforcements if e["rule_id"] == rule["id"]]
|
|
assert len(rule_enforcements) >= 1, f"Rule {i} should have enforcement"
|
|
|
|
enforcement = rule_enforcements[0]
|
|
print(f"\n Rule {i} ({rule['ref']}):")
|
|
print(f" - Enforcement ID: {enforcement['id']}")
|
|
print(f" - Event ID: {enforcement['event_id']}")
|
|
print(f" - Created: {enforcement['created']}")
|
|
|
|
assert enforcement["rule_id"] == rule["id"]
|
|
assert enforcement["event_id"] == event["id"]
|
|
|
|
print(f"\n✓ All {len(rules)} rule trigger notifications validated")
|
|
|
|
print(f"\nNote: Notifier service would send {len(rules)} notifications:")
|
|
for i, rule in enumerate(rules):
|
|
print(f" {i + 1}. rule.triggered - Rule ID: {rule['id']}")
|
|
|
|
print("\n✅ Test passed: Multiple rule trigger notifications validated")
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.notifications
|
|
@pytest.mark.rules
|
|
@pytest.mark.websocket
|
|
def test_rule_criteria_evaluation_notification(client: AttuneClient, test_pack):
|
|
"""
|
|
Test notifications for rule criteria evaluation (match vs no-match).
|
|
|
|
Flow:
|
|
1. Create rule with criteria
|
|
2. Trigger with matching payload - verify notification
|
|
3. Trigger with non-matching payload - verify no notification (rule not fired)
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.16.4: Rule Criteria Evaluation Notification")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create webhook trigger
|
|
print("\n[STEP 1] Creating webhook trigger...")
|
|
trigger_ref = f"criteria_notify_webhook_{unique_ref()}"
|
|
trigger = create_webhook_trigger(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
trigger_ref=trigger_ref,
|
|
description="Webhook for criteria notification test",
|
|
)
|
|
print(f"✓ Created trigger: {trigger['ref']}")
|
|
|
|
# Step 2: Create action
|
|
print("\n[STEP 2] Creating action...")
|
|
action_ref = f"criteria_notify_action_{unique_ref()}"
|
|
action = create_echo_action(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
action_ref=action_ref,
|
|
description="Action for criteria notification test",
|
|
)
|
|
print(f"✓ Created action: {action['ref']}")
|
|
|
|
# Step 3: Create rule with criteria
|
|
print("\n[STEP 3] Creating rule with criteria...")
|
|
rule_ref = f"criteria_notify_rule_{unique_ref()}"
|
|
rule_payload = {
|
|
"ref": rule_ref,
|
|
"pack": pack_ref,
|
|
"trigger": trigger["ref"],
|
|
"action": action["ref"],
|
|
"enabled": True,
|
|
"criteria": "{{ trigger.payload.environment == 'production' }}",
|
|
"parameters": {
|
|
"message": "Production deployment approved",
|
|
},
|
|
}
|
|
rule_response = client.post("/rules", json=rule_payload)
|
|
assert rule_response.status_code == 201, (
|
|
f"Failed to create rule: {rule_response.text}"
|
|
)
|
|
rule = rule_response.json()["data"]
|
|
print(f"✓ Created rule with criteria: {rule['ref']}")
|
|
print(f" Criteria: environment == 'production'")
|
|
|
|
# Step 4: Trigger with MATCHING payload
|
|
print("\n[STEP 4] Triggering with MATCHING payload...")
|
|
webhook_url = f"/webhooks/{trigger['ref']}"
|
|
webhook_response = client.post(
|
|
webhook_url, json={"environment": "production", "version": "v1.2.3"}
|
|
)
|
|
assert webhook_response.status_code == 200
|
|
print(f"✓ Webhook triggered with matching payload")
|
|
|
|
# Wait for enforcement
|
|
time.sleep(2)
|
|
wait_for_enforcement_count(client, expected_count=1, timeout=10)
|
|
enforcements = client.get("/enforcements").json()["data"]
|
|
matching_enforcement = enforcements[0]
|
|
print(f"✓ Enforcement created (criteria matched): {matching_enforcement['id']}")
|
|
|
|
print(f"\nNote: Notifier service would send notification:")
|
|
print(f" - Type: rule.triggered")
|
|
print(f" - Rule ID: {rule['id']}")
|
|
print(f" - Criteria: matched")
|
|
|
|
# Step 5: Trigger with NON-MATCHING payload
|
|
print("\n[STEP 5] Triggering with NON-MATCHING payload...")
|
|
webhook_response = client.post(
|
|
webhook_url, json={"environment": "development", "version": "v1.2.4"}
|
|
)
|
|
assert webhook_response.status_code == 200
|
|
print(f"✓ Webhook triggered with non-matching payload")
|
|
|
|
# Wait briefly
|
|
time.sleep(2)
|
|
|
|
# Should still only have 1 enforcement (rule didn't fire for non-matching)
|
|
enforcements = client.get("/enforcements").json()["data"]
|
|
print(f" Total enforcements: {len(enforcements)}")
|
|
|
|
if len(enforcements) == 1:
|
|
print(f"✓ No new enforcement created (criteria not matched)")
|
|
print(f"✓ Rule correctly filtered by criteria")
|
|
|
|
print(f"\nNote: Notifier service would NOT send notification")
|
|
print(f" (rule criteria not matched)")
|
|
else:
|
|
print(
|
|
f" Note: Additional enforcement found - criteria filtering may need review"
|
|
)
|
|
|
|
# Step 6: Verify the events
|
|
print("\n[STEP 6] Verifying events created...")
|
|
events = client.get("/events").json()["data"]
|
|
webhook_events = [e for e in events if e.get("trigger") == trigger["ref"]]
|
|
print(f" Total webhook events: {len(webhook_events)}")
|
|
print(f" Note: Both triggers created events, but only one matched criteria")
|
|
|
|
print("\n✅ Test passed: Rule criteria evaluation notification validated")
|