Skip to content

Commit ac189fe

Browse files
authored
APIGW: internalize DeleteIntegrationResponse (#13046)
1 parent a7937e8 commit ac189fe

File tree

4 files changed

+246
-60
lines changed

4 files changed

+246
-60
lines changed

localstack-core/localstack/services/apigateway/legacy/provider.py

Lines changed: 87 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -665,66 +665,6 @@ def _delete_children(resource_to_delete: str):
665665
parent_id = moto_resource.parent_id
666666
api_resources[parent_id].remove(resource_id)
667667

668-
def update_integration_response(
669-
self,
670-
context: RequestContext,
671-
rest_api_id: String,
672-
resource_id: String,
673-
http_method: String,
674-
status_code: StatusCode,
675-
patch_operations: ListOfPatchOperation = None,
676-
**kwargs,
677-
) -> IntegrationResponse:
678-
# XXX: THIS IS NOT A COMPLETE IMPLEMENTATION, just the minimum required to get tests going
679-
# TODO: validate patch operations
680-
681-
moto_rest_api = get_moto_rest_api(context, rest_api_id)
682-
moto_resource = moto_rest_api.resources.get(resource_id)
683-
if not moto_resource:
684-
raise NotFoundException("Invalid Resource identifier specified")
685-
686-
moto_method = moto_resource.resource_methods.get(http_method)
687-
if not moto_method:
688-
raise NotFoundException("Invalid Method identifier specified")
689-
690-
integration_response = moto_method.method_integration.integration_responses.get(status_code)
691-
if not integration_response:
692-
raise NotFoundException("Invalid Integration Response identifier specified")
693-
694-
for patch_operation in patch_operations:
695-
op = patch_operation.get("op")
696-
path = patch_operation.get("path")
697-
698-
# for path "/responseTemplates/application~1json"
699-
if "/responseTemplates" in path:
700-
integration_response.response_templates = (
701-
integration_response.response_templates or {}
702-
)
703-
value = patch_operation.get("value")
704-
if not isinstance(value, str):
705-
raise BadRequestException(
706-
f"Invalid patch value '{value}' specified for op '{op}'. Must be a string"
707-
)
708-
param = path.removeprefix("/responseTemplates/")
709-
param = param.replace("~1", "/")
710-
if op == "remove":
711-
integration_response.response_templates.pop(param)
712-
elif op in ("add", "replace"):
713-
integration_response.response_templates[param] = value
714-
715-
elif "/contentHandling" in path and op == "replace":
716-
integration_response.content_handling = patch_operation.get("value")
717-
718-
elif "/selectionPattern" in path and op == "replace":
719-
integration_response.selection_pattern = patch_operation.get("value")
720-
721-
response: IntegrationResponse = integration_response.to_json()
722-
# in case it's empty, we still want to pass it on as ""
723-
# TODO: add a test case for this
724-
response["selectionPattern"] = integration_response.selection_pattern
725-
726-
return response
727-
728668
def update_resource(
729669
self,
730670
context: RequestContext,
@@ -2307,6 +2247,93 @@ def put_integration_response(
23072247

23082248
return response
23092249

2250+
def update_integration_response(
2251+
self,
2252+
context: RequestContext,
2253+
rest_api_id: String,
2254+
resource_id: String,
2255+
http_method: String,
2256+
status_code: StatusCode,
2257+
patch_operations: ListOfPatchOperation = None,
2258+
**kwargs,
2259+
) -> IntegrationResponse:
2260+
# XXX: THIS IS NOT A COMPLETE IMPLEMENTATION, just the minimum required to get tests going
2261+
# TODO: validate patch operations
2262+
2263+
moto_rest_api = get_moto_rest_api(context, rest_api_id)
2264+
moto_resource = moto_rest_api.resources.get(resource_id)
2265+
if not moto_resource:
2266+
raise NotFoundException("Invalid Resource identifier specified")
2267+
2268+
moto_method = moto_resource.resource_methods.get(http_method)
2269+
if not moto_method:
2270+
raise NotFoundException("Invalid Method identifier specified")
2271+
2272+
integration_response = moto_method.method_integration.integration_responses.get(status_code)
2273+
if not integration_response:
2274+
raise NotFoundException("Invalid Integration Response identifier specified")
2275+
2276+
for patch_operation in patch_operations:
2277+
op = patch_operation.get("op")
2278+
path = patch_operation.get("path")
2279+
2280+
# for path "/responseTemplates/application~1json"
2281+
if "/responseTemplates" in path:
2282+
integration_response.response_templates = (
2283+
integration_response.response_templates or {}
2284+
)
2285+
value = patch_operation.get("value")
2286+
if not isinstance(value, str):
2287+
raise BadRequestException(
2288+
f"Invalid patch value '{value}' specified for op '{op}'. Must be a string"
2289+
)
2290+
param = path.removeprefix("/responseTemplates/")
2291+
param = param.replace("~1", "/")
2292+
if op == "remove":
2293+
integration_response.response_templates.pop(param)
2294+
elif op in ("add", "replace"):
2295+
integration_response.response_templates[param] = value
2296+
2297+
elif "/contentHandling" in path and op == "replace":
2298+
integration_response.content_handling = patch_operation.get("value")
2299+
2300+
elif "/selectionPattern" in path and op == "replace":
2301+
integration_response.selection_pattern = patch_operation.get("value")
2302+
2303+
response: IntegrationResponse = integration_response.to_json()
2304+
# in case it's empty, we still want to pass it on as ""
2305+
# TODO: add a test case for this
2306+
response["selectionPattern"] = integration_response.selection_pattern
2307+
2308+
return response
2309+
2310+
def delete_integration_response(
2311+
self,
2312+
context: RequestContext,
2313+
rest_api_id: String,
2314+
resource_id: String,
2315+
http_method: String,
2316+
status_code: StatusCode,
2317+
**kwargs,
2318+
) -> None:
2319+
moto_backend = apigw_models.apigateway_backends[context.account_id][context.region]
2320+
moto_rest_api = moto_backend.apis.get(rest_api_id)
2321+
if not moto_rest_api:
2322+
raise NotFoundException("Invalid Resource identifier specified")
2323+
2324+
if not (moto_resource := moto_rest_api.resources.get(resource_id)):
2325+
raise NotFoundException("Invalid Resource identifier specified")
2326+
2327+
if not (moto_method := moto_resource.resource_methods.get(http_method)):
2328+
raise NotFoundException("Invalid Integration identifier specified")
2329+
2330+
if not moto_method.method_integration:
2331+
raise NotFoundException("Invalid Integration identifier specified")
2332+
if not (
2333+
integration_responses := moto_method.method_integration.integration_responses
2334+
) or not integration_responses.pop(status_code, None):
2335+
raise NotFoundException("Invalid Response status code specified")
2336+
23102337
def get_export(
23112338
self,
23122339
context: RequestContext,

tests/aws/services/apigateway/test_apigateway_api.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3322,6 +3322,85 @@ def test_lifecycle_integration_response(self, aws_client, apigw_create_rest_api,
33223322
)
33233323
snapshot.match("delete-integration-response", delete_response)
33243324

3325+
@markers.aws.validated
3326+
def test_delete_integration_response_errors(
3327+
self, aws_client_factory, apigw_create_rest_api, snapshot
3328+
):
3329+
snapshot.add_transformer(snapshot.transform.key_value("cacheNamespace"))
3330+
apigw_client = aws_client_factory(config=Config(parameter_validation=False)).apigateway
3331+
response = apigw_create_rest_api(name=f"test-api-{short_uid()}")
3332+
api_id = response["id"]
3333+
root_resource_id = response["rootResourceId"]
3334+
3335+
apigw_client.put_method(
3336+
restApiId=api_id,
3337+
resourceId=root_resource_id,
3338+
httpMethod="GET",
3339+
authorizationType="NONE",
3340+
)
3341+
apigw_client.put_integration(
3342+
restApiId=api_id,
3343+
resourceId=root_resource_id,
3344+
httpMethod="GET",
3345+
type="MOCK",
3346+
requestTemplates={"application/json": '{"statusCode": 200}'},
3347+
)
3348+
3349+
put_response = apigw_client.put_integration_response(
3350+
restApiId=api_id,
3351+
resourceId=root_resource_id,
3352+
httpMethod="GET",
3353+
statusCode="200",
3354+
responseTemplates={"application/json": '"created"'},
3355+
selectionPattern="",
3356+
)
3357+
snapshot.match("put-integration-response", put_response)
3358+
3359+
with pytest.raises(ClientError) as e:
3360+
apigw_client.delete_integration_response(
3361+
restApiId=api_id,
3362+
resourceId="bad-resource",
3363+
httpMethod="GET",
3364+
statusCode="200",
3365+
)
3366+
snapshot.match("non-existent-resource", e.value.response)
3367+
3368+
with pytest.raises(ClientError) as e:
3369+
apigw_client.delete_integration_response(
3370+
restApiId="bad-api-id",
3371+
resourceId=root_resource_id,
3372+
httpMethod="GET",
3373+
statusCode="200",
3374+
)
3375+
snapshot.match("non-existent-api-id", e.value.response)
3376+
3377+
with pytest.raises(ClientError) as e:
3378+
apigw_client.delete_integration_response(
3379+
restApiId=api_id,
3380+
resourceId=root_resource_id,
3381+
httpMethod="POST",
3382+
statusCode="200",
3383+
)
3384+
snapshot.match("non-existent-method", e.value.response)
3385+
3386+
with pytest.raises(ClientError) as e:
3387+
apigw_client.delete_integration_response(
3388+
restApiId=api_id,
3389+
resourceId=root_resource_id,
3390+
httpMethod="GET",
3391+
statusCode="201",
3392+
)
3393+
snapshot.match("non-existent-status-code", e.value.response)
3394+
3395+
with pytest.raises(ClientError) as e:
3396+
apigw_client.delete_integration_response(
3397+
restApiId=api_id,
3398+
resourceId=root_resource_id,
3399+
httpMethod="WRONG",
3400+
statusCode="201",
3401+
)
3402+
snapshot.match("bad-method", e.value.response)
3403+
33253404
@markers.aws.validated
33263405
def test_update_method_wrong_param_names(self, aws_client, apigw_create_rest_api, snapshot):
33273406
snapshot.add_transformer(snapshot.transform.key_value("cacheNamespace"))

tests/aws/services/apigateway/test_apigateway_api.snapshot.json

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4763,5 +4763,76 @@
47634763
}
47644764
}
47654765
}
4766+
},
4767+
"tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_delete_integration_response_errors": {
4768+
"recorded-date": "21-08-2025, 17:53:19",
4769+
"recorded-content": {
4770+
"put-integration-response": {
4771+
"responseTemplates": {
4772+
"application/json": "\"created\""
4773+
},
4774+
"selectionPattern": "",
4775+
"statusCode": "200",
4776+
"ResponseMetadata": {
4777+
"HTTPHeaders": {},
4778+
"HTTPStatusCode": 201
4779+
}
4780+
},
4781+
"non-existent-resource": {
4782+
"Error": {
4783+
"Code": "NotFoundException",
4784+
"Message": "Invalid Resource identifier specified"
4785+
},
4786+
"message": "Invalid Resource identifier specified",
4787+
"ResponseMetadata": {
4788+
"HTTPHeaders": {},
4789+
"HTTPStatusCode": 404
4790+
}
4791+
},
4792+
"non-existent-api-id": {
4793+
"Error": {
4794+
"Code": "NotFoundException",
4795+
"Message": "Invalid Resource identifier specified"
4796+
},
4797+
"message": "Invalid Resource identifier specified",
4798+
"ResponseMetadata": {
4799+
"HTTPHeaders": {},
4800+
"HTTPStatusCode": 404
4801+
}
4802+
},
4803+
"non-existent-method": {
4804+
"Error": {
4805+
"Code": "NotFoundException",
4806+
"Message": "Invalid Integration identifier specified"
4807+
},
4808+
"message": "Invalid Integration identifier specified",
4809+
"ResponseMetadata": {
4810+
"HTTPHeaders": {},
4811+
"HTTPStatusCode": 404
4812+
}
4813+
},
4814+
"non-existent-status-code": {
4815+
"Error": {
4816+
"Code": "NotFoundException",
4817+
"Message": "Invalid Response status code specified"
4818+
},
4819+
"message": "Invalid Response status code specified",
4820+
"ResponseMetadata": {
4821+
"HTTPHeaders": {},
4822+
"HTTPStatusCode": 404
4823+
}
4824+
},
4825+
"bad-method": {
4826+
"Error": {
4827+
"Code": "NotFoundException",
4828+
"Message": "Invalid Integration identifier specified"
4829+
},
4830+
"message": "Invalid Integration identifier specified",
4831+
"ResponseMetadata": {
4832+
"HTTPHeaders": {},
4833+
"HTTPStatusCode": 404
4834+
}
4835+
}
4836+
}
47664837
}
47674838
}

tests/aws/services/apigateway/test_apigateway_api.validation.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,15 @@
236236
"tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_update_gateway_response": {
237237
"last_validated_date": "2024-04-15T20:47:11+00:00"
238238
},
239+
"tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_delete_integration_response_errors": {
240+
"last_validated_date": "2025-08-21T17:53:19+00:00",
241+
"durations_in_seconds": {
242+
"setup": 0.84,
243+
"call": 2.14,
244+
"teardown": 0.33,
245+
"total": 3.31
246+
}
247+
},
239248
"tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_integration": {
240249
"last_validated_date": "2025-06-26T11:21:05+00:00",
241250
"durations_in_seconds": {

0 commit comments

Comments
 (0)