Files
intercept/tests/test_geofence.py
T
James Smith 96172ca593 style: apply ruff-format to entire codebase
First-time run of ruff-format via pre-commit hook normalises quote
style, trailing commas, and whitespace across 188 Python files.
No logic changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 14:48:11 +01:00

148 lines
4.9 KiB
Python

"""Tests for geofence haversine, enter/exit detection, and persistence."""
from unittest.mock import MagicMock, patch
import pytest
class TestHaversineDistance:
"""Test haversine_distance accuracy."""
def test_same_point_zero_distance(self):
from utils.geofence import haversine_distance
assert haversine_distance(51.5, -0.1, 51.5, -0.1) == 0.0
def test_known_distance_london_paris(self):
from utils.geofence import haversine_distance
# London to Paris ~340km
dist = haversine_distance(51.5074, -0.1278, 48.8566, 2.3522)
assert 340_000 < dist < 345_000
def test_short_distance(self):
from utils.geofence import haversine_distance
# Two points ~111m apart (0.001 degrees latitude at equator)
dist = haversine_distance(0.0, 0.0, 0.001, 0.0)
assert 100 < dist < 120
def test_antipodal_distance(self):
from utils.geofence import haversine_distance
# North pole to south pole ~20015km
dist = haversine_distance(90.0, 0.0, -90.0, 0.0)
assert 20_000_000 < dist < 20_050_000
class TestGeofenceManager:
"""Test enter/exit detection logic."""
@pytest.fixture(autouse=True)
def _setup(self):
"""Provide a fresh GeofenceManager with mocked DB."""
from utils.geofence import GeofenceManager
with patch("utils.geofence._ensure_table"), patch("utils.geofence.get_db") as mock_db:
# Mock the context manager
mock_conn = MagicMock()
mock_db.return_value.__enter__ = MagicMock(return_value=mock_conn)
mock_db.return_value.__exit__ = MagicMock(return_value=False)
self.manager = GeofenceManager()
# Override list_zones to return test data
self._zones = []
self.manager.list_zones = lambda: self._zones
def test_no_zones_returns_empty(self):
events = self.manager.check_position("TEST1", "aircraft", 51.5, -0.1)
assert events == []
def test_enter_event(self):
self._zones = [
{
"id": 1,
"name": "London",
"lat": 51.5074,
"lon": -0.1278,
"radius_m": 10000,
"alert_on": "enter_exit",
}
]
# First position inside zone
events = self.manager.check_position("AC1", "aircraft", 51.5074, -0.1278)
assert len(events) == 1
assert events[0]["type"] == "geofence_enter"
assert events[0]["zone_name"] == "London"
def test_no_duplicate_enter(self):
self._zones = [
{
"id": 1,
"name": "London",
"lat": 51.5074,
"lon": -0.1278,
"radius_m": 10000,
"alert_on": "enter_exit",
}
]
# First enter
self.manager.check_position("AC1", "aircraft", 51.5074, -0.1278)
# Second check still inside - should not fire enter again
events = self.manager.check_position("AC1", "aircraft", 51.508, -0.128)
assert len(events) == 0
def test_exit_event(self):
self._zones = [
{
"id": 1,
"name": "London",
"lat": 51.5074,
"lon": -0.1278,
"radius_m": 1000,
"alert_on": "enter_exit",
}
]
# Enter
self.manager.check_position("AC1", "aircraft", 51.5074, -0.1278)
# Exit (far away)
events = self.manager.check_position("AC1", "aircraft", 52.0, 0.0)
assert len(events) == 1
assert events[0]["type"] == "geofence_exit"
def test_enter_only_mode(self):
self._zones = [
{
"id": 1,
"name": "London",
"lat": 51.5074,
"lon": -0.1278,
"radius_m": 1000,
"alert_on": "enter",
}
]
# Enter
events = self.manager.check_position("AC1", "aircraft", 51.5074, -0.1278)
assert len(events) == 1
assert events[0]["type"] == "geofence_enter"
# Exit should not fire
events = self.manager.check_position("AC1", "aircraft", 52.0, 0.0)
assert len(events) == 0
def test_metadata_included_in_event(self):
self._zones = [
{
"id": 1,
"name": "Zone",
"lat": 0.0,
"lon": 0.0,
"radius_m": 100000,
"alert_on": "enter_exit",
}
]
events = self.manager.check_position(
"AC1", "aircraft", 0.0, 0.0, metadata={"callsign": "TEST01", "altitude": 35000}
)
assert events[0]["callsign"] == "TEST01"
assert events[0]["altitude"] == 35000