mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 22:59:59 -07:00
- Fix SSE fanout thread AttributeError when source queue is None during interpreter shutdown by snapshotting to local variable with null guard - Fix branded "i" logo rendering oversized on first page load (FOUC) by adding inline width/height to SVG elements across 10 templates - Bump version to 2.26.0 in config.py, pyproject.toml, and CHANGELOG.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
399 lines
14 KiB
Python
399 lines
14 KiB
Python
"""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'}
|