package com.onelostlogician.aws.proxy; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.LambdaLogger; import com.google.common.net.MediaType; import com.googlecode.junittoolbox.ParallelParameterized; import com.onelostlogician.aws.proxy.fixtures.ApiGatewayProxyRequestBuilder; import com.onelostlogician.aws.proxy.fixtures.SampleMethodHandler; import com.onelostlogician.aws.proxy.fixtures.TestingLogger; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.io.IOException; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; import static com.onelostlogician.aws.proxy.Util.randomiseKeyValues; import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.*; import static javax.ws.rs.core.HttpHeaders.ACCEPT; import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; import static javax.ws.rs.core.Response.Status.*; import static org.assertj.core.api.Java6Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; @RunWith(ParallelParameterized.class) public class LambdaProxyHandlerTest { private static final MediaType CONTENT_TYPE_1 = MediaType.create("application", "ContentType1"); private static final MediaType CONTENT_TYPE_2 = MediaType.create("application", "ContentType2"); private static final MediaType ACCEPT_TYPE_1 = MediaType.create("application", "AcceptType1"); private static final MediaType ACCEPT_TYPE_2 = MediaType.create("application", "AcceptType2"); private static final String METHOD = "GET"; private static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; private static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; private static final String ORIGIN_HEADER = "Origin"; private final LambdaProxyHandler<Configuration> handler; private Configuration configuration = mock(Configuration.class); private Context context = mock(Context.class); private LambdaLogger logger = new TestingLogger(); private MethodHandler methodHandler = mock(MethodHandler.class); public LambdaProxyHandlerTest(boolean corsSupport) { handler = new TestLambdaProxyHandler(corsSupport); } @Parameterized.Parameters public static Collection<Object[]> data() { return Stream.of(true, false) .map(b -> singleton(b).toArray()) .collect(toList()); } @Before public void setup() { when(context.getLogger()).thenReturn(logger); } @Test public void shouldReturnBadRequestIfMethodNotRegistered() throws IOException, ParseException { ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod("GET") .withHeaders(new ConcurrentHashMap<>()) .withContext(context) .build(); ApiGatewayProxyResponse response = handler.handleRequest(request, context); assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST.getStatusCode()); assertThat(response.getBody()).isEqualTo(String.format("Lambda cannot handle the method %s", METHOD.toLowerCase())); } @Test public void shouldReturnServerErrorAndReasonWhenMisconfigured() throws ParseException { ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod(METHOD) .build(); LambdaProxyHandler<Configuration> handlerWithFailingConguration = new TestLambdaProxyHandlerWithFailingConguration(); handlerWithFailingConguration.registerMethodHandler(METHOD, c -> methodHandler); ApiGatewayProxyResponse actual = handlerWithFailingConguration.handleRequest(request, context); assertThat(actual).isNotNull(); assertThat(actual.getStatusCode()).isEqualTo(INTERNAL_SERVER_ERROR.getStatusCode()); JSONParser jsonParser = new JSONParser(); JSONObject jsonObject = (JSONObject) jsonParser.parse(actual.getBody()); assertThat(jsonObject.keySet()).contains("message", "cause"); assertThat((String) jsonObject.get("message")).contains("This service is mis-configured. Please contact your system administrator."); assertThat((String) jsonObject.get("cause")).contains("NullPointerException"); } @Test public void shouldReturnBadRequestIfNoHeadersSpecified() throws IOException, ParseException { ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod(METHOD) .withContext(context) .build(); ApiGatewayProxyResponse actual = handler.handleRequest(request, context); assertThat(actual.getStatusCode()).isEqualTo(BAD_REQUEST.getStatusCode()); } @Test public void shouldReturnUnsupportedMediaTypeIfContentTypeNotSpecified() throws IOException, ParseException { ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod(METHOD) .withHeaders(new ConcurrentHashMap<>()) .withContext(context) .build(); handler.registerMethodHandler(METHOD, c -> methodHandler); ApiGatewayProxyResponse response = handler.handleRequest(request, context); assertThat(response.getStatusCode()).isEqualTo(UNSUPPORTED_MEDIA_TYPE.getStatusCode()); assertThat(response.getBody()).isEqualTo(String.format("No %s header", CONTENT_TYPE.toLowerCase())); } @Test public void shouldReturnUnsupportedMediaTypeIfAcceptNotSpecified() throws IOException, ParseException { Map<String, String> headers = new ConcurrentHashMap<>(); headers.put(CONTENT_TYPE, CONTENT_TYPE_1.toString()); randomiseKeyValues(headers); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod(METHOD) .withHeaders(headers) .withContext(context) .build(); handler.registerMethodHandler(METHOD, c -> methodHandler); ApiGatewayProxyResponse response = handler.handleRequest(request, context); assertThat(response.getStatusCode()).isEqualTo(UNSUPPORTED_MEDIA_TYPE.getStatusCode()); assertThat(response.getBody()).isEqualTo(String.format("No %s header", ACCEPT.toLowerCase())); } @Test public void shouldReturnBadRequestForMalformedMediaTypes() throws Exception { String someHeader = "someHeader"; String someValue = "someValue"; Map<String, String> requestHeaders = new ConcurrentHashMap<>(); requestHeaders.put(CONTENT_TYPE, "MalformedContentType"); requestHeaders.put(ACCEPT, ACCEPT_TYPE_1.toString()); requestHeaders.put(someHeader, someValue); randomiseKeyValues(requestHeaders); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod(METHOD) .withHeaders(requestHeaders) .withContext(context) .build(); handler.registerMethodHandler(METHOD, c -> methodHandler); ApiGatewayProxyResponse response = handler.handleRequest(request, context); assertThat(response).isNotNull(); assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST.getStatusCode()); assertThat(response.getBody()).contains("Malformed media type"); } @Test public void shouldReturnResponseFromMethodHandler() throws Exception { String someHeader = "someHeader"; String someValue = "someValue"; Map<String, String> requestHeaders = new ConcurrentHashMap<>(); requestHeaders.put(CONTENT_TYPE, CONTENT_TYPE_1.toString()); requestHeaders.put(ACCEPT, ACCEPT_TYPE_1.toString()); requestHeaders.put(someHeader, someValue); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod(METHOD) .withHeaders(requestHeaders) .withContext(context) .build(); Map<String, String> responseHeaders = new ConcurrentHashMap<>(); responseHeaders.put(someHeader, someValue); when(methodHandler.handle(request, singletonList(CONTENT_TYPE_1), singletonList(ACCEPT_TYPE_1), context)) .thenReturn(new ApiGatewayProxyResponse.ApiGatewayProxyResponseBuilder() .withStatusCode(OK.getStatusCode()) .withHeaders(responseHeaders) .build()); handler.registerMethodHandler(METHOD, c -> methodHandler); ApiGatewayProxyResponse response = handler.handleRequest(request, context); assertThat(response).isNotNull(); assertThat(response.getStatusCode()).isEqualTo(OK.getStatusCode()); assertThat(response.getBody()).isEqualTo(""); assertThat(response.getHeaders()).isEqualTo(responseHeaders); } @Test public void shouldParseSeparatedContentTypes() throws Exception { String someHeader = "someHeader"; String someValue = "someValue"; Map<String, String> requestHeaders = new ConcurrentHashMap<>(); requestHeaders.put(CONTENT_TYPE, CONTENT_TYPE_1.toString() + ", " + CONTENT_TYPE_2.toString()); requestHeaders.put(ACCEPT, ACCEPT_TYPE_1.toString() + ", " + ACCEPT_TYPE_2.toString()); requestHeaders.put(someHeader, someValue); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod(METHOD) .withHeaders(requestHeaders) .withContext(context) .build(); Map<String, String> responseHeaders = new ConcurrentHashMap<>(); responseHeaders.put(someHeader, someValue); List<MediaType> contentTypes = asList(CONTENT_TYPE_1, CONTENT_TYPE_2); List<MediaType> acceptTypes = asList(ACCEPT_TYPE_1, ACCEPT_TYPE_2); when(methodHandler.handle(eq(request), eq(contentTypes), eq(acceptTypes), eq(context))) .thenReturn(new ApiGatewayProxyResponse.ApiGatewayProxyResponseBuilder() .withStatusCode(OK.getStatusCode()) .withHeaders(responseHeaders) .build()); handler.registerMethodHandler(METHOD, c -> methodHandler); handler.handleRequest(request, context); verify(methodHandler).handle(request, contentTypes, acceptTypes, context); } @Test public void shouldMapContentTypeParameters() throws Exception { String someHeader = "someHeader"; String someValue = "someValue"; Map<String, String> requestHeaders = new ConcurrentHashMap<>(); MediaType contentType = CONTENT_TYPE_1.withParameter("q", "0.9"); contentType = contentType.withParameter("b", "hello"); String mediaTypeParametersString = contentType.parameters().asMap().entrySet().stream() .map(entry -> ";" + entry.getKey() + "=" + entry.getValue().iterator().next()) .collect(joining("")); requestHeaders.put(CONTENT_TYPE, CONTENT_TYPE_1.toString() + mediaTypeParametersString + ", " + CONTENT_TYPE_2.toString()); requestHeaders.put(ACCEPT, ACCEPT_TYPE_1.toString() + ", " + ACCEPT_TYPE_2.toString()); requestHeaders.put(someHeader, someValue); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod(METHOD) .withHeaders(requestHeaders) .withContext(context) .build(); Map<String, String> responseHeaders = new ConcurrentHashMap<>(); responseHeaders.put(someHeader, someValue); List<MediaType> contentTypes = asList(contentType, CONTENT_TYPE_2); List<MediaType> acceptTypes = asList(ACCEPT_TYPE_1, ACCEPT_TYPE_2); when(methodHandler.handle(request, contentTypes, acceptTypes, context) ) .thenReturn(new ApiGatewayProxyResponse.ApiGatewayProxyResponseBuilder() .withStatusCode(OK.getStatusCode()) .withHeaders(responseHeaders) .build()); handler.registerMethodHandler(METHOD, c -> methodHandler); handler.handleRequest(request, context); verify(methodHandler).handle(request, contentTypes, acceptTypes, context); } @Test public void shouldReturnResponseFromMethodHandlerWithDifferentlyCasedContentTypeAndAccept() throws Exception { String someHeader = "someHeader"; String someValue = "someValue"; Map<String, String> requestHeaders = new ConcurrentHashMap<>(); requestHeaders.put(CONTENT_TYPE, CONTENT_TYPE_1.toString().toUpperCase()); requestHeaders.put(ACCEPT, ACCEPT_TYPE_1.toString().toUpperCase()); requestHeaders.put(someHeader, someValue); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod(METHOD) .withHeaders(requestHeaders) .withContext(context) .build(); Map<String, String> responseHeaders = new ConcurrentHashMap<>(); responseHeaders.put(someHeader, someValue); when(methodHandler.handle(request, singletonList(CONTENT_TYPE_1), singletonList(ACCEPT_TYPE_1), context)) .thenReturn(new ApiGatewayProxyResponse.ApiGatewayProxyResponseBuilder() .withStatusCode(OK.getStatusCode()) .withHeaders(responseHeaders) .build()); handler.registerMethodHandler(METHOD, c -> methodHandler); ApiGatewayProxyResponse response = handler.handleRequest(request, context); assertThat(response).isNotNull(); assertThat(response.getStatusCode()).isEqualTo(OK.getStatusCode()); assertThat(response.getBody()).isEqualTo(""); assertThat(response.getHeaders()).isEqualTo(responseHeaders); } @Test public void shouldPassThroughErrorMessageFromMethodHandlerInvocationIfDebug() throws Exception { Map<String, String> headers = new ConcurrentHashMap<>(); headers.put(CONTENT_TYPE, CONTENT_TYPE_1.toString()); headers.put(ACCEPT, ACCEPT_TYPE_1.toString()); randomiseKeyValues(headers); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod(METHOD) .withHeaders(headers) .withContext(context) .build(); String message = "Some message"; RuntimeException cause = new RuntimeException(); StackTraceElement[] expectedStackTrace = new StackTraceElement[2]; String declaringClass1 = "declaringClass1"; String methodName1 = "methodName1"; String fileName1 = "fileName1"; int lineNumber1 = 1; expectedStackTrace[0] = new StackTraceElement(declaringClass1, methodName1, fileName1, lineNumber1); String declaringClass2 = "declaringClass2"; String methodName2 = "methodName2"; String fileName2 = "fileName2"; int lineNumber2 = 2; expectedStackTrace[1] = new StackTraceElement(declaringClass2, methodName2, fileName2, lineNumber2); cause.setStackTrace(expectedStackTrace); when(methodHandler.handle(request, singletonList(CONTENT_TYPE_1), singletonList(ACCEPT_TYPE_1), context)) .thenThrow(new RuntimeException(message, cause)); handler.registerMethodHandler(METHOD, c -> methodHandler); ApiGatewayProxyResponse response = handler.handleRequest(request, context); assertThat(response).isNotNull(); assertThat(response.getStatusCode()).isEqualTo(INTERNAL_SERVER_ERROR.getStatusCode()); JSONParser jsonParser = new JSONParser(); JSONObject jsonObject = (JSONObject) jsonParser.parse(response.getBody()); assertThat(jsonObject.keySet()).contains("message", "cause"); assertThat(jsonObject.get("message")).isEqualTo(message); assertThat((String) jsonObject.get("cause")).contains(String.format("%s.%s(%s:%s)", declaringClass1, methodName1, fileName1, lineNumber1)); assertThat((String) jsonObject.get("cause")).contains(String.format("%s.%s(%s:%s)", declaringClass2, methodName2, fileName2, lineNumber2)); } @Test public void corsSupportShouldReturnOkForRegisteredMethodAndMediaTypes() { LambdaProxyHandler<Configuration> handlerWithCORSSupport = new TestLambdaProxyHandler(true); String methodBeingInvestigated = "GET"; Collection<String> supportedMethods = asList(methodBeingInvestigated, "POST"); MediaType mediaType1 = MediaType.create("application", "type1"); MediaType mediaType2 = MediaType.create("application", "type2"); MediaType mediaType3 = MediaType.create("application", "type3"); MediaType mediaType4 = MediaType.create("application", "type4"); Collection<String> requiredHeaders = Stream.of("header1", "header2") .map(Util::randomizeCase) .collect(toList()); SampleMethodHandler sampleMethodHandler = new SampleMethodHandler(requiredHeaders); sampleMethodHandler.registerPerAccept(mediaType1, mock(AcceptMapper.class)); sampleMethodHandler.registerPerAccept(mediaType2, mock(AcceptMapper.class)); sampleMethodHandler.registerPerAccept(mediaType3, mock(AcceptMapper.class)); sampleMethodHandler.registerPerContentType(mediaType4, mock(ContentTypeMapper.class)); supportedMethods.forEach(method -> handlerWithCORSSupport.registerMethodHandler( method, c -> sampleMethodHandler )); Map<String, String> headers = new ConcurrentHashMap<>(); headers.put("Access-Control-Request-Method", methodBeingInvestigated); Collection<String> requestHeaders = requiredHeaders.stream() .map(Util::randomizeCase) .collect(toSet()); requestHeaders.add("header3"); headers.put("Access-Control-Request-Headers", String.join(", ", requestHeaders)); headers.put("Content-Type", mediaType4.toString()); String origin = "http://127.0.0.1:8888"; headers.put("Origin", origin); randomiseKeyValues(headers); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod("OPTIONS") .withHeaders(headers) .withContext(context) .build(); ApiGatewayProxyResponse response = handlerWithCORSSupport.handleRequest(request, context); assertThat(response.getStatusCode()).isEqualTo(OK.getStatusCode()); Map<String, String> responseHeaders = response.getHeaders(); assertThat(responseHeaders).containsKey("Access-Control-Allow-Origin"); assertThat(responseHeaders.get("Access-Control-Allow-Origin")).isEqualTo(origin); assertThat(responseHeaders).containsKey("Access-Control-Allow-Headers"); assertThat(asList(responseHeaders.get("Access-Control-Allow-Headers").split(", "))) .containsAll(requestHeaders.stream().map(String::toLowerCase).collect(toList())); assertThat(responseHeaders).containsKey("Access-Control-Allow-Methods"); assertThat(responseHeaders.get("Access-Control-Allow-Methods")).isEqualTo(methodBeingInvestigated); } @Test public void corsSupportShouldReturnBadRequestWhenRequestDoesNotSpecifyOrigin() { LambdaProxyHandler<Configuration> handlerWithCORSSupport = new TestLambdaProxyHandler(true); String methodBeingInvestigated = "GET"; Collection<String> supportedMethods = asList(methodBeingInvestigated, "POST"); MediaType mediaType1 = MediaType.create("application", "type1"); MediaType mediaType2 = MediaType.create("application", "type2"); MediaType mediaType3 = MediaType.create("application", "type3"); MediaType mediaType4 = MediaType.create("application", "type4"); Collection<String> requiredHeaders = Stream.of("header1", "header2") .map(Util::randomizeCase) .collect(toList()); SampleMethodHandler sampleMethodHandler = new SampleMethodHandler(requiredHeaders); sampleMethodHandler.registerPerAccept(mediaType1, mock(AcceptMapper.class)); sampleMethodHandler.registerPerAccept(mediaType2, mock(AcceptMapper.class)); sampleMethodHandler.registerPerAccept(mediaType3, mock(AcceptMapper.class)); sampleMethodHandler.registerPerContentType(mediaType4, mock(ContentTypeMapper.class)); supportedMethods.forEach(method -> handlerWithCORSSupport.registerMethodHandler( method, c -> sampleMethodHandler )); Map<String, String> headers = new ConcurrentHashMap<>(); headers.put("Access-Control-Request-Method", methodBeingInvestigated); Collection<String> requestHeaders = requiredHeaders.stream() .map(Util::randomizeCase) .collect(toSet()); requestHeaders.add("header3"); headers.put("Access-Control-Request-Headers", String.join(", ", requestHeaders)); headers.put("Content-Type", mediaType4.toString()); randomiseKeyValues(headers); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod("OPTIONS") .withHeaders(headers) .withContext(context) .build(); ApiGatewayProxyResponse response = handlerWithCORSSupport.handleRequest(request, context); assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST.getStatusCode()); assertThat(response.getBody()).contains(String.format("Options method should include the %s header", ORIGIN_HEADER.toLowerCase())); } @Test public void corsSupportShouldReturnBadRequestWhenRequestDoesNotSpecifyMethod() { LambdaProxyHandler<Configuration> handlerWithCorsSupport = new TestLambdaProxyHandler(true); Collection<String> supportedMethods = singletonList("POST"); MediaType mediaType1 = MediaType.create("application", "type1"); MediaType mediaType2 = MediaType.create("application", "type2"); MediaType mediaType3 = MediaType.create("application", "type3"); MediaType mediaType4 = MediaType.create("application", "type4"); Collection<String> requiredHeaders = Stream.of("header1", "header2") .map(Util::randomizeCase) .collect(toList()); SampleMethodHandler sampleMethodHandler = new SampleMethodHandler(requiredHeaders); sampleMethodHandler.registerPerAccept(mediaType1, mock(AcceptMapper.class)); sampleMethodHandler.registerPerAccept(mediaType2, mock(AcceptMapper.class)); sampleMethodHandler.registerPerAccept(mediaType3, mock(AcceptMapper.class)); sampleMethodHandler.registerPerContentType(mediaType4, mock(ContentTypeMapper.class)); supportedMethods.forEach(method -> handlerWithCorsSupport.registerMethodHandler( method, c -> sampleMethodHandler )); Map<String, String> headers = new ConcurrentHashMap<>(); Collection<String> requestHeaders = requiredHeaders.stream() .map(Util::randomizeCase) .collect(toSet()); requestHeaders.add("header3"); headers.put(ACCESS_CONTROL_REQUEST_HEADERS, String.join(", ", requestHeaders)); headers.put("Content-Type", mediaType4.toString()); headers.put("Origin", "http://127.0.0.1:8888"); randomiseKeyValues(headers); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod("OPTIONS") .withHeaders(headers) .withContext(context) .build(); ApiGatewayProxyResponse response = handlerWithCorsSupport.handleRequest(request, context); assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST.getStatusCode()); assertThat(response.getBody()).contains(String.format("Options method should include the %s header", ACCESS_CONTROL_REQUEST_METHOD.toLowerCase())); } @Test public void corsSupportShouldReturnBadRequestForNonRegisteredMethod() { LambdaProxyHandler<Configuration> handlerWithCORSSupport = new TestLambdaProxyHandler(true); String methodBeingInvestigated = "GET"; Collection<String> supportedMethods = singletonList("POST"); MediaType mediaType1 = MediaType.create("application", "type1"); MediaType mediaType2 = MediaType.create("application", "type2"); MediaType mediaType3 = MediaType.create("application", "type3"); MediaType mediaType4 = MediaType.create("application", "type4"); Collection<String> requiredHeaders = Stream.of("header1", "header2") .map(Util::randomizeCase) .collect(toList()); SampleMethodHandler sampleMethodHandler = new SampleMethodHandler(requiredHeaders); sampleMethodHandler.registerPerAccept(mediaType1, mock(AcceptMapper.class)); sampleMethodHandler.registerPerAccept(mediaType2, mock(AcceptMapper.class)); sampleMethodHandler.registerPerAccept(mediaType3, mock(AcceptMapper.class)); sampleMethodHandler.registerPerContentType(mediaType4, mock(ContentTypeMapper.class)); supportedMethods.forEach(method -> handlerWithCORSSupport.registerMethodHandler( method, c -> sampleMethodHandler )); Map<String, String> headers = new ConcurrentHashMap<>(); headers.put(ACCESS_CONTROL_REQUEST_METHOD, methodBeingInvestigated); Collection<String> requestHeaders = new HashSet<>(requiredHeaders); requestHeaders.add("header3"); headers.put(ACCESS_CONTROL_REQUEST_HEADERS, String.join(", ", requestHeaders)); headers.put("Content-Type", mediaType4.toString()); headers.put("Origin", "http://127.0.0.1:8888"); randomiseKeyValues(headers); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod("OPTIONS") .withHeaders(headers) .withContext(context) .build(); ApiGatewayProxyResponse response = handlerWithCORSSupport.handleRequest(request, context); assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST.getStatusCode()); assertThat(response.getBody()).isEqualTo(String.format("Lambda cannot handle the method %s", methodBeingInvestigated.toLowerCase())); } @Test public void corsSupportShouldReturnBadRequestWhenRequiredHeadersNotPresent() { LambdaProxyHandler<Configuration> handlerWithCORSSupport = new TestLambdaProxyHandler(true); String methodBeingInvestigated = "GET"; Collection<String> supportedMethods = asList(methodBeingInvestigated, "POST"); MediaType mediaType1 = MediaType.create("application", "type1"); MediaType mediaType2 = MediaType.create("application", "type2"); MediaType mediaType3 = MediaType.create("application", "type3"); MediaType mediaType4 = MediaType.create("application", "type4"); List<String> requiredHeaders = Stream.of("header1", "header2") .map(Util::randomizeCase) .collect(toList()); SampleMethodHandler sampleMethodHandler = new SampleMethodHandler(requiredHeaders); sampleMethodHandler.registerPerAccept(mediaType1, mock(AcceptMapper.class)); sampleMethodHandler.registerPerAccept(mediaType2, mock(AcceptMapper.class)); sampleMethodHandler.registerPerAccept(mediaType3, mock(AcceptMapper.class)); sampleMethodHandler.registerPerContentType(mediaType4, mock(ContentTypeMapper.class)); supportedMethods.forEach(method -> handlerWithCORSSupport.registerMethodHandler( method, c -> sampleMethodHandler )); Map<String, String> headers = new ConcurrentHashMap<>(); headers.put(ACCESS_CONTROL_REQUEST_METHOD, methodBeingInvestigated); headers.put(ACCESS_CONTROL_REQUEST_HEADERS, ""); headers.put("Content-Type", mediaType4.toString()); headers.put("Origin", "http://127.0.0.1:8888"); randomiseKeyValues(headers); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod("OPTIONS") .withHeaders(headers) .withContext(context) .build(); ApiGatewayProxyResponse response = handlerWithCORSSupport.handleRequest(request, context); assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST.getStatusCode()); assertThat(response.getBody()).contains(String.format("The required header(s) not present: %s", String.join(", ", requiredHeaders.stream().map(String::toLowerCase).collect(toList())))); } @Test public void corsSupportShouldReturnAccessControlAllowOriginHeaderFromMethodHandler() throws Exception { LambdaProxyHandler<Configuration> handlerWithCORSSupport = new TestLambdaProxyHandler(true); String someHeader = "someHeader"; String someValue = "someValue"; Map<String, String> requestHeaders = new ConcurrentHashMap<>(); requestHeaders.put(CONTENT_TYPE, CONTENT_TYPE_1.toString()); requestHeaders.put(ACCEPT, ACCEPT_TYPE_1.toString()); requestHeaders.put(someHeader, someValue); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod(METHOD) .withHeaders(requestHeaders) .withContext(context) .build(); Map<String, String> responseHeaders = new ConcurrentHashMap<>(); responseHeaders.put(someHeader, someValue); String accessControlAllowOriginKey = "Access-Control-Allow-Origin"; String accessControlAllowOriginValue = "*"; when(methodHandler.handle(request, singletonList(CONTENT_TYPE_1), singletonList(ACCEPT_TYPE_1), context)) .thenReturn(new ApiGatewayProxyResponse.ApiGatewayProxyResponseBuilder() .withStatusCode(OK.getStatusCode()) .withHeaders(responseHeaders) .build()); handlerWithCORSSupport.registerMethodHandler(METHOD, c -> methodHandler); ApiGatewayProxyResponse response = handlerWithCORSSupport.handleRequest(request, context); assertThat(response).isNotNull(); assertThat(response.getHeaders()).containsKey(accessControlAllowOriginKey); assertThat(response.getHeaders().get(accessControlAllowOriginKey)).isEqualTo(accessControlAllowOriginValue); } @Test public void mediaTypesAreCreatedWithLowerCaseValues() throws Exception { String someHeader = "someHeader"; String someValue = "someValue"; Map<String, String> requestHeaders = new ConcurrentHashMap<>(); requestHeaders.put(CONTENT_TYPE, CONTENT_TYPE_1.toString()); requestHeaders.put(ACCEPT, ACCEPT_TYPE_1.toString()); requestHeaders.put(someHeader, someValue); ApiGatewayProxyRequest request = new ApiGatewayProxyRequestBuilder() .withHttpMethod(METHOD) .withHeaders(requestHeaders) .withContext(context) .build(); Map<String, String> responseHeaders = new ConcurrentHashMap<>(); responseHeaders.put(someHeader, someValue); when(methodHandler.handle(request, singletonList(CONTENT_TYPE_1), singletonList(ACCEPT_TYPE_1), context)) .thenReturn(new ApiGatewayProxyResponse.ApiGatewayProxyResponseBuilder() .withStatusCode(OK.getStatusCode()) .withHeaders(responseHeaders) .build()); handler.registerMethodHandler(METHOD, c -> methodHandler); handler.handleRequest(request, context); verify(methodHandler).handle(any(), eq(singletonList(CONTENT_TYPE_1)), eq(singletonList(ACCEPT_TYPE_1)), any()); } private class TestLambdaProxyHandler extends LambdaProxyHandler<Configuration> { public TestLambdaProxyHandler(boolean corsSupport) { super(corsSupport); } @Override protected Configuration getConfiguration(ApiGatewayProxyRequest request, Context context) { return configuration; } } private class TestLambdaProxyHandlerWithFailingConguration extends LambdaProxyHandler<Configuration> { public TestLambdaProxyHandlerWithFailingConguration() { super(false); } @Override protected Configuration getConfiguration(ApiGatewayProxyRequest request, Context context) { throw new NullPointerException(); } } }