init commit
This commit is contained in:
175
tests/test_state_manager.py
Normal file
175
tests/test_state_manager.py
Normal file
@@ -0,0 +1,175 @@
|
||||
"""Tests for the state manager."""
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from app.models.alerts import AlertType, TriggeredAlert
|
||||
from app.services.state_manager import StateManager
|
||||
|
||||
|
||||
def make_triggered_alert(
|
||||
alert_type: AlertType = AlertType.TEMPERATURE_LOW,
|
||||
forecast_hour: str = "2024-01-15-12",
|
||||
) -> TriggeredAlert:
|
||||
"""Create a test triggered alert."""
|
||||
return TriggeredAlert(
|
||||
alert_type=alert_type,
|
||||
title="Test Alert",
|
||||
message="Test message",
|
||||
forecast_hour=forecast_hour,
|
||||
value=25.0,
|
||||
threshold=32.0,
|
||||
)
|
||||
|
||||
|
||||
class TestStateManager:
|
||||
"""Test state manager functionality."""
|
||||
|
||||
def test_load_creates_empty_state_if_no_file(self, tmp_path: Path) -> None:
|
||||
"""Test that loading from nonexistent file creates empty state."""
|
||||
state_file = tmp_path / "state.json"
|
||||
manager = StateManager(str(state_file))
|
||||
|
||||
state = manager.load()
|
||||
|
||||
assert len(state.sent_alerts) == 0
|
||||
|
||||
def test_save_creates_file(self, tmp_path: Path) -> None:
|
||||
"""Test that saving creates the state file."""
|
||||
state_file = tmp_path / "subdir" / "state.json"
|
||||
manager = StateManager(str(state_file))
|
||||
|
||||
alert = make_triggered_alert()
|
||||
manager.record_sent(alert)
|
||||
manager.save()
|
||||
|
||||
assert state_file.exists()
|
||||
|
||||
def test_save_and_load_round_trip(self, tmp_path: Path) -> None:
|
||||
"""Test that state survives save/load cycle."""
|
||||
state_file = tmp_path / "state.json"
|
||||
manager = StateManager(str(state_file))
|
||||
|
||||
alert = make_triggered_alert()
|
||||
manager.record_sent(alert)
|
||||
manager.save()
|
||||
|
||||
# Create new manager and load
|
||||
manager2 = StateManager(str(state_file))
|
||||
state = manager2.load()
|
||||
|
||||
assert len(state.sent_alerts) == 1
|
||||
assert alert.dedup_key in state.sent_alerts
|
||||
|
||||
def test_filter_duplicates_removes_known_alerts(self, tmp_path: Path) -> None:
|
||||
"""Test that known alerts are filtered out."""
|
||||
state_file = tmp_path / "state.json"
|
||||
manager = StateManager(str(state_file))
|
||||
|
||||
# Record an alert as sent
|
||||
alert1 = make_triggered_alert(forecast_hour="2024-01-15-12")
|
||||
manager.record_sent(alert1)
|
||||
|
||||
# Try to send the same alert again plus a new one
|
||||
alert2 = make_triggered_alert(forecast_hour="2024-01-15-12") # Duplicate
|
||||
alert3 = make_triggered_alert(forecast_hour="2024-01-15-13") # New
|
||||
|
||||
filtered = manager.filter_duplicates([alert2, alert3])
|
||||
|
||||
assert len(filtered) == 1
|
||||
assert filtered[0].forecast_hour == "2024-01-15-13"
|
||||
|
||||
def test_different_alert_types_not_duplicates(self, tmp_path: Path) -> None:
|
||||
"""Test that different alert types for same hour aren't duplicates."""
|
||||
state_file = tmp_path / "state.json"
|
||||
manager = StateManager(str(state_file))
|
||||
|
||||
alert1 = make_triggered_alert(
|
||||
alert_type=AlertType.TEMPERATURE_LOW, forecast_hour="2024-01-15-12"
|
||||
)
|
||||
manager.record_sent(alert1)
|
||||
|
||||
alert2 = make_triggered_alert(
|
||||
alert_type=AlertType.PRECIPITATION, forecast_hour="2024-01-15-12"
|
||||
)
|
||||
|
||||
filtered = manager.filter_duplicates([alert2])
|
||||
|
||||
assert len(filtered) == 1
|
||||
|
||||
def test_purge_old_records(self, tmp_path: Path) -> None:
|
||||
"""Test that old records are purged."""
|
||||
state_file = tmp_path / "state.json"
|
||||
manager = StateManager(str(state_file), dedup_window_hours=24)
|
||||
|
||||
# Manually create an old record
|
||||
alert = make_triggered_alert()
|
||||
manager.state.sent_alerts[alert.dedup_key] = manager.state.sent_alerts.get(
|
||||
alert.dedup_key
|
||||
) or type(
|
||||
"SentAlertRecord",
|
||||
(),
|
||||
{
|
||||
"dedup_key": alert.dedup_key,
|
||||
"alert_type": alert.alert_type.value,
|
||||
"sent_at": datetime.now() - timedelta(hours=48),
|
||||
"forecast_hour": alert.forecast_hour,
|
||||
"to_dict": lambda self: {
|
||||
"dedup_key": self.dedup_key,
|
||||
"alert_type": self.alert_type,
|
||||
"sent_at": self.sent_at.isoformat(),
|
||||
"forecast_hour": self.forecast_hour,
|
||||
},
|
||||
},
|
||||
)()
|
||||
|
||||
# Use the actual model
|
||||
from app.models.state import SentAlertRecord
|
||||
|
||||
manager.state.sent_alerts[alert.dedup_key] = SentAlertRecord(
|
||||
dedup_key=alert.dedup_key,
|
||||
alert_type=alert.alert_type.value,
|
||||
sent_at=datetime.now() - timedelta(hours=48),
|
||||
forecast_hour=alert.forecast_hour,
|
||||
)
|
||||
|
||||
purged = manager.purge_old_records()
|
||||
|
||||
assert purged == 1
|
||||
assert len(manager.state.sent_alerts) == 0
|
||||
|
||||
def test_atomic_write_creates_temp_file(self, tmp_path: Path) -> None:
|
||||
"""Test that atomic write doesn't leave temp files on success."""
|
||||
state_file = tmp_path / "state.json"
|
||||
manager = StateManager(str(state_file))
|
||||
|
||||
alert = make_triggered_alert()
|
||||
manager.record_sent(alert)
|
||||
manager.save()
|
||||
|
||||
# Check no temp files remain
|
||||
temp_files = list(tmp_path.glob("state_*.tmp"))
|
||||
assert len(temp_files) == 0
|
||||
|
||||
def test_load_handles_corrupt_json(self, tmp_path: Path) -> None:
|
||||
"""Test that corrupt JSON is handled gracefully."""
|
||||
state_file = tmp_path / "state.json"
|
||||
state_file.write_text("not valid json {{{")
|
||||
|
||||
manager = StateManager(str(state_file))
|
||||
state = manager.load()
|
||||
|
||||
# Should return empty state
|
||||
assert len(state.sent_alerts) == 0
|
||||
|
||||
def test_dedup_key_format(self) -> None:
|
||||
"""Test that dedup key has expected format."""
|
||||
alert = make_triggered_alert(
|
||||
alert_type=AlertType.TEMPERATURE_LOW, forecast_hour="2024-01-15-12"
|
||||
)
|
||||
|
||||
assert alert.dedup_key == "temperature_low:2024-01-15-12"
|
||||
Reference in New Issue
Block a user