Files
attune/tests/e2e/tier3/test_t3_02_timer_cancellation.py
2026-02-04 17:46:30 -06:00

336 lines
10 KiB
Python

"""
T3.2: Timer Cancellation Test
Tests that disabling a rule stops timer from executing, and re-enabling
resumes executions.
Priority: LOW
Duration: ~15 seconds
"""
import time
import pytest
from helpers.client import AttuneClient
from helpers.fixtures import create_echo_action, create_interval_timer, unique_ref
from helpers.polling import wait_for_execution_count
@pytest.mark.tier3
@pytest.mark.timer
@pytest.mark.rules
def test_timer_cancellation_via_rule_disable(client: AttuneClient, test_pack):
"""
Test that disabling a rule stops timer executions.
Flow:
1. Create interval timer (every 3 seconds)
2. Wait for 2 executions
3. Disable rule
4. Wait 10 seconds
5. Verify no new executions occurred
"""
print("\n" + "=" * 80)
print("T3.2a: Timer Cancellation via Rule Disable Test")
print("=" * 80)
pack_ref = test_pack["ref"]
# Step 1: Create interval timer and action
print("\n[STEP 1] Creating interval timer (every 3 seconds)...")
trigger_ref = f"cancel_timer_{unique_ref()}"
trigger_response = create_interval_timer(
client=client,
pack_ref=pack_ref,
trigger_ref=trigger_ref,
interval=3,
)
print(f"✓ Interval timer created: {trigger_ref}")
print(f" Interval: 3 seconds")
# Step 2: Create action and rule
print("\n[STEP 2] Creating action and rule...")
action_ref = create_echo_action(
client=client,
pack_ref=pack_ref,
message="Timer tick",
suffix="_cancel",
)
rule_data = {
"name": f"Timer Cancellation Test Rule {unique_ref()}",
"trigger": trigger_ref,
"action": action_ref,
"enabled": True,
}
rule_response = client.create_rule(rule_data)
rule_id = rule_response["id"]
print(f"✓ Rule created: {rule_id}")
print(f" Status: enabled")
# Step 3: Wait for 2 executions
print("\n[STEP 3] Waiting for 2 timer executions...")
wait_for_execution_count(
client=client,
action_ref=action_ref,
expected_count=2,
timeout=15,
operator=">=",
)
executions_before_disable = client.list_executions(action=action_ref)
print(f"{len(executions_before_disable)} executions occurred")
# Step 4: Disable rule
print("\n[STEP 4] Disabling rule...")
update_data = {"enabled": False}
client.update_rule(rule_id, update_data)
print(f"✓ Rule disabled: {rule_id}")
# Step 5: Wait and verify no new executions
print("\n[STEP 5] Waiting 10 seconds to verify no new executions...")
time.sleep(10)
executions_after_disable = client.list_executions(action=action_ref)
new_executions = len(executions_after_disable) - len(executions_before_disable)
print(f" Executions before disable: {len(executions_before_disable)}")
print(f" Executions after disable: {len(executions_after_disable)}")
print(f" New executions: {new_executions}")
if new_executions == 0:
print(f"✓ No new executions (timer successfully stopped)")
else:
print(f"{new_executions} new execution(s) occurred after disable")
# Summary
print("\n" + "=" * 80)
print("TIMER CANCELLATION TEST SUMMARY")
print("=" * 80)
print(f"✓ Timer created: {trigger_ref} (3 second interval)")
print(f"✓ Rule disabled after {len(executions_before_disable)} executions")
print(f"✓ New executions after disable: {new_executions}")
if new_executions == 0:
print("\n✅ TIMER CANCELLATION WORKING!")
else:
print("\n⚠️ Timer may still be firing after rule disable")
print("=" * 80)
# Allow some tolerance for in-flight executions (1 execution max)
assert new_executions <= 1, (
f"Expected 0-1 new executions after disable, got {new_executions}"
)
@pytest.mark.tier3
@pytest.mark.timer
@pytest.mark.rules
def test_timer_resume_after_re_enable(client: AttuneClient, test_pack):
"""
Test that re-enabling a disabled rule resumes timer executions.
"""
print("\n" + "=" * 80)
print("T3.2b: Timer Resume After Re-enable Test")
print("=" * 80)
pack_ref = test_pack["ref"]
# Step 1: Create timer and rule
print("\n[STEP 1] Creating timer and rule...")
trigger_ref = f"resume_timer_{unique_ref()}"
create_interval_timer(
client=client,
pack_ref=pack_ref,
trigger_ref=trigger_ref,
interval=3,
)
action_ref = create_echo_action(
client=client,
pack_ref=pack_ref,
message="Resume test",
suffix="_resume",
)
rule_data = {
"name": f"Timer Resume Test Rule {unique_ref()}",
"trigger": trigger_ref,
"action": action_ref,
"enabled": True,
}
rule_response = client.create_rule(rule_data)
rule_id = rule_response["id"]
print(f"✓ Timer and rule created")
# Step 2: Wait for 1 execution
print("\n[STEP 2] Waiting for initial execution...")
wait_for_execution_count(
client=client,
action_ref=action_ref,
expected_count=1,
timeout=10,
operator=">=",
)
print(f"✓ Initial execution confirmed")
# Step 3: Disable rule
print("\n[STEP 3] Disabling rule...")
client.update_rule(rule_id, {"enabled": False})
time.sleep(1)
executions_after_disable = client.list_executions(action=action_ref)
count_after_disable = len(executions_after_disable)
print(f"✓ Rule disabled (executions: {count_after_disable})")
# Step 4: Wait while disabled
print("\n[STEP 4] Waiting 6 seconds while disabled...")
time.sleep(6)
executions_still_disabled = client.list_executions(action=action_ref)
count_still_disabled = len(executions_still_disabled)
increase_while_disabled = count_still_disabled - count_after_disable
print(f" Executions while disabled: {increase_while_disabled}")
# Step 5: Re-enable rule
print("\n[STEP 5] Re-enabling rule...")
client.update_rule(rule_id, {"enabled": True})
print(f"✓ Rule re-enabled")
# Step 6: Wait for new executions
print("\n[STEP 6] Waiting for executions to resume...")
time.sleep(8)
executions_after_enable = client.list_executions(action=action_ref)
count_after_enable = len(executions_after_enable)
increase_after_enable = count_after_enable - count_still_disabled
print(f" Executions before re-enable: {count_still_disabled}")
print(f" Executions after re-enable: {count_after_enable}")
print(f" New executions: {increase_after_enable}")
if increase_after_enable >= 1:
print(f"✓ Timer resumed (new executions after re-enable)")
else:
print(f"⚠ Timer did not resume")
# Summary
print("\n" + "=" * 80)
print("TIMER RESUME TEST SUMMARY")
print("=" * 80)
print(f"✓ Timer disabled: verified no new executions")
print(f"✓ Timer re-enabled: {increase_after_enable} new execution(s)")
if increase_after_enable >= 1:
print("\n✅ TIMER RESUME WORKING!")
else:
print("\n⚠️ Timer did not resume after re-enable")
print("=" * 80)
assert increase_after_enable >= 1, "Timer should resume after re-enable"
@pytest.mark.tier3
@pytest.mark.timer
@pytest.mark.rules
def test_timer_delete_stops_executions(client: AttuneClient, test_pack):
"""
Test that deleting a rule stops timer executions permanently.
"""
print("\n" + "=" * 80)
print("T3.2c: Timer Delete Stops Executions Test")
print("=" * 80)
pack_ref = test_pack["ref"]
# Step 1: Create timer and rule
print("\n[STEP 1] Creating timer and rule...")
trigger_ref = f"delete_timer_{unique_ref()}"
create_interval_timer(
client=client,
pack_ref=pack_ref,
trigger_ref=trigger_ref,
interval=3,
)
action_ref = create_echo_action(
client=client,
pack_ref=pack_ref,
message="Delete test",
suffix="_delete",
)
rule_data = {
"name": f"Timer Delete Test Rule {unique_ref()}",
"trigger": trigger_ref,
"action": action_ref,
"enabled": True,
}
rule_response = client.create_rule(rule_data)
rule_id = rule_response["id"]
print(f"✓ Timer and rule created")
# Step 2: Wait for 1 execution
print("\n[STEP 2] Waiting for initial execution...")
wait_for_execution_count(
client=client,
action_ref=action_ref,
expected_count=1,
timeout=10,
operator=">=",
)
executions_before_delete = client.list_executions(action=action_ref)
print(f"✓ Initial executions: {len(executions_before_delete)}")
# Step 3: Delete rule
print("\n[STEP 3] Deleting rule...")
try:
client.delete_rule(rule_id)
print(f"✓ Rule deleted: {rule_id}")
except Exception as e:
print(f"⚠ Rule deletion failed: {e}")
pytest.skip("Rule deletion not available")
# Step 4: Wait and verify no new executions
print("\n[STEP 4] Waiting 10 seconds to verify no new executions...")
time.sleep(10)
executions_after_delete = client.list_executions(action=action_ref)
new_executions = len(executions_after_delete) - len(executions_before_delete)
print(f" Executions before delete: {len(executions_before_delete)}")
print(f" Executions after delete: {len(executions_after_delete)}")
print(f" New executions: {new_executions}")
if new_executions == 0:
print(f"✓ No new executions (timer permanently stopped)")
else:
print(f"{new_executions} new execution(s) after rule deletion")
# Summary
print("\n" + "=" * 80)
print("TIMER DELETE TEST SUMMARY")
print("=" * 80)
print(f"✓ Rule deleted: {rule_id}")
print(f"✓ New executions after delete: {new_executions}")
if new_executions == 0:
print("\n✅ TIMER DELETION STOPS EXECUTIONS!")
else:
print("\n⚠️ Timer may still fire after rule deletion")
print("=" * 80)
# Allow 1 in-flight execution tolerance
assert new_executions <= 1, (
f"Expected 0-1 new executions after delete, got {new_executions}"
)