From f9200dd7a9124c78da65b485ce79d34615ef5675 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Fri, 24 Jan 2025 18:40:23 -0800 Subject: [PATCH 1/4] Add port environment variables for Aspire --- .../src/Amazon.Lambda.TestTool/Program.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Program.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Program.cs index 3cbfd6b5e..8cc1c8d10 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Program.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Program.cs @@ -19,4 +19,17 @@ config.SetApplicationName(Constants.ToolName); }); -return await app.RunAsync(args); +var arguments = new List(args); + +if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("LAMBDA_RUNTIME_API_PORT"))) +{ + arguments.Add("--port"); + arguments.Add(Environment.GetEnvironmentVariable("LAMBDA_RUNTIME_API_PORT")!); +} +if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("API_GATEWAY_EMULATOR_PORT"))) +{ + arguments.Add("--api-gateway-emulator-port"); + arguments.Add(Environment.GetEnvironmentVariable("API_GATEWAY_EMULATOR_PORT")!); +} + +return await app.RunAsync(arguments); \ No newline at end of file From 47f826e67f62360a9bbb7358ca9756c86e51affb Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Fri, 24 Jan 2025 18:40:51 -0800 Subject: [PATCH 2/4] Clean up warnings --- .../Extensions/HttpContextExtensions.cs | 4 +++- .../Extensions/InvokeResponseExtensions.cs | 6 +++--- .../Amazon.Lambda.TestTool/Utilities/HttpRequestUtility.cs | 4 ++-- .../Utilities/RouteTemplateUtility.cs | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/HttpContextExtensions.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/HttpContextExtensions.cs index 0707ea209..b0e585f7d 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/HttpContextExtensions.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/HttpContextExtensions.cs @@ -168,9 +168,11 @@ public static async Task ToApiGatewayRequest( if (emulatorMode == ApiGatewayEmulatorMode.Rest) // rest uses encoded value for the path params { +#pragma warning disable SYSLIB0013 // Type or member is obsolete var encodedPathParameters = pathParameters.ToDictionary( kvp => kvp.Key, - kvp => Uri.EscapeUriString(kvp.Value)); // intentionally using EscapeURiString over EscapeDataString since EscapeURiString correctly handles reserved characters :/?#[]@!$&'()*+,;= in this case + kvp => Uri.EscapeUriString(kvp.Value)); // intentionally using EscapeUriString over EscapeDataString since EscapeUriString correctly handles reserved characters :/?#[]@!$&'()*+,;= in this case +#pragma warning restore SYSLIB0013 // Type or member is obsolete pathParameters = encodedPathParameters; } diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/InvokeResponseExtensions.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/InvokeResponseExtensions.cs index 74d0d597b..e4dc4e762 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/InvokeResponseExtensions.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/InvokeResponseExtensions.cs @@ -34,7 +34,7 @@ public static APIGatewayProxyResponse ToApiGatewayProxyResponse(this InvokeRespo string responseJson = reader.ReadToEnd(); try { - return JsonSerializer.Deserialize(responseJson); + return JsonSerializer.Deserialize(responseJson)!; } catch { @@ -132,7 +132,7 @@ private static APIGatewayHttpApiV2ProxyResponse ToHttpApiV2Response(string respo // It has a statusCode property, so try to deserialize as full response try { - return JsonSerializer.Deserialize(response); + return JsonSerializer.Deserialize(response)!; } catch { @@ -155,7 +155,7 @@ private static APIGatewayHttpApiV2ProxyResponse ToHttpApiV2Response(string respo // return "test", it actually comes as "\"test\"" to response. So we need to get the raw string which is what api gateway does. if (jsonElement.ValueKind == JsonValueKind.String) { - response = jsonElement.GetString(); + response = jsonElement.GetString()!; } } diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/HttpRequestUtility.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/HttpRequestUtility.cs index f281c6df5..fce9494cc 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/HttpRequestUtility.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/HttpRequestUtility.cs @@ -107,7 +107,7 @@ public static (IDictionary, IDictionary>) { var key = lowerCaseKeyName ? header.Key.ToLower() : header.Key; singleValueHeaders[key] = header.Value.Last() ?? ""; - multiValueHeaders[key] = [.. header.Value]; + multiValueHeaders[key] = [.. header.Value!]; } return (singleValueHeaders, multiValueHeaders); @@ -133,7 +133,7 @@ public static (IDictionary, IDictionary>) foreach (var param in query) { singleValueParams[param.Key] = param.Value.Last() ?? ""; - multiValueParams[param.Key] = [.. param.Value]; + multiValueParams[param.Key] = [.. param.Value!]; } return (singleValueParams, multiValueParams); diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/RouteTemplateUtility.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/RouteTemplateUtility.cs index 29693e210..a76f9dff4 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/RouteTemplateUtility.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/RouteTemplateUtility.cs @@ -40,7 +40,7 @@ public static Dictionary ExtractPathParameters(string routeTempl foreach (var param in template.Parameters) { - if (routeValues.TryGetValue(param.Name, out var value)) + if (routeValues.TryGetValue(param.Name!, out var value)) { var stringValue = value?.ToString() ?? string.Empty; @@ -51,7 +51,7 @@ public static Dictionary ExtractPathParameters(string routeTempl } // Restore original parameter name - var originalParamName = RestoreOriginalParamName(param.Name); + var originalParamName = RestoreOriginalParamName(param.Name!); result[originalParamName] = stringValue; } } From a45b7fed9198943bc090a2f6246769d0a7471d05 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Fri, 24 Jan 2025 18:41:25 -0800 Subject: [PATCH 3/4] Fix issue loading test tool in project in Visual Studio --- .../src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj index ff52f18f8..fa006d305 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj @@ -1,4 +1,4 @@ - + A tool to help debug and test your .NET AWS Lambda functions locally. @@ -16,7 +16,7 @@ 0.0.1-beta.1 - + From 3ed8d10442b9bcc9d2c377500ca4c02e207ca5e7 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Mon, 27 Jan 2025 10:18:27 -0800 Subject: [PATCH 4/4] Move evaluating environment variables to RunCommand --- .../Commands/RunCommand.cs | 40 ++++++++++++++++++- .../Extensions/ServiceCollectionExtensions.cs | 1 + .../src/Amazon.Lambda.TestTool/Program.cs | 15 +------ .../Services/ApiGatewayRouteConfigService.cs | 2 +- .../Utilities/LocalEnvironmentManager.cs | 12 ++++++ .../ApiGatewayEmulatorProcessTests.cs | 4 +- .../Commands/RunCommandTests.cs | 38 ++++++++++++++++-- 7 files changed, 92 insertions(+), 20 deletions(-) create mode 100644 Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/LocalEnvironmentManager.cs diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Commands/RunCommand.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Commands/RunCommand.cs index 83482d0db..41d06a2ae 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Commands/RunCommand.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Commands/RunCommand.cs @@ -7,6 +7,7 @@ using Amazon.Lambda.TestTool.Models; using Amazon.Lambda.TestTool.Processes; using Amazon.Lambda.TestTool.Services; +using Amazon.Lambda.TestTool.Services.IO; using Spectre.Console.Cli; namespace Amazon.Lambda.TestTool.Commands; @@ -15,8 +16,11 @@ namespace Amazon.Lambda.TestTool.Commands; /// The default command of the application which is responsible for launching the Lambda Runtime API and the API Gateway Emulator. /// public sealed class RunCommand( - IToolInteractiveService toolInteractiveService) : CancellableAsyncCommand + IToolInteractiveService toolInteractiveService, IEnvironmentManager environmentManager) : CancellableAsyncCommand { + public const string LAMBDA_RUNTIME_API_PORT = "LAMBDA_RUNTIME_API_PORT"; + public const string API_GATEWAY_EMULATOR_PORT = "API_GATEWAY_EMULATOR_PORT"; + /// /// The method responsible for executing the . /// @@ -24,6 +28,8 @@ public override async Task ExecuteAsync(CommandContext context, RunCommandS { try { + EvaluateEnvironmentVariables(settings); + var tasks = new List(); var testToolProcess = TestToolProcess.Startup(settings, cancellationTokenSource.Token); @@ -80,4 +86,36 @@ public override async Task ExecuteAsync(CommandContext context, RunCommandS await cancellationTokenSource.CancelAsync(); } } + + private void EvaluateEnvironmentVariables(RunCommandSettings settings) + { + var environmentVariables = environmentManager.GetEnvironmentVariables(); + if (environmentVariables == null) + return; + + if (environmentVariables.Contains(LAMBDA_RUNTIME_API_PORT)) + { + var envValue = environmentVariables[LAMBDA_RUNTIME_API_PORT]?.ToString(); + if (int.TryParse(envValue, out var port)) + { + settings.Port = port; + } + else + { + throw new ArgumentException($"Value for {LAMBDA_RUNTIME_API_PORT} environment variable was not a valid port number"); + } + } + if (environmentVariables.Contains(API_GATEWAY_EMULATOR_PORT)) + { + var envValue = environmentVariables[API_GATEWAY_EMULATOR_PORT]?.ToString(); + if (int.TryParse(envValue, out var port)) + { + settings.ApiGatewayEmulatorPort = port; + } + else + { + throw new ArgumentException($"Value for {API_GATEWAY_EMULATOR_PORT} environment variable was not a valid port number"); + } + } + } } diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ServiceCollectionExtensions.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ServiceCollectionExtensions.cs index 871844975..1afe1704c 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ServiceCollectionExtensions.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ServiceCollectionExtensions.cs @@ -20,6 +20,7 @@ public static void AddCustomServices(this IServiceCollection serviceCollection, { serviceCollection.TryAdd(new ServiceDescriptor(typeof(IToolInteractiveService), typeof(ConsoleInteractiveService), lifetime)); serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDirectoryManager), typeof(DirectoryManager), lifetime)); + serviceCollection.TryAdd(new ServiceDescriptor(typeof(IEnvironmentManager), typeof(EnvironmentManager), lifetime)); } /// diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Program.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Program.cs index 8cc1c8d10..3cbfd6b5e 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Program.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Program.cs @@ -19,17 +19,4 @@ config.SetApplicationName(Constants.ToolName); }); -var arguments = new List(args); - -if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("LAMBDA_RUNTIME_API_PORT"))) -{ - arguments.Add("--port"); - arguments.Add(Environment.GetEnvironmentVariable("LAMBDA_RUNTIME_API_PORT")!); -} -if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("API_GATEWAY_EMULATOR_PORT"))) -{ - arguments.Add("--api-gateway-emulator-port"); - arguments.Add(Environment.GetEnvironmentVariable("API_GATEWAY_EMULATOR_PORT")!); -} - -return await app.RunAsync(arguments); \ No newline at end of file +return await app.RunAsync(args); diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Services/ApiGatewayRouteConfigService.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Services/ApiGatewayRouteConfigService.cs index c708bb766..e8676be36 100644 --- a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Services/ApiGatewayRouteConfigService.cs +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Services/ApiGatewayRouteConfigService.cs @@ -14,7 +14,7 @@ public class ApiGatewayRouteConfigService : IApiGatewayRouteConfigService { private readonly ILogger _logger; private readonly IEnvironmentManager _environmentManager; - private List _routeConfigs = new(); + private readonly List _routeConfigs = new(); /// /// Constructs an instance of . diff --git a/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/LocalEnvironmentManager.cs b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/LocalEnvironmentManager.cs new file mode 100644 index 000000000..35e030144 --- /dev/null +++ b/Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/LocalEnvironmentManager.cs @@ -0,0 +1,12 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Collections; +using Amazon.Lambda.TestTool.Services.IO; + +namespace Amazon.Lambda.TestTool.Utilities; + +public class LocalEnvironmentManager(IDictionary environmentManager) : IEnvironmentManager +{ + public IDictionary GetEnvironmentVariables() => environmentManager; +} diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayEmulatorProcessTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayEmulatorProcessTests.cs index 8523e8ff5..0bc229bcd 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayEmulatorProcessTests.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayEmulatorProcessTests.cs @@ -8,6 +8,7 @@ using Amazon.Lambda.TestTool.Commands.Settings; using Amazon.Lambda.TestTool.Models; using Amazon.Lambda.TestTool.Services; +using Amazon.Lambda.TestTool.Services.IO; using Moq; using Spectre.Console.Cli; using Xunit; @@ -17,6 +18,7 @@ namespace Amazon.Lambda.TestTool.IntegrationTests; public class ApiGatewayEmulatorProcessTests : IAsyncDisposable { + private readonly Mock _mockEnvironmentManager = new Mock(); private readonly Mock _mockInteractiveService = new Mock(); private readonly Mock _mockRemainingArgs = new Mock(); private readonly ITestOutputHelper _testOutputHelper; @@ -245,7 +247,7 @@ private void StartTestToolProcess(ApiGatewayEmulatorMode apiGatewayMode, TestCon }}"); cancellationTokenSource.CancelAfter(5000); var settings = new RunCommandSettings { Port = lambdaPort, NoLaunchWindow = true, ApiGatewayEmulatorMode = apiGatewayMode,ApiGatewayEmulatorPort = apiGatewayPort}; - var command = new RunCommand(_mockInteractiveService.Object); + var command = new RunCommand(_mockInteractiveService.Object, _mockEnvironmentManager.Object); var context = new CommandContext(new List(), _mockRemainingArgs.Object, "run", null); // Act diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Commands/RunCommandTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Commands/RunCommandTests.cs index 7cd3703e7..4e08b7536 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Commands/RunCommandTests.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Commands/RunCommandTests.cs @@ -1,4 +1,4 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 using Amazon.Lambda.TestTool.Commands.Settings; @@ -9,11 +9,14 @@ using Moq; using Amazon.Lambda.TestTool.UnitTests.Helpers; using Xunit; +using Amazon.Lambda.TestTool.Services.IO; +using Amazon.Lambda.TestTool.Utilities; namespace Amazon.Lambda.TestTool.UnitTests.Commands; public class RunCommandTests { + private readonly Mock _mockEnvironmentManager = new Mock(); private readonly Mock _mockInteractiveService = new Mock(); private readonly Mock _mockRemainingArgs = new Mock(); @@ -25,7 +28,7 @@ public async Task ExecuteAsync_LambdaRuntimeApi_SuccessfulLaunch() var cancellationSource = new CancellationTokenSource(); cancellationSource.CancelAfter(5000); var settings = new RunCommandSettings { Port = 9001, NoLaunchWindow = true }; - var command = new RunCommand(_mockInteractiveService.Object); + var command = new RunCommand(_mockInteractiveService.Object, _mockEnvironmentManager.Object); var context = new CommandContext(new List(), _mockRemainingArgs.Object, "run", null); var apiUrl = $"http://{settings.Host}:{settings.Port}"; @@ -48,7 +51,7 @@ public async Task ExecuteAsync_ApiGatewayEmulator_SuccessfulLaunch() var cancellationSource = new CancellationTokenSource(); cancellationSource.CancelAfter(5000); var settings = new RunCommandSettings { Port = 9002, ApiGatewayEmulatorMode = ApiGatewayEmulatorMode.HttpV2, NoLaunchWindow = true}; - var command = new RunCommand(_mockInteractiveService.Object); + var command = new RunCommand(_mockInteractiveService.Object, _mockEnvironmentManager.Object); var context = new CommandContext(new List(), _mockRemainingArgs.Object, "run", null); var apiUrl = $"http://{settings.Host}:{settings.ApiGatewayEmulatorPort}/__lambda_test_tool_apigateway_health__"; @@ -62,4 +65,33 @@ public async Task ExecuteAsync_ApiGatewayEmulator_SuccessfulLaunch() Assert.Equal(CommandReturnCodes.Success, result); Assert.True(isApiRunning); } + + [Fact] + public async Task ExecuteAsync_EnvPorts_SuccessfulLaunch() + { + var environmentManager = new LocalEnvironmentManager(new Dictionary + { + { RunCommand.LAMBDA_RUNTIME_API_PORT, "9432" }, + { RunCommand.API_GATEWAY_EMULATOR_PORT, "9765" } + }); + + // Arrange + Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development"); + var cancellationSource = new CancellationTokenSource(); + cancellationSource.CancelAfter(5000); + var settings = new RunCommandSettings { ApiGatewayEmulatorMode = ApiGatewayEmulatorMode.HttpV2, NoLaunchWindow = true }; + var command = new RunCommand(_mockInteractiveService.Object, environmentManager); + var context = new CommandContext(new List(), _mockRemainingArgs.Object, "run", null); + var apiUrl = $"http://{settings.Host}:9765/__lambda_test_tool_apigateway_health__"; + + // Act + var runningTask = command.ExecuteAsync(context, settings, cancellationSource); + var isApiRunning = await TestHelpers.WaitForApiToStartAsync(apiUrl); + await cancellationSource.CancelAsync(); + + // Assert + var result = await runningTask; + Assert.Equal(CommandReturnCodes.Success, result); + Assert.True(isApiRunning); + } }