Commit a84df597 authored by Dimitrios Gogos's avatar Dimitrios Gogos
Browse files

feat: enhance error handling for unusable location responses from SRM

parent c9fd3508
Loading
Loading
Loading
Loading
Loading
+16 −1
Original line number Diff line number Diff line
@@ -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 (
@@ -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
+62 −0
Original line number Diff line number Diff line
@@ -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)