Loading src/open_exposure_gateway/adapters/http/srm_client.py +16 −1 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ from uuid import UUID import httpx import structlog from pydantic import ValidationError from open_exposure_gateway.core.config import get_settings from open_exposure_gateway.core.exceptions import ( Loading Loading @@ -255,4 +256,18 @@ class SRMClient: message="Device identifier not found.", error_code=ErrorCode.IDENTIFIER_NOT_FOUND, ) from exc try: return SRMLocationResult.model_validate(data) except ValidationError as exc: logger.warning( "SRM returned an unusable location", x_correlator=x_correlator, errors=[ {"loc": ".".join(str(part) for part in error["loc"]), "msg": error["msg"]} for error in exc.errors() ], ) raise DownstreamServiceException( message="SRM returned an unusable location", ) from exc tests/unit/test_srm_client.py +62 −0 Original line number Diff line number Diff line Loading @@ -349,3 +349,65 @@ async def test_location_query_targets_the_internal_base_path( await client.retrieve_location(_location_query()) assert called["path"] == "/internal/network-queries/location" _VALID_AREA = { "area_type": "circle", "center": {"latitude": 45.75, "longitude": 4.86}, "radius_m": 800, } @pytest.mark.parametrize( ("name", "body"), [ ("last_location_time missing", {"area": _VALID_AREA}), ( "area_type in CAMARA casing instead of canonical", { "last_location_time": "2023-10-17T13:18:23.682Z", "area": {**_VALID_AREA, "area_type": "CIRCLE"}, }, ), ( "timestamp without a time zone", {"last_location_time": "2023-10-17T13:18:23", "area": _VALID_AREA}, ), ("area missing entirely", {"last_location_time": "2023-10-17T13:18:23.682Z"}), ], ) async def test_unusable_200_location_body_is_a_downstream_failure( monkeypatch: pytest.MonkeyPatch, name: str, body: dict[str, Any] ) -> None: client = _client(monkeypatch, lambda request: httpx.Response(200, json=body)) with pytest.raises(DownstreamServiceException) as exc_info: await client.retrieve_location(_location_query()) assert exc_info.value.status_code == 503 async def test_unusable_location_body_is_not_echoed_to_the_consumer( monkeypatch: pytest.MonkeyPatch, ) -> None: """pydantic renders the offending input into its error text, and here that input is the subscriber's coordinates. Neither the outward message nor `details` may carry it.""" client = _client( monkeypatch, lambda request: httpx.Response( 200, json={ "area": { "area_type": "circle", "center": {"latitude": 45.754114, "longitude": 4.860374}, "radius_m": 800, } }, ), ) with pytest.raises(DownstreamServiceException) as exc_info: await client.retrieve_location(_location_query()) assert exc_info.value.message == "SRM returned an unusable location" assert "45.754114" not in str(exc_info.value.details) Loading
src/open_exposure_gateway/adapters/http/srm_client.py +16 −1 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ from uuid import UUID import httpx import structlog from pydantic import ValidationError from open_exposure_gateway.core.config import get_settings from open_exposure_gateway.core.exceptions import ( Loading Loading @@ -255,4 +256,18 @@ class SRMClient: message="Device identifier not found.", error_code=ErrorCode.IDENTIFIER_NOT_FOUND, ) from exc try: return SRMLocationResult.model_validate(data) except ValidationError as exc: logger.warning( "SRM returned an unusable location", x_correlator=x_correlator, errors=[ {"loc": ".".join(str(part) for part in error["loc"]), "msg": error["msg"]} for error in exc.errors() ], ) raise DownstreamServiceException( message="SRM returned an unusable location", ) from exc
tests/unit/test_srm_client.py +62 −0 Original line number Diff line number Diff line Loading @@ -349,3 +349,65 @@ async def test_location_query_targets_the_internal_base_path( await client.retrieve_location(_location_query()) assert called["path"] == "/internal/network-queries/location" _VALID_AREA = { "area_type": "circle", "center": {"latitude": 45.75, "longitude": 4.86}, "radius_m": 800, } @pytest.mark.parametrize( ("name", "body"), [ ("last_location_time missing", {"area": _VALID_AREA}), ( "area_type in CAMARA casing instead of canonical", { "last_location_time": "2023-10-17T13:18:23.682Z", "area": {**_VALID_AREA, "area_type": "CIRCLE"}, }, ), ( "timestamp without a time zone", {"last_location_time": "2023-10-17T13:18:23", "area": _VALID_AREA}, ), ("area missing entirely", {"last_location_time": "2023-10-17T13:18:23.682Z"}), ], ) async def test_unusable_200_location_body_is_a_downstream_failure( monkeypatch: pytest.MonkeyPatch, name: str, body: dict[str, Any] ) -> None: client = _client(monkeypatch, lambda request: httpx.Response(200, json=body)) with pytest.raises(DownstreamServiceException) as exc_info: await client.retrieve_location(_location_query()) assert exc_info.value.status_code == 503 async def test_unusable_location_body_is_not_echoed_to_the_consumer( monkeypatch: pytest.MonkeyPatch, ) -> None: """pydantic renders the offending input into its error text, and here that input is the subscriber's coordinates. Neither the outward message nor `details` may carry it.""" client = _client( monkeypatch, lambda request: httpx.Response( 200, json={ "area": { "area_type": "circle", "center": {"latitude": 45.754114, "longitude": 4.860374}, "radius_m": 800, } }, ), ) with pytest.raises(DownstreamServiceException) as exc_info: await client.retrieve_location(_location_query()) assert exc_info.value.message == "SRM returned an unusable location" assert "45.754114" not in str(exc_info.value.details)