""" 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