"""Tests for DSC database operations.""" import tempfile from pathlib import Path from unittest.mock import patch import pytest @pytest.fixture(autouse=True) def temp_db(): """Use a temporary database for each test.""" with tempfile.TemporaryDirectory() as tmpdir: test_db_path = Path(tmpdir) / "test_intercept.db" test_db_dir = Path(tmpdir) with patch("utils.database.DB_PATH", test_db_path), patch("utils.database.DB_DIR", test_db_dir): from utils.database import close_db, init_db init_db() yield test_db_path close_db() class TestDSCAlertsCRUD: """Tests for DSC alerts CRUD operations.""" def test_store_and_get_dsc_alert(self, temp_db): """Test storing and retrieving a DSC alert.""" from utils.database import get_dsc_alert, store_dsc_alert alert_id = store_dsc_alert( source_mmsi="232123456", format_code="100", category="DISTRESS", source_name="MV Test Ship", nature_of_distress="FIRE", latitude=51.5, longitude=-0.1, ) assert alert_id is not None assert alert_id > 0 alert = get_dsc_alert(alert_id) assert alert is not None assert alert["source_mmsi"] == "232123456" assert alert["format_code"] == "100" assert alert["category"] == "DISTRESS" assert alert["source_name"] == "MV Test Ship" assert alert["nature_of_distress"] == "FIRE" assert alert["latitude"] == 51.5 assert alert["longitude"] == -0.1 assert alert["acknowledged"] is False def test_store_minimal_alert(self, temp_db): """Test storing alert with only required fields.""" from utils.database import get_dsc_alert, store_dsc_alert alert_id = store_dsc_alert(source_mmsi="366000001", format_code="116", category="ROUTINE") alert = get_dsc_alert(alert_id) assert alert is not None assert alert["source_mmsi"] == "366000001" assert alert["category"] == "ROUTINE" assert alert["latitude"] is None assert alert["longitude"] is None def test_get_nonexistent_alert(self, temp_db): """Test getting an alert that doesn't exist.""" from utils.database import get_dsc_alert alert = get_dsc_alert(99999) assert alert is None def test_get_dsc_alerts_all(self, temp_db): """Test getting all alerts.""" from utils.database import get_dsc_alerts, store_dsc_alert store_dsc_alert("232123456", "100", "DISTRESS") store_dsc_alert("366000001", "120", "URGENCY") store_dsc_alert("351234567", "116", "ROUTINE") alerts = get_dsc_alerts() assert len(alerts) == 3 def test_get_dsc_alerts_by_category(self, temp_db): """Test filtering alerts by category.""" from utils.database import get_dsc_alerts, store_dsc_alert store_dsc_alert("232123456", "100", "DISTRESS") store_dsc_alert("232123457", "100", "DISTRESS") store_dsc_alert("366000001", "120", "URGENCY") store_dsc_alert("351234567", "116", "ROUTINE") distress_alerts = get_dsc_alerts(category="DISTRESS") urgency_alerts = get_dsc_alerts(category="URGENCY") assert len(distress_alerts) == 2 assert len(urgency_alerts) == 1 def test_get_dsc_alerts_by_acknowledged(self, temp_db): """Test filtering alerts by acknowledgement status.""" from utils.database import acknowledge_dsc_alert, get_dsc_alerts, store_dsc_alert id1 = store_dsc_alert("232123456", "100", "DISTRESS") id2 = store_dsc_alert("366000001", "100", "DISTRESS") store_dsc_alert("351234567", "100", "DISTRESS") acknowledge_dsc_alert(id1) acknowledge_dsc_alert(id2) unacked = get_dsc_alerts(acknowledged=False) acked = get_dsc_alerts(acknowledged=True) assert len(unacked) == 1 assert len(acked) == 2 def test_get_dsc_alerts_by_mmsi(self, temp_db): """Test filtering alerts by source MMSI.""" from utils.database import get_dsc_alerts, store_dsc_alert store_dsc_alert("232123456", "100", "DISTRESS") store_dsc_alert("232123456", "120", "URGENCY") store_dsc_alert("366000001", "100", "DISTRESS") alerts = get_dsc_alerts(source_mmsi="232123456") assert len(alerts) == 2 for alert in alerts: assert alert["source_mmsi"] == "232123456" def test_get_dsc_alerts_pagination(self, temp_db): """Test alert pagination.""" from utils.database import get_dsc_alerts, store_dsc_alert # Create 10 alerts for i in range(10): store_dsc_alert(f"23212345{i}", "100", "DISTRESS") # Get first page page1 = get_dsc_alerts(limit=5, offset=0) assert len(page1) == 5 # Get second page page2 = get_dsc_alerts(limit=5, offset=5) assert len(page2) == 5 # Ensure no overlap page1_ids = {a["id"] for a in page1} page2_ids = {a["id"] for a in page2} assert page1_ids.isdisjoint(page2_ids) def test_get_dsc_alerts_order(self, temp_db): """Test alerts are returned in reverse chronological order.""" from utils.database import get_dsc_alerts, store_dsc_alert id1 = store_dsc_alert("232123456", "100", "DISTRESS") id2 = store_dsc_alert("366000001", "100", "DISTRESS") id3 = store_dsc_alert("351234567", "100", "DISTRESS") alerts = get_dsc_alerts() # ORDER BY received_at DESC, so most recent first # When timestamps are identical, higher IDs are more recent # The actual order depends on the DB implementation # We just verify all 3 are present and it's a list assert len(alerts) == 3 alert_ids = {a["id"] for a in alerts} assert alert_ids == {id1, id2, id3} def test_acknowledge_dsc_alert(self, temp_db): """Test acknowledging a DSC alert.""" from utils.database import acknowledge_dsc_alert, get_dsc_alert, store_dsc_alert alert_id = store_dsc_alert("232123456", "100", "DISTRESS") # Initially not acknowledged alert = get_dsc_alert(alert_id) assert alert["acknowledged"] is False # Acknowledge it result = acknowledge_dsc_alert(alert_id) assert result is True # Now acknowledged alert = get_dsc_alert(alert_id) assert alert["acknowledged"] is True def test_acknowledge_dsc_alert_with_notes(self, temp_db): """Test acknowledging with notes.""" from utils.database import acknowledge_dsc_alert, get_dsc_alert, store_dsc_alert alert_id = store_dsc_alert("232123456", "100", "DISTRESS") acknowledge_dsc_alert(alert_id, notes="Vessel located, rescue underway") alert = get_dsc_alert(alert_id) assert alert["acknowledged"] is True assert alert["notes"] == "Vessel located, rescue underway" def test_acknowledge_nonexistent_alert(self, temp_db): """Test acknowledging an alert that doesn't exist.""" from utils.database import acknowledge_dsc_alert result = acknowledge_dsc_alert(99999) assert result is False def test_get_dsc_alert_summary(self, temp_db): """Test getting alert summary counts.""" from utils.database import acknowledge_dsc_alert, get_dsc_alert_summary, store_dsc_alert # Create various alerts store_dsc_alert("232123456", "100", "DISTRESS") store_dsc_alert("232123457", "100", "DISTRESS") store_dsc_alert("366000001", "120", "URGENCY") store_dsc_alert("351234567", "118", "SAFETY") acked_id = store_dsc_alert("257000001", "100", "DISTRESS") # Acknowledge one distress acknowledge_dsc_alert(acked_id) summary = get_dsc_alert_summary() assert summary["distress"] == 2 # 3 - 1 acknowledged assert summary["urgency"] == 1 assert summary["safety"] == 1 assert summary["total"] == 4 def test_get_dsc_alert_summary_empty(self, temp_db): """Test alert summary with no alerts.""" from utils.database import get_dsc_alert_summary summary = get_dsc_alert_summary() assert summary["distress"] == 0 assert summary["urgency"] == 0 assert summary["safety"] == 0 assert summary["routine"] == 0 assert summary["total"] == 0 def test_cleanup_old_dsc_alerts(self, temp_db): """Test cleanup function behavior.""" from utils.database import acknowledge_dsc_alert, cleanup_old_dsc_alerts, get_dsc_alerts, store_dsc_alert # Create and acknowledge some alerts id1 = store_dsc_alert("232123456", "100", "DISTRESS") id2 = store_dsc_alert("366000001", "100", "DISTRESS") id3 = store_dsc_alert("351234567", "100", "DISTRESS") # Unacknowledged acknowledge_dsc_alert(id1) acknowledge_dsc_alert(id2) # Cleanup with large max_age shouldn't delete recent records deleted = cleanup_old_dsc_alerts(max_age_days=30) assert deleted == 0 # Nothing old enough to delete # All 3 should still be present alerts = get_dsc_alerts() assert len(alerts) == 3 # Verify unacknowledged one is still unacknowledged unacked = get_dsc_alerts(acknowledged=False) assert len(unacked) == 1 assert unacked[0]["id"] == id3 def test_cleanup_preserves_unacknowledged(self, temp_db): """Test cleanup preserves unacknowledged alerts regardless of age.""" from utils.database import cleanup_old_dsc_alerts, get_dsc_alerts, store_dsc_alert # Create unacknowledged alerts store_dsc_alert("232123456", "100", "DISTRESS") store_dsc_alert("366000001", "100", "DISTRESS") # Cleanup with 0 days deleted = cleanup_old_dsc_alerts(max_age_days=0) # All should remain (none were acknowledged) alerts = get_dsc_alerts() assert len(alerts) == 2 assert deleted == 0 def test_store_alert_with_raw_message(self, temp_db): """Test storing alert with raw message data.""" from utils.database import get_dsc_alert, store_dsc_alert raw = "100023212345603660000110010010000000000127" alert_id = store_dsc_alert(source_mmsi="232123456", format_code="100", category="DISTRESS", raw_message=raw) alert = get_dsc_alert(alert_id) assert alert["raw_message"] == raw def test_store_alert_with_destination(self, temp_db): """Test storing alert with destination MMSI.""" from utils.database import get_dsc_alert, store_dsc_alert alert_id = store_dsc_alert( source_mmsi="232123456", format_code="112", category="INDIVIDUAL", dest_mmsi="366000001" ) alert = get_dsc_alert(alert_id) assert alert["dest_mmsi"] == "366000001" class TestDSCDatabaseIntegration: """Integration tests for DSC database operations.""" def test_full_alert_lifecycle(self, temp_db): """Test complete lifecycle of a DSC alert.""" from utils.database import ( acknowledge_dsc_alert, get_dsc_alert, get_dsc_alert_summary, get_dsc_alerts, store_dsc_alert, ) # 1. Store a distress alert alert_id = store_dsc_alert( source_mmsi="232123456", format_code="100", category="DISTRESS", source_name="MV Mayday", nature_of_distress="SINKING", latitude=50.0, longitude=-5.0, ) # 2. Verify it appears in summary summary = get_dsc_alert_summary() assert summary["distress"] == 1 assert summary["total"] == 1 # 3. Verify it appears in unacknowledged list unacked = get_dsc_alerts(acknowledged=False) assert len(unacked) == 1 assert unacked[0]["source_mmsi"] == "232123456" # 4. Acknowledge with notes acknowledge_dsc_alert(alert_id, "Rescue helicopter dispatched") # 5. Verify it's now acknowledged alert = get_dsc_alert(alert_id) assert alert["acknowledged"] is True assert alert["notes"] == "Rescue helicopter dispatched" # 6. Verify summary updated summary = get_dsc_alert_summary() assert summary["distress"] == 0 assert summary["total"] == 0 # 7. Verify it appears in acknowledged list acked = get_dsc_alerts(acknowledged=True) assert len(acked) == 1 def test_multiple_vessel_alerts(self, temp_db): """Test handling alerts from multiple vessels.""" from utils.database import get_dsc_alerts, store_dsc_alert # Simulate multiple vessels in distress vessels = [ ("232123456", "United Kingdom", "FIRE"), ("366000001", "USA", "FLOODING"), ("351234567", "Panama", "COLLISION"), ] for mmsi, _country, nature in vessels: store_dsc_alert(source_mmsi=mmsi, format_code="100", category="DISTRESS", nature_of_distress=nature) # Verify all alerts stored alerts = get_dsc_alerts(category="DISTRESS") assert len(alerts) == 3 # Verify each has correct nature natures = {a["nature_of_distress"] for a in alerts} assert natures == {"FIRE", "FLOODING", "COLLISION"}