48fcb00241
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>
223 lines
7.9 KiB
Python
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
|