From ac5e71d6f210b1eb7c710948620266525f28b59f Mon Sep 17 00:00:00 2001 From: jkingsman Date: Thu, 26 Mar 2026 17:20:13 -0700 Subject: [PATCH] Validate geofence radius to be positive --- app/routers/fanout.py | 8 ++++ tests/test_fanout.py | 85 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/app/routers/fanout.py b/app/routers/fanout.py index da0c9c8..f2ec2de 100644 --- a/app/routers/fanout.py +++ b/app/routers/fanout.py @@ -308,6 +308,14 @@ def _validate_map_upload_config(config: dict) -> None: # Persist the cleaned value (empty string means use the module default) config["api_url"] = api_url config["dry_run"] = bool(config.get("dry_run", True)) + config["geofence_enabled"] = bool(config.get("geofence_enabled", False)) + try: + radius = float(config.get("geofence_radius_km", 0) or 0) + except (TypeError, ValueError): + raise HTTPException(status_code=400, detail="geofence_radius_km must be a number") from None + if radius < 0: + raise HTTPException(status_code=400, detail="geofence_radius_km must be >= 0") + config["geofence_radius_km"] = radius def _enforce_scope(config_type: str, scope: dict) -> dict: diff --git a/tests/test_fanout.py b/tests/test_fanout.py index ad656d7..1ca1031 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -707,6 +707,91 @@ class TestSqsValidation: {"queue_url": "https://sqs.us-east-1.amazonaws.com/123456789012/mesh-events"} ) +class TestMapUploadValidation: + def test_rejects_bad_api_url_scheme(self): + from fastapi import HTTPException + + from app.routers.fanout import _validate_map_upload_config + + with pytest.raises(HTTPException) as exc_info: + _validate_map_upload_config({"api_url": "ftp://example.com"}) + assert exc_info.value.status_code == 400 + assert "api_url" in exc_info.value.detail + + def test_accepts_empty_api_url(self): + from app.routers.fanout import _validate_map_upload_config + + config = {"api_url": ""} + _validate_map_upload_config(config) + assert config["api_url"] == "" + + def test_accepts_valid_api_url(self): + from app.routers.fanout import _validate_map_upload_config + + config = {"api_url": "https://custom.example.com/upload"} + _validate_map_upload_config(config) + assert config["api_url"] == "https://custom.example.com/upload" + + def test_normalizes_dry_run_to_bool(self): + from app.routers.fanout import _validate_map_upload_config + + config = {"dry_run": 1} + _validate_map_upload_config(config) + assert config["dry_run"] is True + + def test_normalizes_geofence_enabled_to_bool(self): + from app.routers.fanout import _validate_map_upload_config + + config = {"geofence_enabled": 1} + _validate_map_upload_config(config) + assert config["geofence_enabled"] is True + + def test_normalizes_geofence_radius_to_float(self): + from app.routers.fanout import _validate_map_upload_config + + config = {"geofence_radius_km": 100} + _validate_map_upload_config(config) + assert config["geofence_radius_km"] == 100.0 + assert isinstance(config["geofence_radius_km"], float) + + def test_rejects_negative_geofence_radius(self): + from fastapi import HTTPException + + from app.routers.fanout import _validate_map_upload_config + + with pytest.raises(HTTPException) as exc_info: + _validate_map_upload_config({"geofence_radius_km": -1}) + assert exc_info.value.status_code == 400 + assert "geofence_radius_km" in exc_info.value.detail + + def test_rejects_non_numeric_geofence_radius(self): + from fastapi import HTTPException + + from app.routers.fanout import _validate_map_upload_config + + with pytest.raises(HTTPException) as exc_info: + _validate_map_upload_config({"geofence_radius_km": "bad"}) + assert exc_info.value.status_code == 400 + assert "geofence_radius_km" in exc_info.value.detail + + def test_accepts_zero_geofence_radius(self): + from app.routers.fanout import _validate_map_upload_config + + config = {"geofence_radius_km": 0} + _validate_map_upload_config(config) + assert config["geofence_radius_km"] == 0.0 + + def test_defaults_applied_when_keys_absent(self): + from app.routers.fanout import _validate_map_upload_config + + config = {} + _validate_map_upload_config(config) + assert config["api_url"] == "" + assert config["dry_run"] is True + assert config["geofence_enabled"] is False + assert config["geofence_radius_km"] == 0.0 + + def test_enforce_scope_sqs_preserves_raw_packets_setting(self): from app.routers.fanout import _enforce_scope