quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.66k stars 2.65k forks source link

NPE when testing POST APIGatewayV2HTTPEvent to lambda #35972

Open thiagohora opened 1 year ago

thiagohora commented 1 year ago

Describe the bug

Following the steps of this article. I was trying to create a simple hello world lambda that receives an API gateway event. However, when trying to push the APIGatewayV2HTTPEvent in a test following the example of the page. The result is a 500 status code due to an NPE.

Expected behavior

Return a 200 status code with the response body of the lambda.

Actual behavior

500 status code:

[INFO] Running com.mylambda.GreetingResourceTest
2023-09-17 10:55:34,741 INFO  [io.qua.ama.lam.run.MockEventServer] (build-26) Mock Lambda Event Server Started
2023-09-17 10:55:35,638 INFO  [io.qua.ama.lam.run.AbstractLambdaPollLoop] (Lambda Thread (TEST)) Listening on: http://localhost:8081/_lambda_/2018-06-01/runtime/invocation/next
2023-09-17 10:55:35,668 INFO  [io.quarkus] (main) test 1.0-SNAPSHOT on JVM (powered by Quarkus 3.3.3) started in 1.925s. 
2023-09-17 10:55:35,669 INFO  [io.quarkus] (main) Profile test activated. 
2023-09-17 10:55:35,669 INFO  [io.quarkus] (main) Installed features: [amazon-lambda, cdi, funqy-http, reactive-routes, resteasy, security, servlet, smallrye-context-propagation, vertx]
2023-09-17 10:55:36,483 ERROR [qua.ama.lam.http] (Lambda Thread (TEST)) Request Failure: java.lang.NullPointerException: Cannot invoke "String.hashCode()" because "name" is null
    at io.netty.handler.codec.http.HttpMethod$EnumNameMap.hashCode(HttpMethod.java:216)
    at io.netty.handler.codec.http.HttpMethod$EnumNameMap.get(HttpMethod.java:206)
    at io.netty.handler.codec.http.HttpMethod.valueOf(HttpMethod.java:117)
    at io.quarkus.amazon.lambda.http.LambdaHttpHandler.nettyDispatch(LambdaHttpHandler.java:192)
    at io.quarkus.amazon.lambda.http.LambdaHttpHandler.handleRequest(LambdaHttpHandler.java:63)
    at io.quarkus.amazon.lambda.http.LambdaHttpHandler.handleRequest(LambdaHttpHandler.java:43)
    at io.quarkus.amazon.lambda.runtime.AmazonLambdaRecorder$1.processRequest(AmazonLambdaRecorder.java:167)
    at io.quarkus.amazon.lambda.runtime.AbstractLambdaPollLoop$1.run(AbstractLambdaPollLoop.java:142)
    at java.base/java.lang.Thread.run(Thread.java:833)

[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 5.128 s <<< FAILURE! -- in com.mylambda.GreetingResourceTest
[ERROR] com.mylambda.GreetingResourceTest.testJaxrs -- Time elapsed: 1.279 s <<< FAILURE!
java.lang.AssertionError: 
1 expectation failed.
JSON path body doesn't match.
Expected: Hello, World!
  Actual: { \"message\": \"Internal Server Error\" }

    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
    at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:73)

How to Reproduce?

Reproducer:

  1. Create a project using the provided archetype: mvn archetype:generate \ -DarchetypeGroupId=io.quarkus \ -DarchetypeArtifactId=quarkus-amazon-lambda-rest-archetype \ -DarchetypeVersion=3.3.3
  2. Add the following test code:
@Path("/hello")
public class GreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello(@Context APIGatewayV2HTTPEvent request) {
        return "hello jaxrs";
    }
}
@QuarkusTest
class GreetingResourceTest {

    @Test
    public void testJaxrs() throws Exception {
        APIGatewayV2HTTPEvent request = request("/hello");

        given()
            .contentType("application/json")
            .accept("*/*")
            .body(request)
            .when()
            .post(AmazonLambdaApi.API_BASE_PATH_TEST)
            .then()
            .statusCode(200)
            .body("body", equalTo("Hello, World!"));
    }

    private APIGatewayV2HTTPEvent request(final String path) {
        return APIGatewayV2HTTPEvent.builder()
                .withRawPath(path)
                .withRequestContext(
                    RequestContext.builder()
                        .withHttp(RequestContext.Http.builder().withPath(path).withMethod("GET").build())
                        .build())
                .build();
    }

}
  1. run `mvn test'

Output of uname -a or ver

No response

Output of java -version

17

GraalVM version (if different from Java)

No response

Quarkus version or git rev

3.3.3

Build tool (ie. output of mvnw --version or gradlew --version)

3.8.6

Additional information

No response

quarkus-bot[bot] commented 1 year ago

/cc @matejvasek (amazon-lambda,funqy), @patriot1burke (amazon-lambda,funqy)

geoand commented 1 year ago

I am pretty sure you need to set withHttp on the builder, no the request context. Can you give it a try?

thiagohora commented 1 year ago

Hi @geoand

Thanks for answering this. There is no withHttp on the APIGatewayV2HTTPEvent.

image

geoand commented 1 year ago

There is on the builder

thiagohora commented 1 year ago

@geoand the builder is based on the fields, the project is using Lombok. You can confirm in the class. the object with an HTTP object is the request context.

/*
 * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
 * the License. A copy of the License is located at
 *
 * http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
 * and limitations under the License.
 */

package com.amazonaws.services.lambda.runtime.events;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;
import java.util.Map;

@AllArgsConstructor
@Builder(setterPrefix = "with")
@Data
@NoArgsConstructor
/**
 * API Gateway v2 event: https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html
 */
public class APIGatewayV2HTTPEvent {
    private String version;
    private String routeKey;
    private String rawPath;
    private String rawQueryString;
    private List<String> cookies;
    private Map<String, String> headers;
    private Map<String, String> queryStringParameters;
    private Map<String, String> pathParameters;
    private Map<String, String> stageVariables;
    private String body;
    private boolean isBase64Encoded;
    private RequestContext requestContext;

    @AllArgsConstructor
    @Builder(setterPrefix = "with")
    @Data
    @NoArgsConstructor
    public static class RequestContext {
        private String routeKey;
        private String accountId;
        private String stage;
        private String apiId;
        private String domainName;
        private String domainPrefix;
        private String time;
        private long timeEpoch;
        private Http http;
        private Authorizer authorizer;
        private String requestId;

        @AllArgsConstructor
        @Builder(setterPrefix = "with")
        @Data
        @NoArgsConstructor
        public static class Authorizer {
            private JWT jwt;
            private Map<String, Object> lambda;
            private IAM iam;

            @AllArgsConstructor
            @Builder(setterPrefix = "with")
            @Data
            @NoArgsConstructor
            public static class JWT {
                private Map<String, String> claims;
                private List<String> scopes;
            }
        }

        @AllArgsConstructor
        @Builder(setterPrefix = "with")
        @Data
        @NoArgsConstructor
        public static class Http {
            private String method;
            private String path;
            private String protocol;
            private String sourceIp;
            private String userAgent;
        }

        @AllArgsConstructor
        @Builder(setterPrefix = "with")
        @Data
        @NoArgsConstructor
        public static class IAM {
            private String accessKey;
            private String accountId;
            private String callerId;
            private CognitoIdentity cognitoIdentity;
            private String principalOrgId;
            private String userArn;
            private String userId;
        }

        @AllArgsConstructor
        @Builder(setterPrefix = "with")
        @Data
        @NoArgsConstructor
        public static class CognitoIdentity {
            private List<String> amr;
            private String identityId;
            private String identityPoolId;
        }
    }
}
geoand commented 1 year ago

You are right, my bad.

@patriot1burke this problem only seems to occur with quarkus-amazon-lambda-rest. quarkus-amazon-lambda-http works as expected.

rtjag commented 4 months ago

We are facing the same issue, any update on this?

Kshitij09 commented 4 months ago

Same, I'm also facing this exception while using quarkus-amazon-lambda-rest

jakob-grabner commented 4 months ago

I could solve the issue by sending an APIGatewayProxyRequestEvent to the lambda instead of APIGatewayV2HTTPEvent. So looks like quarkus-amazon-lambda-rest expects APIGatewayProxyRequestEvent and io.quarkus:quarkus-amazon-lambda-http expects APIGatewayV2HTTPEvent