diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Amazon.Lambda.TestTool.IntegrationTests.csproj b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Amazon.Lambda.TestTool.IntegrationTests.csproj index 8e2088d92..92b0900bf 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Amazon.Lambda.TestTool.IntegrationTests.csproj +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Amazon.Lambda.TestTool.IntegrationTests.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -40,8 +40,4 @@ - - - - diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayIntegrationTestCollection.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayIntegrationTestCollection.cs deleted file mode 100644 index dc628d9b5..000000000 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayIntegrationTestCollection.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -using Xunit; - -namespace Amazon.Lambda.TestTool.IntegrationTests -{ - [CollectionDefinition("ApiGateway Integration Tests")] - public class ApiGatewayIntegrationTestCollection : ICollectionFixture - { - - } -} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayIntegrationTestFixture.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayIntegrationTestFixture.cs deleted file mode 100644 index 9f83cb6ef..000000000 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayIntegrationTestFixture.cs +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -using Amazon.CloudFormation; -using Amazon.APIGateway; -using Amazon.ApiGatewayV2; -using Amazon.Lambda.TestTool.IntegrationTests.Helpers; -using Amazon.Lambda.TestTool.Models; -using System.Reflection; -using Xunit; - -namespace Amazon.Lambda.TestTool.IntegrationTests -{ - public class ApiGatewayIntegrationTestFixture : IAsyncLifetime - { - private readonly Dictionary _testRoutes; - - public CloudFormationHelper CloudFormationHelper { get; private set; } - public ApiGatewayHelper ApiGatewayHelper { get; private set; } - public ApiGatewayTestHelper ApiGatewayTestHelper { get; private set; } - - public string StackName { get; private set; } - - // Main API Gateway IDs and Base URLs - public string MainRestApiId { get; private set; } - public string MainHttpApiV1Id { get; private set; } - public string MainHttpApiV2Id { get; private set; } - public string BinaryMediaTypeRestApiId { get; private set; } // this is the rest api that has binary media types of */* enabled - - public string MainRestApiBaseUrl { get; private set; } - public string MainHttpApiV1BaseUrl { get; private set; } - public string MainHttpApiV2BaseUrl { get; private set; } - public string BinaryMediaTypeRestApiBaseUrl { get; private set; } - - // Lambda Function ARNs - public string ParseAndReturnBodyLambdaFunctionArn { get; private set; } - public string ReturnRawBodyLambdaFunctionArn { get; private set; } - public string ReturnFullEventLambdaFunctionArn { get; private set; } - public string ReturnDecodedParseBinLambdaFunctionArn { get; private set; } - - public ApiGatewayIntegrationTestFixture() - { - var regionEndpoint = RegionEndpoint.USWest2; - CloudFormationHelper = new CloudFormationHelper(new AmazonCloudFormationClient(regionEndpoint)); - ApiGatewayHelper = new ApiGatewayHelper( - new AmazonAPIGatewayClient(regionEndpoint), - new AmazonApiGatewayV2Client(regionEndpoint) - ); - ApiGatewayTestHelper = new ApiGatewayTestHelper(); - _testRoutes = new Dictionary(); - - StackName = string.Empty; - MainRestApiId = string.Empty; - MainHttpApiV1Id = string.Empty; - MainHttpApiV2Id = string.Empty; - BinaryMediaTypeRestApiId = string.Empty; - MainRestApiBaseUrl = string.Empty; - MainHttpApiV1BaseUrl = string.Empty; - MainHttpApiV2BaseUrl = string.Empty; - BinaryMediaTypeRestApiBaseUrl = string.Empty; - ParseAndReturnBodyLambdaFunctionArn = string.Empty; - ReturnRawBodyLambdaFunctionArn = string.Empty; - ReturnFullEventLambdaFunctionArn = string.Empty; - ReturnDecodedParseBinLambdaFunctionArn = string.Empty; - } - - public void RegisterTestRoute(string routeId, TestRouteConfig config) - { - if (string.IsNullOrEmpty(routeId)) - { - throw new ArgumentNullException(nameof(routeId)); - } - - if (config == null) - { - throw new ArgumentNullException(nameof(config)); - } - - if (string.IsNullOrEmpty(config.Path)) - { - throw new ArgumentException("Route path cannot be empty", nameof(config)); - } - - if (string.IsNullOrEmpty(config.HttpMethod)) - { - throw new ArgumentException("HTTP method cannot be empty", nameof(config)); - } - - if (string.IsNullOrEmpty(config.LambdaFunctionArn)) - { - throw new ArgumentException("Lambda function ARN cannot be empty", nameof(config)); - } - - _testRoutes[routeId] = config; - } - - public string GetRouteUrl(string baseUrl, string routeId) - { - if (!_testRoutes.TryGetValue(routeId, out var config)) - { - throw new KeyNotFoundException($"Route {routeId} not found"); - } - return baseUrl.TrimEnd('/') + config.Path; - } - - public string GetAppropriateBaseUrl(ApiGatewayType gatewayType) - { - return gatewayType switch - { - ApiGatewayType.Rest => MainRestApiBaseUrl, - ApiGatewayType.RestWithBinarySupport => BinaryMediaTypeRestApiBaseUrl, - ApiGatewayType.HttpV1 => MainHttpApiV1BaseUrl, - ApiGatewayType.HttpV2 => MainHttpApiV2BaseUrl, - _ => throw new ArgumentException($"Unsupported gateway type: {gatewayType}") - }; - } - - public string GetAppropriateApiId(ApiGatewayType gatewayType) - { - return gatewayType switch - { - ApiGatewayType.Rest => MainRestApiId, - ApiGatewayType.RestWithBinarySupport => BinaryMediaTypeRestApiId, - ApiGatewayType.HttpV1 => MainHttpApiV1Id, - ApiGatewayType.HttpV2 => MainHttpApiV2Id, - _ => throw new ArgumentException($"Unsupported gateway type: {gatewayType}") - }; - } - - public static ApiGatewayEmulatorMode GetEmulatorMode(ApiGatewayType gatewayType) - { - return gatewayType switch - { - ApiGatewayType.Rest or ApiGatewayType.RestWithBinarySupport => ApiGatewayEmulatorMode.Rest, - ApiGatewayType.HttpV1 => ApiGatewayEmulatorMode.HttpV1, - ApiGatewayType.HttpV2 => ApiGatewayEmulatorMode.HttpV2, - _ => throw new ArgumentException($"Unsupported gateway type: {gatewayType}") - }; - } - - - public async Task InitializeAsync() - { - StackName = $"Test-{Guid.NewGuid().ToString("N").Substring(0, 5)}"; - - string templateBody = ReadCloudFormationTemplate("cloudformation-template-apigateway.yaml"); - await CloudFormationHelper.CreateStackAsync(StackName, templateBody); - - await WaitForStackCreationComplete(); - await RetrieveStackOutputs(); - - // Register all test routes using RegisterTestRoute - foreach (var (routeId, config) in TestRoutes.GetDefaultRoutes(this)) - { - RegisterTestRoute(routeId, config); - } - - // Setup all routes - await SetupTestRoutes(); - await WaitForRoutesAvailability(); - } - - private async Task SetupTestRoutes() - { - foreach (var (routeId, config) in _testRoutes) - { - await ApiGatewayHelper.AddRouteToRestApi( - MainRestApiId, - config.LambdaFunctionArn, - config.Path, - config.HttpMethod); - - await ApiGatewayHelper.AddRouteToHttpApi( - MainHttpApiV1Id, - config.LambdaFunctionArn, - "1.0", - config.Path, - config.HttpMethod); - - await ApiGatewayHelper.AddRouteToHttpApi( - MainHttpApiV2Id, - config.LambdaFunctionArn, - "2.0", - config.Path, - config.HttpMethod); - - await ApiGatewayHelper.AddRouteToRestApi( - BinaryMediaTypeRestApiId, - config.LambdaFunctionArn, - config.Path, - config.HttpMethod); - } - } - - private async Task WaitForRoutesAvailability() - { - foreach (var config in _testRoutes.Values) - { - var restUrl = MainRestApiBaseUrl.TrimEnd('/') + config.Path; - var httpV1Url = MainHttpApiV1BaseUrl.TrimEnd('/') + config.Path; - var httpV2Url = MainHttpApiV2BaseUrl.TrimEnd('/') + config.Path; - var binaryUrl = BinaryMediaTypeRestApiBaseUrl.TrimEnd('/') + config.Path; - - await ApiGatewayHelper.WaitForApiAvailability(BinaryMediaTypeRestApiId, binaryUrl, false); - await ApiGatewayHelper.WaitForApiAvailability(MainRestApiId, restUrl, false); - await ApiGatewayHelper.WaitForApiAvailability(MainHttpApiV1Id, httpV1Url, true); - await ApiGatewayHelper.WaitForApiAvailability(MainHttpApiV2Id, httpV2Url, true); - } - } - - private string ReadCloudFormationTemplate(string fileName) - { - var assembly = Assembly.GetExecutingAssembly(); - var resourceName = $"{assembly.GetName().Name}.{fileName}"; - using (var stream = assembly.GetManifestResourceStream(resourceName)) - { - if (stream == null) - { - throw new FileNotFoundException($"CloudFormation template file '{fileName}' not found in assembly resources."); - } - - using (StreamReader reader = new StreamReader(stream)) - { - return reader.ReadToEnd(); - } - } - } - - private async Task WaitForStackCreationComplete() - { - while (true) - { - var status = await CloudFormationHelper.GetStackStatusAsync(StackName); - if (status == StackStatus.CREATE_COMPLETE) - { - break; - } - if (status.ToString().EndsWith("FAILED") || status == StackStatus.DELETE_COMPLETE) - { - throw new Exception($"Stack creation failed. Status: {status}"); - } - await Task.Delay(10000); - } - } - - private async Task RetrieveStackOutputs() - { - MainRestApiId = await CloudFormationHelper.GetOutputValueAsync(StackName, "MainRestApiId"); - MainHttpApiV1Id = await CloudFormationHelper.GetOutputValueAsync(StackName, "MainHttpApiV1Id"); - MainHttpApiV2Id = await CloudFormationHelper.GetOutputValueAsync(StackName, "MainHttpApiV2Id"); - BinaryMediaTypeRestApiId = await CloudFormationHelper.GetOutputValueAsync(StackName, "BinaryMediaTypeRestApiId"); - - MainRestApiBaseUrl = await CloudFormationHelper.GetOutputValueAsync(StackName, "MainRestApiBaseUrl"); - MainHttpApiV1BaseUrl = await CloudFormationHelper.GetOutputValueAsync(StackName, "MainHttpApiV1BaseUrl"); - MainHttpApiV2BaseUrl = await CloudFormationHelper.GetOutputValueAsync(StackName, "MainHttpApiV2BaseUrl"); - BinaryMediaTypeRestApiBaseUrl = await CloudFormationHelper.GetOutputValueAsync(StackName, "BinaryMediaTypeRestApiBaseUrl"); - - ParseAndReturnBodyLambdaFunctionArn = await CloudFormationHelper.GetOutputValueAsync(StackName, "ParseAndReturnBodyLambdaFunctionArn"); - ReturnRawBodyLambdaFunctionArn = await CloudFormationHelper.GetOutputValueAsync(StackName, "ReturnRawBodyLambdaFunctionArn"); - ReturnFullEventLambdaFunctionArn = await CloudFormationHelper.GetOutputValueAsync(StackName, "ReturnFullEventLambdaFunctionArn"); - ReturnDecodedParseBinLambdaFunctionArn = await CloudFormationHelper.GetOutputValueAsync(StackName, "ReturnDecodedParseBinLambdaFunctionArn"); - } - - public async Task DisposeAsync() - { - await CloudFormationHelper.DeleteStackAsync(StackName); - } - } - - public enum ApiGatewayType - { - Rest, - RestWithBinarySupport, - HttpV1, - HttpV2 - } - - public class ApiGatewayTestConfig - { - public required string RouteId { get; init; } - public required ApiGatewayType GatewayType { get; init; } - } -} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayResponseExtensionsAdditionalTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayResponseExtensionsAdditionalTests.cs deleted file mode 100644 index 22d95b857..000000000 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayResponseExtensionsAdditionalTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -using Amazon.Lambda.APIGatewayEvents; -using Microsoft.AspNetCore.Http; -using System.Text.Json; -using Amazon.Lambda.TestTool.Extensions; -using Amazon.Lambda.TestTool.Models; -using System.Text; -using Xunit; - -namespace Amazon.Lambda.TestTool.IntegrationTests -{ - [Collection("ApiGateway Integration Tests")] - public class ApiGatewayResponseExtensionsAdditionalTests - { - private readonly ApiGatewayIntegrationTestFixture _fixture; - private readonly HttpClient _httpClient; - - public ApiGatewayResponseExtensionsAdditionalTests(ApiGatewayIntegrationTestFixture fixture) - { - _fixture = fixture; - _httpClient = new HttpClient(); - } - - [Fact] - public async Task ToHttpResponse_RestAPIGatewayV1DecodesBase64() - { - var testResponse = new APIGatewayProxyResponse - { - StatusCode = 200, - Body = Convert.ToBase64String(Encoding.UTF8.GetBytes("test")), - IsBase64Encoded = true - }; - - var httpContext = new DefaultHttpContext(); - httpContext.Response.Body = new MemoryStream(); - await testResponse.ToHttpResponseAsync(httpContext, ApiGatewayEmulatorMode.Rest); - - var baseUrl = _fixture.GetAppropriateBaseUrl(ApiGatewayType.RestWithBinarySupport); - var url = _fixture.GetRouteUrl(baseUrl, TestRoutes.Ids.DecodeParseBinary); - var actualResponse = await _httpClient.PostAsync(url, new StringContent(JsonSerializer.Serialize(testResponse)), new CancellationTokenSource(5000).Token); - await _fixture.ApiGatewayTestHelper.AssertResponsesEqual(actualResponse, httpContext.Response); - Assert.Equal(200, (int)actualResponse.StatusCode); - var content = await actualResponse.Content.ReadAsStringAsync(); - Assert.Equal("test", content); - } - - [Fact] - public async Task ToHttpResponse_HttpV1APIGatewayV1DecodesBase64() - { - var testResponse = new APIGatewayProxyResponse - { - StatusCode = 200, - Body = Convert.ToBase64String(Encoding.UTF8.GetBytes("test")), - IsBase64Encoded = true - }; - - var httpContext = new DefaultHttpContext(); - httpContext.Response.Body = new MemoryStream(); - await testResponse.ToHttpResponseAsync(httpContext, ApiGatewayEmulatorMode.HttpV1); - - var baseUrl = _fixture.GetAppropriateBaseUrl(ApiGatewayType.HttpV1); - var url = _fixture.GetRouteUrl(baseUrl, TestRoutes.Ids.ParseAndReturnBody); - var actualResponse = await _httpClient.PostAsync(url, new StringContent(JsonSerializer.Serialize(testResponse)), new CancellationTokenSource(5000).Token); - - await _fixture.ApiGatewayTestHelper.AssertResponsesEqual(actualResponse, httpContext.Response); - Assert.Equal(200, (int)actualResponse.StatusCode); - var content = await actualResponse.Content.ReadAsStringAsync(); - Assert.Equal("test", content); - } - } -} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayResponseExtensionsTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayResponseExtensionsTests.cs deleted file mode 100644 index e3752faab..000000000 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayResponseExtensionsTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.TestTool.IntegrationTests.Helpers; -using Amazon.Lambda.TestTool.Models; -using Amazon.Lambda.TestTool.Tests.Common; -using Xunit; -using static Amazon.Lambda.TestTool.Tests.Common.ApiGatewayResponseTestCases; - -namespace Amazon.Lambda.TestTool.IntegrationTests -{ - [Collection("ApiGateway Integration Tests")] - public class ApiGatewayResponseExtensionsTests - { - private readonly ApiGatewayIntegrationTestFixture _fixture; - - public ApiGatewayResponseExtensionsTests(ApiGatewayIntegrationTestFixture fixture) - { - _fixture = fixture; - } - - [Theory] - [MemberData(nameof(ApiGatewayResponseTestCases.V1TestCases), MemberType = typeof(ApiGatewayResponseTestCases))] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] - public async Task IntegrationTest_APIGatewayV1_REST(string testName, ApiGatewayResponseTestCase testCase) - { - await RetryHelper.RetryOperation(async () => - { - var baseUrl = _fixture.GetAppropriateBaseUrl(ApiGatewayType.Rest); - var url = _fixture.GetRouteUrl(baseUrl, TestRoutes.Ids.ParseAndReturnBody); - await RunV1Test(testCase, url, ApiGatewayEmulatorMode.Rest); - return true; - }); - } - - [Theory] - [MemberData(nameof(ApiGatewayResponseTestCases.V1TestCases), MemberType = typeof(ApiGatewayResponseTestCases))] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] - public async Task IntegrationTest_APIGatewayV1_HTTP(string testName, ApiGatewayResponseTestCase testCase) - { - await RetryHelper.RetryOperation(async () => - { - var baseUrl = _fixture.GetAppropriateBaseUrl(ApiGatewayType.HttpV1); - var url = _fixture.GetRouteUrl(baseUrl, TestRoutes.Ids.ParseAndReturnBody); - await RunV1Test(testCase, url, ApiGatewayEmulatorMode.HttpV1); - return true; - }); - } - - [Theory] - [MemberData(nameof(ApiGatewayResponseTestCases.V2TestCases), MemberType = typeof(ApiGatewayResponseTestCases))] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] - public async Task IntegrationTest_APIGatewayV2(string testName, ApiGatewayResponseTestCase testCase) - { - await RetryHelper.RetryOperation(async () => - { - var baseUrl = _fixture.GetAppropriateBaseUrl(ApiGatewayType.HttpV2); - var url = _fixture.GetRouteUrl(baseUrl, TestRoutes.Ids.ParseAndReturnBody); - var testResponse = testCase.Response as APIGatewayHttpApiV2ProxyResponse; - Assert.NotNull(testResponse); - var (actualResponse, httpTestResponse) = await _fixture.ApiGatewayTestHelper.ExecuteTestRequest(testResponse, url); - await _fixture.ApiGatewayTestHelper.AssertResponsesEqual(actualResponse, httpTestResponse); - await testCase.IntegrationAssertions(actualResponse, ApiGatewayEmulatorMode.HttpV2); - return true; - }); - } - - private async Task RunV1Test(ApiGatewayResponseTestCase testCase, string apiUrl, ApiGatewayEmulatorMode emulatorMode) - { - var testResponse = testCase.Response as APIGatewayProxyResponse; - Assert.NotNull(testResponse); - var (actualResponse, httpTestResponse) = await _fixture.ApiGatewayTestHelper.ExecuteTestRequest(testResponse, apiUrl, emulatorMode); - await _fixture.ApiGatewayTestHelper.AssertResponsesEqual(actualResponse, httpTestResponse); - await testCase.IntegrationAssertions(actualResponse, emulatorMode); - } - } -} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Helpers/ApiGatewayHelper.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Helpers/ApiGatewayHelper.cs deleted file mode 100644 index 5818bf3e0..000000000 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Helpers/ApiGatewayHelper.cs +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -using Amazon.APIGateway; -using Amazon.ApiGatewayV2; -using Amazon.APIGateway.Model; -using Amazon.ApiGatewayV2.Model; -using System.Net; -using Amazon.Runtime.Internal.Endpoints.StandardLibrary; -using ConflictException = Amazon.ApiGatewayV2.Model.ConflictException; - -namespace Amazon.Lambda.TestTool.IntegrationTests.Helpers -{ - public class ApiGatewayHelper - { - private readonly IAmazonAPIGateway _apiGatewayV1Client; - private readonly IAmazonApiGatewayV2 _apiGatewayV2Client; - - public ApiGatewayHelper(IAmazonAPIGateway apiGatewayV1Client, IAmazonApiGatewayV2 apiGatewayV2Client) - { - _apiGatewayV1Client = apiGatewayV1Client; - _apiGatewayV2Client = apiGatewayV2Client; - } - - public async Task WaitForApiAvailability(string apiId, string apiUrl, bool isHttpApi, int maxWaitTimeSeconds = 60) - { - var startTime = DateTime.UtcNow; - var successStartTime = DateTime.UtcNow; - var requiredSuccessDuration = TimeSpan.FromSeconds(10); - bool hasBeenSuccessful = false; - - while ((DateTime.UtcNow - startTime).TotalSeconds < maxWaitTimeSeconds) - { - try - { - // Check if the API exists - if (isHttpApi) - { - var response = await _apiGatewayV2Client.GetApiAsync(new GetApiRequest { ApiId = apiId }); - if (response.ApiEndpoint == null) continue; - } - else - { - var response = await _apiGatewayV1Client.GetRestApiAsync(new GetRestApiRequest { RestApiId = apiId }); - if (response.Id == null) continue; - } - - // Try to make a request to the API - using (var httpClient = new HttpClient()) - { - var response = await httpClient.PostAsync(apiUrl, new StringContent("{}")); - - // Check if we get a successful response - if (response.StatusCode != HttpStatusCode.Forbidden && response.StatusCode != HttpStatusCode.NotFound) - { - if (!hasBeenSuccessful) - { - successStartTime = DateTime.UtcNow; - hasBeenSuccessful = true; - } - - if ((DateTime.UtcNow - successStartTime) >= requiredSuccessDuration) - { - return; // API has been responding successfully for at least 10 seconds - } - } - else - { - // Reset the success timer if we get a non-successful response - hasBeenSuccessful = false; - Console.WriteLine($"API responded with status code: {response.StatusCode}"); - } - } - } - catch (Amazon.ApiGatewayV2.Model.NotFoundException) when (isHttpApi) - { - // HTTP API not found yet, continue waiting - hasBeenSuccessful = false; - } - catch (Amazon.APIGateway.Model.NotFoundException) when (!isHttpApi) - { - // REST API not found yet, continue waiting - hasBeenSuccessful = false; - } - catch (Exception ex) - { - // Log unexpected exceptions and reset success timer - Console.WriteLine($"Unexpected error while checking API availability: {ex.Message}"); - hasBeenSuccessful = false; - } - await Task.Delay(1000); // Wait for 1 second before checking again - } - throw new TimeoutException($"API {apiId} did not become consistently available within {maxWaitTimeSeconds} seconds"); - } - - - public async Task AddRouteToRestApi(string restApiId, string lambdaArn, string route = "/test", string httpMethod = "ANY") - { - // Get all resources and find the root resource - var resources = await _apiGatewayV1Client.GetResourcesAsync(new GetResourcesRequest { RestApiId = restApiId }); - var rootResource = resources.Items.First(r => r.Path == "/"); - var rootResourceId = rootResource.Id; - - // Split the route into parts and create each part - var routeParts = route.Trim('/').Split('/'); - string currentPath = ""; - string parentResourceId = rootResourceId; - - foreach (var part in routeParts) - { - currentPath += "/" + part; - - // Check if the resource already exists - var existingResource = resources.Items.FirstOrDefault(r => r.Path == currentPath); - if (existingResource == null) - { - // Create the resource if it doesn't exist - var createResourceResponse = await _apiGatewayV1Client.CreateResourceAsync(new CreateResourceRequest - { - RestApiId = restApiId, - ParentId = parentResourceId, - PathPart = part - }); - parentResourceId = createResourceResponse.Id; - } - else - { - parentResourceId = existingResource.Id; - } - } - - // Create the method and integration - await _apiGatewayV1Client.PutMethodAsync(new PutMethodRequest - { - RestApiId = restApiId, - ResourceId = parentResourceId, - HttpMethod = httpMethod, - AuthorizationType = "NONE" - }); - - await _apiGatewayV1Client.PutIntegrationAsync(new PutIntegrationRequest - { - RestApiId = restApiId, - ResourceId = parentResourceId, - HttpMethod = httpMethod, - Type = Amazon.APIGateway.IntegrationType.AWS_PROXY, - IntegrationHttpMethod = "POST", - Uri = $"arn:aws:apigateway:{_apiGatewayV1Client.Config.RegionEndpoint.SystemName}:lambda:path/2015-03-31/functions/{lambdaArn}/invocations" - }); - - // Create and wait for deployment - var deploymentResponse = await _apiGatewayV1Client.CreateDeploymentAsync(new Amazon.APIGateway.Model.CreateDeploymentRequest - { - RestApiId = restApiId, - StageName = "test" - }); - - return $"https://{restApiId}.execute-api.{_apiGatewayV1Client.Config.RegionEndpoint.SystemName}.amazonaws.com/test{route}"; - } - - public async Task AddRouteToHttpApi(string httpApiId, string lambdaArn, string version, string route = "/test", string httpMethod = "ANY") - { - var createIntegrationResponse = await _apiGatewayV2Client.CreateIntegrationAsync(new CreateIntegrationRequest - { - ApiId = httpApiId, - IntegrationType = Amazon.ApiGatewayV2.IntegrationType.AWS_PROXY, - IntegrationUri = lambdaArn, - PayloadFormatVersion = version, - IntegrationMethod = "POST" - }); - string integrationId = createIntegrationResponse.IntegrationId; - - // Split the route into parts and create each part - var routeParts = route.Trim('/').Split('/'); - var currentPath = ""; - foreach (var part in routeParts) - { - currentPath += "/" + part; - await _apiGatewayV2Client.CreateRouteAsync(new CreateRouteRequest - { - ApiId = httpApiId, - RouteKey = $"{httpMethod} {currentPath}", - Target = $"integrations/{integrationId}" - }); - } - - // Create the final route (if it's not already created) - if (currentPath != "/" + route.Trim('/')) - { - await _apiGatewayV2Client.CreateRouteAsync(new CreateRouteRequest - { - ApiId = httpApiId, - RouteKey = $"{httpMethod} {route}", - Target = $"integrations/{integrationId}" - }); - } - - // Create and wait for deployment - var deployment = await _apiGatewayV2Client.CreateDeploymentAsync(new Amazon.ApiGatewayV2.Model.CreateDeploymentRequest - { - ApiId = httpApiId - }); - - // Create stage if it doesn't exist - try - { - await _apiGatewayV2Client.CreateStageAsync(new Amazon.ApiGatewayV2.Model.CreateStageRequest - { - ApiId = httpApiId, - StageName = "$default", - AutoDeploy = true - }); - } - catch (Amazon.ApiGatewayV2.Model.ConflictException) - { - // Stage already exists, continue - } - - var url = $"https://{httpApiId}.execute-api.{_apiGatewayV2Client.Config.RegionEndpoint.SystemName}.amazonaws.com{route}"; - return url ; - } - - - } -} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Helpers/ApiGatewayTestHelper.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Helpers/ApiGatewayTestHelper.cs deleted file mode 100644 index b4584e198..000000000 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Helpers/ApiGatewayTestHelper.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -using System.Text.Json; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.TestTool.Extensions; -using Amazon.Lambda.TestTool.Models; -using Microsoft.AspNetCore.Http; -using Xunit; - -namespace Amazon.Lambda.TestTool.IntegrationTests.Helpers -{ - public class ApiGatewayTestHelper - { - private readonly HttpClient _httpClient; - - public ApiGatewayTestHelper() - { - _httpClient = new HttpClient(); - } - public async Task<(HttpResponseMessage actualResponse, HttpResponse httpTestResponse)> ExecuteTestRequest(APIGatewayProxyResponse testResponse, string apiUrl, ApiGatewayEmulatorMode emulatorMode) - { - var httpContext = new DefaultHttpContext(); - httpContext.Response.Body = new MemoryStream(); - await testResponse.ToHttpResponseAsync(httpContext, emulatorMode); - var serialized = JsonSerializer.Serialize(testResponse); - var actualResponse = await _httpClient.PostAsync(apiUrl, new StringContent(serialized), new CancellationTokenSource(5000).Token); - return (actualResponse, httpContext.Response); - } - - public async Task<(HttpResponseMessage actualResponse, HttpResponse httpTestResponse)> ExecuteTestRequest(APIGatewayHttpApiV2ProxyResponse testResponse, string apiUrl) - { - var testResponseHttpContext = new DefaultHttpContext(); - testResponseHttpContext.Response.Body = new MemoryStream(); - await testResponse.ToHttpResponseAsync(testResponseHttpContext); - var serialized = JsonSerializer.Serialize(testResponse); - var actualResponse = await _httpClient.PostAsync(apiUrl, new StringContent(serialized), new CancellationTokenSource(5000).Token); - return (actualResponse, testResponseHttpContext.Response); - } - - public async Task AssertResponsesEqual(HttpResponseMessage actualResponse, HttpResponse httpTestResponse) - { - httpTestResponse.Body.Seek(0, SeekOrigin.Begin); - var expectedContent = await new StreamReader(httpTestResponse.Body).ReadToEndAsync(); - var actualContent = await actualResponse.Content.ReadAsStringAsync(); - - Assert.Equal(expectedContent, actualContent); - - Assert.Equal(httpTestResponse.StatusCode, (int)actualResponse.StatusCode); - - // ignore these because they will vary in the real world. we will check manually in other test cases that these are set - var headersToIgnore = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "Date", - "Apigw-Requestid", - "X-Amzn-Trace-Id", - "x-amzn-RequestId", - "x-amz-apigw-id", - "X-Cache", - "Via", - "X-Amz-Cf-Pop", - "X-Amz-Cf-Id" - }; - - foreach (var header in httpTestResponse.Headers) - { - if (headersToIgnore.Contains(header.Key)) continue; - Assert.True(actualResponse.Headers.TryGetValues(header.Key, out var actualValues) || - actualResponse.Content.Headers.TryGetValues(header.Key, out actualValues), - $"Header '{header.Key}={string.Join(", ", header.Value.ToArray())}' not found in actual response"); - - var sortedExpectedValues = header.Value.OrderBy(v => v).ToArray(); - var sortedActualValues = actualValues.OrderBy(v => v).ToArray(); - Assert.Equal(sortedExpectedValues, sortedActualValues); - } - - foreach (var header in actualResponse.Headers.Concat(actualResponse.Content.Headers)) - { - if (headersToIgnore.Contains(header.Key)) continue; - - Assert.True(httpTestResponse.Headers.ContainsKey(header.Key), - $"Header '{header.Key}={string.Join(", ", header.Value)}' not found in test response"); - - var sortedExpectedValues = httpTestResponse.Headers[header.Key].OrderBy(v => v).ToArray(); - var sortedActualValues = header.Value.OrderBy(v => v).ToArray(); - Assert.Equal(sortedExpectedValues, sortedActualValues); - } - } - } -} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Helpers/CloudFormationHelper.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Helpers/CloudFormationHelper.cs deleted file mode 100644 index 1fc1a1ee6..000000000 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Helpers/CloudFormationHelper.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -using Amazon.CloudFormation; -using Amazon.CloudFormation.Model; - -namespace Amazon.Lambda.TestTool.IntegrationTests.Helpers -{ - public class CloudFormationHelper - { - private readonly IAmazonCloudFormation _cloudFormationClient; - - public CloudFormationHelper(IAmazonCloudFormation cloudFormationClient) - { - _cloudFormationClient = cloudFormationClient; - } - - public async Task CreateStackAsync(string stackName, string templateBody) - { - var response = await _cloudFormationClient.CreateStackAsync(new CreateStackRequest - { - StackName = stackName, - TemplateBody = templateBody, - Capabilities = new List { "CAPABILITY_IAM" }, - Tags = new List - { - new Tag { Key = "aws-tests", Value = typeof(CloudFormationHelper).FullName }, - new Tag { Key = "aws-repo", Value = "aws-lambda-dotnet" } - } - }); - return response.StackId; - } - - public async Task GetStackStatusAsync(string stackName) - { - var response = await _cloudFormationClient.DescribeStacksAsync(new DescribeStacksRequest { StackName = stackName }); - return response.Stacks[0].StackStatus; - } - - public async Task DeleteStackAsync(string stackName) - { - await _cloudFormationClient.DeleteStackAsync(new DeleteStackRequest { StackName = stackName }); - } - - public async Task GetOutputValueAsync(string stackName, string outputKey) - { - var response = await _cloudFormationClient.DescribeStacksAsync(new DescribeStacksRequest { StackName = stackName }); - return response.Stacks[0].Outputs.FirstOrDefault(o => o.OutputKey == outputKey)?.OutputValue ?? string.Empty; - } - } -} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Helpers/RetryHelper.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Helpers/RetryHelper.cs deleted file mode 100644 index f6e5e18e4..000000000 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Helpers/RetryHelper.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -namespace Amazon.Lambda.TestTool.IntegrationTests.Helpers -{ - public class RetryHelper - { - public static async Task RetryOperation(Func> operation, int maxRetries = 3, int delayMilliseconds = 20000) - { - for (int i = 0; i < maxRetries; i++) - { - try - { - return await operation(); - } - catch (Exception ex) when (i < maxRetries - 1) - { - Console.WriteLine($"Attempt {i + 1} failed: {ex.Message}. Retrying in {delayMilliseconds}ms..."); - await Task.Delay(delayMilliseconds); - } - } - - // If we've exhausted all retries, run one last time and let any exception propagate - return await operation(); - } - } -} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/HttpContextExtensionsTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/HttpContextExtensionsTests.cs deleted file mode 100644 index de7a510d1..000000000 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/HttpContextExtensionsTests.cs +++ /dev/null @@ -1,418 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -using System.Net; -using System.Text.Json; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.TestTool.Extensions; -using Amazon.Lambda.TestTool.Models; -using Amazon.Lambda.TestTool.Tests.Common; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; -using Xunit; -using static Amazon.Lambda.TestTool.Tests.Common.HttpContextTestCases; -using System.Net.Http.Headers; - -namespace Amazon.Lambda.TestTool.IntegrationTests -{ - [Collection("ApiGateway Integration Tests")] - public class HttpContextExtensionsTests - { - private readonly ApiGatewayIntegrationTestFixture _fixture; - - public HttpContextExtensionsTests(ApiGatewayIntegrationTestFixture fixture) - { - _fixture = fixture; - } - - [Theory] - [MemberData(nameof(HttpContextTestCases.V1TestCases), MemberType = typeof(HttpContextTestCases))] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] - public Task IntegrationTest_APIGatewayV1_REST(string testName, HttpContextTestCase testCase) - { - return RunApiGatewayTest(testCase, new ApiGatewayTestConfig - { - RouteId = TestRoutes.Ids.ReturnFullEvent, - GatewayType = ApiGatewayType.Rest - }); - } - - [Theory] - [MemberData(nameof(HttpContextTestCases.V1TestCases), MemberType = typeof(HttpContextTestCases))] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] - public Task IntegrationTest_APIGatewayV1_HTTP(string testName, HttpContextTestCase testCase) - { - return RunApiGatewayTest(testCase, new ApiGatewayTestConfig - { - RouteId = TestRoutes.Ids.ReturnFullEvent, - GatewayType = ApiGatewayType.HttpV1 - }); - } - - [Theory] - [MemberData(nameof(HttpContextTestCases.V2TestCases), MemberType = typeof(HttpContextTestCases))] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] - public Task IntegrationTest_APIGatewayV2(string testName, HttpContextTestCase testCase) - { - return RunApiGatewayTest(testCase, new ApiGatewayTestConfig - { - RouteId = TestRoutes.Ids.ReturnFullEvent, - GatewayType = ApiGatewayType.HttpV2 - }); - } - - [Fact] - public Task BinaryContentHttpV1() - { - var httpContext = CreateHttpContext("POST", "/test3/api/users/123/avatar", - new Dictionary { { "Content-Type", "application/octet-stream" } }, - body: new byte[] { 1, 2, 3, 4, 5 }); - - var config = new ApiGatewayRouteConfig - { - LambdaResourceName = "UploadAvatarFunction", - Endpoint = "/test3/api/users/{userId}/avatar", - HttpMethod = "POST", - Path = "/test3/api/users/{userId}/avatar" - }; - - var testCase = new HttpContextTestCase - { - HttpContext = httpContext, - ApiGatewayRouteConfig = config, - Assertions = (actualRequest, emulatorMode) => - { - var typedRequest = (APIGatewayProxyRequest)actualRequest; - Assert.True(typedRequest.IsBase64Encoded); - Assert.Equal(Convert.ToBase64String(new byte[] { 1, 2, 3, 4, 5 }), typedRequest.Body); - Assert.Equal("123", typedRequest.PathParameters["userId"]); - Assert.Equal("/test3/api/users/{userId}/avatar", typedRequest.Resource); - Assert.Equal("POST", typedRequest.HttpMethod); - } - }; - - return RunApiGatewayTest( - testCase, - new ApiGatewayTestConfig - { - RouteId = TestRoutes.Ids.ReturnFullEvent, - GatewayType = ApiGatewayType.HttpV1 - }); - } - - [Fact] - public Task BinaryContentRest() - { - var httpContext = CreateHttpContext("POST", "/test4/api/users/123/avatar", - new Dictionary { { "Content-Type", "application/octet-stream" } }, - body: new byte[] { 1, 2, 3, 4, 5 }); - - var config = new ApiGatewayRouteConfig - { - Path = "/test4/api/users/{userId}/avatar", - Endpoint = "/test4/api/users/{userId}/avatar", - HttpMethod = "POST", - LambdaResourceName = "ReturnFullEventLambdaFunction" - }; - - var testCase = new HttpContextTestCase - { - HttpContext = httpContext, - ApiGatewayRouteConfig = config, - Assertions = (actualRequest, emulatorMode) => - { - var typedRequest = (APIGatewayProxyRequest)actualRequest; - Assert.True(typedRequest.IsBase64Encoded); - Assert.Equal(Convert.ToBase64String(new byte[] { 1, 2, 3, 4, 5 }), typedRequest.Body); - Assert.Equal("123", typedRequest.PathParameters["userId"]); - Assert.Equal("/test4/api/users/{userId}/avatar", typedRequest.Resource); - Assert.Equal("POST", typedRequest.HttpMethod); - } - }; - - return RunApiGatewayTest(testCase, new ApiGatewayTestConfig - { - RouteId = TestRoutes.Ids.ReturnFullEvent, - GatewayType = ApiGatewayType.RestWithBinarySupport - }); - } - - private async Task RunApiGatewayTest(HttpContextTestCase testCase, ApiGatewayTestConfig config) where T : class - { - - var baseUrl = _fixture.GetAppropriateBaseUrl(config.GatewayType); - var apiId = _fixture.GetAppropriateApiId(config.GatewayType); - var emulatorMode = ApiGatewayIntegrationTestFixture.GetEmulatorMode(config.GatewayType); - - - Func> converter = config.GatewayType switch - { - ApiGatewayType.Rest or ApiGatewayType.RestWithBinarySupport => - async (context, cfg) => (T)(object)await context.ToApiGatewayRequest(cfg, ApiGatewayEmulatorMode.Rest), - ApiGatewayType.HttpV1 => - async (context, cfg) => (T)(object)await context.ToApiGatewayRequest(cfg, ApiGatewayEmulatorMode.HttpV1), - ApiGatewayType.HttpV2 => - async (context, cfg) => (T)(object)await context.ToApiGatewayHttpV2Request(cfg), - _ => throw new ArgumentException($"Unsupported gateway type: {config.GatewayType}") - }; - - await RunApiGatewayTestInternal( - testCase, - baseUrl, - apiId, - config.RouteId, - converter, - emulatorMode); - } - - private async Task RunApiGatewayTestInternal( - HttpContextTestCase testCase, - string baseUrl, - string apiId, - string routeId, - Func> toApiGatewayRequest, - ApiGatewayEmulatorMode emulatorMode) - where T : class - { - // Get the route config which has the path prefix - var routeConfig = TestRoutes.GetDefaultRoutes(_fixture)[routeId]; - - // Create the route for this specific test - if (emulatorMode == ApiGatewayEmulatorMode.Rest) - { - await _fixture.ApiGatewayHelper.AddRouteToRestApi( - apiId, - routeConfig.LambdaFunctionArn, - testCase.ApiGatewayRouteConfig.Path, - testCase.ApiGatewayRouteConfig.HttpMethod - ); - } - else // HTTP API v1 or v2 - { - await _fixture.ApiGatewayHelper.AddRouteToHttpApi( - apiId, - routeConfig.LambdaFunctionArn, - emulatorMode == ApiGatewayEmulatorMode.HttpV2 ? "2.0" : "1.0", - testCase.ApiGatewayRouteConfig.Path, - testCase.ApiGatewayRouteConfig.HttpMethod - ); - } - - var httpClient = new HttpClient(); - var actualPath = ResolveActualPath(testCase.ApiGatewayRouteConfig.Path, testCase.HttpContext.Request.Path.Value ?? ""); - - var stageName = emulatorMode == ApiGatewayEmulatorMode.Rest ? "/test" : ""; - var fullUrl = baseUrl.TrimEnd('/') + actualPath + testCase.HttpContext.Request.QueryString.Value; - - // Wait for the API to be available - await _fixture.ApiGatewayHelper.WaitForApiAvailability(apiId, fullUrl, emulatorMode != ApiGatewayEmulatorMode.Rest); - - // Create and send the HTTP request - var httpRequest = CreateHttpRequestMessage(testCase.HttpContext, fullUrl); - - // Send request and get response - var response = await httpClient.SendAsync(httpRequest); - var responseContent = await response.Content.ReadAsStringAsync(); - - // Verify response - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString()); - - var actualApiGatewayRequest = JsonSerializer.Deserialize(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - - var expectedApiGatewayRequest = await toApiGatewayRequest(testCase.HttpContext, testCase.ApiGatewayRouteConfig); - - CompareApiGatewayRequests(expectedApiGatewayRequest, actualApiGatewayRequest); - - testCase.Assertions(actualApiGatewayRequest!, emulatorMode); - await Task.Delay(1000); // Small delay between requests - } - - private string ResolveActualPath(string routeWithPlaceholders, string actualPath) - { - var routeParts = routeWithPlaceholders.Split('/'); - var actualParts = actualPath.Split('/'); - - if (routeParts.Length != actualParts.Length) - { - throw new ArgumentException("Route and actual path have different number of segments"); - } - - var resolvedParts = new List(); - for (int i = 0; i < routeParts.Length; i++) - { - if (routeParts[i].StartsWith("{") && routeParts[i].EndsWith("}")) - { - resolvedParts.Add(actualParts[i]); - } - else - { - resolvedParts.Add(routeParts[i]); - } - } - - return string.Join("/", resolvedParts); - } - - private void CompareApiGatewayRequests(T expected, T actual) where T : class? - { - if (expected is APIGatewayProxyRequest v1Expected && actual is APIGatewayProxyRequest v1Actual) - { - CompareApiGatewayV1Requests(v1Expected, v1Actual); - } - else if (expected is APIGatewayHttpApiV2ProxyRequest v2Expected && actual is APIGatewayHttpApiV2ProxyRequest v2Actual) - { - CompareApiGatewayV2Requests(v2Expected, v2Actual); - } - else - { - throw new ArgumentException("Unsupported type for comparison"); - } - } - - private void CompareApiGatewayV1Requests(APIGatewayProxyRequest expected, APIGatewayProxyRequest actual) - { - Assert.Equal(expected.HttpMethod, actual.HttpMethod); - Assert.Equal(expected.Path, actual.Path); - Assert.Equal(expected.Resource, actual.Resource); - Assert.Equal(expected.Body, actual.Body); - Assert.Equal(expected.IsBase64Encoded, actual.IsBase64Encoded); - - CompareHeaders(expected.Headers, actual.Headers); - CompareMultiValueHeaders(expected.MultiValueHeaders, actual.MultiValueHeaders); - CompareDictionaries(expected.QueryStringParameters, actual.QueryStringParameters); - CompareDictionaries(expected.PathParameters, actual.PathParameters); - CompareDictionaries(expected.StageVariables, actual.StageVariables); - CompareDictionaries(expected.MultiValueQueryStringParameters, actual.MultiValueQueryStringParameters); - } - - private void CompareApiGatewayV2Requests(APIGatewayHttpApiV2ProxyRequest expected, APIGatewayHttpApiV2ProxyRequest actual) - { - Assert.Equal(expected.RouteKey, actual.RouteKey); - Assert.Equal(expected.RawPath, actual.RawPath); - Assert.Equal(expected.RawQueryString, actual.RawQueryString); - Assert.Equal(expected.Body, actual.Body); - Assert.Equal(expected.IsBase64Encoded, actual.IsBase64Encoded); - Assert.Equal(expected.Version, actual.Version); - - CompareHeaders(expected.Headers, actual.Headers); - CompareDictionaries(expected.QueryStringParameters, actual.QueryStringParameters); - CompareDictionaries(expected.PathParameters, actual.PathParameters); - CompareStringArrays(expected.Cookies, actual.Cookies); - - CompareRequestContexts(expected.RequestContext, actual.RequestContext); - } - - private void CompareHeaders(IDictionary expected, IDictionary actual) - { - var expectedFiltered = FilterHeaders(expected); - var actualFiltered = FilterHeaders(actual); - - Assert.Equal(expectedFiltered.Count, actualFiltered.Count); - - foreach (var kvp in expectedFiltered) - { - Assert.True(actualFiltered.Keys.Any(k => string.Equals(k, kvp.Key, StringComparison.OrdinalIgnoreCase)), - $"Actual headers do not contain key: {kvp.Key}"); - - var actualValue = actualFiltered.First(pair => string.Equals(pair.Key, kvp.Key, StringComparison.OrdinalIgnoreCase)).Value; - Assert.Equal(kvp.Value, actualValue); - } - } - - private void CompareMultiValueHeaders(IDictionary> expected, IDictionary> actual) - { - var expectedFiltered = FilterHeaders(expected); - var actualFiltered = FilterHeaders(actual); - - Assert.Equal(expectedFiltered.Count, actualFiltered.Count); - - foreach (var kvp in expectedFiltered) - { - Assert.True(actualFiltered.Keys.Any(k => string.Equals(k, kvp.Key, StringComparison.OrdinalIgnoreCase)), - $"Actual headers do not contain key: {kvp.Key}"); - - var actualValue = actualFiltered.First(pair => string.Equals(pair.Key, kvp.Key, StringComparison.OrdinalIgnoreCase)).Value; - Assert.Equal(kvp.Value, actualValue); - } - } - - private IDictionary FilterHeaders(IDictionary headers) where TKey : notnull - { - return headers.Where(kvp => - !(kvp.Key.ToString()!.StartsWith("x-forwarded-", StringComparison.OrdinalIgnoreCase) || // ignore these for now - kvp.Key.ToString()!.StartsWith("cloudfront-", StringComparison.OrdinalIgnoreCase) || // ignore these for now - kvp.Key.ToString()!.StartsWith("via-", StringComparison.OrdinalIgnoreCase) || // ignore these for now - kvp.Key.ToString()!.Equals("x-amzn-trace-id", StringComparison.OrdinalIgnoreCase) || // this is dynamic so ignoring for now - kvp.Key.ToString()!.Equals("cookie", StringComparison.OrdinalIgnoreCase) || // TODO may have to have api gateway v2 not set this in headers - kvp.Key.ToString()!.Equals("host", StringComparison.OrdinalIgnoreCase))) // TODO we may want to set this - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - } - - private void CompareDictionaries(IDictionary? expected, IDictionary? actual) - { - if (expected == null && actual == null) return; - if (expected == null && actual != null) Assert.Fail(); - if (expected != null && actual == null) Assert.Fail(); - Assert.Equal(expected!.Count, actual!.Count); - - foreach (var kvp in expected) - { - Assert.True(actual.ContainsKey(kvp.Key), $"Actual does not contain key: {kvp.Key}"); - Assert.Equal(kvp.Value, actual[kvp.Key]); - } - } - - private void CompareStringArrays(string[] expected, string[] actual) - { - Assert.Equal(expected?.Length, actual?.Length); - if (expected != null) - { - Assert.Equal(expected.OrderBy(x => x), actual?.OrderBy(x => x)); - } - } - - private void CompareRequestContexts(APIGatewayHttpApiV2ProxyRequest.ProxyRequestContext expected, APIGatewayHttpApiV2ProxyRequest.ProxyRequestContext actual) - { - Assert.Equal(expected.RouteKey, actual.RouteKey); - - Assert.Equal(expected.Http.Method, actual.Http.Method); - Assert.Equal(expected.Http.Path, actual.Http.Path); - Assert.Equal(expected.Http.Protocol, actual.Http.Protocol); - Assert.Equal(expected.Http.UserAgent, actual.Http.UserAgent); - } - - private HttpRequestMessage CreateHttpRequestMessage(HttpContext context, string fullUrl) - { - var request = context.Request; - var httpRequest = new HttpRequestMessage(new HttpMethod(request.Method), fullUrl); - - foreach (var header in request.Headers) - { - httpRequest.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); - } - - if (request.ContentLength > 0) - { - var bodyStream = new MemoryStream(); - request.Body.CopyTo(bodyStream); - bodyStream.Position = 0; - httpRequest.Content = new StreamContent(bodyStream); - - // Set Content-Type if present in the original request - if (request.ContentType != null) - { - httpRequest.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(request.ContentType); - } - } - else - { - httpRequest.Content = new StringContent(string.Empty); - } - - httpRequest.Version = HttpVersion.Version11; - - return httpRequest; - } - } -} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/InvokeResponseExtensionsIntegrationTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/InvokeResponseExtensionsIntegrationTests.cs deleted file mode 100644 index e49cf6141..000000000 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/InvokeResponseExtensionsIntegrationTests.cs +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Model; -using Amazon.Lambda.TestTool.Models; -using System.Text; -using System.Text.Json; -using Amazon.Lambda.TestTool.Extensions; -using Xunit; - -namespace Amazon.Lambda.TestTool.IntegrationTests; - -/// -/// Integration tests for InvokeResponseExtensions. -/// -/// -/// Developer's Note: -/// These tests don't have direct access to the intermediate result of the Lambda to API Gateway conversion. -/// Instead, we test the final API Gateway response object to ensure our conversion methods produce results -/// that match the actual API Gateway behavior. This approach allows us to verify the correctness of our -/// conversion methods within the constraints of not having access to AWS's internal conversion process. -/// -[Collection("ApiGateway Integration Tests")] -public class InvokeResponseExtensionsIntegrationTests -{ - private readonly ApiGatewayIntegrationTestFixture _fixture; - - public InvokeResponseExtensionsIntegrationTests(ApiGatewayIntegrationTestFixture fixture) - { - _fixture = fixture; - } - - private ApiGatewayType GetGatewayType(ApiGatewayEmulatorMode emulatorMode) - { - return emulatorMode switch - { - ApiGatewayEmulatorMode.Rest => ApiGatewayType.Rest, - ApiGatewayEmulatorMode.HttpV1 => ApiGatewayType.HttpV1, - ApiGatewayEmulatorMode.HttpV2 => ApiGatewayType.HttpV2, - _ => throw new ArgumentException($"Unsupported emulator mode: {emulatorMode}") - }; - } - - [Theory] - [InlineData(ApiGatewayEmulatorMode.Rest)] - [InlineData(ApiGatewayEmulatorMode.HttpV1)] - public async Task ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversion(ApiGatewayEmulatorMode emulatorMode) - { - // Arrange - var testResponse = new APIGatewayProxyResponse - { - StatusCode = 200, - Body = JsonSerializer.Serialize(new { message = "Hello, World!" }), - Headers = new Dictionary { { "Content-Type", "application/json" } } - }; - var invokeResponse = new InvokeResponse - { - Payload = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testResponse))) - }; - - // Act - var convertedResponse = invokeResponse.ToApiGatewayProxyResponse(emulatorMode); - - // Assert - var baseUrl = _fixture.GetAppropriateBaseUrl(GetGatewayType(emulatorMode)); - var url = _fixture.GetRouteUrl(baseUrl, TestRoutes.Ids.ParseAndReturnBody); - var (actualResponse, httpTestResponse) = await _fixture.ApiGatewayTestHelper.ExecuteTestRequest(convertedResponse, url, emulatorMode); - await _fixture.ApiGatewayTestHelper.AssertResponsesEqual(actualResponse, httpTestResponse); - } - - [Fact] - public async Task ToApiGatewayHttpApiV2ProxyResponse_ValidResponse_MatchesDirectConversion() - { - // Arrange - var testResponse = new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = 200, - Body = JsonSerializer.Serialize(new { message = "Hello, World!" }), - Headers = new Dictionary { { "Content-Type", "application/json" } } - }; - var invokeResponse = new InvokeResponse - { - Payload = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testResponse))) - }; - - // Act - var convertedResponse = invokeResponse.ToApiGatewayHttpApiV2ProxyResponse(); - - // Assert - var baseUrl = _fixture.GetAppropriateBaseUrl(ApiGatewayType.HttpV2); - var url = _fixture.GetRouteUrl(baseUrl, TestRoutes.Ids.ParseAndReturnBody); - var (actualResponse, httpTestResponse) = await _fixture.ApiGatewayTestHelper.ExecuteTestRequest(convertedResponse, url); - await _fixture.ApiGatewayTestHelper.AssertResponsesEqual(actualResponse, httpTestResponse); - } - - [Theory] - [InlineData(ApiGatewayEmulatorMode.Rest, 502, "Internal server error")] - [InlineData(ApiGatewayEmulatorMode.HttpV1, 500, "Internal Server Error")] - public async Task ToApiGatewayProxyResponse_InvalidJson_ReturnsErrorResponse(ApiGatewayEmulatorMode emulatorMode, int expectedStatusCode, string expectedErrorMessage) - { - // Arrange - var invokeResponse = new InvokeResponse - { - Payload = new MemoryStream(Encoding.UTF8.GetBytes("Not a valid proxy response object")) - }; - - // Act - var convertedResponse = invokeResponse.ToApiGatewayProxyResponse(emulatorMode); - - // Assert - Assert.Equal(expectedStatusCode, convertedResponse.StatusCode); - Assert.Contains(expectedErrorMessage, convertedResponse.Body); - - var baseUrl = _fixture.GetAppropriateBaseUrl(GetGatewayType(emulatorMode)); - var url = _fixture.GetRouteUrl(baseUrl, TestRoutes.Ids.ParseAndReturnBody); - var (actualResponse, _) = await _fixture.ApiGatewayTestHelper.ExecuteTestRequest(convertedResponse, url, emulatorMode); - Assert.Equal(expectedStatusCode, (int)actualResponse.StatusCode); - var content = await actualResponse.Content.ReadAsStringAsync(); - Assert.Contains(expectedErrorMessage, content); - } - - /// - /// Tests various Lambda return values to verify API Gateway's handling of responses. - /// - /// The payload returned by the Lambda function. - /// - /// This test demonstrates a discrepancy between the official AWS documentation - /// and the actual observed behavior of API Gateway HTTP API v2 with Lambda - /// proxy integrations (payload format version 2.0). - /// - /// Official documentation states: - /// "If your Lambda function returns valid JSON and doesn't return a statusCode, - /// API Gateway assumes a 200 status code and treats the entire response as the body." - /// - /// However, the observed behavior (which this test verifies) is: - /// - API Gateway does not validate whether the returned data is valid JSON. - /// - Any response from the Lambda function that is not a properly formatted - /// API Gateway response object (i.e., an object with a 'statusCode' property) - /// is treated as a raw body in a 200 OK response. - /// - This includes valid JSON objects without a statusCode, JSON arrays, - /// primitive values, and invalid JSON strings. - /// - /// This test ensures that our ToApiGatewayHttpApiV2ProxyResponse method - /// correctly replicates this observed behavior, rather than the documented behavior. - /// - [Theory] - [InlineData("{\"name\": \"John Doe\", \"age\":", "{\"name\": \"John Doe\", \"age\":")] // Invalid JSON (partial object) - [InlineData("{\"name\": \"John Doe\", \"age\": 30}", "{\"name\": \"John Doe\", \"age\": 30}")] // Valid JSON object without statusCode - [InlineData("[1, 2, 3, 4, 5]", "[1, 2, 3, 4, 5]")] // JSON array - [InlineData("Hello, World!", "Hello, World!")] // String primitive - [InlineData("42", "42")] // Number primitive - [InlineData("true", "true")] // Boolean primitive - [InlineData("\"test\"", "test")] // JSON string that should be unescaped - [InlineData("\"Hello, World!\"", "Hello, World!")] // JSON string with spaces - [InlineData("\"\"", "")] // Empty JSON string - [InlineData("\"Special \\\"quoted\\\" text\"", "Special \"quoted\" text")] // JSON string with escaped quotes - public async Task ToApiGatewayHttpApiV2ProxyResponse_VariousPayloads_ReturnsAsRawBody(string inputPayload, string expectedResponsePayload) - { - // Arrange - var invokeResponse = new InvokeResponse - { - Payload = new MemoryStream(Encoding.UTF8.GetBytes(inputPayload)) - }; - - // Act - var actualConvertedResponse = invokeResponse.ToApiGatewayHttpApiV2ProxyResponse(); - - // Assert - Assert.Equal(200, actualConvertedResponse.StatusCode); - Assert.Equal(expectedResponsePayload, actualConvertedResponse.Body); - Assert.Equal("application/json", actualConvertedResponse.Headers["Content-Type"]); - - // Verify against actual API Gateway behavior - var baseUrl = _fixture.GetAppropriateBaseUrl(ApiGatewayType.HttpV2); - var url = _fixture.GetRouteUrl(baseUrl, TestRoutes.Ids.ParseAndReturnBody); - var (actualResponse, httpTestResponse) = await _fixture.ApiGatewayTestHelper.ExecuteTestRequest(actualConvertedResponse, url); - await _fixture.ApiGatewayTestHelper.AssertResponsesEqual(actualResponse, httpTestResponse); - - // Additional checks for API Gateway specific behavior - Assert.Equal(200, (int)actualResponse.StatusCode); - var actualContent = await actualResponse.Content.ReadAsStringAsync(); - Assert.Equal(expectedResponsePayload, actualContent); - Assert.Equal("application/json", actualResponse.Content.Headers.ContentType?.ToString()); - } - - [Fact] - public async Task ToApiGatewayHttpApiV2ProxyResponse_StatusCodeAsFloat_ReturnsInternalServerError() - { - // Arrange - var responsePayload = "{\"statusCode\": 200.5, \"body\": \"Hello\", \"headers\": {\"Content-Type\": \"text/plain\"}}"; - var invokeResponse = new InvokeResponse - { - Payload = new MemoryStream(Encoding.UTF8.GetBytes(responsePayload)) - }; - - // Act - var convertedResponse = invokeResponse.ToApiGatewayHttpApiV2ProxyResponse(); - - // Assert - Assert.Equal(500, convertedResponse.StatusCode); - Assert.Equal("{\"message\":\"Internal Server Error\"}", convertedResponse.Body); - Assert.Equal("application/json", convertedResponse.Headers["Content-Type"]); - - // Verify against actual API Gateway behavior - var baseUrl = _fixture.GetAppropriateBaseUrl(ApiGatewayType.HttpV2); - var url = _fixture.GetRouteUrl(baseUrl, TestRoutes.Ids.ParseAndReturnBody); - var (actualResponse, httpTestResponse) = await _fixture.ApiGatewayTestHelper.ExecuteTestRequest(convertedResponse, url); - await _fixture.ApiGatewayTestHelper.AssertResponsesEqual(actualResponse, httpTestResponse); - - // Additional checks for API Gateway specific behavior - Assert.Equal(500, (int)actualResponse.StatusCode); - var content = await actualResponse.Content.ReadAsStringAsync(); - Assert.Equal("{\"message\":\"Internal Server Error\"}", content); - Assert.Equal("application/json", actualResponse.Content.Headers.ContentType?.ToString()); - } -} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/TestRoutes.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/TestRoutes.cs deleted file mode 100644 index cf888d22e..000000000 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/TestRoutes.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -namespace Amazon.Lambda.TestTool.IntegrationTests -{ - public class TestRouteConfig - { - public required string Path { get; set; } - public required string HttpMethod { get; set; } - public required string LambdaFunctionArn { get; set; } - public required string Description { get; set; } - } - - public static class TestRoutes - { - public static class Ids - { - public const string ParseAndReturnBody = "ParseAndReturnBody"; - public const string ReturnRawBody = "ReturnRawBody"; - public const string ReturnFullEvent = "ReturnFullEvent"; - public const string DecodeParseBinary = "BinaryMediaType"; - } - - public static class Paths - { - public const string ParseAndReturnBody = "/parse-and-return"; - public const string ReturnRawBody = "/return-raw"; - public const string ReturnFullEvent = "/return-full"; - public const string BinaryMediaType = "/binary"; - } - - public static Dictionary GetDefaultRoutes(ApiGatewayIntegrationTestFixture fixture) - { - return new Dictionary - { - [Ids.ParseAndReturnBody] = new TestRouteConfig - { - Path = Paths.ParseAndReturnBody, - HttpMethod = "POST", - LambdaFunctionArn = fixture.ParseAndReturnBodyLambdaFunctionArn, - Description = "Test parsing and returning body", - }, - [Ids.ReturnRawBody] = new TestRouteConfig - { - Path = Paths.ReturnRawBody, - HttpMethod = "POST", - LambdaFunctionArn = fixture.ReturnRawBodyLambdaFunctionArn, - Description = "Test returning raw body", - }, - [Ids.ReturnFullEvent] = new TestRouteConfig - { - Path = Paths.ReturnFullEvent, - HttpMethod = "POST", - LambdaFunctionArn = fixture.ReturnFullEventLambdaFunctionArn, - Description = "Test returning full event", - }, - [Ids.DecodeParseBinary] = new TestRouteConfig - { - Path = Paths.BinaryMediaType, - HttpMethod = "POST", - LambdaFunctionArn = fixture.ReturnDecodedParseBinLambdaFunctionArn, - Description = "Test binary media type handling", - } - }; - } - } -} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/cloudformation-template-apigateway.yaml b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/cloudformation-template-apigateway.yaml deleted file mode 100644 index df37f013b..000000000 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/cloudformation-template-apigateway.yaml +++ /dev/null @@ -1,288 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Description: 'CloudFormation template for API Gateway and Lambda integration tests' - -Resources: - # Lambda Functions - ParseAndReturnBodyLambdaFunction: - Type: 'AWS::Lambda::Function' - Properties: - FunctionName: !Sub '${AWS::StackName}-ParseAndReturnBodyFunction' - Handler: index.handler - Role: !GetAtt LambdaExecutionRole.Arn - Code: - ZipFile: | - exports.handler = async (event) => { - return JSON.parse(event.body); - }; - Runtime: nodejs20.x - - ReturnDecodedParseBinLambdaFunction: - Type: 'AWS::Lambda::Function' - Properties: - FunctionName: !Sub '${AWS::StackName}-ReturnDecodedParseBinFunction' - Handler: index.handler - Role: !GetAtt LambdaExecutionRole.Arn - Code: - ZipFile: | - exports.handler = async (event) => { - const decodedBody = atob(event.body); - const parsedBody = JSON.parse(decodedBody.toString('utf8')); - return parsedBody; - }; - Runtime: nodejs20.x - - ReturnRawBodyLambdaFunction: - Type: 'AWS::Lambda::Function' - Properties: - FunctionName: !Sub '${AWS::StackName}-ReturnRawBodyFunction' - Handler: index.handler - Role: !GetAtt LambdaExecutionRole.Arn - Code: - ZipFile: | - exports.handler = async (event, context, callback) => { - console.log(event); - callback(null, event.body); - }; - Runtime: nodejs20.x - - ReturnFullEventLambdaFunction: - Type: 'AWS::Lambda::Function' - Properties: - FunctionName: !Sub '${AWS::StackName}-ReturnFullEventFunction' - Handler: index.handler - Role: !GetAtt LambdaExecutionRole.Arn - Code: - ZipFile: | - exports.handler = async (event) => { - return { - statusCode: 200, - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(event) - }; - }; - Runtime: nodejs20.x - - # IAM Role for Lambda Functions - LambdaExecutionRole: - Type: 'AWS::IAM::Role' - Properties: - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Principal: - Service: lambda.amazonaws.com - Action: 'sts:AssumeRole' - ManagedPolicyArns: - - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' - - # Main REST API Gateway (without binary media types) - MainRestApi: - Type: 'AWS::ApiGateway::RestApi' - Properties: - Name: !Sub '${AWS::StackName}-MainRestAPI' - EndpointConfiguration: - Types: - - REGIONAL - - # Main HTTP API v1 - MainHttpApiV1: - Type: 'AWS::ApiGatewayV2::Api' - Properties: - Name: !Sub '${AWS::StackName}-MainHttpAPIv1' - ProtocolType: HTTP - - # Main HTTP API v2 - MainHttpApiV2: - Type: 'AWS::ApiGatewayV2::Api' - Properties: - Name: !Sub '${AWS::StackName}-MainHttpAPIv2' - ProtocolType: HTTP - - # Special REST API for binary media type tests - BinaryMediaTypeRestApi: - Type: 'AWS::ApiGateway::RestApi' - Properties: - Name: !Sub '${AWS::StackName}-BinaryMediaTypeRestApi' - EndpointConfiguration: - Types: - - REGIONAL - BinaryMediaTypes: - - '*/*' - - # Add a dummy root method for the initial deployment - MainRestApiRootMethod: - Type: 'AWS::ApiGateway::Method' - Properties: - HttpMethod: GET - ResourceId: !GetAtt MainRestApi.RootResourceId - RestApiId: !Ref MainRestApi - AuthorizationType: NONE - Integration: - Type: AWS_PROXY - IntegrationHttpMethod: POST - Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ReturnFullEventLambdaFunction.Arn}/invocations' - - MainRestApiDeployment: - Type: 'AWS::ApiGateway::Deployment' - DependsOn: MainRestApiRootMethod # Add explicit dependency - Properties: - RestApiId: !Ref MainRestApi - StageName: 'test' - - MainHttpV1Stage: - Type: 'AWS::ApiGatewayV2::Stage' - Properties: - ApiId: !Ref MainHttpApiV1 - StageName: '$default' - AutoDeploy: true - - MainHttpV2Stage: - Type: 'AWS::ApiGatewayV2::Stage' - Properties: - ApiId: !Ref MainHttpApiV2 - StageName: '$default' - AutoDeploy: true - - # Lambda Permissions for REST API - LambdaPermissionMainRestApi: - Type: 'AWS::Lambda::Permission' - Properties: - Action: 'lambda:InvokeFunction' - FunctionName: !GetAtt ParseAndReturnBodyLambdaFunction.Arn - Principal: apigateway.amazonaws.com - SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MainRestApi}/*' - - LambdaPermissionMainRestApi2: - Type: 'AWS::Lambda::Permission' - Properties: - Action: 'lambda:InvokeFunction' - FunctionName: !GetAtt ReturnRawBodyLambdaFunction.Arn - Principal: apigateway.amazonaws.com - SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MainRestApi}/*' - - LambdaPermissionMainRestApi3: - Type: 'AWS::Lambda::Permission' - Properties: - Action: 'lambda:InvokeFunction' - FunctionName: !GetAtt ReturnFullEventLambdaFunction.Arn - Principal: apigateway.amazonaws.com - SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MainRestApi}/*' - - # Lambda Permissions for HTTP API v1 - LambdaPermissionMainHttpApiV1: - Type: 'AWS::Lambda::Permission' - Properties: - Action: 'lambda:InvokeFunction' - FunctionName: !GetAtt ParseAndReturnBodyLambdaFunction.Arn - Principal: apigateway.amazonaws.com - SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MainHttpApiV1}/*' - - LambdaPermissionMainHttpApiV12: - Type: 'AWS::Lambda::Permission' - Properties: - Action: 'lambda:InvokeFunction' - FunctionName: !GetAtt ReturnRawBodyLambdaFunction.Arn - Principal: apigateway.amazonaws.com - SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MainHttpApiV1}/*' - - LambdaPermissionMainHttpApiV13: - Type: 'AWS::Lambda::Permission' - Properties: - Action: 'lambda:InvokeFunction' - FunctionName: !GetAtt ReturnFullEventLambdaFunction.Arn - Principal: apigateway.amazonaws.com - SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MainHttpApiV1}/*' - - # Lambda Permissions for HTTP API v2 - LambdaPermissionMainHttpApiV2: - Type: 'AWS::Lambda::Permission' - Properties: - Action: 'lambda:InvokeFunction' - FunctionName: !GetAtt ParseAndReturnBodyLambdaFunction.Arn - Principal: apigateway.amazonaws.com - SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MainHttpApiV2}/*' - - LambdaPermissionMainHttpApiV22: - Type: 'AWS::Lambda::Permission' - Properties: - Action: 'lambda:InvokeFunction' - FunctionName: !GetAtt ReturnRawBodyLambdaFunction.Arn - Principal: apigateway.amazonaws.com - SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MainHttpApiV2}/*' - - LambdaPermissionMainHttpApiV23: - Type: 'AWS::Lambda::Permission' - Properties: - Action: 'lambda:InvokeFunction' - FunctionName: !GetAtt ReturnFullEventLambdaFunction.Arn - Principal: apigateway.amazonaws.com - SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MainHttpApiV2}/*' - - # Lambda Permissions for Binary Media Type REST API - LambdaPermissionBinaryMediaTypeRestApi: - Type: 'AWS::Lambda::Permission' - Properties: - Action: 'lambda:InvokeFunction' - FunctionName: !GetAtt ReturnDecodedParseBinLambdaFunction.Arn - Principal: apigateway.amazonaws.com - SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${BinaryMediaTypeRestApi}/*' - - LambdaPermissionBinaryMediaTypeRestApi2: - Type: 'AWS::Lambda::Permission' - Properties: - Action: 'lambda:InvokeFunction' - FunctionName: !GetAtt ReturnFullEventLambdaFunction.Arn - Principal: apigateway.amazonaws.com - SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${BinaryMediaTypeRestApi}/*' - -Outputs: - MainRestApiId: - Description: 'ID of the Main REST API' - Value: !Ref MainRestApi - - MainRestApiBaseUrl: - Description: 'Base URL of the Main REST API' - Value: !Sub 'https://${MainRestApi}.execute-api.${AWS::Region}.amazonaws.com/test' - - MainHttpApiV1Id: - Description: 'ID of the Main HTTP API v1' - Value: !Ref MainHttpApiV1 - - MainHttpApiV1BaseUrl: - Description: 'Base URL of the Main HTTP API v1' - Value: !Sub 'https://${MainHttpApiV1}.execute-api.${AWS::Region}.amazonaws.com' - - MainHttpApiV2Id: - Description: 'ID of the Main HTTP API v2' - Value: !Ref MainHttpApiV2 - - MainHttpApiV2BaseUrl: - Description: 'Base URL of the Main HTTP API v2' - Value: !Sub 'https://${MainHttpApiV2}.execute-api.${AWS::Region}.amazonaws.com' - - BinaryMediaTypeRestApiId: - Description: 'ID of the Binary Media Type REST API' - Value: !Ref BinaryMediaTypeRestApi - - BinaryMediaTypeRestApiBaseUrl: - Description: 'Base URL of the Binary Media Type REST API' - Value: !Sub 'https://${BinaryMediaTypeRestApi}.execute-api.${AWS::Region}.amazonaws.com/test' - - ParseAndReturnBodyLambdaFunctionArn: - Description: 'ARN of the Parse and Return Body Lambda Function' - Value: !GetAtt ParseAndReturnBodyLambdaFunction.Arn - - ReturnRawBodyLambdaFunctionArn: - Description: 'ARN of the Return Raw Body Lambda Function' - Value: !GetAtt ReturnRawBodyLambdaFunction.Arn - - ReturnFullEventLambdaFunctionArn: - Description: 'ARN of the Return Full Event Lambda Function' - Value: !GetAtt ReturnFullEventLambdaFunction.Arn - - ReturnDecodedParseBinLambdaFunctionArn: - Description: 'ARN of the Return Decoded Parse Bin Lambda Function' - Value: !GetAtt ReturnDecodedParseBinLambdaFunction.Arn diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.Tests.Common/ApiGatewayResponseTestCases.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.Tests.Common/ApiGatewayResponseTestCases.cs deleted file mode 100644 index b5b62ede8..000000000 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.Tests.Common/ApiGatewayResponseTestCases.cs +++ /dev/null @@ -1,734 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -using System.Text; -using System.Text.Json; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.TestTool.Models; -using Microsoft.AspNetCore.Http; -using Xunit; - -namespace Amazon.Lambda.TestTool.Tests.Common; - -public static class ApiGatewayResponseTestCases -{ - public static IEnumerable V1TestCases() - { - // V1 (APIGatewayProxyResponse) test cases - yield return new object[] - { - "V1_SimpleJsonResponse", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayProxyResponse - { - StatusCode = 200, - Body = JsonSerializer.Serialize(new { message = "Hello, World!" }), - Headers = new Dictionary { { "Content-Type", "application/json" } } - }, - Assertions = (response, emulatormode) => - { - Assert.Equal(200, response.StatusCode); - Assert.Equal("application/json", response.ContentType); - Assert.Equal("{\"message\":\"Hello, World!\"}", ReadResponseBody(response)); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString()); - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("{\"message\":\"Hello, World!\"}", content); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V1_SetsCorrectStatusCode", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayProxyResponse - { - StatusCode = 201, - Body = "{\"message\":\"Created\"}" - }, - Assertions = (response, emulatormode) => - { - Assert.Equal(201, response.StatusCode); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.Equal(201, (int)response.StatusCode); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V1_SetsHeaders", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayProxyResponse - { - StatusCode = 200, - Headers = new Dictionary - { - { "Content-Type", "application/json" }, - { "X-Custom-Header", "CustomValue" } - }, - Body = "{\"message\":\"With Headers\"}" - }, - Assertions = (response, emulatormode) => - { - Assert.Equal("application/json", response.Headers["Content-Type"]); - Assert.Equal("CustomValue", response.Headers["X-Custom-Header"]); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString()); - Assert.True(response.Headers.Contains("X-Custom-Header")); - Assert.Equal("CustomValue", response.Headers.GetValues("X-Custom-Header").First()); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V1_SetsMultiValueHeaders", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayProxyResponse - { - StatusCode = 200, - MultiValueHeaders = new Dictionary> - { - { "X-Multi-Header", new List { "Value1", "Value2" } } - }, - Body = "{\"message\":\"With MultiValueHeaders\"}" - }, - Assertions = (response, emulatormode) => - { - Assert.Equal(new[] { "Value1", "Value2" }, response.Headers["X-Multi-Header"]); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.True(response.Headers.Contains("X-Multi-Header")); - var multiHeaderValues = response.Headers.GetValues("X-Multi-Header").ToList(); - Assert.Contains("Value1", multiHeaderValues); - Assert.Contains("Value2", multiHeaderValues); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V1_SetsBodyNonBase64", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayProxyResponse - { - StatusCode = 200, - Body = "{\"message\":\"Hello, World!\"}", - IsBase64Encoded = false - }, - Assertions = (response, emulatormode) => - { - Assert.Equal("{\"message\":\"Hello, World!\"}", ReadResponseBody(response)); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("{\"message\":\"Hello, World!\"}", content); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V1_DefaultsToCorrectContentTYpe", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayProxyResponse - { - StatusCode = 200, - Body = "Hello, World!" - }, - Assertions = (response, emulatorMode) => - { - if (emulatorMode == ApiGatewayEmulatorMode.HttpV1) - { - Assert.Equal("text/plain; charset=utf-8", response.ContentType); - } else - { - Assert.Equal("application/json", response.ContentType); - } - }, - IntegrationAssertions = async (response, emulatorMode) => - { - if (emulatorMode == ApiGatewayEmulatorMode.HttpV1) - { - Assert.Equal("text/plain; charset=utf-8", response.Content.Headers.ContentType?.ToString()); - } - else - { - Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString()); - } - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V1_HandlesHeadersCorrectly", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayProxyResponse - { - Headers = new Dictionary - { - { "Content-Type", "application/json" }, - { "myheader", "test,other" }, - { "anotherheader", "secondvalue" } - }, - MultiValueHeaders = new Dictionary> - { - { "headername", new List { "headervalue", "headervalue2" } } - }, - Body = "{\"message\":\"With Multiple Headers\"}", - StatusCode = 200 - - }, - Assertions = (response, emulatormode) => - { - Assert.Equal("application/json", response.Headers["Content-Type"]); - Assert.Equal("test,other", response.Headers["myheader"]); - Assert.Equal("secondvalue", response.Headers["anotherheader"]); - Assert.Equal(new[] { "headervalue", "headervalue2" }, response.Headers["headername"]); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString()); - Assert.Equal("test,other", response.Headers.GetValues("myheader").First()); - Assert.Equal("secondvalue", response.Headers.GetValues("anotherheader").First()); - var headernameValues = response.Headers.GetValues("headername").ToList(); - Assert.Contains("headervalue", headernameValues); - Assert.Contains("headervalue2", headernameValues); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V1_CombinesSingleAndMultiValueHeaders", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayProxyResponse - { - Headers = new Dictionary - { - { "Content-Type", "application/json" }, - { "X-Custom-Header", "single-value" }, - { "Combined-Header", "single-value" } - }, - MultiValueHeaders = new Dictionary> - { - { "X-Multi-Header", new List { "multi-value1", "multi-value2" } }, - { "Combined-Header", new List { "multi-value1", "multi-value2" } } - }, - Body = "{\"message\":\"With Combined Headers\"}", - StatusCode = 200 - }, - Assertions = (response, emulatormode) => - { - Assert.Equal("application/json", response.Headers["Content-Type"]); - Assert.Equal("single-value", response.Headers["X-Custom-Header"]); - Assert.Equal(new[] { "multi-value1", "multi-value2" }, response.Headers["X-Multi-Header"]); - Assert.Equal(new[] { "multi-value1", "multi-value2", "single-value" }, response.Headers["Combined-Header"]); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString()); - Assert.Equal("single-value", response.Headers.GetValues("X-Custom-Header").First()); - var multiHeaderValues = response.Headers.GetValues("X-Multi-Header").ToList(); - Assert.Contains("multi-value1", multiHeaderValues); - Assert.Contains("multi-value2", multiHeaderValues); - var combinedHeaderValues = response.Headers.GetValues("Combined-Header").ToList(); - Assert.Contains("multi-value1", combinedHeaderValues); - Assert.Contains("multi-value2", combinedHeaderValues); - Assert.Contains("single-value", combinedHeaderValues); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V1_SetsContentLength", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayProxyResponse - { - Body = "{\"message\":\"Hello, World!\"}", - IsBase64Encoded = false, - StatusCode = 200 - }, - Assertions = (response, emulatorMode) => - { - Assert.Equal("{\"message\":\"Hello, World!\"}".Length, response.ContentLength); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.Equal("{\"message\":\"Hello, World!\"}".Length, response.Content.Headers.ContentLength); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V1_HandlesZeroStatusCode", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayProxyResponse - { - StatusCode = 0, - Body = "{\"key\":\"This body should be replaced\"}" - }, - Assertions = (response, emulatorMode) => - { - string error; - int contentLength; - int statusCode; - if (emulatorMode == ApiGatewayEmulatorMode.Rest) - { - error = " \"Internal server error\"}"; - contentLength = 36; - statusCode = 502; - } - else - { - error = "\"Internal Server Error\"}"; - contentLength = 35; - statusCode = 500; - } - Assert.Equal(statusCode, response.StatusCode); - Assert.Equal("application/json", response.ContentType); - Assert.Equal("{\"message\":"+error, ReadResponseBody(response)); - Assert.Equal(contentLength, response.ContentLength); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - string error; - int contentLength; - int statusCode; - - if (emulatorMode == ApiGatewayEmulatorMode.Rest) - { - error = " \"Internal server error\"}"; - contentLength = 36; - statusCode = 502; - } - else - { - error = "\"Internal Server Error\"}"; - contentLength = 35; - statusCode = 500; - } - Assert.Equal(statusCode, (int)response.StatusCode); - Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString()); - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("{\"message\":"+error, content); - Assert.Equal(contentLength, response.Content.Headers.ContentLength); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V1_UsesProvidedContentType", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayProxyResponse - { - StatusCode = 200, - Body = "Hello, World!", - Headers = new Dictionary - { - { "Content-Type", "application/json" } - } - }, - Assertions = (response, emulatormode) => - { - Assert.Equal("application/json", response.ContentType); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString()); - await Task.CompletedTask; - } - } - }; - yield return new object[] - { - "V1_APIHeaders", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayProxyResponse - { - StatusCode = 200, - Body = "Test body" - }, - Assertions = (response, emulatorMode) => - { - Assert.True(response.Headers.ContainsKey("Date")); - - if (emulatorMode == ApiGatewayEmulatorMode.Rest) - { - Assert.True(response.Headers.ContainsKey("x-amzn-RequestId")); - Assert.True(response.Headers.ContainsKey("x-amz-apigw-id")); - Assert.True(response.Headers.ContainsKey("X-Amzn-Trace-Id")); - - Assert.Matches(@"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", response.Headers["x-amzn-RequestId"]); - Assert.Matches(@"^[A-Za-z0-9_\-]{15}=$", response.Headers["x-amz-apigw-id"]); - Assert.Matches(@"^Root=1-[0-9a-f]{8}-[0-9a-f]{24};Parent=[0-9a-f]{16};Sampled=0;Lineage=1:[0-9a-f]{8}:0$", response.Headers["X-Amzn-Trace-Id"]); - } - else // HttpV1 or HttpV2 - { - Assert.True(response.Headers.ContainsKey("Apigw-Requestid")); - Assert.Matches(@"^[A-Za-z0-9_\-]{15}=$", response.Headers["Apigw-Requestid"]); - } - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.True(response.Headers.Contains("Date")); - - if (emulatorMode == ApiGatewayEmulatorMode.Rest) - { - Assert.True(response.Headers.Contains("x-amzn-RequestId")); - Assert.True(response.Headers.Contains("x-amz-apigw-id")); - Assert.True(response.Headers.Contains("X-Amzn-Trace-Id")); - - Assert.Matches(@"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", response.Headers.GetValues("x-amzn-RequestId").First()); - Assert.Matches(@"^[A-Za-z0-9_\-]{15}=$", response.Headers.GetValues("x-amz-apigw-id").First()); - Assert.Matches(@"^Root=1-[0-9a-f]{8}-[0-9a-f]{24};Parent=[0-9a-f]{16};Sampled=0;Lineage=1:[0-9a-f]{8}:0$", response.Headers.GetValues("X-Amzn-Trace-Id").First()); - } - else // HttpV1 or HttpV2 - { - Assert.True(response.Headers.Contains("Apigw-Requestid")); - Assert.Matches(@"^[A-Za-z0-9_\-]{15}=$", response.Headers.GetValues("Apigw-Requestid").First()); - } - - await Task.CompletedTask; - } - } - }; - - } - - public static IEnumerable V2TestCases() - { - // V2 (APIGatewayHttpApiV2ProxyResponse) test cases - yield return new object[] - { - "V2_SimpleJsonResponse", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = 200, - Body = JsonSerializer.Serialize(new { message = "Hello, World!" }), - Headers = new Dictionary { { "Content-Type", "application/json" } } - }, - Assertions = (response, emulatorMode) => - { - Assert.Equal(200, response.StatusCode); - Assert.Equal("application/json", response.ContentType); - Assert.Equal("{\"message\":\"Hello, World!\"}", ReadResponseBody(response)); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString()); - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("{\"message\":\"Hello, World!\"}", content); - } - } - }; - - yield return new object[] - { - "V2_SetsCorrectStatusCode", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = 201, - Body = "{\"message\":\"Created\"}" - }, - Assertions = (response, emulatorMode) => - { - Assert.Equal(201, response.StatusCode); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.Equal(201, (int)response.StatusCode); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V2_HandlesZeroStatusCode", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = 0, - Body = "{\"key\":\"This body should be replaced\"}" - }, - Assertions = (response, emulatorMode) => - { - string error; - int contentLength; - int statusCode; - error = "\"Internal Server Error\"}"; - contentLength = 35; - statusCode = 500; - Assert.Equal(statusCode, response.StatusCode); - Assert.Equal("application/json", response.ContentType); - Assert.Equal("{\"message\":"+error, ReadResponseBody(response)); - Assert.Equal(contentLength, response.ContentLength); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - string error; - int contentLength; - int statusCode; - - error = "\"Internal Server Error\"}"; - contentLength = 35; - statusCode = 500; - Assert.Equal(statusCode, (int)response.StatusCode); - Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString()); - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("{\"message\":"+error, content); - Assert.Equal(contentLength, response.Content.Headers.ContentLength); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V2_SetsHeaders", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = 200, - Headers = new Dictionary - { - { "Content-Type", "application/json" }, - { "X-Custom-Header", "CustomValue" } - }, - Body = "{\"message\":\"With Headers\"}" - }, - Assertions = (response, emulatorMode) => - { - Assert.Equal("application/json", response.Headers["Content-Type"]); - Assert.Equal("CustomValue", response.Headers["X-Custom-Header"]); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString()); - Assert.True(response.Headers.Contains("X-Custom-Header")); - Assert.Equal("CustomValue", response.Headers.GetValues("X-Custom-Header").First()); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V2_SetsBodyNonBase64", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = 200, - Body = "{\"message\":\"Hello, API Gateway v2!\"}", - IsBase64Encoded = false - }, - Assertions = (response, emulatorMode) => - { - Assert.Equal("{\"message\":\"Hello, API Gateway v2!\"}", ReadResponseBody(response)); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("{\"message\":\"Hello, API Gateway v2!\"}", content); - } - } - }; - - yield return new object[] - { - "V2_SetsBodyBase64", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = 200, - Body = Convert.ToBase64String(Encoding.UTF8.GetBytes("{\"message\":\"Hello, API Gateway v2!\"}")), - IsBase64Encoded = true - }, - Assertions = (response, emulatormode) => - { - Assert.Equal("{\"message\":\"Hello, API Gateway v2!\"}", ReadResponseBody(response)); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("{\"message\":\"Hello, API Gateway v2!\"}", content); - } - } - }; - - yield return new object[] - { - "V2_DefaultsToTextPlainContentType", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = 200, - Body = "Hello, World!" - }, - Assertions = (response, emulatorMode) => - { - Assert.Equal("text/plain; charset=utf-8", response.ContentType); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.Equal("text/plain; charset=utf-8", response.Content.Headers.ContentType?.ToString()); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V2_HandlesHeadersCorrectly", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = 200, - Headers = new Dictionary - { - { "Content-Type", "application/json" }, - { "myheader", "test,shouldhavesecondvalue" }, - { "anotherheader", "secondvalue" } - }, - Body = "{\"message\":\"With Headers\"}" - }, - Assertions = (response, emulatorMode) => - { - Assert.Equal("application/json", response.Headers["Content-Type"]); - Assert.Equal("test,shouldhavesecondvalue", response.Headers["myheader"]); - Assert.Equal("secondvalue", response.Headers["anotherheader"]); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString()); - Assert.Equal("test,shouldhavesecondvalue", response.Headers.GetValues("myheader").First()); - Assert.Equal("secondvalue", response.Headers.GetValues("anotherheader").First()); - await Task.CompletedTask; - } - } - }; - - yield return new object[] - { - "V2_DoesNotOverrideExplicitValues", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = 201, - Body = "{\"key\":\"value\"}", - Headers = new Dictionary - { - { "Content-Type", "application/xml" } - } - }, - Assertions = (response, emulatorMode) => - { - Assert.Equal(201, response.StatusCode); - Assert.Equal("application/xml", response.ContentType); - Assert.Equal("{\"key\":\"value\"}", ReadResponseBody(response)); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.Equal(201, (int)response.StatusCode); - Assert.Equal("application/xml", response.Content.Headers.ContentType?.ToString()); - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("{\"key\":\"value\"}", content); - } - } - }; - - yield return new object[] - { - "V2_HttpAPIHeaders", - new ApiGatewayResponseTestCase - { - Response = new APIGatewayHttpApiV2ProxyResponse - { - StatusCode = 200, - Body = "Test body" - }, - Assertions = (response, emulatorMode) => - { - Assert.True(response.Headers.ContainsKey("Date")); - Assert.True(response.Headers.ContainsKey("Apigw-Requestid")); - - Assert.Matches(@"^[A-Za-z0-9_\-]{15}=$", response.Headers["Apigw-Requestid"]); - }, - IntegrationAssertions = async (response, emulatorMode) => - { - Assert.True(response.Headers.Contains("Date")); - Assert.True(response.Headers.Contains("Apigw-Requestid")); - - Assert.Matches(@"^[A-Za-z0-9_\-]{15}=$", response.Headers.GetValues("Apigw-Requestid").First()); - await Task.CompletedTask; - } - } - }; - - } - - private static string ReadResponseBody(HttpResponse response) - { - response.Body.Seek(0, SeekOrigin.Begin); - using var reader = new StreamReader(response.Body); - return reader.ReadToEnd(); - } - - public class ApiGatewayResponseTestCase - { - public required object Response { get; set; } - public required Action Assertions { get; set; } - public required Func IntegrationAssertions { get; set; } - } - -} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/ApiGatewayTestHelper.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/ApiGatewayTestHelper.cs new file mode 100644 index 000000000..523610b91 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/ApiGatewayTestHelper.cs @@ -0,0 +1,120 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using Amazon.Lambda.APIGatewayEvents; +using Amazon.Lambda.TestTool.Extensions; +using Amazon.Lambda.TestTool.Models; +using Amazon.Lambda.TestTool.UnitTests.SnapshotHelper; +using Microsoft.AspNetCore.Http; +using Xunit; + +namespace Amazon.Lambda.TestTool.UnitTests +{ + public class ApiGatewayTestHelper + { + private readonly SnapshotTestHelper _snapshots = new(new JsonSerializerOptions + { + WriteIndented = true, + Converters = { new HttpResponseMessageConverter() } + } + ); + + public async Task VerifyApiGatewayResponseAsync( + APIGatewayProxyResponse response, + ApiGatewayEmulatorMode emulatorMode, + string snapshotName) + { + // Convert response to HttpResponse (simulates what API Gateway would do) + var convertedResponse = await ConvertToHttpResponseAsync(response, emulatorMode); + + // Load the expected response from snapshot + var expectedResponse = await _snapshots.LoadSnapshot(snapshotName); + + // Compare the responses + await AssertResponsesEqual(expectedResponse, convertedResponse); + } + + public async Task VerifyHttpApiV2ResponseAsync( + APIGatewayHttpApiV2ProxyResponse response, + string snapshotName) + { + // Convert response to HttpResponse (simulates what API Gateway would do) + var convertedResponse = await ConvertToHttpResponseAsync(response); + + // Load the expected response from snapshot + var expectedResponse = await _snapshots.LoadSnapshot(snapshotName); + + // Compare the responses + await AssertResponsesEqual(expectedResponse, convertedResponse); + } + + private async Task ConvertToHttpResponseAsync( + APIGatewayProxyResponse response, + ApiGatewayEmulatorMode emulatorMode) + { + var context = new DefaultHttpContext(); + context.Response.Body = new MemoryStream(); + await response.ToHttpResponseAsync(context, emulatorMode); + return context.Response; + } + + private async Task ConvertToHttpResponseAsync( + APIGatewayHttpApiV2ProxyResponse response) + { + var context = new DefaultHttpContext(); + context.Response.Body = new MemoryStream(); + await response.ToHttpResponseAsync(context); + return context.Response; + } + + private async Task AssertResponsesEqual(HttpResponseMessage expected, HttpResponse actual) + { + actual.Body.Seek(0, SeekOrigin.Begin); + var actualContent = await new StreamReader(actual.Body).ReadToEndAsync(); + var expectedContent = await expected.Content.ReadAsStringAsync(); + + Assert.Equal(actualContent, expectedContent); + + Assert.Equal(actual.StatusCode, (int)expected.StatusCode); + + // ignore these because they will vary in the real world. we will check manually in other test cases that these are set + var headersToIgnore = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Date", + "Apigw-Requestid", + "X-Amzn-Trace-Id", + "x-amzn-RequestId", + "x-amz-apigw-id", + "X-Cache", + "Via", + "X-Amz-Cf-Pop", + "X-Amz-Cf-Id" + }; + + foreach (var header in actual.Headers) + { + if (headersToIgnore.Contains(header.Key)) continue; + Assert.True(expected.Headers.TryGetValues(header.Key, out var expectedValues) || + expected.Content.Headers.TryGetValues(header.Key, out expectedValues), + $"Header '{header.Key}={string.Join(", ", header.Value.ToArray())}' not found in expected response"); + + var sortedActualValues = header.Value.OrderBy(v => v).ToArray(); + var sortedExpectedValues = expectedValues.OrderBy(v => v).ToArray(); + Assert.Equal(sortedActualValues, sortedExpectedValues); + } + + foreach (var header in expected.Headers.Concat(expected.Content.Headers)) + { + if (headersToIgnore.Contains(header.Key)) continue; + + Assert.True(actual.Headers.ContainsKey(header.Key), + $"Header '{header.Key}={string.Join(", ", header.Value)}' not found in actual response"); + + var sortedActualValues = actual.Headers[header.Key].OrderBy(v => v).ToArray(); + var sortedExpectedValues = header.Value.OrderBy(v => v).ToArray(); + Assert.Equal(sortedActualValues, sortedExpectedValues); + } + } + } +} 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 index 7611995ba..ba5d7f7aa 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/ApiGatewayResponseExtensionsTests.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/ApiGatewayResponseExtensionsTests.cs @@ -5,77 +5,88 @@ using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.TestTool.Extensions; using Amazon.Lambda.TestTool.Models; -using Amazon.Lambda.TestTool.Tests.Common; using Microsoft.AspNetCore.Http; using Xunit; -using static Amazon.Lambda.TestTool.Tests.Common.ApiGatewayResponseTestCases; +using static Amazon.Lambda.TestTool.UnitTests.Extensions.ApiGatewayResponseTestCases; namespace Amazon.Lambda.TestTool.UnitTests.Extensions { - public class ApiGatewayResponseExtensionsUnitTests + public class ApiGatewayResponseExtensionsTests { + private ApiGatewayTestHelper _helper = new(); + [Theory] - [MemberData(nameof(ApiGatewayResponseTestCases.V1TestCases), MemberType = typeof(ApiGatewayResponseTestCases))] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] - public async Task ToHttpResponse_ConvertsCorrectlyV1(string testName, ApiGatewayResponseTestCase testCase) + [MemberData(nameof(V1TestCases), MemberType = typeof(ApiGatewayResponseTestCases))] + public async Task APIGatewayV1_REST(string testName, ApiGatewayResponseTestCase testCase) { - // Arrange - var httpContext = new DefaultHttpContext(); - httpContext.Response.Body = new MemoryStream(); - await ((APIGatewayProxyResponse)testCase.Response).ToHttpResponseAsync(httpContext, ApiGatewayEmulatorMode.HttpV1); - - // Assert - testCase.Assertions(httpContext.Response, ApiGatewayEmulatorMode.HttpV1); + await RunV1Test(testCase, ApiGatewayEmulatorMode.Rest, testName); } [Theory] - [MemberData(nameof(ApiGatewayResponseTestCases.V1TestCases), MemberType = typeof(ApiGatewayResponseTestCases))] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] - public async Task ToHttpResponse_ConvertsCorrectlyV1Rest(string testName, ApiGatewayResponseTestCase testCase) + [MemberData(nameof(V1TestCases), MemberType = typeof(ApiGatewayResponseTestCases))] + public async Task APIGatewayV1_HTTP(string testName, ApiGatewayResponseTestCase testCase) { - // Arrange - var httpContext = new DefaultHttpContext(); - httpContext.Response.Body = new MemoryStream(); - await ((APIGatewayProxyResponse)testCase.Response).ToHttpResponseAsync(httpContext, ApiGatewayEmulatorMode.Rest); - - // Assert - testCase.Assertions(httpContext.Response, ApiGatewayEmulatorMode.Rest); + await RunV1Test(testCase, ApiGatewayEmulatorMode.HttpV1, testName); } [Theory] - [MemberData(nameof(ApiGatewayResponseTestCases.V2TestCases), MemberType = typeof(ApiGatewayResponseTestCases))] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] - public async Task ToHttpResponse_ConvertsCorrectlyV2(string testName, ApiGatewayResponseTestCase testCase) + [MemberData(nameof(V2TestCases), MemberType = typeof(ApiGatewayResponseTestCases))] + public async Task APIGatewayV2(string testName, ApiGatewayResponseTestCase testCase) { - // Arrange + var testResponse = testCase.Response as APIGatewayHttpApiV2ProxyResponse; + Assert.NotNull(testResponse); + await _helper.VerifyHttpApiV2ResponseAsync(testResponse, testName); + } + + [Fact] + public async Task ToHttpResponse_RestAPIGatewayV1DecodesBase64() + { + var testResponse = new APIGatewayProxyResponse + { + StatusCode = 200, + Body = Convert.ToBase64String("test"u8.ToArray()), + IsBase64Encoded = true + }; + var httpContext = new DefaultHttpContext(); httpContext.Response.Body = new MemoryStream(); - await ((APIGatewayHttpApiV2ProxyResponse)testCase.Response).ToHttpResponseAsync(httpContext); + await testResponse.ToHttpResponseAsync(httpContext, ApiGatewayEmulatorMode.Rest); + + httpContext.Response.Body.Position = 0; - // Assert - testCase.Assertions(httpContext.Response, ApiGatewayEmulatorMode.HttpV2); + Assert.Equal(200, (int)httpContext.Response.StatusCode); + var content = await new StreamReader(httpContext.Response.Body).ReadToEndAsync(); + Assert.Equal("test", content); } - [Theory] - [InlineData(ApiGatewayEmulatorMode.HttpV1)] - [InlineData(ApiGatewayEmulatorMode.Rest)] - public async Task ToHttpResponse_APIGatewayV1DecodesBase64(ApiGatewayEmulatorMode emulatorMode) + [Fact] + public async Task ToHttpResponse_HttpV1APIGatewayV1DecodesBase64() { - var apiResponse = new APIGatewayProxyResponse + var testResponse = new APIGatewayProxyResponse { StatusCode = 200, - Body = Convert.ToBase64String(Encoding.UTF8.GetBytes("test")), + Body = Convert.ToBase64String("test"u8.ToArray()), IsBase64Encoded = true }; var httpContext = new DefaultHttpContext(); httpContext.Response.Body = new MemoryStream(); - await apiResponse.ToHttpResponseAsync(httpContext, emulatorMode); + await testResponse.ToHttpResponseAsync(httpContext, ApiGatewayEmulatorMode.HttpV1); + + httpContext.Response.Body.Position = 0; + + Assert.Equal(200, (int)httpContext.Response.StatusCode); + var content = await new StreamReader(httpContext.Response.Body).ReadToEndAsync(); + Assert.Equal("test", content); + } + + private async Task RunV1Test(ApiGatewayResponseTestCase testCase, ApiGatewayEmulatorMode emulatorMode, string testName) + { + var testResponse = testCase.Response as APIGatewayProxyResponse; + Assert.NotNull(testResponse); + var testCaseName = testName + emulatorMode; + await _helper.VerifyApiGatewayResponseAsync(testResponse, emulatorMode, testCaseName); - httpContext.Response.Body.Seek(0, SeekOrigin.Begin); - using var reader = new StreamReader(httpContext.Response.Body); - var bodyContent = await reader.ReadToEndAsync(); - Assert.Equal("test", bodyContent); } } } diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/ApiGatewayResponseTestCases.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/ApiGatewayResponseTestCases.cs new file mode 100644 index 000000000..302d46d74 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/ApiGatewayResponseTestCases.cs @@ -0,0 +1,367 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Text; +using System.Text.Json; +using Amazon.Lambda.APIGatewayEvents; + +namespace Amazon.Lambda.TestTool.UnitTests.Extensions; + +public static class ApiGatewayResponseTestCases +{ + public static IEnumerable V1TestCases() + { + // V1 (APIGatewayProxyResponse) test cases + yield return new object[] + { + "V1_SimpleJsonResponse", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayProxyResponse + { + StatusCode = 200, + Body = JsonSerializer.Serialize(new { message = "Hello, World!" }), + Headers = new Dictionary { { "Content-Type", "application/json" } } + }, + } + }; + + yield return new object[] + { + "V1_SetsCorrectStatusCode", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayProxyResponse + { + StatusCode = 201, + Body = "{\"message\":\"Created\"}" + }, + } + }; + + yield return new object[] + { + "V1_SetsHeaders", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayProxyResponse + { + StatusCode = 200, + Headers = new Dictionary + { + { "Content-Type", "application/json" }, + { "X-Custom-Header", "CustomValue" } + }, + Body = "{\"message\":\"With Headers\"}" + }, + } + }; + + yield return new object[] + { + "V1_SetsMultiValueHeaders", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayProxyResponse + { + StatusCode = 200, + MultiValueHeaders = new Dictionary> + { + { "X-Multi-Header", new List { "Value1", "Value2" } } + }, + Body = "{\"message\":\"With MultiValueHeaders\"}" + }, + } + }; + + yield return new object[] + { + "V1_SetsBodyNonBase64", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayProxyResponse + { + StatusCode = 200, + Body = "{\"message\":\"Hello, World!\"}", + IsBase64Encoded = false + }, + } + }; + + yield return new object[] + { + "V1_DefaultsToCorrectContentTYpe", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayProxyResponse + { + StatusCode = 200, + Body = "Hello, World!" + }, + } + }; + + yield return new object[] + { + "V1_HandlesHeadersCorrectly", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayProxyResponse + { + Headers = new Dictionary + { + { "Content-Type", "application/json" }, + { "myheader", "test,other" }, + { "anotherheader", "secondvalue" } + }, + MultiValueHeaders = new Dictionary> + { + { "headername", new List { "headervalue", "headervalue2" } } + }, + Body = "{\"message\":\"With Multiple Headers\"}", + StatusCode = 200 + + }, + } + }; + + yield return new object[] + { + "V1_CombinesSingleAndMultiValueHeaders", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayProxyResponse + { + Headers = new Dictionary + { + { "Content-Type", "application/json" }, + { "X-Custom-Header", "single-value" }, + { "Combined-Header", "single-value" } + }, + MultiValueHeaders = new Dictionary> + { + { "X-Multi-Header", new List { "multi-value1", "multi-value2" } }, + { "Combined-Header", new List { "multi-value1", "multi-value2" } } + }, + Body = "{\"message\":\"With Combined Headers\"}", + StatusCode = 200 + }, + } + }; + + yield return new object[] + { + "V1_SetsContentLength", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayProxyResponse + { + Body = "{\"message\":\"Hello, World!\"}", + IsBase64Encoded = false, + StatusCode = 200 + }, + } + }; + + yield return new object[] + { + "V1_HandlesZeroStatusCode", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayProxyResponse + { + StatusCode = 0, + Body = "{\"key\":\"This body should be replaced\"}" + }, + } + }; + + yield return new object[] + { + "V1_UsesProvidedContentType", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayProxyResponse + { + StatusCode = 200, + Body = "Hello, World!", + Headers = new Dictionary + { + { "Content-Type", "application/json" } + } + }, + } + }; + yield return new object[] + { + "V1_APIHeaders", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayProxyResponse + { + StatusCode = 200, + Body = "Test body" + }, + } + }; + + } + + public static IEnumerable V2TestCases() + { + // V2 (APIGatewayHttpApiV2ProxyResponse) test cases + yield return new object[] + { + "V2_SimpleJsonResponse", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayHttpApiV2ProxyResponse + { + StatusCode = 200, + Body = JsonSerializer.Serialize(new { message = "Hello, World!" }), + Headers = new Dictionary { { "Content-Type", "application/json" } } + }, + } + }; + + yield return new object[] + { + "V2_SetsCorrectStatusCode", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayHttpApiV2ProxyResponse + { + StatusCode = 201, + Body = "{\"message\":\"Created\"}" + }, + } + }; + + yield return new object[] + { + "V2_HandlesZeroStatusCode", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayHttpApiV2ProxyResponse + { + StatusCode = 0, + Body = "{\"key\":\"This body should be replaced\"}" + }, + } + }; + + yield return new object[] + { + "V2_SetsHeaders", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayHttpApiV2ProxyResponse + { + StatusCode = 200, + Headers = new Dictionary + { + { "Content-Type", "application/json" }, + { "X-Custom-Header", "CustomValue" } + }, + Body = "{\"message\":\"With Headers\"}" + }, + } + }; + + yield return new object[] + { + "V2_SetsBodyNonBase64", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayHttpApiV2ProxyResponse + { + StatusCode = 200, + Body = "{\"message\":\"Hello, API Gateway v2!\"}", + IsBase64Encoded = false + }, + } + }; + + yield return new object[] + { + "V2_SetsBodyBase64", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayHttpApiV2ProxyResponse + { + StatusCode = 200, + Body = Convert.ToBase64String(Encoding.UTF8.GetBytes("{\"message\":\"Hello, API Gateway v2!\"}")), + IsBase64Encoded = true + }, + } + }; + + yield return new object[] + { + "V2_DefaultsToTextPlainContentType", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayHttpApiV2ProxyResponse + { + StatusCode = 200, + Body = "Hello, World!" + }, + } + }; + + yield return new object[] + { + "V2_HandlesHeadersCorrectly", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayHttpApiV2ProxyResponse + { + StatusCode = 200, + Headers = new Dictionary + { + { "Content-Type", "application/json" }, + { "myheader", "test,shouldhavesecondvalue" }, + { "anotherheader", "secondvalue" } + }, + Body = "{\"message\":\"With Headers\"}" + }, + } + }; + + yield return new object[] + { + "V2_DoesNotOverrideExplicitValues", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayHttpApiV2ProxyResponse + { + StatusCode = 201, + Body = "{\"key\":\"value\"}", + Headers = new Dictionary + { + { "Content-Type", "application/xml" } + } + }, + } + }; + + yield return new object[] + { + "V2_HttpAPIHeaders", + new ApiGatewayResponseTestCase + { + Response = new APIGatewayHttpApiV2ProxyResponse + { + StatusCode = 200, + Body = "Test body" + }, + } + }; + + } + + public class ApiGatewayResponseTestCase + { + public required object Response { get; set; } + } +} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/HttpContextExtensionsTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/HttpContextExtensionsTests.cs index 27e6cf896..eff9c3338 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/HttpContextExtensionsTests.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/HttpContextExtensionsTests.cs @@ -1,250 +1,277 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.TestTool.Extensions; using Amazon.Lambda.TestTool.Models; -using Amazon.Lambda.TestTool.Tests.Common; +using Amazon.Lambda.TestTool.UnitTests.SnapshotHelper; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Primitives; using Xunit; -using static Amazon.Lambda.TestTool.Tests.Common.HttpContextTestCases; +using static Amazon.Lambda.TestTool.UnitTests.Extensions.HttpContextTestCases; namespace Amazon.Lambda.TestTool.UnitTests.Extensions { public class HttpContextExtensionsTests { - [Theory] - [MemberData(nameof(HttpContextTestCases.V1TestCases), MemberType = typeof(HttpContextTestCases))] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] - public async Task ToApiGatewayRequestRest_ConvertsCorrectly(string testName, HttpContextTestCase testCase) - { - // Arrange - var context = testCase.HttpContext; - - // Act - var result = await context.ToApiGatewayRequest(testCase.ApiGatewayRouteConfig, ApiGatewayEmulatorMode.Rest); + private readonly SnapshotTestHelper _snapshots; - // Assert - testCase.Assertions(result, ApiGatewayEmulatorMode.Rest); + public HttpContextExtensionsTests() + { + _snapshots = new SnapshotTestHelper(); } [Theory] - [MemberData(nameof(HttpContextTestCases.V1TestCases), MemberType = typeof(HttpContextTestCases))] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] - public async Task ToApiGatewayRequestV1_ConvertsCorrectly(string testName, HttpContextTestCase testCase) + [MemberData(nameof(V1TestCases), MemberType = typeof(HttpContextTestCases))] + public Task APIGatewayV1_REST(string testName, HttpContextTestCase testCase) { - // Arrange - var context = testCase.HttpContext; - - // Act - var result = await context.ToApiGatewayRequest(testCase.ApiGatewayRouteConfig, ApiGatewayEmulatorMode.HttpV1); - - // Assert - testCase.Assertions(result, ApiGatewayEmulatorMode.HttpV1); + var testCaseName = testName + ApiGatewayEmulatorMode.Rest; + return RunApiGatewayTest(testCase, ApiGatewayEmulatorMode.Rest, testCaseName); } [Theory] - [MemberData(nameof(HttpContextTestCases.V2TestCases), MemberType = typeof(HttpContextTestCases))] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] - public async Task ToApiGatewayHttpV2Request_ConvertsCorrectly(string testName, HttpContextTestCase testCase) + [MemberData(nameof(V1TestCases), MemberType = typeof(HttpContextTestCases))] + public Task APIGatewayV1_HTTP(string testName, HttpContextTestCase testCase) { - // Arrange - var context = testCase.HttpContext; + var testCaseName = testName + ApiGatewayEmulatorMode.HttpV1; - // Act - var result = await context.ToApiGatewayHttpV2Request(testCase.ApiGatewayRouteConfig); + return RunApiGatewayTest(testCase, ApiGatewayEmulatorMode.HttpV1, testCaseName); + } - // Assert - testCase.Assertions(result, ApiGatewayEmulatorMode.HttpV2); + [Theory] + [MemberData(nameof(V2TestCases), MemberType = typeof(HttpContextTestCases))] + public Task APIGatewayV2(string testName, HttpContextTestCase testCase) + { + return RunApiGatewayTest(testCase, ApiGatewayEmulatorMode.HttpV2, testName); } [Fact] - public async Task ToApiGatewayHttpV2Request_EmptyCollections() + public Task BinaryContentHttpV1() { + var httpContext = CreateHttpContext("POST", "/test3/api/users/123/avatar", + new Dictionary { { "Content-Type", "application/octet-stream" } }, + body: new byte[] { 1, 2, 3, 4, 5 }); - var httpContext = CreateHttpContext("POST", "/test10/api/notmatchingpath/123/orders"); var config = new ApiGatewayRouteConfig { - LambdaResourceName = "TestLambdaFunction", - Endpoint = "/test10/api/users/{userId}/orders", + LambdaResourceName = "UploadAvatarFunction", + Endpoint = "/test3/api/users/{userId}/avatar", HttpMethod = "POST", - Path = "/test10/api/users/{userId}/orders" + Path = "/test3/api/users/{userId}/avatar" }; - // Act - var result = await httpContext.ToApiGatewayHttpV2Request(config); - Assert.Equal(2, result.Headers.Count); - Assert.Equal("0", result.Headers["content-length"]); - Assert.Equal("text/plain; charset=utf-8", result.Headers["content-type"]); - Assert.Null(result.QueryStringParameters); - Assert.Null(result.PathParameters); - Assert.Null(result.Cookies); + var testCase = new HttpContextTestCase + { + HttpContext = httpContext, + ApiGatewayRouteConfig = config, + Assertions = (actualRequest, emulatorMode) => + { + var typedRequest = (APIGatewayProxyRequest)actualRequest; + Assert.True(typedRequest.IsBase64Encoded); + Assert.Equal(Convert.ToBase64String(new byte[] { 1, 2, 3, 4, 5 }), typedRequest.Body); + Assert.Equal("123", typedRequest.PathParameters["userId"]); + Assert.Equal("/test3/api/users/{userId}/avatar", typedRequest.Resource); + Assert.Equal("POST", typedRequest.HttpMethod); + } + }; + + return RunApiGatewayTest(testCase, ApiGatewayEmulatorMode.HttpV1, nameof(BinaryContentHttpV1)); } [Fact] - public async Task ToApiGatewayHttpV1Request_EmptyCollections() + public Task BinaryContentRest() { - var httpContext = CreateHttpContext("POST", "/test10/api/notmatchingpath/123/orders"); + var httpContext = CreateHttpContext("POST", "/test4/api/users/123/avatar", + new Dictionary { { "Content-Type", "application/octet-stream" } }, + body: new byte[] { 1, 2, 3, 4, 5 }); + var config = new ApiGatewayRouteConfig { - LambdaResourceName = "TestLambdaFunction", - Endpoint = "/test10/api/users/{userId}/orders", + Path = "/test4/api/users/{userId}/avatar", + Endpoint = "/test4/api/users/{userId}/avatar", HttpMethod = "POST", - Path = "/test10/api/users/{userId}/orders" + LambdaResourceName = "ReturnFullEventLambdaFunction" + }; + + var testCase = new HttpContextTestCase + { + HttpContext = httpContext, + ApiGatewayRouteConfig = config, + Assertions = (actualRequest, emulatorMode) => + { + var typedRequest = (APIGatewayProxyRequest)actualRequest; + Assert.True(typedRequest.IsBase64Encoded); + Assert.Equal(Convert.ToBase64String(new byte[] { 1, 2, 3, 4, 5 }), typedRequest.Body); + Assert.Equal("123", typedRequest.PathParameters["userId"]); + Assert.Equal("/test4/api/users/{userId}/avatar", typedRequest.Resource); + Assert.Equal("POST", typedRequest.HttpMethod); + } }; - // Act - var result = await httpContext.ToApiGatewayRequest(config, ApiGatewayEmulatorMode.HttpV1); - Assert.Equal(2, result.Headers.Count); - Assert.Equal("0", result.Headers["content-length"]); - Assert.Equal("text/plain; charset=utf-8", result.Headers["content-type"]); - Assert.Equal(new List { "0" }, result.MultiValueHeaders["content-length"]); - Assert.Equal(new List { "text/plain; charset=utf-8" }, result.MultiValueHeaders["content-type"]); - Assert.Null(result.QueryStringParameters); - Assert.Null(result.MultiValueQueryStringParameters); - Assert.Null(result.PathParameters); + return RunApiGatewayTest(testCase, ApiGatewayEmulatorMode.Rest, nameof(BinaryContentRest)); } - [Theory] - [InlineData(ApiGatewayEmulatorMode.Rest)] - [InlineData(ApiGatewayEmulatorMode.HttpV1)] - public async Task ToApiGateway_MultiValueHeader(ApiGatewayEmulatorMode emulatorMode) + private async Task RunApiGatewayTest(HttpContextTestCase testCase, ApiGatewayEmulatorMode emulatorMode, string testName) where T : class { - var httpContext = CreateHttpContext("POST", "/test1/api/users/123/orders", - new Dictionary - { - { "Accept", new StringValues(new[] { "text/html", "application/json" }) }, - }); - var config = new ApiGatewayRouteConfig + + Func> converter = emulatorMode switch { - LambdaResourceName = "TestLambdaFunction", - Endpoint = "/test1/api/users/{userId}/orders", - HttpMethod = "POST", - Path = "/test1/api/users/{userId}/orders" + ApiGatewayEmulatorMode.Rest => + async (context, cfg) => (T)(object)await context.ToApiGatewayRequest(cfg, ApiGatewayEmulatorMode.Rest), + ApiGatewayEmulatorMode.HttpV1 => + async (context, cfg) => (T)(object)await context.ToApiGatewayRequest(cfg, ApiGatewayEmulatorMode.HttpV1), + ApiGatewayEmulatorMode.HttpV2 => + async (context, cfg) => (T)(object)await context.ToApiGatewayHttpV2Request(cfg), + _ => throw new ArgumentException($"Unsupported gateway type: {emulatorMode}") }; - var result = await httpContext.ToApiGatewayRequest(config, emulatorMode); - Assert.Equal(["text/html", "application/json"], result.MultiValueHeaders["accept"]); + await RunApiGatewayTestInternal( + testCase, + converter, + emulatorMode, + testName); } + private async Task RunApiGatewayTestInternal( + HttpContextTestCase testCase, + Func> toApiGatewayRequest, + ApiGatewayEmulatorMode emulatorMode, + string testName) + where T : class + { + T snapshot; + snapshot = await _snapshots.LoadSnapshot(testName); + var expectedApiGatewayRequest = await toApiGatewayRequest(testCase.HttpContext, testCase.ApiGatewayRouteConfig); + CompareApiGatewayRequests(expectedApiGatewayRequest, snapshot); + testCase.Assertions(expectedApiGatewayRequest!, emulatorMode); + } - [Fact] - public async Task ToApiGatewayHttpV1_EncodedAndUnicodeHeader() - { - var httpContext = CreateHttpContext("POST", "/test1/api/users/123/orders", - new Dictionary - { - { "X-Encoded-Header", "value%20with%20spaces" }, - { "X-Unicode-Header", "☕ Coffee" }, - { "X-Mixed-Header", "Hello%2C%20World%21%20☕" }, - { "X-Raw-Unicode", "\u2615 Coffee" } - }); - var config = new ApiGatewayRouteConfig + private void CompareApiGatewayRequests(T expected, T actual) where T : class? + { + if (expected is APIGatewayProxyRequest v1Expected && actual is APIGatewayProxyRequest v1Actual) { - LambdaResourceName = "TestLambdaFunction", - Endpoint = "/test1/api/users/{userId}/orders", - HttpMethod = "POST", - Path = "/test1/api/users/{userId}/orders" - }; + CompareApiGatewayV1Requests(v1Expected, v1Actual); + } + else if (expected is APIGatewayHttpApiV2ProxyRequest v2Expected && actual is APIGatewayHttpApiV2ProxyRequest v2Actual) + { + CompareApiGatewayV2Requests(v2Expected, v2Actual); + } + else + { + throw new ArgumentException("Unsupported type for comparison"); + } + } + + private void CompareApiGatewayV1Requests(APIGatewayProxyRequest expected, APIGatewayProxyRequest actual) + { + Assert.Equal(expected.HttpMethod, actual.HttpMethod); + Assert.Equal(expected.Path, actual.Path); + Assert.Equal(expected.Resource, actual.Resource); + Assert.Equal(expected.Body, actual.Body); + Assert.Equal(expected.IsBase64Encoded, actual.IsBase64Encoded); - var result = await httpContext.ToApiGatewayRequest(config, ApiGatewayEmulatorMode.HttpV1); - Assert.Equal("value%20with%20spaces", result.Headers["X-Encoded-Header"]); - Assert.Equal("☕ Coffee", result.Headers["X-Unicode-Header"]); - Assert.Equal("Hello%2C%20World%21%20☕", result.Headers["X-Mixed-Header"]); - Assert.Equal("\u2615 Coffee", result.Headers["X-Raw-Unicode"]); - Assert.Equal(new List { "value%20with%20spaces" }, result.MultiValueHeaders["X-Encoded-Header"]); - Assert.Equal(new List { "☕ Coffee" }, result.MultiValueHeaders["X-Unicode-Header"]); - Assert.Equal(new List { "Hello%2C%20World%21%20☕" }, result.MultiValueHeaders["X-Mixed-Header"]); - Assert.Equal(new List { "\u2615 Coffee" }, result.MultiValueHeaders["X-Raw-Unicode"]); - } - - - // Keeping this commented out for now. We have a backlog item DOTNET-7862 for this - //[Fact] - //public void ToApiGatewayRest_EncodedAndUnicodeHeader() - //{ - // var httpContext = CreateHttpContext("POST", "/test1/api/users/123/orders", - // new Dictionary - // { - // { "X-Encoded-Header", "value%20with%20spaces" }, - // { "X-Unicode-Header", "☕ Coffee" }, - // { "X-Mixed-Header", "Hello%2C%20World%21%20☕" }, - // { "X-Raw-Unicode", "\u2615 Coffee" } - // }); - // var config = new ApiGatewayRouteConfig - // { - // LambdaResourceName = "TestLambdaFunction", - // Endpoint = "/test1/api/users/{userId}/orders", - // HttpMethod = "POST", - // Path = "/test1/api/users/{userId}/orders" - // }; - - // var result = httpContext.ToApiGatewayRequest(config, ApiGatewayEmulatorMode.Rest); - // Assert.Equal("value%20with%20spaces", result.Headers["X-Encoded-Header"]); - // Assert.Equal("¬リユ Coffee", result.Headers["X-Unicode-Header"]); - // Assert.Equal("Hello%2C%20World%21%20¬リユ", result.Headers["X-Mixed-Header"]); - // Assert.Equal("\u2615 Coffee", result.Headers["X-Raw-Unicode"]); - // Assert.Equal(new List { "value%20with%20spaces" }, result.MultiValueHeaders["X-Encoded-Header"]); - // Assert.Equal(new List { "¬リユ Coffee" }, result.MultiValueHeaders["X-Unicode-Header"]); // in reality this is what rest api thinks it is - // Assert.Equal(new List { "Hello%2C%20World%21%20☕" }, result.MultiValueHeaders["X-Mixed-Header"]); - // Assert.Equal(new List { "\u2615 Coffee" }, result.MultiValueHeaders["X-Raw-Unicode"]); - //} + CompareHeaders(expected.Headers, actual.Headers); + CompareMultiValueHeaders(expected.MultiValueHeaders, actual.MultiValueHeaders); + CompareDictionaries(expected.QueryStringParameters, actual.QueryStringParameters); + CompareDictionaries(expected.PathParameters, actual.PathParameters); + CompareDictionaries(expected.StageVariables, actual.StageVariables); + CompareDictionaries(expected.MultiValueQueryStringParameters, actual.MultiValueQueryStringParameters); + } - [Fact] - public async Task ToApiGateway_EncodedAndUnicodeHeaderV2() - { - var httpContext = CreateHttpContext("POST", "/test1/api/users/123/orders", - new Dictionary - { - { "X-Encoded-Header", "value%20with%20spaces" }, - { "X-Unicode-Header", "☕ Coffee" }, - { "X-Mixed-Header", "Hello%2C%20World%21%20☕" }, - { "X-Raw-Unicode", "\u2615 Coffee" } - }); - var config = new ApiGatewayRouteConfig + private void CompareApiGatewayV2Requests(APIGatewayHttpApiV2ProxyRequest expected, APIGatewayHttpApiV2ProxyRequest actual) + { + Assert.Equal(expected.RouteKey, actual.RouteKey); + Assert.Equal(expected.RawPath, actual.RawPath); + Assert.Equal(expected.RawQueryString, actual.RawQueryString); + Assert.Equal(expected.Body, actual.Body); + Assert.Equal(expected.IsBase64Encoded, actual.IsBase64Encoded); + Assert.Equal(expected.Version, actual.Version); + + CompareHeaders(expected.Headers, actual.Headers); + CompareDictionaries(expected.QueryStringParameters, actual.QueryStringParameters); + CompareDictionaries(expected.PathParameters, actual.PathParameters); + CompareStringArrays(expected.Cookies, actual.Cookies); + + CompareRequestContexts(expected.RequestContext, actual.RequestContext); + } + + private void CompareHeaders(IDictionary expected, IDictionary actual) + { + var expectedFiltered = FilterHeaders(expected); + var actualFiltered = FilterHeaders(actual); + + Assert.Equal(expectedFiltered.Count, actualFiltered.Count); + + foreach (var kvp in expectedFiltered) { - LambdaResourceName = "TestLambdaFunction", - Endpoint = "/test1/api/users/{userId}/orders", - HttpMethod = "POST", - Path = "/test1/api/users/{userId}/orders" - }; + Assert.True(actualFiltered.Keys.Any(k => string.Equals(k, kvp.Key, StringComparison.OrdinalIgnoreCase)), + $"Actual headers do not contain key: {kvp.Key}"); - var result = await httpContext.ToApiGatewayHttpV2Request(config); - Assert.Equal("value%20with%20spaces", result.Headers["x-encoded-header"]); - Assert.Equal("☕ Coffee", result.Headers["x-unicode-header"]); - Assert.Equal("Hello%2C%20World%21%20☕", result.Headers["x-mixed-header"]); - Assert.Equal("\u2615 Coffee", result.Headers["x-raw-unicode"]); + var actualValue = actualFiltered.First(pair => string.Equals(pair.Key, kvp.Key, StringComparison.OrdinalIgnoreCase)).Value; + Assert.Equal(kvp.Value, actualValue); + } } - [Theory] - [InlineData(ApiGatewayEmulatorMode.Rest)] - [InlineData(ApiGatewayEmulatorMode.HttpV1)] - public async Task BinaryContentHttpV1(ApiGatewayEmulatorMode emulatorMode) + private void CompareMultiValueHeaders(IDictionary> expected, IDictionary> actual) { - // Arrange - var httpContext = CreateHttpContext("POST", "/test3/api/users/123/avatar", - new Dictionary { { "Content-Type", "application/octet-stream" } }, - body: new byte[] { 1, 2, 3, 4, 5 }); + var expectedFiltered = FilterHeaders(expected); + var actualFiltered = FilterHeaders(actual); - var config = new ApiGatewayRouteConfig + Assert.Equal(expectedFiltered.Count, actualFiltered.Count); + + foreach (var kvp in expectedFiltered) { - LambdaResourceName = "UploadAvatarFunction", - Endpoint = "/test3/api/users/{userId}/avatar", - HttpMethod = "POST", - Path = "/test3/api/users/{userId}/avatar" - }; + Assert.True(actualFiltered.Keys.Any(k => string.Equals(k, kvp.Key, StringComparison.OrdinalIgnoreCase)), + $"Actual headers do not contain key: {kvp.Key}"); + + var actualValue = actualFiltered.First(pair => string.Equals(pair.Key, kvp.Key, StringComparison.OrdinalIgnoreCase)).Value; + Assert.Equal(kvp.Value, actualValue); + } + } - // Act - var result = await httpContext.ToApiGatewayRequest(config, emulatorMode); + private IDictionary FilterHeaders(IDictionary headers) where TKey : notnull + { + return headers.Where(kvp => + !(kvp.Key.ToString()!.StartsWith("x-forwarded-", StringComparison.OrdinalIgnoreCase) || // ignore these for now + kvp.Key.ToString()!.StartsWith("cloudfront-", StringComparison.OrdinalIgnoreCase) || // ignore these for now + kvp.Key.ToString()!.StartsWith("via-", StringComparison.OrdinalIgnoreCase) || // ignore these for now + kvp.Key.ToString()!.Equals("x-amzn-trace-id", StringComparison.OrdinalIgnoreCase) || // this is dynamic so ignoring for now + kvp.Key.ToString()!.Equals("cookie", StringComparison.OrdinalIgnoreCase) || // TODO may have to have api gateway v2 not set this in headers + kvp.Key.ToString()!.Equals("host", StringComparison.OrdinalIgnoreCase))) // TODO we may want to set this + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + + private void CompareDictionaries(IDictionary? expected, IDictionary? actual) + { + if (expected == null && actual == null) return; + if (expected == null && actual != null) Assert.Fail(); + if (expected != null && actual == null) Assert.Fail(); + Assert.Equal(expected!.Count, actual!.Count); + + foreach (var kvp in expected) + { + Assert.True(actual.ContainsKey(kvp.Key), $"Actual does not contain key: {kvp.Key}"); + Assert.Equal(kvp.Value, actual[kvp.Key]); + } + } + + private void CompareStringArrays(string[] expected, string[] actual) + { + Assert.Equal(expected?.Length, actual?.Length); + if (expected != null) + { + Assert.Equal(expected.OrderBy(x => x), actual?.OrderBy(x => x)); + } + } + + private void CompareRequestContexts(APIGatewayHttpApiV2ProxyRequest.ProxyRequestContext expected, APIGatewayHttpApiV2ProxyRequest.ProxyRequestContext actual) + { + Assert.Equal(expected.RouteKey, actual.RouteKey); - // Assert - Assert.True(result.IsBase64Encoded); - Assert.Equal(Convert.ToBase64String(new byte[] { 1, 2, 3, 4, 5 }), result.Body); - Assert.Equal("123", result.PathParameters["userId"]); - Assert.Equal("/test3/api/users/{userId}/avatar", result.Resource); - Assert.Equal("POST", result.HttpMethod); - Assert.Equal("application/octet-stream", result.Headers["Content-Type"]); + Assert.Equal(expected.Http.Method, actual.Http.Method); + Assert.Equal(expected.Http.Path, actual.Http.Path); + Assert.Equal(expected.Http.Protocol, actual.Http.Protocol); + Assert.Equal(expected.Http.UserAgent, actual.Http.UserAgent); } } } diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.Tests.Common/HttpContextTestCases.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/HttpContextTestCases.cs similarity index 99% rename from Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.Tests.Common/HttpContextTestCases.cs rename to Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/HttpContextTestCases.cs index 22b20a93e..06d74b7c3 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.Tests.Common/HttpContextTestCases.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/HttpContextTestCases.cs @@ -1,14 +1,13 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -using System.Net.Http; using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.TestTool.Models; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Xunit; -namespace Amazon.Lambda.TestTool.Tests.Common +namespace Amazon.Lambda.TestTool.UnitTests.Extensions { public static class HttpContextTestCases { diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/InvokeResponseExtensionsTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/InvokeResponseExtensionsTests.cs index cc5a3db46..f07f70a47 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/InvokeResponseExtensionsTests.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/InvokeResponseExtensionsTests.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 using System.Text; +using System.Text.Json; +using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.Model; using Amazon.Lambda.TestTool.Extensions; using Amazon.Lambda.TestTool.Models; @@ -9,8 +11,179 @@ namespace Amazon.Lambda.TestTool.UnitTests.Extensions; +/// +/// Unit tests for InvokeResponseExtensions. +/// +/// +/// Developer's Note: +/// These tests don't have direct access to the intermediate result of the Lambda to API Gateway conversion. +/// Instead, we test the final API Gateway response object to ensure our conversion methods produce results +/// that match the actual API Gateway behavior. This approach allows us to verify the correctness of our +/// conversion methods within the constraints of not having access to AWS's internal conversion process. +/// public class InvokeResponseExtensionsTests { + + private readonly ApiGatewayTestHelper _helper = new(); + + [Theory] + [InlineData(ApiGatewayEmulatorMode.Rest)] + [InlineData(ApiGatewayEmulatorMode.HttpV1)] + public async Task ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversion(ApiGatewayEmulatorMode emulatorMode) + { + // Arrange + var testResponse = new APIGatewayProxyResponse + { + StatusCode = 200, + Body = JsonSerializer.Serialize(new { message = "Hello, World!" }), + Headers = new Dictionary { { "Content-Type", "application/json" } } + }; + var invokeResponse = new InvokeResponse + { + Payload = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testResponse))) + }; + + // Act + var apiGatewayProxyResponse = invokeResponse.ToApiGatewayProxyResponse(emulatorMode); + + var testName = nameof(ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversion) + emulatorMode; + + // Assert + await _helper.VerifyApiGatewayResponseAsync(apiGatewayProxyResponse, emulatorMode, testName); + } + + [Fact] + public async Task ToApiGatewayHttpApiV2ProxyResponse_ValidResponse_MatchesDirectConversion() + { + // Arrange + var testResponse = new APIGatewayHttpApiV2ProxyResponse + { + StatusCode = 200, + Body = JsonSerializer.Serialize(new { message = "Hello, World!" }), + Headers = new Dictionary { { "Content-Type", "application/json" } } + }; + var invokeResponse = new InvokeResponse + { + Payload = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testResponse))) + }; + + // Act + var apiGatewayHttpApiV2ProxyResponse = invokeResponse.ToApiGatewayHttpApiV2ProxyResponse(); + + // Assert + await _helper.VerifyHttpApiV2ResponseAsync(apiGatewayHttpApiV2ProxyResponse, nameof(ToApiGatewayHttpApiV2ProxyResponse_ValidResponse_MatchesDirectConversion)); + } + + [Theory] + [InlineData(ApiGatewayEmulatorMode.Rest, 502, "Internal server error")] + [InlineData(ApiGatewayEmulatorMode.HttpV1, 500, "Internal Server Error")] + public async Task ToApiGatewayProxyResponse_InvalidJson_ReturnsErrorResponse(ApiGatewayEmulatorMode emulatorMode, int expectedStatusCode, string expectedErrorMessage) + { + // Arrange + var invokeResponse = new InvokeResponse + { + Payload = new MemoryStream(Encoding.UTF8.GetBytes("Not a valid proxy response object")) + }; + + // Act + var apiGatewayProxyResponse = invokeResponse.ToApiGatewayProxyResponse(emulatorMode); + + var testName = nameof(ToApiGatewayProxyResponse_InvalidJson_ReturnsErrorResponse) + emulatorMode; + + // Assert + Assert.Equal(expectedStatusCode, apiGatewayProxyResponse.StatusCode); + Assert.Contains(expectedErrorMessage, apiGatewayProxyResponse.Body); + + await _helper.VerifyApiGatewayResponseAsync( + apiGatewayProxyResponse, + emulatorMode, + testName); + } + + /// + /// Tests various Lambda return values to verify API Gateway's handling of responses. + /// + /// The payload returned by the Lambda function. + /// + /// This test demonstrates a discrepancy between the official AWS documentation + /// and the actual observed behavior of API Gateway HTTP API v2 with Lambda + /// proxy integrations (payload format version 2.0). + /// + /// Official documentation states: + /// "If your Lambda function returns valid JSON and doesn't return a statusCode, + /// API Gateway assumes a 200 status code and treats the entire response as the body." + /// + /// However, the observed behavior (which this test verifies) is: + /// - API Gateway does not validate whether the returned data is valid JSON. + /// - Any response from the Lambda function that is not a properly formatted + /// API Gateway response object (i.e., an object with a 'statusCode' property) + /// is treated as a raw body in a 200 OK response. + /// - This includes valid JSON objects without a statusCode, JSON arrays, + /// primitive values, and invalid JSON strings. + /// + /// This test ensures that our ToApiGatewayHttpApiV2ProxyResponse method + /// correctly replicates this observed behavior, rather than the documented behavior. + /// + [Theory] + [InlineData("Invalid_JSON_Partial_Object", "{\"name\": \"John Doe\", \"age\":", "{\"name\": \"John Doe\", \"age\":")] // Invalid JSON (partial object) + [InlineData("Valid_JSON_Object", "{\"name\": \"John Doe\", \"age\": 30}", "{\"name\": \"John Doe\", \"age\": 30}")] // Valid JSON object without statusCode + [InlineData("JSON_Array", "[1, 2, 3, 4, 5]", "[1, 2, 3, 4, 5]")] // JSON array + [InlineData("string", "Hello, World!", "Hello, World!")] // String primitive + [InlineData("number", "42", "42")] // Number primitive + [InlineData("boolean", "true", "true")] // Boolean primitive + [InlineData("string_unescaped", "\"test\"", "test")] // JSON string that should be unescaped + [InlineData("string_spaces", "\"Hello, World!\"", "Hello, World!")] // JSON string with spaces + [InlineData("empty_string", "\"\"", "")] // Empty JSON string + [InlineData("json_special", "\"Special \\\"quoted\\\" text\"", "Special \"quoted\" text")] // JSON string with escaped quotes + public async Task ToApiGatewayHttpApiV2ProxyResponse_VariousPayloads_ReturnsAsRawBody( + string testName, + string inputPayload, + string expectedResponsePayload) + { + // Arrange + var invokeResponse = new InvokeResponse + { + Payload = new MemoryStream(Encoding.UTF8.GetBytes(inputPayload)) + }; + + // Act + var apiGatewayHttpApiV2ProxyResponse = invokeResponse.ToApiGatewayHttpApiV2ProxyResponse(); + + var testCaseName = nameof(ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversion) + testName; + + // Assert + Assert.Equal(200, apiGatewayHttpApiV2ProxyResponse.StatusCode); + Assert.Equal(expectedResponsePayload, apiGatewayHttpApiV2ProxyResponse.Body); + Assert.Equal("application/json", apiGatewayHttpApiV2ProxyResponse.Headers["Content-Type"]); + + await _helper.VerifyHttpApiV2ResponseAsync( + apiGatewayHttpApiV2ProxyResponse, + testCaseName); + } + + [Fact] + public async Task ToApiGatewayHttpApiV2ProxyResponse_StatusCodeAsFloat_ReturnsInternalServerError() + { + // Arrange + var responsePayload = "{\"statusCode\": 200.5, \"body\": \"Hello\", \"headers\": {\"Content-Type\": \"text/plain\"}}"; + var invokeResponse = new InvokeResponse + { + Payload = new MemoryStream(Encoding.UTF8.GetBytes(responsePayload)) + }; + + // Act + var apiGatewayHttpApiV2ProxyResponse = invokeResponse.ToApiGatewayHttpApiV2ProxyResponse(); + + // Assert + Assert.Equal(500, apiGatewayHttpApiV2ProxyResponse.StatusCode); + Assert.Equal("{\"message\":\"Internal Server Error\"}", apiGatewayHttpApiV2ProxyResponse.Body); + Assert.Equal("application/json", apiGatewayHttpApiV2ProxyResponse.Headers["Content-Type"]); + + await _helper.VerifyHttpApiV2ResponseAsync( + apiGatewayHttpApiV2ProxyResponse, + nameof(ToApiGatewayHttpApiV2ProxyResponse_StatusCodeAsFloat_ReturnsInternalServerError)); + } + [Theory] [InlineData("{\"statusCode\": 200, \"body\": \"Hello\", \"headers\": {\"Content-Type\": \"text/plain\"}}", ApiGatewayEmulatorMode.Rest, 200, "Hello", "text/plain")] [InlineData("{\"statusCode\": 201, \"body\": \"Created\", \"headers\": {\"Content-Type\": \"application/json\"}}", ApiGatewayEmulatorMode.HttpV1, 201, "Created", "application/json")] @@ -38,27 +211,6 @@ public void ToApiGatewayProxyResponse_InvalidOrEmptyJson_ReturnsErrorResponse(st Assert.Equal("application/json", result.Headers["Content-Type"]); } - [Theory] - [InlineData("{\"statusCode\": 200, \"body\": \"Hello\", \"headers\": {\"Content-Type\": \"text/plain\"}}", 200, "Hello", "text/plain")] - [InlineData("{\"statusCode\": \"invalid\", \"body\": \"Hello\"}", 500, "{\"message\":\"Internal Server Error\"}", "application/json")] - [InlineData("{\"message\": \"Hello, World!\"}", 200, "{\"message\": \"Hello, World!\"}", "application/json")] - [InlineData("test", 200, "test", "application/json")] - [InlineData("\"test\"", 200, "test", "application/json")] - [InlineData("42", 200, "42", "application/json")] - [InlineData("true", 200, "true", "application/json")] - [InlineData("[1,2,3]", 200, "[1,2,3]", "application/json")] - [InlineData("{invalid json}", 200, "{invalid json}", "application/json")] - [InlineData("", 200, "", "application/json")] - public void ToApiGatewayHttpApiV2ProxyResponse_VariousInputs_ReturnsExpectedResult(string payload, int expectedStatusCode, string expectedBody, string expectedContentType) - { - var invokeResponse = CreateInvokeResponse(payload); - var result = invokeResponse.ToApiGatewayHttpApiV2ProxyResponse(); - - Assert.Equal(expectedStatusCode, result.StatusCode); - Assert.Equal(expectedBody, result.Body); - Assert.Equal(expectedContentType, result.Headers["Content-Type"]); - } - [Fact] public void ToApiGatewayProxyResponse_UnsupportedEmulatorMode_ThrowsNotSupportedException() { @@ -68,23 +220,6 @@ public void ToApiGatewayProxyResponse_UnsupportedEmulatorMode_ThrowsNotSupported invokeResponse.ToApiGatewayProxyResponse(ApiGatewayEmulatorMode.HttpV2)); } - [Fact] - public void ToApiGatewayHttpApiV2ProxyResponse_StatusCodeAsFloat_ReturnsInternalServerError() - { - // Arrange - var payload = "{\"statusCode\": 200.5, \"body\": \"Hello\", \"headers\": {\"Content-Type\": \"text/plain\"}}"; - var invokeResponse = CreateInvokeResponse(payload); - - // Act - var result = invokeResponse.ToApiGatewayHttpApiV2ProxyResponse(); - - // Assert - Assert.Equal(500, result.StatusCode); - Assert.Equal("{\"message\":\"Internal Server Error\"}", result.Body); - Assert.Equal("application/json", result.Headers["Content-Type"]); - } - - private InvokeResponse CreateInvokeResponse(string payload) { return new InvokeResponse diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/SnapshotHelper/ApiGatewayTestSnapshot.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/SnapshotHelper/ApiGatewayTestSnapshot.cs new file mode 100644 index 000000000..c3e81f68b --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/SnapshotHelper/ApiGatewayTestSnapshot.cs @@ -0,0 +1,89 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Net; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Amazon.Lambda.TestTool.UnitTests.SnapshotHelper; + +/// +/// Provides custom JSON conversion for HttpResponseMessage objects. +/// +public class HttpResponseMessageConverter : JsonConverter +{ + /// + /// Reads and converts JSON to an HttpResponseMessage object. + /// + /// The Utf8JsonReader to read data from. + /// The type of object to convert. + /// An object that specifies serialization options. + /// The converted HttpResponseMessage. + public override HttpResponseMessage Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using var document = JsonDocument.ParseValue(ref reader); + var element = document.RootElement; + + var response = new HttpResponseMessage((HttpStatusCode)element.GetProperty("StatusCode").GetInt32()); + + // Set content + var content = element.GetProperty("Content").GetString(); + response.Content = new StringContent(content ?? ""); + + // Clear default headers that StringContent adds + response.Headers.Clear(); + response.Content.Headers.Clear(); + + // Set headers + if (element.TryGetProperty("Headers", out var headersElement)) + { + foreach (var header in headersElement.EnumerateObject()) + { + var values = header.Value.EnumerateArray() + .Select(v => v.GetString()) + .Where(v => v != null) + .ToList(); + + // Try to add to either Headers or Content.Headers + if (!response.Headers.TryAddWithoutValidation(header.Name, values)) + { + response.Content.Headers.TryAddWithoutValidation(header.Name, values); + } + } + } + + return response; + } + + /// + /// Writes an HttpResponseMessage object to JSON. + /// + /// The Utf8JsonWriter to write to. + /// The HttpResponseMessage to convert. + /// An object that specifies serialization options. + public override void Write(Utf8JsonWriter writer, HttpResponseMessage value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + // Write status code + writer.WriteNumber("StatusCode", (int)value.StatusCode); + + // Write content + writer.WriteString("Content", value.Content.ReadAsStringAsync().Result); + + // Write headers + writer.WriteStartObject("Headers"); + foreach (var header in value.Headers.Concat(value.Content.Headers)) + { + writer.WriteStartArray(header.Key); + foreach (var headerValue in header.Value) + { + writer.WriteStringValue(headerValue); + } + writer.WriteEndArray(); + } + writer.WriteEndObject(); + + writer.WriteEndObject(); + } +} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/SnapshotHelper/SnapshotTestHelper.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/SnapshotHelper/SnapshotTestHelper.cs new file mode 100644 index 000000000..1ff5a204a --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/SnapshotHelper/SnapshotTestHelper.cs @@ -0,0 +1,72 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; + +namespace Amazon.Lambda.TestTool.UnitTests.SnapshotHelper; + +/// +/// Provides functionality for saving and loading snapshot tests, primarily used for testing purposes. +/// +public class SnapshotTestHelper +{ + private readonly string _snapshotDirectory; + private readonly JsonSerializerOptions _serializerOptions; + + /// + /// Initializes a new instance of the SnapshotTestHelper class. + /// + /// Custom JSON serializer options. If null, default options with indented writing will be used. + /// The directory name where snapshots will be stored. Defaults to "Snapshots". + public SnapshotTestHelper(JsonSerializerOptions? serializerOptions = null, string snapshotDirectory = "Snapshots") + { + var projectDir = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../")); + _snapshotDirectory = Path.Combine(projectDir, snapshotDirectory); + _serializerOptions = serializerOptions ?? new JsonSerializerOptions + { + WriteIndented = true + }; + } + + /// + /// Saves a snapshot of the specified value to a JSON file. + /// + /// The type of the value to be saved. + /// The value to save as a snapshot. + /// The name of the snapshot file (without extension). + /// A task that represents the asynchronous save operation. + public async Task SaveSnapshot(T value, string snapshotName) + { + Directory.CreateDirectory(_snapshotDirectory); + var filePath = GetSnapshotPath(snapshotName); + var serialized = JsonSerializer.Serialize(value, _serializerOptions); + await File.WriteAllTextAsync(filePath, serialized); + } + + /// + /// Loads a snapshot from a JSON file and deserializes it to the specified type. + /// + /// The type to deserialize the snapshot into. + /// The name of the snapshot file (without extension). + /// The deserialized snapshot object. + /// Thrown when the specified snapshot file does not exist. + public async Task LoadSnapshot(string snapshotName) + { + var filePath = GetSnapshotPath(snapshotName); + if (!File.Exists(filePath)) + { + throw new FileNotFoundException($"Snapshot file not found: {filePath}"); + } + + var content = await File.ReadAllTextAsync(filePath); + return JsonSerializer.Deserialize(content, _serializerOptions)!; + } + + /// + /// Gets the full file path for a snapshot file. + /// + /// The name of the snapshot file (without extension). + /// The full file path including the .json extension. + private string GetSnapshotPath(string snapshotName) => + Path.Combine(_snapshotDirectory, $"{snapshotName}.json"); +} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/BinaryContentHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/BinaryContentHttpV1.json new file mode 100644 index 000000000..57728f30c --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/BinaryContentHttpV1.json @@ -0,0 +1,85 @@ +{ + "Resource": "/test3/api/users/{userId}/avatar", + "Path": "/test3/api/users/123/avatar", + "HttpMethod": "POST", + "Headers": { + "Content-Length": "5", + "Content-Type": "application/octet-stream", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "X-Amzn-Trace-Id": "Root=1-67fd88fd-72d8964e1fed2bce3edd5aac", + "X-Forwarded-For": "IP_ADDRESS", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "MultiValueHeaders": { + "Content-Length": [ + "5" + ], + "Content-Type": [ + "application/octet-stream" + ], + "Host": ["EXAMPLE.execute-api.REGION.amazonaws.com"], + "X-Amzn-Trace-Id": [ + "Root=1-67fd88fd-72d8964e1fed2bce3edd5aac" + ], + "X-Forwarded-For": ["IP_ADDRESS"], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "QueryStringParameters": null, + "MultiValueQueryStringParameters": null, + "PathParameters": { + "userId": "123" + }, + "StageVariables": null, + "RequestContext": { + "Path": "/test3/api/users/123/avatar", + "AccountId": "ACCOUNT_ID", + "ResourceId": "POST /test3/api/users/{userId}/avatar", + "Stage": "$default", + "RequestId": "JCJXri3_PHcEP_Q=", + "Identity": { + "CognitoIdentityPoolId": null, + "AccountId": null, + "CognitoIdentityId": null, + "Caller": null, + "ApiKey": null, + "ApiKeyId": null, + "AccessKey": null, + "SourceIp": "IP_ADDRESS", + "CognitoAuthenticationType": null, + "CognitoAuthenticationProvider": null, + "UserArn": null, + "UserAgent": "", + "User": null, + "ClientCert": null + }, + "ResourcePath": "/test3/api/users/{userId}/avatar", + "HttpMethod": "POST", + "ApiId": "EXAMPLE", + "ExtendedRequestId": "JCJXri3_PHcEP_Q=", + "ConnectionId": null, + "ConnectedAt": 0, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "EventType": null, + "MessageId": null, + "RouteKey": null, + "Authorizer": null, + "OperationName": null, + "Error": null, + "IntegrationLatency": null, + "MessageDirection": null, + "RequestTime": "14/Apr/2025:22:15:25 \u002B0000", + "RequestTimeEpoch": 1744668925528, + "Status": null + }, + "Body": "AQIDBAU=", + "IsBase64Encoded": true +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/BinaryContentRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/BinaryContentRest.json new file mode 100644 index 000000000..173e35f87 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/BinaryContentRest.json @@ -0,0 +1,81 @@ +{ + "Resource": "/test4/api/users/{userId}/avatar", + "Path": "/test4/api/users/123/avatar", + "HttpMethod": "POST", + "Headers": { + "Content-Type": "application/octet-stream", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "X-Amzn-Trace-Id": "Root=1-67fd8927-4b9cc8347ead8aa60c674ac2", + "X-Forwarded-For": "IP_ADDRESS", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "MultiValueHeaders": { + "Content-Type": [ + "application/octet-stream" + ], + "Host": ["EXAMPLE.execute-api.REGION.amazonaws.com"], + "X-Amzn-Trace-Id": [ + "Root=1-67fd8927-4b9cc8347ead8aa60c674ac2" + ], + "X-Forwarded-For": ["IP_ADDRESS"], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "QueryStringParameters": null, + "MultiValueQueryStringParameters": null, + "PathParameters": { + "userId": "123" + }, + "StageVariables": null, + "RequestContext": { + "Path": "/test/test4/api/users/123/avatar", + "AccountId": "ACCOUNT_ID", + "ResourceId": "RESOURCE_ID", + "Stage": "test", + "RequestId": "7a1ab4cd-edd9-4437-86d3-7978341c597d", + "Identity": { + "CognitoIdentityPoolId": null, + "AccountId": null, + "CognitoIdentityId": null, + "Caller": null, + "ApiKey": null, + "ApiKeyId": null, + "AccessKey": null, + "SourceIp": "IP_ADDRESS", + "CognitoAuthenticationType": null, + "CognitoAuthenticationProvider": null, + "UserArn": null, + "UserAgent": null, + "User": null, + "ClientCert": null + }, + "ResourcePath": "/test4/api/users/{userId}/avatar", + "HttpMethod": "POST", + "ApiId": "EXAMPLE", + "ExtendedRequestId": "JCJePHSSPHcEtwQ=", + "ConnectionId": null, + "ConnectedAt": 0, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "EventType": null, + "MessageId": null, + "RouteKey": null, + "Authorizer": null, + "OperationName": null, + "Error": null, + "IntegrationLatency": null, + "MessageDirection": null, + "RequestTime": "14/Apr/2025:22:16:07 \u002B0000", + "RequestTimeEpoch": 1744668967561, + "Status": null + }, + "Body": "AQIDBAU=", + "IsBase64Encoded": true +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayHttpApiV2ProxyResponse_StatusCodeAsFloat_ReturnsInternalServerError.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayHttpApiV2ProxyResponse_StatusCodeAsFloat_ReturnsInternalServerError.json new file mode 100644 index 000000000..9c10b683b --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayHttpApiV2ProxyResponse_StatusCodeAsFloat_ReturnsInternalServerError.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 500, + "Content": "{\u0022message\u0022:\u0022Internal Server Error\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:21 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJp2jd_vHcEPSQ=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "35" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayHttpApiV2ProxyResponse_ValidResponse_MatchesDirectConversion.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayHttpApiV2ProxyResponse_ValidResponse_MatchesDirectConversion.json new file mode 100644 index 000000000..709feb687 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayHttpApiV2ProxyResponse_ValidResponse_MatchesDirectConversion.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022Hello, World!\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:21 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJpvgQCvHcEPjA=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "27" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_InvalidJson_ReturnsErrorResponseHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_InvalidJson_ReturnsErrorResponseHttpV1.json new file mode 100644 index 000000000..b1aee45f3 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_InvalidJson_ReturnsErrorResponseHttpV1.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 500, + "Content": "{\u0022message\u0022:\u0022Internal Server Error\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:20 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJpqjdkvHcESPQ=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "35" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_InvalidJson_ReturnsErrorResponseRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_InvalidJson_ReturnsErrorResponseRest.json new file mode 100644 index 000000000..581139ae6 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_InvalidJson_ReturnsErrorResponseRest.json @@ -0,0 +1,29 @@ +{ + "StatusCode": 502, + "Content": "{\u0022message\u0022:\u0022Internal server error\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:20 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "5f0c2904-275c-4054-9516-bfb8d083ca5c" + ], + "x-amz-apigw-id": [ + "JCJpsEPhPHcEYng=" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd8970-7b93429f14d1f5622f3dbbdd;Parent=1b82af92b8de065c;Sampled=0;Lineage=1:6e5eb7d9:0" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "35" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionHttpV1.json new file mode 100644 index 000000000..2a69734cc --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionHttpV1.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022Hello, World!\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:21 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJp0hfdvHcEStw=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "27" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionInvalid_JSON_Partial_Object.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionInvalid_JSON_Partial_Object.json new file mode 100644 index 000000000..ebc67aa47 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionInvalid_JSON_Partial_Object.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "{\u0022name\u0022: \u0022John Doe\u0022, \u0022age\u0022:", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:19 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJpejtwvHcEP4Q=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "27" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionJSON_Array.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionJSON_Array.json new file mode 100644 index 000000000..cc7259d1e --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionJSON_Array.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "[1, 2, 3, 4, 5]", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:18 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJpZjR6vHcEP8g=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "15" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionRest.json new file mode 100644 index 000000000..338542cac --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionRest.json @@ -0,0 +1,29 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022Hello, World!\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:21 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "f85bec66-ed59-4668-a56e-ee22ffeb64b4" + ], + "x-amz-apigw-id": [ + "JCJpxEpoPHcENcQ=" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd8971-0b7317956f1709703a7ac1b6;Parent=7bf631ebae3cc567;Sampled=0;Lineage=1:6e5eb7d9:0" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "27" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionValid_JSON_Object.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionValid_JSON_Object.json new file mode 100644 index 000000000..920c36e31 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionValid_JSON_Object.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "{\u0022name\u0022: \u0022John Doe\u0022, \u0022age\u0022: 30}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:19 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJpbg4wPHcEPeA=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "31" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionboolean.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionboolean.json new file mode 100644 index 000000000..0231edb25 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionboolean.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "true", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:20 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJplh_ePHcEP_Q=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "4" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionempty_string.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionempty_string.json new file mode 100644 index 000000000..5677c163c --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionempty_string.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:18 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJpPi4DvHcEPVw=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "0" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionjson_special.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionjson_special.json new file mode 100644 index 000000000..78417df32 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionjson_special.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "Special \u0022quoted\u0022 text", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:18 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJpXh5MvHcEP2Q=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "21" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionnumber.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionnumber.json new file mode 100644 index 000000000..d248d5512 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionnumber.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "42", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:18 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJpUgEovHcEPXQ=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "2" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionstring.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionstring.json new file mode 100644 index 000000000..21d65a329 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionstring.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "Hello, World!", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:20 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJpngwcPHcEP7A=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "13" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionstring_spaces.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionstring_spaces.json new file mode 100644 index 000000000..973d47910 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionstring_spaces.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "Hello, World!", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:19 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJpggYbvHcEQ_Q=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "13" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionstring_unescaped.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionstring_unescaped.json new file mode 100644 index 000000000..bab8bc76d --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/ToApiGatewayProxyResponse_ValidResponse_MatchesDirectConversionstring_unescaped.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "test", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:17:19 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCJpjjKCPHcEPYA=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "4" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_APIHeadersHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_APIHeadersHttpV1.json new file mode 100644 index 000000000..00ee8e260 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_APIHeadersHttpV1.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "Test body", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:19 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCIn6iOMPHcEP2Q=" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Content-Length": [ + "9" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_APIHeadersRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_APIHeadersRest.json new file mode 100644 index 000000000..3668d5889 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_APIHeadersRest.json @@ -0,0 +1,29 @@ +{ + "StatusCode": 200, + "Content": "Test body", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:22 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "224e0b2e-0dfa-481a-ad65-b647686cb362" + ], + "x-amz-apigw-id": [ + "JCIoWGiIvHcEsxg=" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd87ce-39b57ec370e7a7551e8f43db;Parent=752c273a1dbf0dc3;Sampled=0;Lineage=1:6e5eb7d9:0" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "9" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_CombinesSingleAndMultiValueHeadersHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_CombinesSingleAndMultiValueHeadersHttpV1.json new file mode 100644 index 000000000..232116d82 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_CombinesSingleAndMultiValueHeadersHttpV1.json @@ -0,0 +1,35 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022With Combined Headers\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:18 GMT" + ], + "Connection": [ + "keep-alive" + ], + "X-Custom-Header": [ + "single-value" + ], + "Combined-Header": [ + "single-value", + "multi-value1", + "multi-value2" + ], + "X-Multi-Header": [ + "multi-value1", + "multi-value2" + ], + "Apigw-Requestid": [ + "JCInwiC6PHcEPqA=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "35" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_CombinesSingleAndMultiValueHeadersRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_CombinesSingleAndMultiValueHeadersRest.json new file mode 100644 index 000000000..7ace4927b --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_CombinesSingleAndMultiValueHeadersRest.json @@ -0,0 +1,41 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022With Combined Headers\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:21 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "81f577be-40b5-4090-b5fd-fbd5864d11de" + ], + "x-amz-apigw-id": [ + "JCIoNGABvHcEdvw=" + ], + "X-Custom-Header": [ + "single-value" + ], + "Combined-Header": [ + "multi-value1", + "multi-value2", + "single-value" + ], + "X-Multi-Header": [ + "multi-value1", + "multi-value2" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd87cd-7c1728c00ed2a9ab47410203;Parent=2c9d25f546eb4264;Sampled=0;Lineage=1:6e5eb7d9:0" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "35" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_DefaultsToCorrectContentTYpeHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_DefaultsToCorrectContentTYpeHttpV1.json new file mode 100644 index 000000000..1992983f0 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_DefaultsToCorrectContentTYpeHttpV1.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "Hello, World!", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:18 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCInsioPPHcEP-w=" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Content-Length": [ + "13" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_DefaultsToCorrectContentTYpeRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_DefaultsToCorrectContentTYpeRest.json new file mode 100644 index 000000000..e97915691 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_DefaultsToCorrectContentTYpeRest.json @@ -0,0 +1,29 @@ +{ + "StatusCode": 200, + "Content": "Hello, World!", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:21 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "8ec8d88e-4536-4e8e-a5f2-28877a4cc5b2" + ], + "x-amz-apigw-id": [ + "JCIoIEenvHcEGNA=" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd87cd-548ca2e2768cf411287e4266;Parent=3389559e9a470afb;Sampled=0;Lineage=1:6e5eb7d9:0" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "13" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_HandlesHeadersCorrectlyHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_HandlesHeadersCorrectlyHttpV1.json new file mode 100644 index 000000000..73e040eef --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_HandlesHeadersCorrectlyHttpV1.json @@ -0,0 +1,33 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022With Multiple Headers\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:18 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Myheader": [ + "test,other" + ], + "Anotherheader": [ + "secondvalue" + ], + "Headername": [ + "headervalue", + "headervalue2" + ], + "Apigw-Requestid": [ + "JCInujOivHcEPVw=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "35" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_HandlesHeadersCorrectlyRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_HandlesHeadersCorrectlyRest.json new file mode 100644 index 000000000..85e78f91d --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_HandlesHeadersCorrectlyRest.json @@ -0,0 +1,39 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022With Multiple Headers\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:21 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "60a62529-4157-4038-a796-1c57440bc5f6" + ], + "anotherheader": [ + "secondvalue" + ], + "x-amz-apigw-id": [ + "JCIoKGHFPHcEaIQ=" + ], + "headername": [ + "headervalue", + "headervalue2" + ], + "myheader": [ + "test,other" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd87cd-619e118449cb17b736abd8bc;Parent=23e83a8204296606;Sampled=0;Lineage=1:6e5eb7d9:0" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "35" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_HandlesZeroStatusCodeHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_HandlesZeroStatusCodeHttpV1.json new file mode 100644 index 000000000..5ebcb1caa --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_HandlesZeroStatusCodeHttpV1.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 500, + "Content": "{\u0022message\u0022:\u0022Internal Server Error\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:19 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCIn1jYWPHcEPYA=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "35" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_HandlesZeroStatusCodeRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_HandlesZeroStatusCodeRest.json new file mode 100644 index 000000000..75c06db80 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_HandlesZeroStatusCodeRest.json @@ -0,0 +1,29 @@ +{ + "StatusCode": 502, + "Content": "{\u0022message\u0022: \u0022Internal server error\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:22 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "2f7d670c-f2d1-4b0b-b3fc-ebf72f7ef67a" + ], + "x-amzn-ErrorType": [ + "InternalServerErrorException" + ], + "x-amz-apigw-id": [ + "JCIoRE-lvHcEpYQ=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "36" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsBodyNonBase64HttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsBodyNonBase64HttpV1.json new file mode 100644 index 000000000..08212191b --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsBodyNonBase64HttpV1.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022Hello, World!\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:18 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCInpgsKvHcEP2A=" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Content-Length": [ + "27" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsBodyNonBase64Rest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsBodyNonBase64Rest.json new file mode 100644 index 000000000..c615513ab --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsBodyNonBase64Rest.json @@ -0,0 +1,29 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022Hello, World!\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:21 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "d8da867e-ae50-4e05-83ce-e5165c9e9586" + ], + "x-amz-apigw-id": [ + "JCIoGGcWvHcEm5Q=" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd87cd-7a12883c7a049d3c313ce630;Parent=5c509f06483696a4;Sampled=0;Lineage=1:6e5eb7d9:0" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "27" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsContentLengthHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsContentLengthHttpV1.json new file mode 100644 index 000000000..f65b37cf9 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsContentLengthHttpV1.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022Hello, World!\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:19 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCInzjkJPHcEP8g=" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Content-Length": [ + "27" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsContentLengthRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsContentLengthRest.json new file mode 100644 index 000000000..b88537014 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsContentLengthRest.json @@ -0,0 +1,29 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022Hello, World!\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:21 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "98e123eb-4ffc-42c3-b4e6-9223b85c852c" + ], + "x-amz-apigw-id": [ + "JCIoPGCVPHcEdvw=" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd87cd-114aa6b332dc7ac812d45ba5;Parent=7d089a4e04c6b482;Sampled=0;Lineage=1:6e5eb7d9:0" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "27" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsCorrectStatusCodeHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsCorrectStatusCodeHttpV1.json new file mode 100644 index 000000000..c43af4752 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsCorrectStatusCodeHttpV1.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 201, + "Content": "{\u0022message\u0022:\u0022Created\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:17 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCIniiXWPHcEPHA=" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Content-Length": [ + "21" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsCorrectStatusCodeRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsCorrectStatusCodeRest.json new file mode 100644 index 000000000..c1a989ce4 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsCorrectStatusCodeRest.json @@ -0,0 +1,29 @@ +{ + "StatusCode": 201, + "Content": "{\u0022message\u0022:\u0022Created\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:20 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "a5e7a2f4-03ad-41a0-838a-ce70db8c1e6f" + ], + "x-amz-apigw-id": [ + "JCIn_HvOvHcEdIQ=" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd87cc-469a8eac2157be907b932892;Parent=73bab1a764c16624;Sampled=0;Lineage=1:6e5eb7d9:0" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "21" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsHeadersHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsHeadersHttpV1.json new file mode 100644 index 000000000..cc6a435fb --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsHeadersHttpV1.json @@ -0,0 +1,26 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022With Headers\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:17 GMT" + ], + "Connection": [ + "keep-alive" + ], + "X-Custom-Header": [ + "CustomValue" + ], + "Apigw-Requestid": [ + "JCInkj0BvHcEPwg=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "26" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsHeadersRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsHeadersRest.json new file mode 100644 index 000000000..7e1ce9a5e --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsHeadersRest.json @@ -0,0 +1,32 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022With Headers\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:20 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "f373ffe9-0c95-4dea-939c-c4b4107f5b2b" + ], + "x-amz-apigw-id": [ + "JCIoBGy1vHcEByg=" + ], + "X-Custom-Header": [ + "CustomValue" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd87cc-1c44b97f31e2497b61a72ba5;Parent=24865536df2a4944;Sampled=0;Lineage=1:6e5eb7d9:0" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "26" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsMultiValueHeadersHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsMultiValueHeadersHttpV1.json new file mode 100644 index 000000000..28606f801 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsMultiValueHeadersHttpV1.json @@ -0,0 +1,27 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022With MultiValueHeaders\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:17 GMT" + ], + "Connection": [ + "keep-alive" + ], + "X-Multi-Header": [ + "Value1", + "Value2" + ], + "Apigw-Requestid": [ + "JCInniZlPHcEP1g=" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Content-Length": [ + "36" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsMultiValueHeadersRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsMultiValueHeadersRest.json new file mode 100644 index 000000000..8d5ab9c78 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SetsMultiValueHeadersRest.json @@ -0,0 +1,33 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022With MultiValueHeaders\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:20 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "4e98bdf1-03fa-4601-b886-5f8b40f4798e" + ], + "x-amz-apigw-id": [ + "JCIoDGzFPHcEDbA=" + ], + "X-Multi-Header": [ + "Value1", + "Value2" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd87cc-7da269a52a5e078b4c255580;Parent=5c6edcf395e1147f;Sampled=0;Lineage=1:6e5eb7d9:0" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "36" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SimpleGetRequestHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SimpleGetRequestHttpV1.json new file mode 100644 index 000000000..fee9c6b79 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SimpleGetRequestHttpV1.json @@ -0,0 +1,108 @@ +{ + "Resource": "/test1/api/users/{userId}/orders", + "Path": "/test1/api/users/123/orders", + "HttpMethod": "POST", + "Headers": { + "Content-Length": "0", + "Content-Type": "text/plain; charset=utf-8", + "Cookie": "session=abc123; theme=dark", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "User-Agent": "TestAgent", + "X-Amzn-Trace-Id": "Root=1-67fd8937-6cd643e155b4258a556982c5", + "X-Custom-Header": "value1", + "X-Forwarded-For": "IP_ADDRESS", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "MultiValueHeaders": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Cookie": [ + "session=abc123; theme=dark" + ], + "Host": ["EXAMPLE.execute-api.REGION.amazonaws.com"], + "User-Agent": [ + "TestAgent" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd8937-6cd643e155b4258a556982c5" + ], + "X-Custom-Header": [ + "value1" + ], + "X-Forwarded-For": ["IP_ADDRESS"], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "QueryStringParameters": { + "status": "pending", + "tag": "urgent" + }, + "MultiValueQueryStringParameters": { + "status": [ + "pending" + ], + "tag": [ + "important", + "urgent" + ] + }, + "PathParameters": { + "userId": "123" + }, + "StageVariables": null, + "RequestContext": { + "Path": "/test1/api/users/123/orders", + "AccountId": "ACCOUNT_ID", + "ResourceId": "POST /test1/api/users/{userId}/orders", + "Stage": "$default", + "RequestId": "JCJgriq-vHcEPMg=", + "Identity": { + "CognitoIdentityPoolId": null, + "AccountId": null, + "CognitoIdentityId": null, + "Caller": null, + "ApiKey": null, + "ApiKeyId": null, + "AccessKey": null, + "SourceIp": "IP_ADDRESS", + "CognitoAuthenticationType": null, + "CognitoAuthenticationProvider": null, + "UserArn": null, + "UserAgent": "TestAgent", + "User": null, + "ClientCert": null + }, + "ResourcePath": "/test1/api/users/{userId}/orders", + "HttpMethod": "POST", + "ApiId": "EXAMPLE", + "ExtendedRequestId": "JCJgriq-vHcEPMg=", + "ConnectionId": null, + "ConnectedAt": 0, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "EventType": null, + "MessageId": null, + "RouteKey": null, + "Authorizer": null, + "OperationName": null, + "Error": null, + "IntegrationLatency": null, + "MessageDirection": null, + "RequestTime": "14/Apr/2025:22:16:23 \u002B0000", + "RequestTimeEpoch": 1744668983126, + "Status": null + }, + "Body": null, + "IsBase64Encoded": false +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SimpleGetRequestRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SimpleGetRequestRest.json new file mode 100644 index 000000000..4abba0544 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SimpleGetRequestRest.json @@ -0,0 +1,104 @@ +{ + "Resource": "/test1/api/users/{userId}/orders", + "Path": "/test1/api/users/123/orders", + "HttpMethod": "POST", + "Headers": { + "Content-Type": "text/plain; charset=utf-8", + "Cookie": "session=abc123; theme=dark", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "User-Agent": "TestAgent", + "X-Amzn-Trace-Id": "Root=1-67fd87f5-7643e6a875203f06341af04f", + "X-Custom-Header": "value1", + "X-Forwarded-For": "IP_ADDRESS", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "MultiValueHeaders": { + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Cookie": [ + "session=abc123; theme=dark" + ], + "Host": ["EXAMPLE.execute-api.REGION.amazonaws.com"], + "User-Agent": [ + "TestAgent" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd87f5-7643e6a875203f06341af04f" + ], + "X-Custom-Header": [ + "value1" + ], + "X-Forwarded-For": ["IP_ADDRESS"], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "QueryStringParameters": { + "status": "pending", + "tag": "urgent" + }, + "MultiValueQueryStringParameters": { + "status": [ + "pending" + ], + "tag": [ + "important", + "urgent" + ] + }, + "PathParameters": { + "userId": "123" + }, + "StageVariables": null, + "RequestContext": { + "Path": "/test/test1/api/users/123/orders", + "AccountId": "ACCOUNT_ID", + "ResourceId": "RESOURCE_ID", + "Stage": "test", + "RequestId": "4acd49e9-9210-4d04-8e04-0f26a4e92537", + "Identity": { + "CognitoIdentityPoolId": null, + "AccountId": null, + "CognitoIdentityId": null, + "Caller": null, + "ApiKey": null, + "ApiKeyId": null, + "AccessKey": null, + "SourceIp": "IP_ADDRESS", + "CognitoAuthenticationType": null, + "CognitoAuthenticationProvider": null, + "UserArn": null, + "UserAgent": "TestAgent", + "User": null, + "ClientCert": null + }, + "ResourcePath": "/test1/api/users/{userId}/orders", + "HttpMethod": "POST", + "ApiId": "EXAMPLE", + "ExtendedRequestId": "JCIudEUfPHcEhog=", + "ConnectionId": null, + "ConnectedAt": 0, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "EventType": null, + "MessageId": null, + "RouteKey": null, + "Authorizer": null, + "OperationName": null, + "Error": null, + "IntegrationLatency": null, + "MessageDirection": null, + "RequestTime": "14/Apr/2025:22:11:01 \u002B0000", + "RequestTimeEpoch": 1744668661718, + "Status": null + }, + "Body": null, + "IsBase64Encoded": false +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SimpleJsonResponseHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SimpleJsonResponseHttpV1.json new file mode 100644 index 000000000..c6815450c --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SimpleJsonResponseHttpV1.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022Hello, World!\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:17 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCInggGjPHcEP3w=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "27" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SimpleJsonResponseRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SimpleJsonResponseRest.json new file mode 100644 index 000000000..67dcec0db --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SimpleJsonResponseRest.json @@ -0,0 +1,29 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022Hello, World!\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:20 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "8af0f750-fdfc-4b2e-bf85-0218623df93e" + ], + "x-amz-apigw-id": [ + "JCIn8ELXPHcEC1A=" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd87cc-09a86408172acd0726c798d5;Parent=136d0e43afa6f0c4;Sampled=0;Lineage=1:6e5eb7d9:0" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "27" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SpecialCharactersInPathHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SpecialCharactersInPathHttpV1.json new file mode 100644 index 000000000..ef2ccd81a --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SpecialCharactersInPathHttpV1.json @@ -0,0 +1,86 @@ +{ + "Resource": "/test5/api/users/{username}/orders/{orderName}", + "Path": "/test5/api/users/****%20Doe/orders/Summer%20Sale%202023", + "HttpMethod": "POST", + "Headers": { + "Content-Length": "0", + "Content-Type": "text/plain; charset=utf-8", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "X-Amzn-Trace-Id": "Root=1-67fd895b-766061a83ad2b27b1782b470", + "X-Forwarded-For": "IP_ADDRESS", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "MultiValueHeaders": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Host": ["EXAMPLE.execute-api.REGION.amazonaws.com"], + "X-Amzn-Trace-Id": [ + "Root=1-67fd895b-766061a83ad2b27b1782b470" + ], + "X-Forwarded-For": ["IP_ADDRESS"], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "QueryStringParameters": null, + "MultiValueQueryStringParameters": null, + "PathParameters": { + "orderName": "Summer Sale 2023", + "username": "**** Doe" + }, + "StageVariables": null, + "RequestContext": { + "Path": "/test5/api/users/**** Doe/orders/Summer Sale 2023", + "AccountId": "ACCOUNT_ID", + "ResourceId": "POST /test5/api/users/{username}/orders/{orderName}", + "Stage": "$default", + "RequestId": "JCJmXjUAPHcEP4w=", + "Identity": { + "CognitoIdentityPoolId": null, + "AccountId": null, + "CognitoIdentityId": null, + "Caller": null, + "ApiKey": null, + "ApiKeyId": null, + "AccessKey": null, + "SourceIp": "IP_ADDRESS", + "CognitoAuthenticationType": null, + "CognitoAuthenticationProvider": null, + "UserArn": null, + "UserAgent": "", + "User": null, + "ClientCert": null + }, + "ResourcePath": "/test5/api/users/{username}/orders/{orderName}", + "HttpMethod": "POST", + "ApiId": "EXAMPLE", + "ExtendedRequestId": "JCJmXjUAPHcEP4w=", + "ConnectionId": null, + "ConnectedAt": 0, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "EventType": null, + "MessageId": null, + "RouteKey": null, + "Authorizer": null, + "OperationName": null, + "Error": null, + "IntegrationLatency": null, + "MessageDirection": null, + "RequestTime": "14/Apr/2025:22:16:59 \u002B0000", + "RequestTimeEpoch": 1744669019524, + "Status": null + }, + "Body": null, + "IsBase64Encoded": false +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SpecialCharactersInPathRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SpecialCharactersInPathRest.json new file mode 100644 index 000000000..cee2b253a --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_SpecialCharactersInPathRest.json @@ -0,0 +1,82 @@ +{ + "Resource": "/test5/api/users/{username}/orders/{orderName}", + "Path": "/test5/api/users/****%20Doe/orders/Summer%20Sale%202023", + "HttpMethod": "POST", + "Headers": { + "Content-Type": "text/plain; charset=utf-8", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "X-Amzn-Trace-Id": "Root=1-67fd8860-650d70a10ef73c6b16106e8d", + "X-Forwarded-For": "IP_ADDRESS", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "MultiValueHeaders": { + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Host": ["EXAMPLE.execute-api.REGION.amazonaws.com"], + "X-Amzn-Trace-Id": [ + "Root=1-67fd8860-650d70a10ef73c6b16106e8d" + ], + "X-Forwarded-For": ["IP_ADDRESS"], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "QueryStringParameters": null, + "MultiValueQueryStringParameters": null, + "PathParameters": { + "username": "****%20Doe", + "orderName": "Summer%20Sale%202023" + }, + "StageVariables": null, + "RequestContext": { + "Path": "/test/test5/api/users/****%20Doe/orders/Summer%20Sale%202023", + "AccountId": "ACCOUNT_ID", + "ResourceId": "RESOURCE_ID", + "Stage": "test", + "RequestId": "dbcb1121-909c-4d1a-aff3-958e94138b79", + "Identity": { + "CognitoIdentityPoolId": null, + "AccountId": null, + "CognitoIdentityId": null, + "Caller": null, + "ApiKey": null, + "ApiKeyId": null, + "AccessKey": null, + "SourceIp": "IP_ADDRESS", + "CognitoAuthenticationType": null, + "CognitoAuthenticationProvider": null, + "UserArn": null, + "UserAgent": null, + "User": null, + "ClientCert": null + }, + "ResourcePath": "/test5/api/users/{username}/orders/{orderName}", + "HttpMethod": "POST", + "ApiId": "EXAMPLE", + "ExtendedRequestId": "JCI_JHpQvHcEhVw=", + "ConnectionId": null, + "ConnectedAt": 0, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "EventType": null, + "MessageId": null, + "RouteKey": null, + "Authorizer": null, + "OperationName": null, + "Error": null, + "IntegrationLatency": null, + "MessageDirection": null, + "RequestTime": "14/Apr/2025:22:12:48 \u002B0000", + "RequestTimeEpoch": 1744668768586, + "Status": null + }, + "Body": null, + "IsBase64Encoded": false +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UnicodeCharactersInPathHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UnicodeCharactersInPathHttpV1.json new file mode 100644 index 000000000..0fe4ee2cf --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UnicodeCharactersInPathHttpV1.json @@ -0,0 +1,86 @@ +{ + "Resource": "/test6/api/products/{productName}/reviews/{reviewTitle}", + "Path": "/test6/api/products/%E2%98%95%20Coffee/reviews/%F0%9F%98%8A%20Happy", + "HttpMethod": "POST", + "Headers": { + "Content-Length": "0", + "Content-Type": "text/plain; charset=utf-8", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "X-Amzn-Trace-Id": "Root=1-67fd896c-70b58b4501a8b49c5ef26f03", + "X-Forwarded-For": "IP_ADDRESS", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "MultiValueHeaders": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Host": ["EXAMPLE.execute-api.REGION.amazonaws.com"], + "X-Amzn-Trace-Id": [ + "Root=1-67fd896c-70b58b4501a8b49c5ef26f03" + ], + "X-Forwarded-For": ["IP_ADDRESS"], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "QueryStringParameters": null, + "MultiValueQueryStringParameters": null, + "PathParameters": { + "productName": "\u2615 Coffee", + "reviewTitle": "\uD83D\uDE0A Happy" + }, + "StageVariables": null, + "RequestContext": { + "Path": "/test6/api/products/\u2615 Coffee/reviews/\uD83D\uDE0A Happy", + "AccountId": "ACCOUNT_ID", + "ResourceId": "POST /test6/api/products/{productName}/reviews/{reviewTitle}", + "Stage": "$default", + "RequestId": "JCJpDi3HPHcESow=", + "Identity": { + "CognitoIdentityPoolId": null, + "AccountId": null, + "CognitoIdentityId": null, + "Caller": null, + "ApiKey": null, + "ApiKeyId": null, + "AccessKey": null, + "SourceIp": "IP_ADDRESS", + "CognitoAuthenticationType": null, + "CognitoAuthenticationProvider": null, + "UserArn": null, + "UserAgent": "", + "User": null, + "ClientCert": null + }, + "ResourcePath": "/test6/api/products/{productName}/reviews/{reviewTitle}", + "HttpMethod": "POST", + "ApiId": "EXAMPLE", + "ExtendedRequestId": "JCJpDi3HPHcESow=", + "ConnectionId": null, + "ConnectedAt": 0, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "EventType": null, + "MessageId": null, + "RouteKey": null, + "Authorizer": null, + "OperationName": null, + "Error": null, + "IntegrationLatency": null, + "MessageDirection": null, + "RequestTime": "14/Apr/2025:22:17:16 \u002B0000", + "RequestTimeEpoch": 1744669036726, + "Status": null + }, + "Body": null, + "IsBase64Encoded": false +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UnicodeCharactersInPathRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UnicodeCharactersInPathRest.json new file mode 100644 index 000000000..c7b18fe91 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UnicodeCharactersInPathRest.json @@ -0,0 +1,82 @@ +{ + "Resource": "/test6/api/products/{productName}/reviews/{reviewTitle}", + "Path": "/test6/api/products/%E2%98%95%20Coffee/reviews/%F0%9F%98%8A%20Happy", + "HttpMethod": "POST", + "Headers": { + "Content-Type": "text/plain; charset=utf-8", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "X-Amzn-Trace-Id": "Root=1-67fd8892-563c02fe4dcb438f51739a1b", + "X-Forwarded-For": "IP_ADDRESS", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "MultiValueHeaders": { + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Host": ["EXAMPLE.execute-api.REGION.amazonaws.com"], + "X-Amzn-Trace-Id": [ + "Root=1-67fd8892-563c02fe4dcb438f51739a1b" + ], + "X-Forwarded-For": ["IP_ADDRESS"], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "QueryStringParameters": null, + "MultiValueQueryStringParameters": null, + "PathParameters": { + "productName": "%E2%98%95%20Coffee", + "reviewTitle": "%F0%9F%98%8A%20Happy" + }, + "StageVariables": null, + "RequestContext": { + "Path": "/test/test6/api/products/%E2%98%95%20Coffee/reviews/%F0%9F%98%8A%20Happy", + "AccountId": "ACCOUNT_ID", + "ResourceId": "RESOURCE_ID", + "Stage": "test", + "RequestId": "5005340a-1104-4054-98d0-afda9e163eb1", + "Identity": { + "CognitoIdentityPoolId": null, + "AccountId": null, + "CognitoIdentityId": null, + "Caller": null, + "ApiKey": null, + "ApiKeyId": null, + "AccessKey": null, + "SourceIp": "IP_ADDRESS", + "CognitoAuthenticationType": null, + "CognitoAuthenticationProvider": null, + "UserArn": null, + "UserAgent": null, + "User": null, + "ClientCert": null + }, + "ResourcePath": "/test6/api/products/{productName}/reviews/{reviewTitle}", + "HttpMethod": "POST", + "ApiId": "EXAMPLE", + "ExtendedRequestId": "JCJG5GmOvHcEK7g=", + "ConnectionId": null, + "ConnectedAt": 0, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "EventType": null, + "MessageId": null, + "RouteKey": null, + "Authorizer": null, + "OperationName": null, + "Error": null, + "IntegrationLatency": null, + "MessageDirection": null, + "RequestTime": "14/Apr/2025:22:13:38 \u002B0000", + "RequestTimeEpoch": 1744668818115, + "Status": null + }, + "Body": null, + "IsBase64Encoded": false +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UrlEncodedQueryStringHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UrlEncodedQueryStringHttpV1.json new file mode 100644 index 000000000..c6e29d61d --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UrlEncodedQueryStringHttpV1.json @@ -0,0 +1,94 @@ +{ + "Resource": "/test4/api/search", + "Path": "/test4/api/search", + "HttpMethod": "POST", + "Headers": { + "Content-Length": "0", + "Content-Type": "text/plain; charset=utf-8", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "X-Amzn-Trace-Id": "Root=1-67fd894d-3c512d985fc0f8aa38cbeaac", + "X-Forwarded-For": "IP_ADDRESS", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "MultiValueHeaders": { + "Content-Length": [ + "0" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Host": ["EXAMPLE.execute-api.REGION.amazonaws.com"], + "X-Amzn-Trace-Id": [ + "Root=1-67fd894d-3c512d985fc0f8aa38cbeaac" + ], + "X-Forwarded-For": ["IP_ADDRESS"], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "QueryStringParameters": { + "q": "Hello World", + "tag": ".NET Core" + }, + "MultiValueQueryStringParameters": { + "q": [ + "Hello World" + ], + "tag": [ + "C# Programming", + ".NET Core" + ] + }, + "PathParameters": null, + "StageVariables": null, + "RequestContext": { + "Path": "/test4/api/search", + "AccountId": "ACCOUNT_ID", + "ResourceId": "POST /test4/api/search", + "Stage": "$default", + "RequestId": "JCJkHhmUPHcEPXA=", + "Identity": { + "CognitoIdentityPoolId": null, + "AccountId": null, + "CognitoIdentityId": null, + "Caller": null, + "ApiKey": null, + "ApiKeyId": null, + "AccessKey": null, + "SourceIp": "IP_ADDRESS", + "CognitoAuthenticationType": null, + "CognitoAuthenticationProvider": null, + "UserArn": null, + "UserAgent": "", + "User": null, + "ClientCert": null + }, + "ResourcePath": "/test4/api/search", + "HttpMethod": "POST", + "ApiId": "EXAMPLE", + "ExtendedRequestId": "JCJkHhmUPHcEPXA=", + "ConnectionId": null, + "ConnectedAt": 0, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "EventType": null, + "MessageId": null, + "RouteKey": null, + "Authorizer": null, + "OperationName": null, + "Error": null, + "IntegrationLatency": null, + "MessageDirection": null, + "RequestTime": "14/Apr/2025:22:16:45 \u002B0000", + "RequestTimeEpoch": 1744669005175, + "Status": null + }, + "Body": null, + "IsBase64Encoded": false +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UrlEncodedQueryStringRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UrlEncodedQueryStringRest.json new file mode 100644 index 000000000..11caab563 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UrlEncodedQueryStringRest.json @@ -0,0 +1,90 @@ +{ + "Resource": "/test4/api/search", + "Path": "/test4/api/search", + "HttpMethod": "POST", + "Headers": { + "Content-Type": "text/plain; charset=utf-8", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "X-Amzn-Trace-Id": "Root=1-67fd8822-75aa8f5e7a30566958eef084", + "X-Forwarded-For": "IP_ADDRESS", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "MultiValueHeaders": { + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Host": ["EXAMPLE.execute-api.REGION.amazonaws.com"], + "X-Amzn-Trace-Id": [ + "Root=1-67fd8822-75aa8f5e7a30566958eef084" + ], + "X-Forwarded-For": ["IP_ADDRESS"], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "QueryStringParameters": { + "q": "Hello World", + "tag": ".NET Core" + }, + "MultiValueQueryStringParameters": { + "q": [ + "Hello World" + ], + "tag": [ + "C# Programming", + ".NET Core" + ] + }, + "PathParameters": null, + "StageVariables": null, + "RequestContext": { + "Path": "/test/test4/api/search", + "AccountId": "ACCOUNT_ID", + "ResourceId": "RESOURCE_ID", + "Stage": "test", + "RequestId": "bc5e4f00-8cfb-4473-83c7-e6e1735ee418", + "Identity": { + "CognitoIdentityPoolId": null, + "AccountId": null, + "CognitoIdentityId": null, + "Caller": null, + "ApiKey": null, + "ApiKeyId": null, + "AccessKey": null, + "SourceIp": "IP_ADDRESS", + "CognitoAuthenticationType": null, + "CognitoAuthenticationProvider": null, + "UserArn": null, + "UserAgent": null, + "User": null, + "ClientCert": null + }, + "ResourcePath": "/test4/api/search", + "HttpMethod": "POST", + "ApiId": "EXAMPLE", + "ExtendedRequestId": "JCI1hFtPPHcEDUw=", + "ConnectionId": null, + "ConnectedAt": 0, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "EventType": null, + "MessageId": null, + "RouteKey": null, + "Authorizer": null, + "OperationName": null, + "Error": null, + "IntegrationLatency": null, + "MessageDirection": null, + "RequestTime": "14/Apr/2025:22:11:46 \u002B0000", + "RequestTimeEpoch": 1744668706991, + "Status": null + }, + "Body": null, + "IsBase64Encoded": false +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UsesProvidedContentTypeHttpV1.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UsesProvidedContentTypeHttpV1.json new file mode 100644 index 000000000..4b5a1c50d --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UsesProvidedContentTypeHttpV1.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "Hello, World!", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:19 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCIn4gyyPHcEP6g=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "13" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UsesProvidedContentTypeRest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UsesProvidedContentTypeRest.json new file mode 100644 index 000000000..367da5bfe --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V1_UsesProvidedContentTypeRest.json @@ -0,0 +1,29 @@ +{ + "StatusCode": 200, + "Content": "Hello, World!", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:22 GMT" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "244fe60b-0535-4085-ad6a-eddd235e4a6d" + ], + "x-amz-apigw-id": [ + "JCIoUEtDPHcEsnA=" + ], + "X-Amzn-Trace-Id": [ + "Root=1-67fd87ce-3a13e69076acb7a07dacd9c3;Parent=48a841fdc26e5fb0;Sampled=0;Lineage=1:6e5eb7d9:0" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "13" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_BinaryContent.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_BinaryContent.json new file mode 100644 index 000000000..32c540c68 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_BinaryContent.json @@ -0,0 +1,46 @@ +{ + "Version": "2.0", + "RouteKey": "POST /test11/api/users/{userId}/avatar", + "RawPath": "/test11/api/users/123/avatar", + "RawQueryString": "", + "Cookies": null, + "Headers": { + "content-length": "5", + "content-type": "application/octet-stream", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "x-amzn-trace-id": "Root=1-67fd88b8-2f95df5a03c1bc3a6168b6d8", + "X-Forwarded-For": "IP_ADDRESS", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "QueryStringParameters": null, + "RequestContext": { + "AccountId": "ACCOUNT_ID", + "ApiId": "EXAMPLE", + "Authorizer": null, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "Http": { + "Method": "POST", + "Path": "/test11/api/users/123/avatar", + "Protocol": "HTTP/1.1", + "SourceIp": "IP_ADDRESS", + "UserAgent": "" + }, + "RequestId": "JCJM3j95PHcEJHg=", + "RouteId": null, + "RouteKey": "POST /test11/api/users/{userId}/avatar", + "Stage": "$default", + "Time": "14/Apr/2025:22:14:16 \u002B0000", + "TimeEpoch": 1744668856359, + "Authentication": null + }, + "Body": "AQIDBAU=", + "PathParameters": { + "userId": "123" + }, + "IsBase64Encoded": true, + "StageVariables": null +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_DefaultsToTextPlainContentType.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_DefaultsToTextPlainContentType.json new file mode 100644 index 000000000..9abdb6221 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_DefaultsToTextPlainContentType.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "Hello, World!", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:16 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCInWhFRvHcEJdA=" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Content-Length": [ + "13" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_DoesNotOverrideExplicitValues.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_DoesNotOverrideExplicitValues.json new file mode 100644 index 000000000..6ba107677 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_DoesNotOverrideExplicitValues.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 201, + "Content": "{\u0022key\u0022:\u0022value\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:16 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCInbgLtPHcEJIQ=" + ], + "Content-Type": [ + "application/xml" + ], + "Content-Length": [ + "15" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_HandlesHeadersCorrectly.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_HandlesHeadersCorrectly.json new file mode 100644 index 000000000..69ead7397 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_HandlesHeadersCorrectly.json @@ -0,0 +1,29 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022With Headers\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:16 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Myheader": [ + "test,shouldhavesecondvalue" + ], + "Anotherheader": [ + "secondvalue" + ], + "Apigw-Requestid": [ + "JCInYgIKPHcEJbQ=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "26" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_HandlesZeroStatusCode.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_HandlesZeroStatusCode.json new file mode 100644 index 000000000..2a18f512c --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_HandlesZeroStatusCode.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 500, + "Content": "{\u0022message\u0022:\u0022Internal Server Error\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:15 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCInMhIpPHcEJ3w=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "35" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_HttpAPIHeaders.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_HttpAPIHeaders.json new file mode 100644 index 000000000..a0c208646 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_HttpAPIHeaders.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "Test body", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:16 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCIndgObPHcEJVQ=" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Content-Length": [ + "9" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SetsBodyBase64.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SetsBodyBase64.json new file mode 100644 index 000000000..df8af3852 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SetsBodyBase64.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022Hello, API Gateway v2!\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:16 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCInTjApPHcEJhw=" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Content-Length": [ + "36" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SetsBodyNonBase64.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SetsBodyNonBase64.json new file mode 100644 index 000000000..2013cf99b --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SetsBodyNonBase64.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022Hello, API Gateway v2!\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:15 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCInRg47PHcEJXQ=" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Content-Length": [ + "36" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SetsCorrectStatusCode.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SetsCorrectStatusCode.json new file mode 100644 index 000000000..64039de16 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SetsCorrectStatusCode.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 201, + "Content": "{\u0022message\u0022:\u0022Created\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:15 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCInKgOZPHcEJhg=" + ], + "Content-Type": [ + "text/plain; charset=utf-8" + ], + "Content-Length": [ + "21" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SetsHeaders.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SetsHeaders.json new file mode 100644 index 000000000..c1e3a7f77 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SetsHeaders.json @@ -0,0 +1,26 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022With Headers\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:15 GMT" + ], + "Connection": [ + "keep-alive" + ], + "X-Custom-Header": [ + "CustomValue" + ], + "Apigw-Requestid": [ + "JCInPj6JPHcEJow=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "26" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SimpleGetRequest.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SimpleGetRequest.json new file mode 100644 index 000000000..0433a08b2 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SimpleGetRequest.json @@ -0,0 +1,55 @@ +{ + "Version": "2.0", + "RouteKey": "POST /test9/api/users/{userId}/orders", + "RawPath": "/test9/api/users/123/orders", + "RawQueryString": "status=pending\u0026tag=important\u0026tag=urgent", + "Cookies": [ + "session=abc123", + "theme=dark" + ], + "Headers": { + "accept": "text/html, application/json", + "content-length": "0", + "content-type": "text/plain; charset=utf-8", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "user-agent": "TestAgent", + "x-amzn-trace-id": "Root=1-67fd88a4-127eb8e773d2bb6f7df47ab6", + "x-custom-header": "value1", + "X-Forwarded-For": "IP_ADDRESS", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "QueryStringParameters": { + "status": "pending", + "tag": "important,urgent" + }, + "RequestContext": { + "AccountId": "ACCOUNT_ID", + "ApiId": "EXAMPLE", + "Authorizer": null, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "Http": { + "Method": "POST", + "Path": "/test9/api/users/123/orders", + "Protocol": "HTTP/1.1", + "SourceIp": "IP_ADDRESS", + "UserAgent": "TestAgent" + }, + "RequestId": "JCJJxhz4PHcEJgQ=", + "RouteId": null, + "RouteKey": "POST /test9/api/users/{userId}/orders", + "Stage": "$default", + "Time": "14/Apr/2025:22:13:56 \u002B0000", + "TimeEpoch": 1744668836573, + "Authentication": null + }, + "Body": null, + "PathParameters": { + "userId": "123" + }, + "IsBase64Encoded": false, + "StageVariables": null +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SimpleJsonResponse.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SimpleJsonResponse.json new file mode 100644 index 000000000..6f3b8cf3b --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SimpleJsonResponse.json @@ -0,0 +1,23 @@ +{ + "StatusCode": 200, + "Content": "{\u0022message\u0022:\u0022Hello, World!\u0022}", + "Headers": { + "Date": [ + "Mon, 14 Apr 2025 22:10:14 GMT" + ], + "Connection": [ + "keep-alive" + ], + "Apigw-Requestid": [ + "JCInHg8UPHcEJsA=" + ], + "Content-Type": [ + "application/json" + ], + "Content-Length": [ + "27" + ] + } +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SpecialCharactersInPath.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SpecialCharactersInPath.json new file mode 100644 index 000000000..06a10a2a7 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_SpecialCharactersInPath.json @@ -0,0 +1,47 @@ +{ + "Version": "2.0", + "RouteKey": "POST /test13/api/users/{username}/orders/{orderName}", + "RawPath": "/test13/api/users/**** Doe/orders/Summer Sale 2023", + "RawQueryString": "", + "Cookies": null, + "Headers": { + "content-length": "0", + "content-type": "text/plain; charset=utf-8", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "x-amzn-trace-id": "Root=1-67fd88db-518e8b296009540c739c0ca7", + "X-Forwarded-For": "IP_ADDRESS", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "QueryStringParameters": null, + "RequestContext": { + "AccountId": "ACCOUNT_ID", + "ApiId": "EXAMPLE", + "Authorizer": null, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "Http": { + "Method": "POST", + "Path": "/test13/api/users/**** Doe/orders/Summer Sale 2023", + "Protocol": "HTTP/1.1", + "SourceIp": "IP_ADDRESS", + "UserAgent": "" + }, + "RequestId": "JCJSViW5PHcESow=", + "RouteId": null, + "RouteKey": "POST /test13/api/users/{username}/orders/{orderName}", + "Stage": "$default", + "Time": "14/Apr/2025:22:14:51 \u002B0000", + "TimeEpoch": 1744668891302, + "Authentication": null + }, + "Body": null, + "PathParameters": { + "orderName": "Summer Sale 2023", + "username": "**** Doe" + }, + "IsBase64Encoded": false, + "StageVariables": null +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_UnicodeCharactersInPath.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_UnicodeCharactersInPath.json new file mode 100644 index 000000000..6796b4a8f --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_UnicodeCharactersInPath.json @@ -0,0 +1,47 @@ +{ + "Version": "2.0", + "RouteKey": "POST /test14/api/products/{productName}/reviews/{reviewTitle}", + "RawPath": "/test14/api/products/\u2615 Coffee/reviews/\uD83D\uDE0A Happy", + "RawQueryString": "", + "Cookies": null, + "Headers": { + "content-length": "0", + "content-type": "text/plain; charset=utf-8", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "x-amzn-trace-id": "Root=1-67fd88e9-487b329f49f6278e32bb642c", + "X-Forwarded-For": "IP_ADDRESS", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "QueryStringParameters": null, + "RequestContext": { + "AccountId": "ACCOUNT_ID", + "ApiId": "EXAMPLE", + "Authorizer": null, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "Http": { + "Method": "POST", + "Path": "/test14/api/products/\u2615 Coffee/reviews/\uD83D\uDE0A Happy", + "Protocol": "HTTP/1.1", + "SourceIp": "IP_ADDRESS", + "UserAgent": "" + }, + "RequestId": "JCJUkhKBvHcESVw=", + "RouteId": null, + "RouteKey": "POST /test14/api/products/{productName}/reviews/{reviewTitle}", + "Stage": "$default", + "Time": "14/Apr/2025:22:15:05 \u002B0000", + "TimeEpoch": 1744668905625, + "Authentication": null + }, + "Body": null, + "PathParameters": { + "productName": "\u2615 Coffee", + "reviewTitle": "\uD83D\uDE0A Happy" + }, + "IsBase64Encoded": false, + "StageVariables": null +} + + diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_UrlEncodedQueryString.json b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_UrlEncodedQueryString.json new file mode 100644 index 000000000..c53399786 --- /dev/null +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Snapshots/V2_UrlEncodedQueryString.json @@ -0,0 +1,47 @@ +{ + "Version": "2.0", + "RouteKey": "POST /test12/api/search", + "RawPath": "/test12/api/search", + "RawQueryString": "q=Hello%20World\u0026tag=C%23%20Programming\u0026tag=.NET%20Core", + "Cookies": null, + "Headers": { + "content-length": "0", + "content-type": "text/plain; charset=utf-8", + "Host": "EXAMPLE.execute-api.REGION.amazonaws.com", + "x-amzn-trace-id": "Root=1-67fd88cb-4981526a50466e9f2be0fbed", + "X-Forwarded-For": "IP_ADDRESS", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "QueryStringParameters": { + "q": "Hello World", + "tag": "C# Programming,.NET Core" + }, + "RequestContext": { + "AccountId": "ACCOUNT_ID", + "ApiId": "EXAMPLE", + "Authorizer": null, + "DomainName": "EXAMPLE.execute-api.REGION.amazonaws.com", + "DomainPrefix": "EXAMPLE", + "Http": { + "Method": "POST", + "Path": "/test12/api/search", + "Protocol": "HTTP/1.1", + "SourceIp": "IP_ADDRESS", + "UserAgent": "" + }, + "RequestId": "JCJP4iwYPHcEJgQ=", + "RouteId": null, + "RouteKey": "POST /test12/api/search", + "Stage": "$default", + "Time": "14/Apr/2025:22:14:35 \u002B0000", + "TimeEpoch": 1744668875623, + "Authentication": null + }, + "Body": null, + "PathParameters": null, + "IsBase64Encoded": false, + "StageVariables": null +} + +