From 3e41671fe805fb8559fedd7e1cc315c4102f211a Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Tue, 10 Dec 2024 11:40:28 -0500 Subject: [PATCH] Add api gateway extensions Add docs move to new folder --- .../Amazon.Lambda.TestTool.csproj | 2 + .../ApiGatewayResponseExtensions.cs | 108 +++++++++++ .../ApiGatewayResponseExtensionsTests.cs | 170 ++++++++++++++++++ 3 files changed, 280 insertions(+) create mode 100644 Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs create mode 100644 Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/ApiGatewayResponseExtensionsTests.cs diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj index 7afdd2e7a..d99ff4f83 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj @@ -17,6 +17,8 @@ + + diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs new file mode 100644 index 000000000..255e82145 --- /dev/null +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs @@ -0,0 +1,108 @@ +namespace Amazon.Lambda.TestTool.Extensions; + +using Amazon.Lambda.APIGatewayEvents; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using System; +using System.IO; +using System.Text; + +/// +/// Provides extension methods for converting API Gateway responses to HttpResponse objects. +/// +public static class ApiGatewayResponseExtensions +{ + /// + /// Converts an APIGatewayProxyResponse to an HttpResponse. + /// + /// The API Gateway proxy response to convert. + /// An HttpResponse object representing the API Gateway response. + public static HttpResponse ToHttpResponse(this APIGatewayProxyResponse apiResponse) + { + var httpContext = new DefaultHttpContext(); + var response = httpContext.Response; + + response.StatusCode = apiResponse.StatusCode; + + SetResponseHeaders(response, apiResponse.Headers, apiResponse.MultiValueHeaders); + + SetResponseBody(response, apiResponse.Body, apiResponse.IsBase64Encoded); + + return response; + } + + /// + /// Converts an APIGatewayHttpApiV2ProxyResponse to an HttpResponse. + /// + /// The API Gateway HTTP API v2 proxy response to convert. + /// An HttpResponse object representing the API Gateway response. + public static HttpResponse ToHttpResponse(this APIGatewayHttpApiV2ProxyResponse apiResponse) + { + var httpContext = new DefaultHttpContext(); + var response = httpContext.Response; + + response.StatusCode = apiResponse.StatusCode; + + SetResponseHeaders(response, apiResponse.Headers); + + if (apiResponse.Cookies != null) + { + foreach (var cookie in apiResponse.Cookies) + { + response.Headers.Append("Set-Cookie", cookie); + } + } + + SetResponseBody(response, apiResponse.Body, apiResponse.IsBase64Encoded); + + return response; + } + + /// + /// Sets the headers on the HttpResponse object. + /// + /// The HttpResponse object to modify. + /// The single-value headers to set. + /// The multi-value headers to set. + private static void SetResponseHeaders(HttpResponse response, IDictionary? headers, IDictionary>? multiValueHeaders = null) + { + if (headers != null) + { + foreach (var header in headers) + { + response.Headers[header.Key] = header.Value; + } + } + + if (multiValueHeaders != null) + { + foreach (var header in multiValueHeaders) + { + response.Headers[header.Key] = new StringValues(header.Value.ToArray()); + } + } + } + + /// + /// Sets the body of the HttpResponse object. + /// + /// The HttpResponse object to modify. + /// The body content to set. + /// Indicates whether the body is Base64 encoded. + private static void SetResponseBody(HttpResponse response, string? body, bool isBase64Encoded) + { + if (!string.IsNullOrEmpty(body)) + { + byte[] bodyBytes; + if (isBase64Encoded) + { + bodyBytes = Convert.FromBase64String(body); + } + else + { + bodyBytes = Encoding.UTF8.GetBytes(body); + } + response.Body = new MemoryStream(bodyBytes); + } + } +} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/ApiGatewayResponseExtensionsTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/ApiGatewayResponseExtensionsTests.cs new file mode 100644 index 000000000..c4aa4f6d7 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/ApiGatewayResponseExtensionsTests.cs @@ -0,0 +1,170 @@ +namespace Amazon.Lambda.TestTool.UnitTests.Extensions; + +using Amazon.Lambda.APIGatewayEvents; +using Amazon.Lambda.TestTool.Extensions; +using System.IO; +using System.Text; +using Xunit; + +public class ApiGatewayResponseExtensionsTests +{ + [Fact] + public void ToHttpResponse_APIGatewayProxyResponse_SetsCorrectStatusCode() + { + var apiResponse = new APIGatewayProxyResponse + { + StatusCode = 200 + }; + + var httpResponse = apiResponse.ToHttpResponse(); + + Assert.Equal(200, httpResponse.StatusCode); + } + + [Fact] + public void ToHttpResponse_APIGatewayProxyResponse_SetsHeaders() + { + var apiResponse = new APIGatewayProxyResponse + { + Headers = new Dictionary + { + { "Content-Type", "application/json" }, + { "X-Custom-Header", "CustomValue" } + } + }; + + var httpResponse = apiResponse.ToHttpResponse(); + + Assert.Equal("application/json", httpResponse.Headers["Content-Type"]); + Assert.Equal("CustomValue", httpResponse.Headers["X-Custom-Header"]); + } + + [Fact] + public void ToHttpResponse_APIGatewayProxyResponse_SetsMultiValueHeaders() + { + var apiResponse = new APIGatewayProxyResponse + { + MultiValueHeaders = new Dictionary> + { + { "X-Multi-Header", new List { "Value1", "Value2" } } + } + }; + + var httpResponse = apiResponse.ToHttpResponse(); + + Assert.Equal(new[] { "Value1", "Value2" }, httpResponse.Headers["X-Multi-Header"]); + } + + [Fact] + public void ToHttpResponse_APIGatewayProxyResponse_SetsBodyNonBase64() + { + var apiResponse = new APIGatewayProxyResponse + { + Body = "Hello, World!", + IsBase64Encoded = false + }; + + var httpResponse = apiResponse.ToHttpResponse(); + + httpResponse.Body.Seek(0, SeekOrigin.Begin); + var bodyContent = new StreamReader(httpResponse.Body).ReadToEnd(); + Assert.Equal("Hello, World!", bodyContent); + } + + [Fact] + public void ToHttpResponse_APIGatewayProxyResponse_SetsBodyBase64() + { + var originalBody = "Hello, World!"; + var base64Body = Convert.ToBase64String(Encoding.UTF8.GetBytes(originalBody)); + var apiResponse = new APIGatewayProxyResponse + { + Body = base64Body, + IsBase64Encoded = true + }; + + var httpResponse = apiResponse.ToHttpResponse(); + + httpResponse.Body.Seek(0, SeekOrigin.Begin); + var bodyContent = new StreamReader(httpResponse.Body).ReadToEnd(); + Assert.Equal(originalBody, bodyContent); + } + + [Fact] + public void ToHttpResponse_APIGatewayHttpApiV2ProxyResponse_SetsCorrectStatusCode() + { + var apiResponse = new APIGatewayHttpApiV2ProxyResponse + { + StatusCode = 201 + }; + + var httpResponse = apiResponse.ToHttpResponse(); + + Assert.Equal(201, httpResponse.StatusCode); + } + + [Fact] + public void ToHttpResponse_APIGatewayHttpApiV2ProxyResponse_SetsHeaders() + { + var apiResponse = new APIGatewayHttpApiV2ProxyResponse + { + Headers = new Dictionary + { + { "Content-Type", "application/xml" }, + { "X-Custom-Header", "CustomValue" } + } + }; + + var httpResponse = apiResponse.ToHttpResponse(); + + Assert.Equal("application/xml", httpResponse.Headers["Content-Type"]); + Assert.Equal("CustomValue", httpResponse.Headers["X-Custom-Header"]); + } + + [Fact] + public void ToHttpResponse_APIGatewayHttpApiV2ProxyResponse_SetsCookies() + { + var apiResponse = new APIGatewayHttpApiV2ProxyResponse + { + Cookies = new[] { "session=abc123; Path=/", "theme=dark; Max-Age=3600" } + }; + + var httpResponse = apiResponse.ToHttpResponse(); + + Assert.Contains(httpResponse.Headers["Set-Cookie"], v => v == "session=abc123; Path=/"); + Assert.Contains(httpResponse.Headers["Set-Cookie"], v => v == "theme=dark; Max-Age=3600"); + } + + [Fact] + public void ToHttpResponse_APIGatewayHttpApiV2ProxyResponse_SetsBodyNonBase64() + { + var apiResponse = new APIGatewayHttpApiV2ProxyResponse + { + Body = "Hello, API Gateway v2!", + IsBase64Encoded = false + }; + + var httpResponse = apiResponse.ToHttpResponse(); + + httpResponse.Body.Seek(0, SeekOrigin.Begin); + var bodyContent = new StreamReader(httpResponse.Body).ReadToEnd(); + Assert.Equal("Hello, API Gateway v2!", bodyContent); + } + + [Fact] + public void ToHttpResponse_APIGatewayHttpApiV2ProxyResponse_SetsBodyBase64() + { + var originalBody = "Hello, API Gateway v2!"; + var base64Body = Convert.ToBase64String(Encoding.UTF8.GetBytes(originalBody)); + var apiResponse = new APIGatewayHttpApiV2ProxyResponse + { + Body = base64Body, + IsBase64Encoded = true + }; + + var httpResponse = apiResponse.ToHttpResponse(); + + httpResponse.Body.Seek(0, SeekOrigin.Begin); + var bodyContent = new StreamReader(httpResponse.Body).ReadToEnd(); + Assert.Equal(originalBody, bodyContent); + } +}