Commit ecebaa90 authored by Sergio Gimenez's avatar Sergio Gimenez
Browse files

fix(federation): relay FM's 409 when a partner already has an agreement

FM now keeps one agreement per partner, edited in place: a second
POST /internal/agreements answers 409 agreement-exists, and so does a
PATCH that would bring an expired agreement back next to a live one.
The relay's FM contract listed no 409 for either route, so the operator
got a 502 instead of FM's ProblemDetails telling them which agreement to
edit. Add 409 to both, in the status allowlists and the OpenAPI
responses, and reword the PATCH summary now that any term can change.

Co-Authored-By: default avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 8b1048c8
Loading
Loading
Loading
Loading
Loading
+7 −6
Changes for src/open_exposure_gateway/api/gsma/federation_manager/v1_4_0/router.py: 7 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -121,10 +121,11 @@ _FM_PARTNER_GET_STATUSES = frozenset({200, 404, 500})
_FM_PARTNER_UPDATE_STATUSES = frozenset({200, 400, 404, 409, 422, 500, 501})

# Agreements are FM's own API too: 400 for a bad body, 404 for an unknown partner or
# agreement, 422 for a validity period that ends before it starts.
# agreement, 409 when the partner already has its one agreement, 422 for a validity period
# that ends before it starts.
_FM_LOCAL_ZONES_STATUSES = frozenset({200, 500})
_FM_AGREEMENT_CREATE_STATUSES = frozenset({201, 400, 404, 422, 500})
_FM_AGREEMENT_UPDATE_STATUSES = frozenset({200, 400, 404, 422, 500})
_FM_AGREEMENT_CREATE_STATUSES = frozenset({201, 400, 404, 409, 422, 500})
_FM_AGREEMENT_UPDATE_STATUSES = frozenset({200, 400, 404, 409, 422, 500})

_PROBLEM_JSON = "application/problem+json"

@@ -395,7 +396,7 @@ async def list_local_zones(
    summary="Record what a partner may use here: zones, their GSMA terms, allowed requests",
    operation_id="create_agreement",
    response_model=Agreement,
    responses=_responses(400, 401, 404, 422, 500, 502, 503),
    responses=_responses(400, 401, 404, 409, 422, 500, 502, 503),
)
async def create_agreement(
    request: Request,
@@ -415,10 +416,10 @@ async def create_agreement(
@router.patch(
    "/agreements/{agreementId}",
    tags=["Federation Manager"],
    summary="Change an agreement's status, usage limits or validity",
    summary="Change any of a partner's agreement terms in place",
    operation_id="update_agreement",
    response_model=Agreement,
    responses=_responses(400, 401, 404, 422, 500, 502, 503),
    responses=_responses(400, 401, 404, 409, 422, 500, 502, 503),
)
async def update_agreement(
    agreementId: UUID,
+2 −2
Changes for tests/unit/test_federation_manager_endpoints.py: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -951,7 +951,7 @@ def test_agreements_relay_to_fm_unaltered(


@pytest.mark.parametrize(("http_method", "path", "body", "status"), _AGREEMENT_ENDPOINTS)
@pytest.mark.parametrize("fm_status", [400, 404, 422, 500])
@pytest.mark.parametrize("fm_status", [400, 404, 409, 422, 500])
def test_agreement_errors_from_fm_are_relayed(
    client: TestClient,
    fake_fm_client: _FakeFmClient,
@@ -979,7 +979,7 @@ def test_agreement_errors_from_fm_are_relayed(

@pytest.mark.parametrize(("http_method", "path", "body", "status"), _AGREEMENT_ENDPOINTS)
@pytest.mark.parametrize(
    ("fm_status", "fm_body"), [(409, _AGREEMENT_BODY), (None, b"{}"), (None, _PARTNER_BODY)]
    ("fm_status", "fm_body"), [(403, _AGREEMENT_BODY), (None, b"{}"), (None, _PARTNER_BODY)]
)
def test_agreement_answers_outside_fm_contract_return_502(
    client: TestClient,