Files
giglez/tests/unit/test_gps_validator.py
T
Trilltechnician 48fcb00241 Phase 3 Complete: Web Interface MVP
Major Achievements:
-  Full web interface (1,520+ lines of frontend code)
-  Interactive Leaflet.js map with marker clustering
-  Drag-and-drop upload system with GPS input
-  Search & filter UI with multi-criteria
-  Statistics dashboard with Chart.js
-  Responsive mobile-friendly design

Backend:
-  FastAPI static file serving
-  Simplified server mode (main_simple.py)
-  Improved startup script with port auto-selection
-  PostgreSQL schema ready (requires setup)

Database:
-  SQLite populated with 85 Flipper Zero signatures
-  Device matching system operational
-  Frequency-based search working

Documentation:
-  PHASE_3_COMPLETE.md - Technical summary
-  WEB_INTERFACE_README.md - User guide
-  WEBAPP_STARTUP_GUIDE.md - Troubleshooting
-  POSTGRESQL_SETUP_EXPLANATION.md - DB setup guide
-  DATABASE_POPULATION_SUCCESS.md - Import report
-  DEVICE_IDENTIFICATION_REPORT.md - Matching analysis

Files Created:
- templates/index.html (260 lines)
- static/css/main.css (500 lines)
- static/js/*.js (760 lines total)
- src/api/main_simple.py (simplified server)
- start_web.sh (auto port selection)

Status: Production MVP Ready
Next: Phase 4 - API & Integration

🛰️ Generated with Claude Code
https://claude.com/claude-code

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-12 18:21:11 -08:00

223 lines
7.9 KiB
Python

"""
Unit Tests for GPS Validator
Tests GPS coordinate validation, Null Island detection, accuracy thresholds
"""
import pytest
from src.gps.validator import (
validate_gps_coordinates,
is_null_island,
is_valid_accuracy,
anonymize_gps,
calculate_distance,
GPSCoordinate,
GPSValidator
)
@pytest.mark.unit
class TestGPSValidation:
"""Test GPS coordinate validation"""
def test_valid_coordinates(self, sample_gps_coordinates):
"""Test valid GPS coordinates"""
for lat, lon, accuracy, location in sample_gps_coordinates:
assert validate_gps_coordinates(lat, lon, accuracy), \
f"Valid coordinates rejected: {location}"
def test_invalid_coordinates(self, invalid_gps_coordinates):
"""Test invalid GPS coordinates"""
for lat, lon, accuracy, reason in invalid_gps_coordinates:
assert not validate_gps_coordinates(lat, lon, accuracy), \
f"Invalid coordinates accepted: {reason}"
def test_null_island_detection(self):
"""Test Null Island (0, 0) detection"""
assert is_null_island(0.0, 0.0)
assert is_null_island(0.0001, 0.0001) # Close to null island
assert not is_null_island(40.7128, -74.0060) # NYC
def test_accuracy_validation(self):
"""Test GPS accuracy threshold validation"""
assert is_valid_accuracy(5.0) # High quality
assert is_valid_accuracy(25.0) # Medium quality
assert is_valid_accuracy(50.0) # Threshold
assert not is_valid_accuracy(51.0) # Over threshold
assert not is_valid_accuracy(100.0) # Poor accuracy
assert not is_valid_accuracy(0.0) # Invalid
assert not is_valid_accuracy(-5.0) # Invalid
def test_latitude_bounds(self):
"""Test latitude boundary validation"""
assert validate_gps_coordinates(90.0, 0.0, 5.0) # Max valid
assert validate_gps_coordinates(-90.0, 0.0, 5.0) # Min valid
assert not validate_gps_coordinates(90.1, 0.0, 5.0) # Over max
assert not validate_gps_coordinates(-90.1, 0.0, 5.0) # Under min
def test_longitude_bounds(self):
"""Test longitude boundary validation"""
assert validate_gps_coordinates(0.0, 180.0, 5.0) # Max valid
assert validate_gps_coordinates(0.0, -180.0, 5.0) # Min valid
assert not validate_gps_coordinates(0.0, 180.1, 5.0) # Over max
assert not validate_gps_coordinates(0.0, -180.1, 5.0) # Under min
@pytest.mark.unit
class TestGPSAnonymization:
"""Test GPS coordinate anonymization"""
def test_anonymize_10m_precision(self):
"""Test 10m precision anonymization"""
lat, lon = anonymize_gps(40.712846, -74.005974, precision_meters=10)
assert lat == pytest.approx(40.71285, abs=0.00001)
assert lon == pytest.approx(-74.00597, abs=0.00001)
def test_anonymize_100m_precision(self):
"""Test 100m precision anonymization"""
lat, lon = anonymize_gps(40.712846, -74.005974, precision_meters=100)
assert lat == pytest.approx(40.713, abs=0.001)
assert lon == pytest.approx(-74.006, abs=0.001)
def test_anonymize_1km_precision(self):
"""Test 1km precision anonymization"""
lat, lon = anonymize_gps(40.712846, -74.005974, precision_meters=1000)
assert lat == pytest.approx(40.71, abs=0.01)
assert lon == pytest.approx(-74.01, abs=0.01)
@pytest.mark.unit
class TestDistanceCalculation:
"""Test Haversine distance calculation"""
def test_distance_same_point(self):
"""Test distance between same point"""
distance = calculate_distance(40.7128, -74.0060, 40.7128, -74.0060)
assert distance == pytest.approx(0.0, abs=0.001)
def test_distance_nyc_to_london(self):
"""Test distance NYC to London"""
# NYC: 40.7128, -74.0060
# London: 51.5074, -0.1278
distance = calculate_distance(40.7128, -74.0060, 51.5074, -0.1278)
assert distance == pytest.approx(5570, abs=20) # ~5570 km (geopy calculation)
def test_distance_symmetry(self):
"""Test distance calculation is symmetric"""
d1 = calculate_distance(40.7128, -74.0060, 51.5074, -0.1278)
d2 = calculate_distance(51.5074, -0.1278, 40.7128, -74.0060)
assert d1 == pytest.approx(d2)
@pytest.mark.unit
class TestGPSCoordinate:
"""Test GPSCoordinate dataclass"""
def test_create_valid_coordinate(self):
"""Test creating valid GPS coordinate"""
coord = GPSCoordinate(
latitude=40.7128,
longitude=-74.0060,
accuracy=5.0
)
assert coord.latitude == 40.7128
assert coord.longitude == -74.0060
assert coord.accuracy == 5.0
def test_create_invalid_coordinate(self):
"""Test creating invalid GPS coordinate raises ValueError"""
with pytest.raises(ValueError):
GPSCoordinate(
latitude=91.0, # Invalid
longitude=0.0,
accuracy=5.0
)
def test_is_high_quality(self):
"""Test high-quality detection"""
high_quality = GPSCoordinate(40.7128, -74.0060, accuracy=8.0)
assert high_quality.is_high_quality()
medium_quality = GPSCoordinate(40.7128, -74.0060, accuracy=25.0)
assert not medium_quality.is_high_quality()
def test_to_dict(self):
"""Test serialization to dictionary"""
coord = GPSCoordinate(40.7128, -74.0060, accuracy=5.0)
data = coord.to_dict()
assert data['latitude'] == 40.7128
assert data['longitude'] == -74.0060
assert data['accuracy'] == 5.0
@pytest.mark.unit
class TestGPSValidatorClass:
"""Test GPSValidator class with configurable thresholds"""
def test_default_thresholds(self):
"""Test validator with default thresholds"""
validator = GPSValidator()
is_valid, reason = validator.validate(40.7128, -74.0060, 5.0)
assert is_valid
assert reason is None
def test_custom_max_accuracy(self):
"""Test custom maximum accuracy threshold"""
validator = GPSValidator(max_accuracy=30.0)
# Should accept 25m
is_valid, reason = validator.validate(40.7128, -74.0060, 25.0)
assert is_valid
# Should reject 35m
is_valid, reason = validator.validate(40.7128, -74.0060, 35.0)
assert not is_valid
assert "accuracy" in reason.lower()
def test_strict_mode(self):
"""Test strict mode (requires accuracy data)"""
validator = GPSValidator(strict_mode=True)
# Should reject without accuracy
is_valid, reason = validator.validate(40.7128, -74.0060, None)
assert not is_valid
assert "accuracy" in reason.lower()
def test_allow_null_island(self):
"""Test allowing Null Island coordinates"""
validator = GPSValidator(allow_null_island=True)
is_valid, reason = validator.validate(0.0, 0.0, 5.0)
assert is_valid
def test_validation_statistics(self):
"""Test validation statistics tracking"""
validator = GPSValidator()
# Validate several coordinates
validator.validate(40.7128, -74.0060, 5.0) # Valid
validator.validate(91.0, 0.0, 5.0) # Invalid latitude
validator.validate(0.0, 0.0, 5.0) # Null Island
stats = validator.get_statistics()
assert stats['total_validated'] == 3
assert stats['total_accepted'] == 1
assert stats['total_rejected'] == 2
assert stats['acceptance_rate'] == pytest.approx(1/3, abs=0.01)
assert 'out_of_bounds' in stats['rejection_reasons']
assert 'null_island' in stats['rejection_reasons']
def test_reset_statistics(self):
"""Test statistics reset"""
validator = GPSValidator()
validator.validate(40.7128, -74.0060, 5.0)
validator.reset_statistics()
stats = validator.get_statistics()
assert stats['total_validated'] == 0
assert stats['total_accepted'] == 0