From b088caf76dbab9c52a7fe821a2a7e1421774edea Mon Sep 17 00:00:00 2001 From: Raymond Wang <14915548+wchengru@users.noreply.github.com> Date: Fri, 10 Sep 2021 17:16:02 -0700 Subject: [PATCH 1/6] Resolve dedupe_function_routes, add integration tests --- samcli/lib/providers/api_collector.py | 2 +- .../local/start_api/start_api_integ_base.py | 1 + .../local/start_api/test_start_api.py | 53 +++++++++++++++ .../cfn-http-api-and-rest-api-gateways.yaml | 68 +++++++++++++++++++ .../cfn-http-api-with-swagger-body.yaml | 2 + tests/integration/testdata/start_api/main.py | 3 + .../start_api/swagger-rest-api-template.yaml | 8 +++ .../start_api/swagger-template-http-api.yaml | 19 ++++++ 8 files changed, 155 insertions(+), 1 deletion(-) diff --git a/samcli/lib/providers/api_collector.py b/samcli/lib/providers/api_collector.py index bf2896040c..33c117d23e 100644 --- a/samcli/lib/providers/api_collector.py +++ b/samcli/lib/providers/api_collector.py @@ -151,7 +151,7 @@ def dedupe_function_routes(routes: List[Route]) -> List[Route]: grouped_routes: Dict[str, Route] = {} for route in routes: - key = "{}-{}-{}".format(route.stack_path, route.function_name, route.path) + key = "{}-{}-{}-{}".format(route.stack_path, route.function_name, route.path, route.operation_name or "") config = grouped_routes.get(key, None) methods = route.methods if config: diff --git a/tests/integration/local/start_api/start_api_integ_base.py b/tests/integration/local/start_api/start_api_integ_base.py index 3896badf1e..fa7ffb7a52 100644 --- a/tests/integration/local/start_api/start_api_integ_base.py +++ b/tests/integration/local/start_api/start_api_integ_base.py @@ -52,6 +52,7 @@ def build(cls): ["ParameterKey={},ParameterValue={}".format(key, value) for key, value in cls.build_overrides.items()] ) command_list += ["--parameter-overrides", overrides_arg] + command_list += ["--debug"] working_dir = str(Path(cls.template).resolve().parents[0]) run_command(command_list, cwd=working_dir) diff --git a/tests/integration/local/start_api/test_start_api.py b/tests/integration/local/start_api/test_start_api.py index 0ddb8d5a31..5830ba0a52 100644 --- a/tests/integration/local/start_api/test_start_api.py +++ b/tests/integration/local/start_api/test_start_api.py @@ -664,6 +664,30 @@ def test_patch_call_with_path_setup_with_any_swagger(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), {"hello": "world"}) + @pytest.mark.flaky(reruns=3) + @pytest.mark.timeout(timeout=600, method="thread") + def test_http_api_payload_v1_should_not_have_operation_id(self): + response = requests.get(self.url + "/httpapi-operation-id-v1", timeout=300) + self.assertEqual(response.status_code, 200) + + response_data = response.json() + self.assertEqual(response_data.get("version", {}), "1.0") + # operationName or operationId shouldn't be processed by Httpapi swaggers + self.assertIsNone(response_data.get("requestContext", {}).get("operationName")) + self.assertIsNone(response_data.get("requestContext", {}).get("operationId")) + + @pytest.mark.flaky(reruns=3) + @pytest.mark.timeout(timeout=600, method="thread") + def test_http_api_payload_v2_should_not_have_operation_id(self): + response = requests.get(self.url + "/httpapi-operation-id-v2", timeout=300) + self.assertEqual(response.status_code, 200) + + response_data = response.json() + self.assertEqual(response_data.get("version", {}), "2.0") + # operationName or operationId shouldn't be processed by Httpapi swaggers + self.assertIsNone(response_data.get("requestContext", {}).get("operationName")) + self.assertIsNone(response_data.get("requestContext", {}).get("operationId")) + class TestStartApiWithSwaggerRestApis(StartApiIntegBaseClass): template_path = "/testdata/start_api/swagger-rest-api-template.yaml" @@ -792,6 +816,17 @@ def test_binary_response(self): self.assertEqual(response.headers.get("Content-Type"), "image/gif") self.assertEqual(response.content, expected) + @pytest.mark.flaky(reruns=3) + @pytest.mark.timeout(timeout=600, method="thread") + def test_rest_api_operation_id(self): + """ + Binary data is returned correctly + """ + response = requests.get(self.url + "/printeventwithoperationidfunction", timeout=300) + self.assertEqual(response.status_code, 200) + response_data = response.json() + self.assertIsNone(response_data.get("requestContext", {}).get("operationId"), "MyOperationName") + class TestServiceResponses(StartApiIntegBaseClass): """ @@ -1691,6 +1726,14 @@ def test_http_api_is_reachable(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), {"hello": "world"}) + @pytest.mark.flaky(reruns=3) + @pytest.mark.timeout(timeout=600, method="thread") + def test_http_api_with_operation_name_is_reachable(self): + response = requests.get(self.url + "/http-api-with-operation-name", timeout=300) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {"operation_name": "MyOperationName"}) + @pytest.mark.flaky(reruns=3) @pytest.mark.timeout(timeout=600, method="thread") def test_rest_api_is_reachable(self): @@ -1699,6 +1742,12 @@ def test_rest_api_is_reachable(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), {"hello": "world"}) + @pytest.mark.flaky(reruns=3) + @pytest.mark.timeout(timeout=600, method="thread") + def test_rest_api_with_operation_name_is_reachable(self): + response = requests.get(self.url + "/rest-api-with-operation-name", timeout=300) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {"operation_name": "MyOperationName"}) class TestCFNTemplateHttpApiWithSwaggerBody(StartApiIntegBaseClass): template_path = "/testdata/start_api/cfn-http-api-with-swagger-body.yaml" @@ -1716,6 +1765,10 @@ def test_swagger_got_parsed_and_api_is_reachable_and_payload_version_is_2(self): self.assertEqual(response_data.get("version", {}), "2.0") self.assertIsNone(response_data.get("multiValueHeaders")) self.assertIsNotNone(response_data.get("cookies")) + # operationName or operationId shouldn't be processed by Httpapi swaggers + self.assertIsNone(response_data.get("requestContext", {}).get("operationName")) + self.assertIsNone(response_data.get("requestContext", {}).get("operationId")) + class TestWarmContainersBaseClass(StartApiIntegBaseClass): diff --git a/tests/integration/testdata/start_api/cfn-http-api-and-rest-api-gateways.yaml b/tests/integration/testdata/start_api/cfn-http-api-and-rest-api-gateways.yaml index dffa1873f9..ce0f4a7f58 100644 --- a/tests/integration/testdata/start_api/cfn-http-api-and-rest-api-gateways.yaml +++ b/tests/integration/testdata/start_api/cfn-http-api-and-rest-api-gateways.yaml @@ -19,6 +19,20 @@ Resources: Value: SAM Timeout: 3 Type: AWS::Lambda::Function + HelloWorldFunctionWithOperationName: + Properties: + Handler: main.operation_name_handler + Code: '.' + Role: + Fn::GetAtt: + - HelloWorldFunctionRole + - Arn + Runtime: python3.6 + Tags: + - Key: lambda:createdBy + Value: SAM + Timeout: 3 + Type: AWS::Lambda::Function HelloWorldFunctionRole: Properties: AssumeRolePolicyDocument: @@ -81,6 +95,15 @@ Resources: IntegrationMethod: POST IntegrationUri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorldFunction.Arn}/invocations + MyIntegrationWithOperationName: + Type: 'AWS::ApiGatewayV2::Integration' + Properties: + ApiId: !Ref HTTPAPIGateway + PayloadFormatVersion: "1.0" + IntegrationType: AWS_PROXY + IntegrationMethod: POST + IntegrationUri: + Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorldFunctionWithOperationName.Arn}/invocations MyRoute: Type: AWS::ApiGatewayV2::Route Properties: @@ -90,6 +113,16 @@ Resources: - / - - integrations - !Ref MyIntegration + MyRouteWithOperationName: + Type: AWS::ApiGatewayV2::Route + Properties: + ApiId: !Ref HTTPAPIGateway + OperationName: 'MyOperationName' + RouteKey: 'GET /http-api-with-operation-name' + Target: !Join + - / + - - integrations + - !Ref MyIntegrationWithOperationName RestApiGateway: Type: AWS::ApiGateway::RestApi Properties: @@ -141,4 +174,39 @@ Resources: - Fn::GetAtt: - HelloWorldFunction - Arn + - /invocations + RestApiGatewayWithOperationNameResource: + Type: AWS::ApiGateway::Resource + Properties: + ParentId: + Fn::GetAtt: + - RestApiGateway + - RootResourceId + PathPart: "rest-api-with-operation-name" + RestApiId: + Ref: RestApiGateway + RestApiGatewayWithOperationNameMethod: + Type: AWS::ApiGateway::Method + Properties: + HttpMethod: GET + OperationName: 'MyOperationName' + ResourceId: + Ref: RestApiGatewayWithOperationNameResource + RestApiId: + Ref: RestApiGateway + AuthorizationType: NONE + Integration: + IntegrationHttpMethod: POST + Type: AWS_PROXY + Uri: + Fn::Join: + - "" + - - "arn:" + - Ref: AWS::Partition + - ":apigateway:" + - Ref: AWS::Region + - :lambda:path/2015-03-31/functions/ + - Fn::GetAtt: + - HelloWorldFunctionWithOperationName + - Arn - /invocations \ No newline at end of file diff --git a/tests/integration/testdata/start_api/cfn-http-api-with-swagger-body.yaml b/tests/integration/testdata/start_api/cfn-http-api-with-swagger-body.yaml index 606d89b863..4714f79df0 100644 --- a/tests/integration/testdata/start_api/cfn-http-api-with-swagger-body.yaml +++ b/tests/integration/testdata/start_api/cfn-http-api-with-swagger-body.yaml @@ -61,6 +61,7 @@ Resources: /echoeventbody: get: responses: {} + operationId: 'postOperationIdShouldNotBeInHttpApi' x-amazon-apigateway-integration: httpMethod: POST payloadFormatVersion: '2.0' @@ -76,6 +77,7 @@ Resources: - x-apigateway-header allowMethods: - GET + - POST allowOrigins: - https://example.com maxAge: 600 diff --git a/tests/integration/testdata/start_api/main.py b/tests/integration/testdata/start_api/main.py index 19dc6f51c2..a742f49a4c 100644 --- a/tests/integration/testdata/start_api/main.py +++ b/tests/integration/testdata/start_api/main.py @@ -8,6 +8,9 @@ def handler(event, context): return {"statusCode": 200, "body": json.dumps({"hello": "world"})} +def operation_name_handler(event, context): + return {"statusCode": 200, "body": json.dumps({"operation_name": event["requestContext"].get("operationName", "")})} + def echo_event_handler(event, context): return {"statusCode": 200, "body": json.dumps(event)} diff --git a/tests/integration/testdata/start_api/swagger-rest-api-template.yaml b/tests/integration/testdata/start_api/swagger-rest-api-template.yaml index e28aec0f7b..379bc95f30 100644 --- a/tests/integration/testdata/start_api/swagger-rest-api-template.yaml +++ b/tests/integration/testdata/start_api/swagger-rest-api-template.yaml @@ -72,6 +72,14 @@ Resources: type: aws_proxy uri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyNonServerlessLambdaFunction.Arn}/invocations + "/printeventwithoperationidfunction": + get: + x-amazon-apigateway-integration: + operationid: 'MyOperationName' + httpMethod: POST + type: aws_proxy + uri: + Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EchoEventBodyFunction.Arn}/invocations swagger: '2.0' x-amazon-apigateway-binary-media-types: - image/gif diff --git a/tests/integration/testdata/start_api/swagger-template-http-api.yaml b/tests/integration/testdata/start_api/swagger-template-http-api.yaml index 6e5d0cb756..9d907e5f62 100644 --- a/tests/integration/testdata/start_api/swagger-template-http-api.yaml +++ b/tests/integration/testdata/start_api/swagger-template-http-api.yaml @@ -69,6 +69,25 @@ Resources: type: aws_proxy uri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EchoBase64EventBodyFunction.Arn}/invocations + "/httpapi-operation-id-v1": + get: + responses: {} + x-amazon-apigateway-integration: + operationid: 'OperationNameShouldNotAppear' + httpMethod: GET + type: aws_proxy + payloadFormatVersion: '1.0' + uri: + Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EchoEventHandlerHttpApiFunction.Arn}/invocations + "/httpapi-operation-id-v2": + get: + responses: {} + x-amazon-apigateway-integration: + httpMethod: GET + type: aws_proxy + payloadFormatVersion: '2.0' + uri: + Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EchoEventHandlerHttpApiFunction.Arn}/invocations MyHttpApiLambdaFunction: Type: AWS::Serverless::Function From e1c0306146ee03f36b5746219ad664c9166e0791 Mon Sep 17 00:00:00 2001 From: Raymond Wang <14915548+wchengru@users.noreply.github.com> Date: Fri, 10 Sep 2021 17:22:02 -0700 Subject: [PATCH 2/6] black reformat --- tests/integration/local/start_api/test_start_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/local/start_api/test_start_api.py b/tests/integration/local/start_api/test_start_api.py index 5830ba0a52..ee2a179128 100644 --- a/tests/integration/local/start_api/test_start_api.py +++ b/tests/integration/local/start_api/test_start_api.py @@ -1749,6 +1749,7 @@ def test_rest_api_with_operation_name_is_reachable(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), {"operation_name": "MyOperationName"}) + class TestCFNTemplateHttpApiWithSwaggerBody(StartApiIntegBaseClass): template_path = "/testdata/start_api/cfn-http-api-with-swagger-body.yaml" @@ -1770,7 +1771,6 @@ def test_swagger_got_parsed_and_api_is_reachable_and_payload_version_is_2(self): self.assertIsNone(response_data.get("requestContext", {}).get("operationId")) - class TestWarmContainersBaseClass(StartApiIntegBaseClass): def setUp(self): self.url = "http://127.0.0.1:{}".format(self.port) From e0376ea320d13e62275ebf269563fd0e436233e3 Mon Sep 17 00:00:00 2001 From: Raymond Wang <14915548+wchengru@users.noreply.github.com> Date: Mon, 13 Sep 2021 09:52:42 -0700 Subject: [PATCH 3/6] fix an issue that V2 swagger still have opertaion name --- samcli/commands/local/lib/swagger/parser.py | 28 ++++++++++++++++++- .../local/start_api/start_api_integ_base.py | 1 - .../local/start_api/test_start_api.py | 2 +- .../start_api/swagger-rest-api-template.yaml | 2 +- .../start_api/swagger-template-http-api.yaml | 2 +- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/samcli/commands/local/lib/swagger/parser.py b/samcli/commands/local/lib/swagger/parser.py index 386774bc90..1f695fdd4d 100644 --- a/samcli/commands/local/lib/swagger/parser.py +++ b/samcli/commands/local/lib/swagger/parser.py @@ -87,13 +87,19 @@ def get_routes(self, event_type=Route.API): # Convert to a more commonly used method notation method = self._ANY_METHOD payload_format_version = self._get_payload_format_version(method_config) + # The OperationName is only sent to the Lambda Function from API Gateway V1(Rest API). + # For Http Apis (v2), API Gateway never sends the OperationName. + if event_type == Route.API: + operation_id = self._get_operation_id(method_config) + else: + operation_id = None route = Route( function_name, full_path, methods=[method], event_type=event_type, payload_format_version=payload_format_version, - operation_name=method_config.get("operationId"), + operation_name=operation_id, stack_path=self.stack_path, ) result.append(route) @@ -169,3 +175,23 @@ def _get_payload_format_version(self, method_config): return None return integration.get("payloadFormatVersion") + + def _get_operation_id(self, method_config): + """ + Get the "operationId" from the Integration defined in the method configuration. + + Parameters + ---------- + method_config : dict + Dictionary containing the method configuration which might contain integration settings + + Returns + ------- + string or None + Payload format version, if exists. None, if not. + """ + integration = self._get_integration(method_config) + if integration is None: + return None + + return integration.get("operationId") diff --git a/tests/integration/local/start_api/start_api_integ_base.py b/tests/integration/local/start_api/start_api_integ_base.py index fa7ffb7a52..3896badf1e 100644 --- a/tests/integration/local/start_api/start_api_integ_base.py +++ b/tests/integration/local/start_api/start_api_integ_base.py @@ -52,7 +52,6 @@ def build(cls): ["ParameterKey={},ParameterValue={}".format(key, value) for key, value in cls.build_overrides.items()] ) command_list += ["--parameter-overrides", overrides_arg] - command_list += ["--debug"] working_dir = str(Path(cls.template).resolve().parents[0]) run_command(command_list, cwd=working_dir) diff --git a/tests/integration/local/start_api/test_start_api.py b/tests/integration/local/start_api/test_start_api.py index ee2a179128..5b16429a45 100644 --- a/tests/integration/local/start_api/test_start_api.py +++ b/tests/integration/local/start_api/test_start_api.py @@ -825,7 +825,7 @@ def test_rest_api_operation_id(self): response = requests.get(self.url + "/printeventwithoperationidfunction", timeout=300) self.assertEqual(response.status_code, 200) response_data = response.json() - self.assertIsNone(response_data.get("requestContext", {}).get("operationId"), "MyOperationName") + self.assertEqual(response_data.get("requestContext", {}).get("operationName"), "MyOperationName") class TestServiceResponses(StartApiIntegBaseClass): diff --git a/tests/integration/testdata/start_api/swagger-rest-api-template.yaml b/tests/integration/testdata/start_api/swagger-rest-api-template.yaml index 379bc95f30..f37b4ddd3a 100644 --- a/tests/integration/testdata/start_api/swagger-rest-api-template.yaml +++ b/tests/integration/testdata/start_api/swagger-rest-api-template.yaml @@ -75,7 +75,7 @@ Resources: "/printeventwithoperationidfunction": get: x-amazon-apigateway-integration: - operationid: 'MyOperationName' + operationId: 'MyOperationName' httpMethod: POST type: aws_proxy uri: diff --git a/tests/integration/testdata/start_api/swagger-template-http-api.yaml b/tests/integration/testdata/start_api/swagger-template-http-api.yaml index 9d907e5f62..0f42bd2dd2 100644 --- a/tests/integration/testdata/start_api/swagger-template-http-api.yaml +++ b/tests/integration/testdata/start_api/swagger-template-http-api.yaml @@ -73,7 +73,7 @@ Resources: get: responses: {} x-amazon-apigateway-integration: - operationid: 'OperationNameShouldNotAppear' + operationId: 'OperationNameShouldNotAppear' httpMethod: GET type: aws_proxy payloadFormatVersion: '1.0' From 97a1354a4e92bd1e5754ac1084e4de7a6f325f79 Mon Sep 17 00:00:00 2001 From: Raymond Wang <14915548+wchengru@users.noreply.github.com> Date: Mon, 13 Sep 2021 11:03:17 -0700 Subject: [PATCH 4/6] fix operationId issue --- samcli/commands/local/lib/swagger/parser.py | 2 +- .../testdata/start_api/cfn-http-api-with-swagger-body.yaml | 1 - .../testdata/start_api/swagger-rest-api-template.yaml | 2 +- .../testdata/start_api/swagger-template-http-api.yaml | 3 ++- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samcli/commands/local/lib/swagger/parser.py b/samcli/commands/local/lib/swagger/parser.py index 1f695fdd4d..a3f53fdd49 100644 --- a/samcli/commands/local/lib/swagger/parser.py +++ b/samcli/commands/local/lib/swagger/parser.py @@ -90,7 +90,7 @@ def get_routes(self, event_type=Route.API): # The OperationName is only sent to the Lambda Function from API Gateway V1(Rest API). # For Http Apis (v2), API Gateway never sends the OperationName. if event_type == Route.API: - operation_id = self._get_operation_id(method_config) + operation_id = method_config.get("operationId") else: operation_id = None route = Route( diff --git a/tests/integration/testdata/start_api/cfn-http-api-with-swagger-body.yaml b/tests/integration/testdata/start_api/cfn-http-api-with-swagger-body.yaml index 4714f79df0..c092b2db3d 100644 --- a/tests/integration/testdata/start_api/cfn-http-api-with-swagger-body.yaml +++ b/tests/integration/testdata/start_api/cfn-http-api-with-swagger-body.yaml @@ -77,7 +77,6 @@ Resources: - x-apigateway-header allowMethods: - GET - - POST allowOrigins: - https://example.com maxAge: 600 diff --git a/tests/integration/testdata/start_api/swagger-rest-api-template.yaml b/tests/integration/testdata/start_api/swagger-rest-api-template.yaml index f37b4ddd3a..a59fa781da 100644 --- a/tests/integration/testdata/start_api/swagger-rest-api-template.yaml +++ b/tests/integration/testdata/start_api/swagger-rest-api-template.yaml @@ -73,9 +73,9 @@ Resources: uri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyNonServerlessLambdaFunction.Arn}/invocations "/printeventwithoperationidfunction": + operationId: 'MyOperationName' get: x-amazon-apigateway-integration: - operationId: 'MyOperationName' httpMethod: POST type: aws_proxy uri: diff --git a/tests/integration/testdata/start_api/swagger-template-http-api.yaml b/tests/integration/testdata/start_api/swagger-template-http-api.yaml index 0f42bd2dd2..0f62b2aad1 100644 --- a/tests/integration/testdata/start_api/swagger-template-http-api.yaml +++ b/tests/integration/testdata/start_api/swagger-template-http-api.yaml @@ -72,8 +72,8 @@ Resources: "/httpapi-operation-id-v1": get: responses: {} + operationId: 'OperationNameShouldNotAppear' x-amazon-apigateway-integration: - operationId: 'OperationNameShouldNotAppear' httpMethod: GET type: aws_proxy payloadFormatVersion: '1.0' @@ -82,6 +82,7 @@ Resources: "/httpapi-operation-id-v2": get: responses: {} + operationId: 'OperationNameShouldNotAppear' x-amazon-apigateway-integration: httpMethod: GET type: aws_proxy From fc702f8777e167b91cf404ae23a6329ed063d49a Mon Sep 17 00:00:00 2001 From: Raymond Wang <14915548+wchengru@users.noreply.github.com> Date: Mon, 13 Sep 2021 12:10:24 -0700 Subject: [PATCH 5/6] Do not send any HttpApi operationId/OpertaionName --- samcli/commands/local/lib/swagger/parser.py | 8 +------- samcli/local/apigw/local_apigw_service.py | 9 ++++++++- tests/integration/local/start_api/test_start_api.py | 8 +++++--- .../start_api/swagger-rest-api-template.yaml | 2 +- tests/unit/local/apigw/test_local_apigw_service.py | 12 +++++++++--- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/samcli/commands/local/lib/swagger/parser.py b/samcli/commands/local/lib/swagger/parser.py index a3f53fdd49..5184e8b11e 100644 --- a/samcli/commands/local/lib/swagger/parser.py +++ b/samcli/commands/local/lib/swagger/parser.py @@ -87,19 +87,13 @@ def get_routes(self, event_type=Route.API): # Convert to a more commonly used method notation method = self._ANY_METHOD payload_format_version = self._get_payload_format_version(method_config) - # The OperationName is only sent to the Lambda Function from API Gateway V1(Rest API). - # For Http Apis (v2), API Gateway never sends the OperationName. - if event_type == Route.API: - operation_id = method_config.get("operationId") - else: - operation_id = None route = Route( function_name, full_path, methods=[method], event_type=event_type, payload_format_version=payload_format_version, - operation_name=operation_id, + operation_name=method_config.get("operationId"), stack_path=self.stack_path, ) result.append(route) diff --git a/samcli/local/apigw/local_apigw_service.py b/samcli/local/apigw/local_apigw_service.py index 79ab5a1f30..e09649af18 100644 --- a/samcli/local/apigw/local_apigw_service.py +++ b/samcli/local/apigw/local_apigw_service.py @@ -324,13 +324,20 @@ def _request_handler(self, **kwargs): route_key, ) else: + + # The OperationName is only sent to the Lambda Function from API Gateway V1(Rest API). + # For Http Apis (v2), API Gateway never sends the OperationName. + if route.event_type == Route.API: + operation_name = route.operation_name + else: + operation_name = None event = self._construct_v_1_0_event( request, self.port, self.api.binary_media_types, self.api.stage_name, self.api.stage_variables, - route.operation_name, + operation_name, ) except UnicodeDecodeError: return ServiceErrorResponses.lambda_failure_response() diff --git a/tests/integration/local/start_api/test_start_api.py b/tests/integration/local/start_api/test_start_api.py index 5b16429a45..575d837351 100644 --- a/tests/integration/local/start_api/test_start_api.py +++ b/tests/integration/local/start_api/test_start_api.py @@ -824,8 +824,7 @@ def test_rest_api_operation_id(self): """ response = requests.get(self.url + "/printeventwithoperationidfunction", timeout=300) self.assertEqual(response.status_code, 200) - response_data = response.json() - self.assertEqual(response_data.get("requestContext", {}).get("operationName"), "MyOperationName") + self.assertEqual(response.json().get("requestContext", {}).get("operationName"), "MyOperationName") class TestServiceResponses(StartApiIntegBaseClass): @@ -1732,7 +1731,10 @@ def test_http_api_with_operation_name_is_reachable(self): response = requests.get(self.url + "/http-api-with-operation-name", timeout=300) self.assertEqual(response.status_code, 200) - self.assertEqual(response.json(), {"operation_name": "MyOperationName"}) + response_data = response.json() + # operationName or operationId shouldn't be processed by Httpapi + self.assertIsNone(response_data.get("requestContext", {}).get("operationName")) + self.assertIsNone(response_data.get("requestContext", {}).get("operationId")) @pytest.mark.flaky(reruns=3) @pytest.mark.timeout(timeout=600, method="thread") diff --git a/tests/integration/testdata/start_api/swagger-rest-api-template.yaml b/tests/integration/testdata/start_api/swagger-rest-api-template.yaml index a59fa781da..c98741b7cb 100644 --- a/tests/integration/testdata/start_api/swagger-rest-api-template.yaml +++ b/tests/integration/testdata/start_api/swagger-rest-api-template.yaml @@ -73,8 +73,8 @@ Resources: uri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyNonServerlessLambdaFunction.Arn}/invocations "/printeventwithoperationidfunction": - operationId: 'MyOperationName' get: + operationId: 'MyOperationName' x-amazon-apigateway-integration: httpMethod: POST type: aws_proxy diff --git a/tests/unit/local/apigw/test_local_apigw_service.py b/tests/unit/local/apigw/test_local_apigw_service.py index 5597d6d17a..b66b3855df 100644 --- a/tests/unit/local/apigw/test_local_apigw_service.py +++ b/tests/unit/local/apigw/test_local_apigw_service.py @@ -22,7 +22,12 @@ class TestApiGatewayService(TestCase): def setUp(self): self.function_name = Mock() - self.api_gateway_route = Route(methods=["GET"], function_name=self.function_name, path="/") + self.api_gateway_route = Route( + methods=["GET"], + function_name=self.function_name, + path="/", + operation_name="getRestApi", + ) self.http_gateway_route = Route( methods=["GET"], function_name=self.function_name, path="/", event_type=Route.HTTP ) @@ -77,6 +82,7 @@ def test_api_request_must_invoke_lambda(self, request_mock): self.api_service.service_response = make_response_mock self.api_service._get_current_route = MagicMock() + self.api_service._get_current_route.return_value = self.api_gateway_route self.api_service._get_current_route.methods = [] self.api_service._get_current_route.return_value.payload_format_version = "2.0" self.api_service._construct_v_1_0_event = Mock() @@ -95,7 +101,7 @@ def test_api_request_must_invoke_lambda(self, request_mock): self.assertEqual(result, make_response_mock) self.lambda_runner.invoke.assert_called_with(ANY, ANY, stdout=ANY, stderr=self.stderr) - self.api_service._construct_v_1_0_event.assert_called_with(ANY, ANY, ANY, ANY, ANY, ANY) + self.api_service._construct_v_1_0_event.assert_called_with(ANY, ANY, ANY, ANY, ANY, "getRestApi") @patch.object(LocalApigwService, "get_request_methods_endpoints") def test_http_request_must_invoke_lambda(self, request_mock): @@ -151,7 +157,7 @@ def test_http_v1_payload_request_must_invoke_lambda(self, request_mock): self.assertEqual(result, make_response_mock) self.lambda_runner.invoke.assert_called_with(ANY, ANY, stdout=ANY, stderr=self.stderr) - self.http_service._construct_v_1_0_event.assert_called_with(ANY, ANY, ANY, ANY, ANY, "getV1") + self.http_service._construct_v_1_0_event.assert_called_with(ANY, ANY, ANY, ANY, ANY, None) @patch.object(LocalApigwService, "get_request_methods_endpoints") def test_http_v2_payload_request_must_invoke_lambda(self, request_mock): From 1197bdb1213bbba80faf0dff502386cfb3eb76e3 Mon Sep 17 00:00:00 2001 From: Raymond Wang <14915548+wchengru@users.noreply.github.com> Date: Mon, 13 Sep 2021 12:12:41 -0700 Subject: [PATCH 6/6] remove unused code --- samcli/commands/local/lib/swagger/parser.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/samcli/commands/local/lib/swagger/parser.py b/samcli/commands/local/lib/swagger/parser.py index 5184e8b11e..386774bc90 100644 --- a/samcli/commands/local/lib/swagger/parser.py +++ b/samcli/commands/local/lib/swagger/parser.py @@ -169,23 +169,3 @@ def _get_payload_format_version(self, method_config): return None return integration.get("payloadFormatVersion") - - def _get_operation_id(self, method_config): - """ - Get the "operationId" from the Integration defined in the method configuration. - - Parameters - ---------- - method_config : dict - Dictionary containing the method configuration which might contain integration settings - - Returns - ------- - string or None - Payload format version, if exists. None, if not. - """ - integration = self._get_integration(method_config) - if integration is None: - return None - - return integration.get("operationId")