re-uploading work
This commit is contained in:
328
tests/e2e/tier1/test_t1_02_date_timer.py
Normal file
328
tests/e2e/tier1/test_t1_02_date_timer.py
Normal file
@@ -0,0 +1,328 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
T1.2: Date Timer (One-Shot Execution)
|
||||
|
||||
Tests that an action executes once at a specific future time.
|
||||
|
||||
Test Flow:
|
||||
1. Create date timer trigger (5 seconds from now)
|
||||
2. Create action with unique marker output
|
||||
3. Create rule linking timer → action
|
||||
4. Wait 7 seconds
|
||||
5. Verify exactly 1 execution occurred
|
||||
6. Wait additional 10 seconds
|
||||
7. Verify no additional executions
|
||||
|
||||
Success Criteria:
|
||||
- Timer fires once at scheduled time (±1 second)
|
||||
- Exactly 1 enforcement created
|
||||
- Exactly 1 execution created
|
||||
- No duplicate executions after timer expires
|
||||
- Timer marked as expired/completed
|
||||
"""
|
||||
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytest
|
||||
from helpers import (
|
||||
AttuneClient,
|
||||
create_date_timer,
|
||||
create_echo_action,
|
||||
create_rule,
|
||||
timestamp_future,
|
||||
wait_for_event_count,
|
||||
wait_for_execution_count,
|
||||
wait_for_execution_status,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.tier1
|
||||
@pytest.mark.timer
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.timeout(30)
|
||||
class TestDateTimerAutomation:
|
||||
"""Test date timer (one-shot) automation flow"""
|
||||
|
||||
def test_date_timer_fires_once(self, client: AttuneClient, pack_ref: str):
|
||||
"""Test that date timer fires exactly once at scheduled time"""
|
||||
|
||||
fire_in_seconds = 5
|
||||
buffer_time = 3
|
||||
|
||||
print(f"\n=== T1.2: Date Timer One-Shot Execution ===")
|
||||
print(f"Scheduled to fire in: {fire_in_seconds}s")
|
||||
|
||||
# Step 1: Create date timer trigger
|
||||
print("\n[1/5] Creating date timer trigger...")
|
||||
fire_at = timestamp_future(fire_in_seconds)
|
||||
trigger = create_date_timer(
|
||||
client=client,
|
||||
fire_at=fire_at,
|
||||
pack_ref=pack_ref,
|
||||
)
|
||||
print(f"✓ Created trigger: {trigger['label']} (ID: {trigger['id']})")
|
||||
print(f" Scheduled for: {fire_at}")
|
||||
assert trigger["ref"] == "core.datetimetimer"
|
||||
assert "sensor" in trigger
|
||||
assert trigger["sensor"]["enabled"] is True
|
||||
assert trigger["fire_at"] == fire_at
|
||||
|
||||
# Step 2: Create echo action with unique marker
|
||||
print("\n[2/5] Creating echo action...")
|
||||
action = create_echo_action(client=client, pack_ref=pack_ref)
|
||||
action_ref = action["ref"]
|
||||
unique_message = f"Date timer fired at {fire_at}"
|
||||
print(f"✓ Created action: {action_ref} (ID: {action['id']})")
|
||||
|
||||
# Step 3: Create rule linking trigger → action
|
||||
print("\n[3/5] Creating rule...")
|
||||
rule = create_rule(
|
||||
client=client,
|
||||
trigger_id=trigger["id"],
|
||||
action_ref=action_ref,
|
||||
pack_ref=pack_ref,
|
||||
enabled=True,
|
||||
action_parameters={"message": unique_message},
|
||||
)
|
||||
print(f"✓ Created rule: {rule['label']} (ID: {rule['id']})")
|
||||
|
||||
# Step 4: Wait for timer to fire
|
||||
print(
|
||||
f"\n[4/5] Waiting for timer to fire (timeout: {fire_in_seconds + buffer_time}s)..."
|
||||
)
|
||||
print(f" Current time: {datetime.utcnow().isoformat()}Z")
|
||||
print(f" Fire time: {fire_at}")
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# Wait for exactly 1 event
|
||||
events = wait_for_event_count(
|
||||
client=client,
|
||||
expected_count=1,
|
||||
trigger_id=trigger["id"],
|
||||
timeout=fire_in_seconds + buffer_time,
|
||||
poll_interval=0.5,
|
||||
operator=">=",
|
||||
)
|
||||
|
||||
fire_time = time.time()
|
||||
actual_delay = fire_time - start_time
|
||||
|
||||
print(f"✓ Timer fired after {actual_delay:.2f}s")
|
||||
print(f" Expected: ~{fire_in_seconds}s")
|
||||
print(f" Difference: {abs(actual_delay - fire_in_seconds):.2f}s")
|
||||
|
||||
# Verify timing precision (±2 seconds tolerance)
|
||||
assert abs(actual_delay - fire_in_seconds) < 2.0, (
|
||||
f"Timer fired at {actual_delay:.1f}s, expected ~{fire_in_seconds}s (±2s)"
|
||||
)
|
||||
|
||||
# Verify event
|
||||
assert len(events) >= 1, "Expected at least 1 event"
|
||||
event = events[0]
|
||||
print(f"\n Event details:")
|
||||
print(f" ID: {event['id']}")
|
||||
print(f" Trigger ID: {event['trigger']}")
|
||||
print(f" Created: {event['created']}")
|
||||
assert event["trigger"] == trigger["id"]
|
||||
|
||||
# Step 5: Verify execution completed
|
||||
print(f"\n[5/5] Verifying execution completed...")
|
||||
|
||||
executions = wait_for_execution_count(
|
||||
client=client,
|
||||
expected_count=1,
|
||||
action_ref=action_ref,
|
||||
timeout=15,
|
||||
poll_interval=0.5,
|
||||
operator=">=",
|
||||
)
|
||||
|
||||
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 needed
|
||||
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", (
|
||||
f"Execution failed with status: {execution['status']}"
|
||||
)
|
||||
print(f"✓ Execution succeeded")
|
||||
|
||||
# Step 6: Wait additional time to ensure no duplicate fires
|
||||
print(f"\nWaiting additional 10s to verify no duplicate fires...")
|
||||
time.sleep(10)
|
||||
|
||||
# Check event count again
|
||||
final_events = client.list_events(trigger_id=trigger["id"])
|
||||
print(f"✓ Final event count: {len(final_events)}")
|
||||
|
||||
# Should still be exactly 1 event
|
||||
assert len(final_events) == 1, (
|
||||
f"Expected exactly 1 event, found {len(final_events)} (duplicate fire detected)"
|
||||
)
|
||||
|
||||
# Check execution count again
|
||||
final_executions = client.list_executions(action_ref=action_ref)
|
||||
print(f"✓ Final execution count: {len(final_executions)}")
|
||||
|
||||
assert len(final_executions) == 1, (
|
||||
f"Expected exactly 1 execution, found {len(final_executions)}"
|
||||
)
|
||||
|
||||
# Final summary
|
||||
total_time = time.time() - start_time
|
||||
print("\n=== Test Summary ===")
|
||||
print(f"✓ Date timer fired once at scheduled time")
|
||||
print(
|
||||
f"✓ Timing precision: {abs(actual_delay - fire_in_seconds):.2f}s deviation"
|
||||
)
|
||||
print(f"✓ Exactly 1 event created")
|
||||
print(f"✓ Exactly 1 execution completed")
|
||||
print(f"✓ No duplicate fires detected")
|
||||
print(f"✓ Total test duration: {total_time:.1f}s")
|
||||
print(f"✓ Test PASSED")
|
||||
|
||||
def test_date_timer_past_date(self, client: AttuneClient, pack_ref: str):
|
||||
"""Test that date timer with past date fires immediately or fails gracefully"""
|
||||
|
||||
print(f"\n=== T1.2b: Date Timer with Past Date ===")
|
||||
|
||||
# Step 1: Create date timer with past date (1 hour ago)
|
||||
print("\n[1/4] Creating date timer with past date...")
|
||||
past_date = timestamp_future(-3600) # 1 hour ago
|
||||
print(f" Date: {past_date} (past)")
|
||||
|
||||
trigger = create_date_timer(
|
||||
client=client,
|
||||
fire_at=past_date,
|
||||
pack_ref=pack_ref,
|
||||
)
|
||||
print(f"✓ Trigger created: {trigger['label']} (ID: {trigger['id']})")
|
||||
|
||||
# Step 2: Create action and rule
|
||||
print("\n[2/4] Creating action and rule...")
|
||||
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,
|
||||
action_parameters={"message": "Past date timer"},
|
||||
)
|
||||
print(f"✓ Action and rule created")
|
||||
|
||||
# Step 3: Check if timer fires immediately
|
||||
print("\n[3/4] Checking timer behavior...")
|
||||
print(" Waiting up to 10s to see if timer fires immediately...")
|
||||
|
||||
try:
|
||||
# Wait briefly to see if event is created
|
||||
events = wait_for_event_count(
|
||||
client=client,
|
||||
expected_count=1,
|
||||
trigger_id=trigger["id"],
|
||||
timeout=10,
|
||||
poll_interval=0.5,
|
||||
operator=">=",
|
||||
)
|
||||
|
||||
print(f"✓ Timer fired immediately (behavior: fire on past date)")
|
||||
print(f" Events created: {len(events)}")
|
||||
|
||||
# Verify execution completed
|
||||
executions = wait_for_execution_count(
|
||||
client=client,
|
||||
expected_count=1,
|
||||
action_ref=action["ref"],
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
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")
|
||||
|
||||
except TimeoutError:
|
||||
# Timer may not fire for past dates - this is also acceptable behavior
|
||||
print(f"✓ Timer did not fire (behavior: skip past date)")
|
||||
print(f" This is acceptable behavior - past dates are ignored")
|
||||
|
||||
# Step 4: Verify no ongoing fires
|
||||
print("\n[4/4] Verifying timer is one-shot...")
|
||||
time.sleep(5)
|
||||
|
||||
final_events = client.list_events(trigger_id=trigger["id"])
|
||||
print(f"✓ Final event count: {len(final_events)}")
|
||||
|
||||
# Should be 0 or 1, never more than 1
|
||||
assert len(final_events) <= 1, (
|
||||
f"Expected 0 or 1 event, found {len(final_events)} (timer firing repeatedly)"
|
||||
)
|
||||
|
||||
print("\n=== Test Summary ===")
|
||||
print(f"✓ Past date timer handled gracefully")
|
||||
print(f"✓ No repeated fires detected")
|
||||
print(f"✓ Test PASSED")
|
||||
|
||||
def test_date_timer_far_future(self, client: AttuneClient, pack_ref: str):
|
||||
"""Test creating date timer far in the future (doesn't fire during test)"""
|
||||
|
||||
print(f"\n=== T1.2c: Date Timer Far Future ===")
|
||||
|
||||
# Create timer for 1 hour from now
|
||||
future_time = timestamp_future(3600)
|
||||
|
||||
print(f"\n[1/3] Creating date timer for far future...")
|
||||
print(f" Time: {future_time} (+1 hour)")
|
||||
|
||||
trigger = create_date_timer(
|
||||
client=client,
|
||||
fire_at=future_time,
|
||||
pack_ref=pack_ref,
|
||||
)
|
||||
print(f"✓ Trigger created: {trigger['label']} (ID: {trigger['id']})")
|
||||
|
||||
# Create action and rule
|
||||
print("\n[2/3] Creating action and rule...")
|
||||
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")
|
||||
|
||||
# Verify timer doesn't fire prematurely
|
||||
print("\n[3/3] Verifying timer doesn't fire prematurely...")
|
||||
time.sleep(3)
|
||||
|
||||
events = client.list_events(trigger_id=trigger["id"])
|
||||
executions = client.list_executions(action_ref=action["ref"])
|
||||
|
||||
print(f" Events: {len(events)}")
|
||||
print(f" Executions: {len(executions)}")
|
||||
|
||||
assert len(events) == 0, "Timer fired prematurely"
|
||||
assert len(executions) == 0, "Execution created prematurely"
|
||||
|
||||
print("\n✓ Timer correctly waiting for future time")
|
||||
print("✓ Test PASSED")
|
||||
Reference in New Issue
Block a user