Skip to content

Commit

Permalink
fix APIGW import integration method for AWS int. (localstack#9810)
Browse files Browse the repository at this point in the history
  • Loading branch information
bentsku authored Dec 17, 2023
1 parent 451e196 commit 61e691f
Show file tree
Hide file tree
Showing 4 changed files with 321 additions and 3 deletions.
15 changes: 12 additions & 3 deletions localstack/services/apigateway/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1157,9 +1157,18 @@ def add_path_methods(rel_path: str, parts: List[str], parent_id=""):
integration_type = (
i_type.upper() if (i_type := method_integration.get("type")) else None
)
# TODO: validate more cases like this
# if the integration is AWS_PROXY with lambda, the only accepted integration method is POST
integration_method = method_name if integration_type != "AWS_PROXY" else "POST"

match integration_type:
case "AWS_PROXY":
# if the integration is AWS_PROXY with lambda, the only accepted integration method is POST
integration_method = "POST"
case "AWS":
integration_method = (
method_integration.get("httpMethod") or method_name
).upper()
case _:
integration_method = method_name

connection_type = (
ConnectionType.INTERNET
if integration_type in (IntegrationType.HTTP, IntegrationType.HTTP_PROXY)
Expand Down
89 changes: 89 additions & 0 deletions tests/aws/files/openapi-http-method-integration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{
"openapi": "3.0.1",
"info": {
"title": "test-http-method",
"description": "Test httpMethod for AWS integration",
"version": "2022-04-12T15:36:55Z"
},
"paths": {
"/": {
"get": {
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Empty"
}
}
}
}
},
"x-amazon-apigateway-integration": {
"type": "aws",
"httpMethod": "POST",
"uri": "${lambda_invocation_arn}",
"responses": {
"default": {
"statusCode": "200"
}
},
"requestParameters": {
"integration.request.header.X-Amz-Invocation-Type": "'Event'"
},
"requestTemplates": {
"application/json": "#set($allParams = $input.params())\n{\n \"params\" : {\n #foreach($type in $allParams.keySet())\n #set($params = $allParams.get($type))\n \"$type\" : {\n #foreach($paramName in $params.keySet())\n \"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\n #if($foreach.hasNext),#end\n #end\n }\n #if($foreach.hasNext),#end\n #end\n }\n}\n"
},
"passthroughBehavior": "when_no_templates",
"contentHandling": "CONVERT_TO_TEXT"
}
},
"options": {
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Empty"
}
}
},
"description": "200 response",
"headers": {
"Access-Control-Allow-Headers": {
"schema": {
"type": "string"
}
}
}
}
},
"x-amazon-apigateway-integration": {
"passthroughBehavior": "when_no_match",
"httpMethod": "POST",
"requestTemplates": {
"application/json": "{\"statusCode\": 200}"
},
"responses": {
"default": {
"responseParameters": {
"method.response.header.Access-Control-Allow-Headers": "'Content-Type'"
},
"statusCode": "200"
}
},
"type": "mock"
}
}
}
},
"components": {
"schemas": {
"Empty": {
"title": "Empty Schema",
"type": "object"
}
}
}
}
32 changes: 32 additions & 0 deletions tests/aws/services/apigateway/test_apigateway_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
PARENT_DIR, "../../files/openapi.spec.circular-ref-with-request-body.json"
)
OAS_30_STAGE_VARIABLES = os.path.join(PARENT_DIR, "../../files/openapi.spec.stage-variables.json")
OAS30_HTTP_METHOD_INT = os.path.join(PARENT_DIR, "../../files/openapi-http-method-integration.json")
TEST_LAMBDA_PYTHON_ECHO = os.path.join(PARENT_DIR, "../lambda_/functions/lambda_echo.py")


Expand Down Expand Up @@ -754,3 +755,34 @@ def call_api():
assert res.ok

retry(call_api, retries=5, sleep=2)

@markers.aws.validated
@markers.snapshot.skip_snapshot_verify(
paths=[
"$.resources.items..resourceMethods.GET",
"$.resources.items..resourceMethods.OPTIONS",
]
)
def test_import_with_http_method_integration(
self,
import_apigw,
aws_client,
apigw_snapshot_imported_resources,
apigateway_placeholder_authorizer_lambda_invocation_arn,
snapshot,
):
snapshot.add_transformer(snapshot.transform.key_value("uri"))
spec_file = load_file(OAS30_HTTP_METHOD_INT)
spec_file = spec_file.replace(
"${lambda_invocation_arn}", apigateway_placeholder_authorizer_lambda_invocation_arn
)
import_resp, root_id = import_apigw(body=spec_file, failOnWarnings=True)
rest_api_id = import_resp["id"]

response = aws_client.apigateway.get_resources(restApiId=rest_api_id)
response["items"] = sorted(response["items"], key=itemgetter("path"))
snapshot.match("resources", response)

# this fixture will iterate over every resource and match its method, methodResponse, integration and
# integrationResponse
apigw_snapshot_imported_resources(rest_api_id=rest_api_id, resources=response)
188 changes: 188 additions & 0 deletions tests/aws/services/apigateway/test_apigateway_import.snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -4586,5 +4586,193 @@
}
}
}
},
"tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_http_method_integration": {
"recorded-date": "05-12-2023, 22:27:16",
"recorded-content": {
"resources": {
"items": [
{
"id": "<id:1>",
"path": "/",
"resourceMethods": {
"GET": {},
"OPTIONS": {}
}
}
],
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
},
"method-root-get": {
"apiKeyRequired": false,
"authorizationType": "NONE",
"httpMethod": "GET",
"methodIntegration": {
"cacheKeyParameters": [],
"cacheNamespace": "<id:1>",
"contentHandling": "CONVERT_TO_TEXT",
"httpMethod": "POST",
"integrationResponses": {
"200": {
"statusCode": "200"
}
},
"passthroughBehavior": "WHEN_NO_TEMPLATES",
"requestParameters": {
"integration.request.header.X-Amz-Invocation-Type": "'Event'"
},
"requestTemplates": {
"application/json": "#set($allParams = $input.params())\n{\n \"params\" : {\n #foreach($type in $allParams.keySet())\n #set($params = $allParams.get($type))\n \"$type\" : {\n #foreach($paramName in $params.keySet())\n \"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\n #if($foreach.hasNext),#end\n #end\n }\n #if($foreach.hasNext),#end\n #end\n }\n}\n"
},
"timeoutInMillis": 29000,
"type": "AWS",
"uri": "<uri:1>"
},
"methodResponses": {
"200": {
"responseModels": {
"application/json": "Empty"
},
"statusCode": "200"
}
},
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
},
"method-response-root-get": {
"responseModels": {
"application/json": "Empty"
},
"statusCode": "200",
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
},
"integration-root-get": {
"cacheKeyParameters": [],
"cacheNamespace": "<id:1>",
"contentHandling": "CONVERT_TO_TEXT",
"httpMethod": "POST",
"integrationResponses": {
"200": {
"statusCode": "200"
}
},
"passthroughBehavior": "WHEN_NO_TEMPLATES",
"requestParameters": {
"integration.request.header.X-Amz-Invocation-Type": "'Event'"
},
"requestTemplates": {
"application/json": "#set($allParams = $input.params())\n{\n \"params\" : {\n #foreach($type in $allParams.keySet())\n #set($params = $allParams.get($type))\n \"$type\" : {\n #foreach($paramName in $params.keySet())\n \"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\n #if($foreach.hasNext),#end\n #end\n }\n #if($foreach.hasNext),#end\n #end\n }\n}\n"
},
"timeoutInMillis": 29000,
"type": "AWS",
"uri": "<uri:1>",
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
},
"integration-response-root-get": {
"statusCode": "200",
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
},
"method-root-options": {
"apiKeyRequired": false,
"authorizationType": "NONE",
"httpMethod": "OPTIONS",
"methodIntegration": {
"cacheKeyParameters": [],
"cacheNamespace": "<id:1>",
"integrationResponses": {
"200": {
"responseParameters": {
"method.response.header.Access-Control-Allow-Headers": "'Content-Type'"
},
"statusCode": "200"
}
},
"passthroughBehavior": "WHEN_NO_MATCH",
"requestTemplates": {
"application/json": {
"statusCode": 200
}
},
"timeoutInMillis": 29000,
"type": "MOCK"
},
"methodResponses": {
"200": {
"responseModels": {
"application/json": "Empty"
},
"responseParameters": {
"method.response.header.Access-Control-Allow-Headers": false
},
"statusCode": "200"
}
},
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
},
"method-response-root-options": {
"responseModels": {
"application/json": "Empty"
},
"responseParameters": {
"method.response.header.Access-Control-Allow-Headers": false
},
"statusCode": "200",
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
},
"integration-root-options": {
"cacheKeyParameters": [],
"cacheNamespace": "<id:1>",
"integrationResponses": {
"200": {
"responseParameters": {
"method.response.header.Access-Control-Allow-Headers": "'Content-Type'"
},
"statusCode": "200"
}
},
"passthroughBehavior": "WHEN_NO_MATCH",
"requestTemplates": {
"application/json": {
"statusCode": 200
}
},
"timeoutInMillis": 29000,
"type": "MOCK",
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
},
"integration-response-root-options": {
"responseParameters": {
"method.response.header.Access-Control-Allow-Headers": "'Content-Type'"
},
"statusCode": "200",
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
}
}
}
}

0 comments on commit 61e691f

Please sign in to comment.