From c8a6091f20a1c94b73f4db7f265a42295340587c Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Mon, 27 Jan 2025 12:16:37 +0000 Subject: [PATCH 01/12] refactor and add idempotency --- libraries/AWS.Lambda.Powertools.sln | 63 +++++ .../e2e/InfraShared/FunctionConstruct.cs | 86 ++++++ .../FunctionConstructProps.cs | 3 +- .../tests/e2e/InfraShared/IdempotencyStack.cs | 75 ++++++ .../tests/e2e/InfraShared/InfraShared.csproj | 19 ++ .../src/AOT-Function/AOT-Function.csproj | 33 +++ .../AOT-Function/src/AOT-Function/Function.cs | 38 +++ .../aws-lambda-tools-defaults.json | 16 ++ .../Function/src/Function/Function.cs | 22 ++ .../Function/src/Function/Function.csproj | 21 ++ .../Function/src/Function/TestHelper.cs | 11 + .../Function/aws-lambda-tools-defaults.json | 13 + .../test/Function.Tests/Function.Tests.csproj | 23 ++ .../test/Function.Tests/FunctionTests.cs | 246 ++++++++++++++++++ libraries/tests/e2e/infra-aot/CoreAotStack.cs | 5 +- .../tests/e2e/infra-aot/FunctionConstruct.cs | 64 ++--- libraries/tests/e2e/infra-aot/InfraAot.csproj | 1 + libraries/tests/e2e/infra-aot/Program.cs | 4 + libraries/tests/e2e/infra/CoreStack.cs | 4 +- .../tests/e2e/infra/FunctionConstruct.cs | 2 +- libraries/tests/e2e/infra/Infra.csproj | 2 +- libraries/tests/e2e/infra/Program.cs | 3 + 22 files changed, 715 insertions(+), 39 deletions(-) create mode 100644 libraries/tests/e2e/InfraShared/FunctionConstruct.cs rename libraries/tests/e2e/{functions/TestUtils => InfraShared}/FunctionConstructProps.cs (84%) create mode 100644 libraries/tests/e2e/InfraShared/IdempotencyStack.cs create mode 100644 libraries/tests/e2e/InfraShared/InfraShared.csproj create mode 100644 libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj create mode 100644 libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/Function.cs create mode 100644 libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/aws-lambda-tools-defaults.json create mode 100644 libraries/tests/e2e/functions/core/idempotency/Function/src/Function/Function.cs create mode 100644 libraries/tests/e2e/functions/core/idempotency/Function/src/Function/Function.csproj create mode 100644 libraries/tests/e2e/functions/core/idempotency/Function/src/Function/TestHelper.cs create mode 100644 libraries/tests/e2e/functions/core/idempotency/Function/src/Function/aws-lambda-tools-defaults.json create mode 100644 libraries/tests/e2e/functions/core/idempotency/Function/test/Function.Tests/Function.Tests.csproj create mode 100644 libraries/tests/e2e/functions/core/idempotency/Function/test/Function.Tests/FunctionTests.cs diff --git a/libraries/AWS.Lambda.Powertools.sln b/libraries/AWS.Lambda.Powertools.sln index 05638eb5..0e2f35c0 100644 --- a/libraries/AWS.Lambda.Powertools.sln +++ b/libraries/AWS.Lambda.Powertools.sln @@ -83,6 +83,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-Function", "tests\e2e\f EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfraAot", "tests\e2e\infra-aot\InfraAot.csproj", "{24AC34AD-AEC9-4CFB-BB01-C3C81938AB95}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Idempotency", "Idempotency", "{9D292892-EF57-4930-8472-24BBBE02EBF4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Function", "tests\e2e\functions\core\idempotency\Function\src\Function\Function.csproj", "{6696859B-685D-4482-9237-22FD649357F3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-Function", "tests\e2e\functions\core\idempotency\AOT-Function\src\AOT-Function\AOT-Function.csproj", "{222BB89F-10D8-4CB0-86BC-17F52FBE588F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Function.Tests", "tests\e2e\functions\core\idempotency\Function\test\Function.Tests\Function.Tests.csproj", "{96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfraShared", "tests\e2e\InfraShared\InfraShared.csproj", "{D303B458-9D84-4DDF-8781-2C0211672329}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -432,6 +442,54 @@ Global {24AC34AD-AEC9-4CFB-BB01-C3C81938AB95}.Release|x64.Build.0 = Release|Any CPU {24AC34AD-AEC9-4CFB-BB01-C3C81938AB95}.Release|x86.ActiveCfg = Release|Any CPU {24AC34AD-AEC9-4CFB-BB01-C3C81938AB95}.Release|x86.Build.0 = Release|Any CPU + {6696859B-685D-4482-9237-22FD649357F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6696859B-685D-4482-9237-22FD649357F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6696859B-685D-4482-9237-22FD649357F3}.Debug|x64.ActiveCfg = Debug|Any CPU + {6696859B-685D-4482-9237-22FD649357F3}.Debug|x64.Build.0 = Debug|Any CPU + {6696859B-685D-4482-9237-22FD649357F3}.Debug|x86.ActiveCfg = Debug|Any CPU + {6696859B-685D-4482-9237-22FD649357F3}.Debug|x86.Build.0 = Debug|Any CPU + {6696859B-685D-4482-9237-22FD649357F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6696859B-685D-4482-9237-22FD649357F3}.Release|Any CPU.Build.0 = Release|Any CPU + {6696859B-685D-4482-9237-22FD649357F3}.Release|x64.ActiveCfg = Release|Any CPU + {6696859B-685D-4482-9237-22FD649357F3}.Release|x64.Build.0 = Release|Any CPU + {6696859B-685D-4482-9237-22FD649357F3}.Release|x86.ActiveCfg = Release|Any CPU + {6696859B-685D-4482-9237-22FD649357F3}.Release|x86.Build.0 = Release|Any CPU + {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Debug|x64.ActiveCfg = Debug|Any CPU + {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Debug|x64.Build.0 = Debug|Any CPU + {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Debug|x86.ActiveCfg = Debug|Any CPU + {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Debug|x86.Build.0 = Debug|Any CPU + {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Release|Any CPU.Build.0 = Release|Any CPU + {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Release|x64.ActiveCfg = Release|Any CPU + {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Release|x64.Build.0 = Release|Any CPU + {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Release|x86.ActiveCfg = Release|Any CPU + {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Release|x86.Build.0 = Release|Any CPU + {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Debug|x64.ActiveCfg = Debug|Any CPU + {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Debug|x64.Build.0 = Debug|Any CPU + {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Debug|x86.ActiveCfg = Debug|Any CPU + {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Debug|x86.Build.0 = Debug|Any CPU + {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Release|Any CPU.Build.0 = Release|Any CPU + {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Release|x64.ActiveCfg = Release|Any CPU + {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Release|x64.Build.0 = Release|Any CPU + {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Release|x86.ActiveCfg = Release|Any CPU + {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Release|x86.Build.0 = Release|Any CPU + {D303B458-9D84-4DDF-8781-2C0211672329}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D303B458-9D84-4DDF-8781-2C0211672329}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D303B458-9D84-4DDF-8781-2C0211672329}.Debug|x64.ActiveCfg = Debug|Any CPU + {D303B458-9D84-4DDF-8781-2C0211672329}.Debug|x64.Build.0 = Debug|Any CPU + {D303B458-9D84-4DDF-8781-2C0211672329}.Debug|x86.ActiveCfg = Debug|Any CPU + {D303B458-9D84-4DDF-8781-2C0211672329}.Debug|x86.Build.0 = Debug|Any CPU + {D303B458-9D84-4DDF-8781-2C0211672329}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D303B458-9D84-4DDF-8781-2C0211672329}.Release|Any CPU.Build.0 = Release|Any CPU + {D303B458-9D84-4DDF-8781-2C0211672329}.Release|x64.ActiveCfg = Release|Any CPU + {D303B458-9D84-4DDF-8781-2C0211672329}.Release|x64.Build.0 = Release|Any CPU + {D303B458-9D84-4DDF-8781-2C0211672329}.Release|x86.ActiveCfg = Release|Any CPU + {D303B458-9D84-4DDF-8781-2C0211672329}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution @@ -470,5 +528,10 @@ Global {8DDAFE37-ED59-4710-9415-8EBA44CC6437} = {3C9FA701-31FF-4747-B324-E0D252EAFD63} {8DDED681-AE8D-45EB-A22E-2FFB88620F9B} = {3C9FA701-31FF-4747-B324-E0D252EAFD63} {24AC34AD-AEC9-4CFB-BB01-C3C81938AB95} = {93DEAC72-245F-4FC9-A7B5-DAE7EF7E1AB7} + {9D292892-EF57-4930-8472-24BBBE02EBF4} = {AAFA39E9-66A3-4B9A-AFE9-EAF74A85A7F0} + {6696859B-685D-4482-9237-22FD649357F3} = {9D292892-EF57-4930-8472-24BBBE02EBF4} + {222BB89F-10D8-4CB0-86BC-17F52FBE588F} = {9D292892-EF57-4930-8472-24BBBE02EBF4} + {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164} = {9D292892-EF57-4930-8472-24BBBE02EBF4} + {D303B458-9D84-4DDF-8781-2C0211672329} = {93DEAC72-245F-4FC9-A7B5-DAE7EF7E1AB7} EndGlobalSection EndGlobal diff --git a/libraries/tests/e2e/InfraShared/FunctionConstruct.cs b/libraries/tests/e2e/InfraShared/FunctionConstruct.cs new file mode 100644 index 00000000..1b752de2 --- /dev/null +++ b/libraries/tests/e2e/InfraShared/FunctionConstruct.cs @@ -0,0 +1,86 @@ +using Amazon.CDK; +using Amazon.CDK.AWS.Lambda; +using Constructs; + +namespace InfraShared; + +public class FunctionConstruct : Construct +{ + public Function Function { get; set; } + + public FunctionConstruct(Construct scope, string id, FunctionConstructProps props) : base(scope, id) + { + var framework = props.Runtime == Runtime.DOTNET_6 ? "net6.0" : "net8.0"; + var distPath = $"{props.DistPath}/deploy_{props.Architecture.Name}_{props.Runtime.Name}.zip"; + var command = props.IsAot + ? $"dotnet-lambda package -pl {props.SourcePath} -cmd ../../../ -o {distPath} -f net8.0 -farch {props.Architecture.Name} -cifb public.ecr.aws/sam/build-dotnet8" + : $"dotnet-lambda package -pl {props.SourcePath} -o {distPath} -f {framework} -farch {props.Architecture.Name}"; + + Function = new Function(this, id, new FunctionProps + { + Runtime = props.Runtime, + Architecture = props.Architecture, + FunctionName = props.Name, + Handler = props.Handler, + Tracing = Tracing.ACTIVE, + Timeout = Duration.Seconds(10), + Code = Code.FromCustomCommand(distPath, + new[] + { + command + }, + new CustomCommandOptions + { + CommandOptions = new Dictionary { { "shell", true } } + }) + }); + } + + + // public FunctionConstruct(Construct scope, string id, FunctionConstructProps props) : base(scope, id) + // { + // if(props.IsAot) + // { + // var distPath = $"{props.DistPath}/deploy_{props.Architecture.Name}_{props.Runtime.Name}.zip"; + // _ = new Function(this, id, new FunctionProps + // { + // Runtime = props.Runtime, + // Architecture = props.Architecture, + // FunctionName = props.Name, + // Handler = props.Handler, + // Tracing = Tracing.ACTIVE, + // Timeout = Duration.Seconds(10), + // Code = Code.FromCustomCommand(distPath, + // [ + // $"dotnet-lambda package -pl {props.SourcePath} -cmd ../../../ -o {distPath} -f net8.0 -farch {props.Architecture.Name} -cifb public.ecr.aws/sam/build-dotnet8" + // ], + // new CustomCommandOptions + // { + // CommandOptions = new Dictionary {{"shell", true }} + // }) + // }); + // } + // else + // { + // var framework = props.Runtime == Runtime.DOTNET_6 ? "net6.0" : "net8.0"; + // var distPath = $"{props.DistPath}/deploy_{props.Architecture.Name}_{props.Runtime.Name}.zip"; + // _ = new Function(this, id, new FunctionProps + // { + // Runtime = props.Runtime, + // Architecture = props.Architecture, + // FunctionName = props.Name, + // Handler = props.Handler, + // Tracing = Tracing.ACTIVE, + // Timeout = Duration.Seconds(10), + // Code = Code.FromCustomCommand(distPath, + // [ + // $"dotnet-lambda package -pl {props.SourcePath} -o {distPath} -f {framework} -farch {props.Architecture.Name}" + // ], + // new CustomCommandOptions + // { + // CommandOptions = new Dictionary { { "shell", true } } + // }) + // }); + // } + // } +} \ No newline at end of file diff --git a/libraries/tests/e2e/functions/TestUtils/FunctionConstructProps.cs b/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs similarity index 84% rename from libraries/tests/e2e/functions/TestUtils/FunctionConstructProps.cs rename to libraries/tests/e2e/InfraShared/FunctionConstructProps.cs index 85aa0666..fc38939d 100644 --- a/libraries/tests/e2e/functions/TestUtils/FunctionConstructProps.cs +++ b/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs @@ -1,6 +1,6 @@ using Amazon.CDK.AWS.Lambda; -namespace TestUtils; +namespace InfraShared; public class FunctionConstructProps { @@ -10,4 +10,5 @@ public class FunctionConstructProps public required string Handler { get; set; } public required string SourcePath { get; set; } public required string DistPath { get; set; } + public bool IsAot { get; set; } = false; } \ No newline at end of file diff --git a/libraries/tests/e2e/InfraShared/IdempotencyStack.cs b/libraries/tests/e2e/InfraShared/IdempotencyStack.cs new file mode 100644 index 00000000..33a3acb0 --- /dev/null +++ b/libraries/tests/e2e/InfraShared/IdempotencyStack.cs @@ -0,0 +1,75 @@ +using Amazon.CDK; +using Amazon.CDK.AWS.DynamoDB; +using Amazon.CDK.AWS.Lambda; +using Constructs; +using Attribute = Amazon.CDK.AWS.DynamoDB.Attribute; + +namespace InfraShared; + +public class IdempotencyStackProps : IStackProps +{ + public required Runtime Runtime { get; set; } + public required string Name { get; set; } + public required string Handler { get; set; } +} + +public class IdempotencyStack : Stack +{ + public IdempotencyStack(Construct scope, string id, IdempotencyStackProps props) : base(scope, id, props) + { + Table = new Table(this, "Idempotency", new TableProps + { + PartitionKey = new Attribute + { + Name = "Id", + Type = AttributeType.STRING + }, + TableName = "IdempotencyTable", + BillingMode = BillingMode.PAY_PER_REQUEST, + TimeToLiveAttribute = "expiration" + }); + + var utility = "Idempotency"; + var basePath = $"../functions/core/{utility}/Function/src/Function"; + var distPath = $"../functions/core/{utility}/Function/dist"; + + CreateFunctionConstruct(this, $"{utility}_X64_net8", Runtime.DOTNET_8, Architecture.X86_64, + $"E2ETestLambda_X64_NET8_{utility}", basePath, distPath); + CreateFunctionConstruct(this, $"{utility}_arm_net8", Runtime.DOTNET_8, Architecture.ARM_64, + $"E2ETestLambda_ARM_NET8_{utility}", basePath, distPath); + CreateFunctionConstruct(this, $"{utility}_X64_net6", Runtime.DOTNET_6, Architecture.X86_64, + $"E2ETestLambda_X64_NET6_{utility}", basePath, distPath); + CreateFunctionConstruct(this, $"{utility}_arm_net6", Runtime.DOTNET_6, Architecture.ARM_64, + $"E2ETestLambda_ARM_NET6_{utility}", basePath, distPath); + } + + public Table Table { get; set; } + + private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime, Architecture architecture, + string name, string sourcePath, string distPath) + { + var lambdaFunction = new FunctionConstruct(scope, id, new FunctionConstructProps + { + Runtime = runtime, + Architecture = architecture, + Name = name, + Handler = "AOT-Function", + SourcePath = sourcePath, + DistPath = distPath, + IsAot = true + }); + + // var lambdaFunction = new Function(this, "IdempotencyFunction", new FunctionProps + // { + // Runtime = props.Runtime, + // Handler = props.Handler, + // Code = Code.FromAsset("path/to/your/lambda/code"), + // Environment = new Dictionary + // { + // { "TABLE_NAME", table.TableName } + // } + // }); + + Table.GrantReadWriteData(lambdaFunction.Function); + } +} diff --git a/libraries/tests/e2e/InfraShared/InfraShared.csproj b/libraries/tests/e2e/InfraShared/InfraShared.csproj new file mode 100644 index 00000000..dc08a5e1 --- /dev/null +++ b/libraries/tests/e2e/InfraShared/InfraShared.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + InfraShared + enable + enable + + + + + + + + + + diff --git a/libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj b/libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj new file mode 100644 index 00000000..96b79385 --- /dev/null +++ b/libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj @@ -0,0 +1,33 @@ + + + Exe + net8.0 + enable + enable + Lambda + + true + + true + + true + + partial + + + + + + + + + + TestHelper.cs + + + + + + \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/Function.cs b/libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/Function.cs new file mode 100644 index 00000000..3a4c9beb --- /dev/null +++ b/libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/Function.cs @@ -0,0 +1,38 @@ +using Amazon.Lambda.Core; +using Amazon.Lambda.RuntimeSupport; +using System.Text.Json.Serialization; +using Amazon.Lambda.APIGatewayEvents; +using Amazon.Lambda.Serialization.SystemTextJson; +using Helpers; + +namespace AOT_Function; + +public static class Function +{ + private static async Task Main() + { + Func handler = FunctionHandler; + await LambdaBootstrapBuilder.Create(handler, + new SourceGeneratorLambdaJsonSerializer()) + .Build() + .RunAsync(); + } + + public static APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) + { + TestHelper.TestMethod(apigwProxyEvent); + + return new APIGatewayProxyResponse() + { + StatusCode = 200, + Body = apigwProxyEvent.Body.ToUpper() + }; + } +} + +[JsonSerializable(typeof(APIGatewayProxyRequest))] +[JsonSerializable(typeof(APIGatewayProxyResponse))] +public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext +{ + +} \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/aws-lambda-tools-defaults.json b/libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/aws-lambda-tools-defaults.json new file mode 100644 index 00000000..be3c7ec1 --- /dev/null +++ b/libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/aws-lambda-tools-defaults.json @@ -0,0 +1,16 @@ +{ + "Information": [ + "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", + "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", + "dotnet lambda help", + "All the command line options for the Lambda command can be specified in this file." + ], + "profile": "", + "region": "", + "configuration": "Release", + "function-runtime": "dotnet8", + "function-memory-size": 512, + "function-timeout": 30, + "function-handler": "AOT-Function", + "msbuild-parameters": "--self-contained true" +} \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/Function.cs b/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/Function.cs new file mode 100644 index 00000000..04b2d5b2 --- /dev/null +++ b/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/Function.cs @@ -0,0 +1,22 @@ +using Amazon.Lambda.APIGatewayEvents; +using Amazon.Lambda.Core; +using Helpers; + +// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. +[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] + +namespace Function; + +public class Function +{ + public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) + { + TestHelper.TestMethod(apigwProxyEvent); + + return new APIGatewayProxyResponse() + { + StatusCode = 200, + Body = apigwProxyEvent.Body.ToUpper() + }; + } +} \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/Function.csproj b/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/Function.csproj new file mode 100644 index 00000000..2648fecf --- /dev/null +++ b/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/Function.csproj @@ -0,0 +1,21 @@ + + + net6.0;net8.0 + enable + enable + true + Lambda + + true + + true + + + + + + + + + + \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/TestHelper.cs b/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/TestHelper.cs new file mode 100644 index 00000000..9d2766a4 --- /dev/null +++ b/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/TestHelper.cs @@ -0,0 +1,11 @@ +using Amazon.Lambda.APIGatewayEvents; + +namespace Helpers; + +public static class TestHelper +{ + public static void TestMethod(APIGatewayProxyRequest apigwProxyEvent) + { + + } +} \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/aws-lambda-tools-defaults.json b/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/aws-lambda-tools-defaults.json new file mode 100644 index 00000000..307a7dca --- /dev/null +++ b/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/aws-lambda-tools-defaults.json @@ -0,0 +1,13 @@ +{ + "Information": [ + "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", + "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", + "dotnet lambda help", + "All the command line options for the Lambda command can be specified in this file." + ], + "profile": "", + "region": "", + "configuration": "Release", + "function-memory-size": 512, + "function-timeout": 30 +} \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/idempotency/Function/test/Function.Tests/Function.Tests.csproj b/libraries/tests/e2e/functions/core/idempotency/Function/test/Function.Tests/Function.Tests.csproj new file mode 100644 index 00000000..c5eb20fb --- /dev/null +++ b/libraries/tests/e2e/functions/core/idempotency/Function/test/Function.Tests/Function.Tests.csproj @@ -0,0 +1,23 @@ + + + net8.0 + enable + enable + true + Logging.E2E.Tests + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/idempotency/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/core/idempotency/Function/test/Function.Tests/FunctionTests.cs new file mode 100644 index 00000000..bbae4b4a --- /dev/null +++ b/libraries/tests/e2e/functions/core/idempotency/Function/test/Function.Tests/FunctionTests.cs @@ -0,0 +1,246 @@ +using System.Text.Json; +using Amazon.Lambda; +using Amazon.Lambda.APIGatewayEvents; +using Xunit; +using Amazon.Lambda.Model; +using TestUtils; +using Xunit.Abstractions; + +namespace Function.Tests; + +[Trait("Category", "E2E")] +public class FunctionTests +{ + private readonly ITestOutputHelper _testOutputHelper; + private readonly AmazonLambdaClient _lambdaClient; + + public FunctionTests(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + _lambdaClient = new AmazonLambdaClient(); + } + + [Trait("Category", "AOT")] + [Theory] + [InlineData("E2ETestLambda_X64_AOT_NET8_idempotency")] + // [InlineData("E2ETestLambda_ARM_AOT_NET8_idempotency")] + public async Task AotFunctionTest(string functionName) + { + await TestFunction(functionName); + } + + [Theory] + [InlineData("E2ETestLambda_X64_NET6_idempotency")] + [InlineData("E2ETestLambda_ARM_NET6_idempotency")] + [InlineData("E2ETestLambda_X64_NET8_idempotency")] + [InlineData("E2ETestLambda_ARM_NET8_idempotency")] + public async Task FunctionTest(string functionName) + { + await TestFunction(functionName); + } + + internal async Task TestFunction(string functionName) + { + var request = new InvokeRequest + { + FunctionName = functionName, + InvocationType = InvocationType.RequestResponse, + Payload = await File.ReadAllTextAsync("../../../../../../../payload.json"), + LogType = LogType.Tail + }; + + // run twice for cold and warm start + for (int i = 0; i < 2; i++) + { + var response = await _lambdaClient.InvokeAsync(request); + + if (string.IsNullOrEmpty(response.LogResult)) + { + Assert.Fail("No LogResult field returned in the response of Lambda invocation."); + } + + var payload = System.Text.Encoding.UTF8.GetString(response.Payload.ToArray()); + var parsedPayload = JsonSerializer.Deserialize(payload); + + if (parsedPayload == null) + { + Assert.Fail("Failed to parse payload."); + } + + Assert.Equal(200, parsedPayload.StatusCode); + Assert.Equal("HELLO WORLD", parsedPayload.Body); + + // Assert Output log from Lambda execution + AssertOutputLog(functionName, response); + } + } + + private void AssertOutputLog(string functionName, InvokeResponse response) + { + // Extract and parse log + var logResult = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(response.LogResult)); + _testOutputHelper.WriteLine(logResult); + var output = OutputLogParser.ParseLogSegments(logResult, out var report); + var isColdStart = report.initDuration != "N/A"; + + // Assert Logging utility + // AssertEventLog(functionName, isColdStart, output[0]); + // AssertInformationLog(functionName, isColdStart, output[1]); + // AssertWarningLog(functionName, isColdStart, output[2]); + // AssertExceptionLog(functionName, isColdStart, output[3]); + } + + private void AssertEventLog(string functionName, bool isColdStart, string output) + { + using JsonDocument doc = JsonDocument.Parse(output); + JsonElement root = doc.RootElement; + + AssertDefaultLoggingProperties.ArePresent(functionName, isColdStart, output); + + if (!isColdStart) + { + Assert.True(root.TryGetProperty("LookupInfo", out JsonElement lookupInfoElement)); + Assert.True(lookupInfoElement.TryGetProperty("LookupId", out JsonElement lookupIdElement)); + Assert.Equal("c6af9ac6-7b61-11e6-9a41-93e8deadbeef", lookupIdElement.GetString()); + } + + Assert.True(root.TryGetProperty("Level", out JsonElement levelElement)); + Assert.Equal("Information", levelElement.GetString()); + + Assert.True(root.TryGetProperty("Message", out JsonElement messageElement)); + Assert.True(messageElement.TryGetProperty("Resource", out JsonElement resourceElement)); + Assert.Equal("/{proxy+}", resourceElement.GetString()); + + Assert.True(messageElement.TryGetProperty("Path", out JsonElement pathElement)); + Assert.Equal("/path/to/resource", pathElement.GetString()); + + Assert.True(messageElement.TryGetProperty("HttpMethod", out JsonElement httpMethodElement)); + Assert.Equal("POST", httpMethodElement.GetString()); + + Assert.True(messageElement.TryGetProperty("Headers", out JsonElement headersElement)); + Assert.True(headersElement.TryGetProperty("Accept-Encoding", out JsonElement acceptEncodingElement)); + Assert.Equal("gzip, deflate, sdch", acceptEncodingElement.GetString()); + + Assert.True(headersElement.TryGetProperty("Accept-Language", out JsonElement acceptLanguageElement)); + Assert.Equal("en-US,en;q=0.8", acceptLanguageElement.GetString()); + + Assert.True(headersElement.TryGetProperty("Cache-Control", out JsonElement cacheControlElement)); + Assert.Equal("max-age=0", cacheControlElement.GetString()); + + Assert.True( + messageElement.TryGetProperty("QueryStringParameters", out JsonElement queryStringParametersElement)); + Assert.True(queryStringParametersElement.TryGetProperty("Foo", out JsonElement fooElement)); + Assert.Equal("bar", fooElement.GetString()); + + Assert.True(messageElement.TryGetProperty("RequestContext", out JsonElement requestContextElement)); + Assert.True(requestContextElement.TryGetProperty("Path", out JsonElement requestContextPathElement)); + Assert.Equal("/prod/path/to/resource", requestContextPathElement.GetString()); + + Assert.True(requestContextElement.TryGetProperty("AccountId", out JsonElement accountIdElement)); + Assert.Equal("123456789012", accountIdElement.GetString()); + + Assert.True(requestContextElement.TryGetProperty("ResourceId", out JsonElement resourceIdElement)); + Assert.Equal("123456", resourceIdElement.GetString()); + + Assert.True(requestContextElement.TryGetProperty("Stage", out JsonElement stageElement)); + Assert.Equal("prod", stageElement.GetString()); + + Assert.True(requestContextElement.TryGetProperty("RequestId", out JsonElement requestIdElement)); + Assert.Equal("c6af9ac6-7b61-11e6-9a41-93e8deadbeef", requestIdElement.GetString()); + + Assert.True(requestContextElement.TryGetProperty("ResourcePath", out JsonElement resourcePathElement)); + Assert.Equal("/{proxy+}", resourcePathElement.GetString()); + + Assert.True( + requestContextElement.TryGetProperty("HttpMethod", out JsonElement requestContextHttpMethodElement)); + Assert.Equal("POST", requestContextHttpMethodElement.GetString()); + + Assert.True(requestContextElement.TryGetProperty("ApiId", out JsonElement apiIdElement)); + Assert.Equal("1234567890", apiIdElement.GetString()); + + Assert.True(requestContextElement.TryGetProperty("RequestTime", out JsonElement requestTimeElement)); + Assert.Equal("09/Apr/2015:12:34:56 +0000", requestTimeElement.GetString()); + + Assert.True(requestContextElement.TryGetProperty("RequestTimeEpoch", out JsonElement requestTimeEpochElement)); + Assert.Equal(1428582896000, requestTimeEpochElement.GetInt64()); + + Assert.True(messageElement.TryGetProperty("Body", out JsonElement bodyElement)); + Assert.Equal("hello world", bodyElement.GetString()); + + Assert.True(messageElement.TryGetProperty("IsBase64Encoded", out JsonElement isBase64EncodedElement)); + Assert.False(isBase64EncodedElement.GetBoolean()); + } + + private void AssertInformationLog(string functionName, bool isColdStart, string output) + { + using JsonDocument doc = JsonDocument.Parse(output); + JsonElement root = doc.RootElement; + + AssertDefaultLoggingProperties.ArePresent(functionName, isColdStart, output); + + if (!isColdStart) + { + Assert.True(root.TryGetProperty("LookupInfo", out JsonElement lookupInfoElement)); + Assert.True(lookupInfoElement.TryGetProperty("LookupId", out JsonElement lookupIdElement)); + Assert.Equal("c6af9ac6-7b61-11e6-9a41-93e8deadbeef", lookupIdElement.GetString()); + } + + Assert.True(root.TryGetProperty("Level", out JsonElement levelElement)); + Assert.Equal("Information", levelElement.GetString()); + + Assert.True(root.TryGetProperty("Message", out JsonElement messageElement)); + Assert.Equal("Processing request started", messageElement.GetString()); + } + + private static void AssertWarningLog(string functionName, bool isColdStart, string output) + { + using JsonDocument doc = JsonDocument.Parse(output); + JsonElement root = doc.RootElement; + + AssertDefaultLoggingProperties.ArePresent(functionName, isColdStart, output); + + Assert.True(root.TryGetProperty("LookupInfo", out JsonElement lookupInfoElement)); + Assert.True(lookupInfoElement.TryGetProperty("LookupId", out JsonElement lookupIdElement)); + Assert.Equal("c6af9ac6-7b61-11e6-9a41-93e8deadbeef", lookupIdElement.GetString()); + + Assert.True(root.TryGetProperty("Level", out JsonElement levelElement)); + Assert.Equal("Warning", levelElement.GetString()); + + Assert.True(root.TryGetProperty("Test1", out JsonElement test1Element)); + Assert.Equal("value1", test1Element.GetString()); + + Assert.True(root.TryGetProperty("Test2", out JsonElement test2Element)); + Assert.Equal("value2", test2Element.GetString()); + + Assert.True(root.TryGetProperty("Message", out JsonElement messageElement)); + Assert.Equal("Warn with additional keys", messageElement.GetString()); + } + + private void AssertExceptionLog(string functionName, bool isColdStart, string output) + { + using JsonDocument doc = JsonDocument.Parse(output); + JsonElement root = doc.RootElement; + + AssertDefaultLoggingProperties.ArePresent(functionName, isColdStart, output); + + Assert.True(root.TryGetProperty("LookupInfo", out JsonElement lookupInfoElement)); + Assert.True(lookupInfoElement.TryGetProperty("LookupId", out JsonElement lookupIdElement)); + Assert.Equal("c6af9ac6-7b61-11e6-9a41-93e8deadbeef", lookupIdElement.GetString()); + + Assert.True(root.TryGetProperty("Level", out JsonElement levelElement)); + Assert.Equal("Error", levelElement.GetString()); + + Assert.True(root.TryGetProperty("Message", out JsonElement messageElement)); + Assert.Equal("Oops something went wrong", messageElement.GetString()); + + Assert.True(root.TryGetProperty("Exception", out JsonElement exceptionElement)); + Assert.True(exceptionElement.TryGetProperty("Type", out JsonElement exceptionTypeElement)); + Assert.Equal("System.InvalidOperationException", exceptionTypeElement.GetString()); + + Assert.True(exceptionElement.TryGetProperty("Message", out JsonElement exceptionMessageElement)); + Assert.Equal("Parent exception message", exceptionMessageElement.GetString()); + + Assert.False(root.TryGetProperty("Test1", out JsonElement _)); + Assert.False(root.TryGetProperty("Test2", out JsonElement _)); + } +} \ No newline at end of file diff --git a/libraries/tests/e2e/infra-aot/CoreAotStack.cs b/libraries/tests/e2e/infra-aot/CoreAotStack.cs index 40197ab5..b45e513a 100644 --- a/libraries/tests/e2e/infra-aot/CoreAotStack.cs +++ b/libraries/tests/e2e/infra-aot/CoreAotStack.cs @@ -1,7 +1,7 @@ using Amazon.CDK; using Amazon.CDK.AWS.Lambda; using Constructs; -using TestUtils; +using InfraShared; using Architecture = Amazon.CDK.AWS.Lambda.Architecture; namespace InfraAot; @@ -39,7 +39,8 @@ private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime Name = name, Handler = "AOT-Function", SourcePath = sourcePath, - DistPath = distPath + DistPath = distPath, + IsAot = true }); } } \ No newline at end of file diff --git a/libraries/tests/e2e/infra-aot/FunctionConstruct.cs b/libraries/tests/e2e/infra-aot/FunctionConstruct.cs index aa4e92cb..4e80d066 100644 --- a/libraries/tests/e2e/infra-aot/FunctionConstruct.cs +++ b/libraries/tests/e2e/infra-aot/FunctionConstruct.cs @@ -1,32 +1,32 @@ -using System.Collections.Generic; -using Amazon.CDK; -using Amazon.CDK.AWS.Lambda; -using Constructs; -using TestUtils; - -namespace InfraAot; - -public class FunctionConstruct : Construct -{ - public FunctionConstruct(Construct scope, string id, FunctionConstructProps props) : base(scope, id) - { - var distPath = $"{props.DistPath}/deploy_{props.Architecture.Name}_{props.Runtime.Name}.zip"; - _ = new Function(this, id, new FunctionProps - { - Runtime = props.Runtime, - Architecture = props.Architecture, - FunctionName = props.Name, - Handler = props.Handler, - Tracing = Tracing.ACTIVE, - Timeout = Duration.Seconds(10), - Code = Code.FromCustomCommand(distPath, - [ - $"dotnet-lambda package -pl {props.SourcePath} -cmd ../../../ -o {distPath} -f net8.0 -farch {props.Architecture.Name} -cifb public.ecr.aws/sam/build-dotnet8" - ], - new CustomCommandOptions - { - CommandOptions = new Dictionary {{"shell", true }} - }) - }); - } -} \ No newline at end of file +// using System.Collections.Generic; +// using Amazon.CDK; +// using Amazon.CDK.AWS.Lambda; +// using Constructs; +// using InfraShared; +// +// namespace InfraAot; +// +// public class FunctionConstruct : Construct +// { +// public FunctionConstruct(Construct scope, string id, FunctionConstructProps props) : base(scope, id) +// { +// var distPath = $"{props.DistPath}/deploy_{props.Architecture.Name}_{props.Runtime.Name}.zip"; +// _ = new Function(this, id, new FunctionProps +// { +// Runtime = props.Runtime, +// Architecture = props.Architecture, +// FunctionName = props.Name, +// Handler = props.Handler, +// Tracing = Tracing.ACTIVE, +// Timeout = Duration.Seconds(10), +// Code = Code.FromCustomCommand(distPath, +// [ +// $"dotnet-lambda package -pl {props.SourcePath} -cmd ../../../ -o {distPath} -f net8.0 -farch {props.Architecture.Name} -cifb public.ecr.aws/sam/build-dotnet8" +// ], +// new CustomCommandOptions +// { +// CommandOptions = new Dictionary {{"shell", true }} +// }) +// }); +// } +// } \ No newline at end of file diff --git a/libraries/tests/e2e/infra-aot/InfraAot.csproj b/libraries/tests/e2e/infra-aot/InfraAot.csproj index e1c7a0f9..1e7b2b1e 100644 --- a/libraries/tests/e2e/infra-aot/InfraAot.csproj +++ b/libraries/tests/e2e/infra-aot/InfraAot.csproj @@ -18,5 +18,6 @@ + diff --git a/libraries/tests/e2e/infra-aot/Program.cs b/libraries/tests/e2e/infra-aot/Program.cs index d50d0f5f..6ee80930 100644 --- a/libraries/tests/e2e/infra-aot/Program.cs +++ b/libraries/tests/e2e/infra-aot/Program.cs @@ -1,4 +1,5 @@ using Amazon.CDK; +using InfraShared; namespace InfraAot { @@ -29,6 +30,9 @@ public static void Main(string[] args) { Architecture = architecture }); + + _ = new IdempotencyStack(app, "IdempotencyStack-AOT", new StackProps { }); + app.Synth(); } } diff --git a/libraries/tests/e2e/infra/CoreStack.cs b/libraries/tests/e2e/infra/CoreStack.cs index 23455b64..d77c725a 100644 --- a/libraries/tests/e2e/infra/CoreStack.cs +++ b/libraries/tests/e2e/infra/CoreStack.cs @@ -1,7 +1,7 @@ using Amazon.CDK; using Amazon.CDK.AWS.Lambda; using Constructs; -using TestUtils; +using InfraShared; using Architecture = Amazon.CDK.AWS.Lambda.Architecture; namespace Infra @@ -40,7 +40,7 @@ private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime Name = name, Handler = "Function::Function.Function::FunctionHandler", SourcePath = sourcePath, - DistPath = distPath + DistPath = distPath, }); } } diff --git a/libraries/tests/e2e/infra/FunctionConstruct.cs b/libraries/tests/e2e/infra/FunctionConstruct.cs index 127d1875..aeb9d71f 100644 --- a/libraries/tests/e2e/infra/FunctionConstruct.cs +++ b/libraries/tests/e2e/infra/FunctionConstruct.cs @@ -2,7 +2,7 @@ using Amazon.CDK; using Amazon.CDK.AWS.Lambda; using Constructs; -using TestUtils; +using InfraShared; namespace Infra; diff --git a/libraries/tests/e2e/infra/Infra.csproj b/libraries/tests/e2e/infra/Infra.csproj index e1c7a0f9..9b7b55f6 100644 --- a/libraries/tests/e2e/infra/Infra.csproj +++ b/libraries/tests/e2e/infra/Infra.csproj @@ -17,6 +17,6 @@ - + diff --git a/libraries/tests/e2e/infra/Program.cs b/libraries/tests/e2e/infra/Program.cs index 6d1267c8..208a6182 100644 --- a/libraries/tests/e2e/infra/Program.cs +++ b/libraries/tests/e2e/infra/Program.cs @@ -1,4 +1,5 @@ using Amazon.CDK; +using InfraShared; namespace Infra { @@ -10,6 +11,8 @@ public static void Main(string[] args) _ = new CoreStack(app, "CoreStack", new StackProps { }); + _ = new IdempotencyStack(app, "IdempotencyStack", new StackProps { }); + app.Synth(); } } From ff1bc3757fedf317fc10eed4d1259cd4a431ce72 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Mon, 27 Jan 2025 15:17:28 +0000 Subject: [PATCH 02/12] refactor. non aot infra setup --- libraries/AWS.Lambda.Powertools.sln | 96 +++++++++---------- .../e2e/InfraShared/FunctionConstruct.cs | 12 ++- .../e2e/InfraShared/FunctionConstructProps.cs | 9 +- .../tests/e2e/InfraShared/IdempotencyStack.cs | 50 ++++------ .../src/AOT-Function/AOT-Function.csproj | 2 +- .../AOT-Function/src/AOT-Function/Function.cs | 0 .../aws-lambda-tools-defaults.json | 0 .../Function/src/Function/Function.cs | 0 .../Function/src/Function/Function.csproj | 2 +- .../Function/src/Function/TestHelper.cs | 0 .../Function/aws-lambda-tools-defaults.json | 0 .../test/Function.Tests/Function.Tests.csproj | 2 +- .../test/Function.Tests/FunctionTests.cs | 0 .../e2e/functions/{core => }/payload.json | 0 .../tests/e2e/infra-aot/AOTStackProps.cs | 8 -- libraries/tests/e2e/infra-aot/CoreAotStack.cs | 4 +- libraries/tests/e2e/infra-aot/Program.cs | 18 ++-- .../tests/e2e/infra/FunctionConstruct.cs | 66 ++++++------- libraries/tests/e2e/infra/Program.cs | 2 +- 19 files changed, 130 insertions(+), 141 deletions(-) rename libraries/tests/e2e/functions/{core => }/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj (92%) rename libraries/tests/e2e/functions/{core => }/idempotency/AOT-Function/src/AOT-Function/Function.cs (100%) rename libraries/tests/e2e/functions/{core => }/idempotency/AOT-Function/src/AOT-Function/aws-lambda-tools-defaults.json (100%) rename libraries/tests/e2e/functions/{core => }/idempotency/Function/src/Function/Function.cs (100%) rename libraries/tests/e2e/functions/{core => }/idempotency/Function/src/Function/Function.csproj (88%) rename libraries/tests/e2e/functions/{core => }/idempotency/Function/src/Function/TestHelper.cs (100%) rename libraries/tests/e2e/functions/{core => }/idempotency/Function/src/Function/aws-lambda-tools-defaults.json (100%) rename libraries/tests/e2e/functions/{core => }/idempotency/Function/test/Function.Tests/Function.Tests.csproj (93%) rename libraries/tests/e2e/functions/{core => }/idempotency/Function/test/Function.Tests/FunctionTests.cs (100%) rename libraries/tests/e2e/functions/{core => }/payload.json (100%) delete mode 100644 libraries/tests/e2e/infra-aot/AOTStackProps.cs diff --git a/libraries/AWS.Lambda.Powertools.sln b/libraries/AWS.Lambda.Powertools.sln index 0e2f35c0..ba9f2700 100644 --- a/libraries/AWS.Lambda.Powertools.sln +++ b/libraries/AWS.Lambda.Powertools.sln @@ -47,13 +47,13 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infra", "Infra", "{93DEAC72-245F-4FC9-A7B5-DAE7EF7E1AB7}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Functions", "Functions", "{CDAE55EB-9438-4F54-B7ED-931D64324D5F}" + ProjectSection(SolutionItems) = preProject + tests\e2e\functions\payload.json = tests\e2e\functions\payload.json + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infra", "tests\e2e\infra\Infra.csproj", "{AA532674-A61C-41E6-8F9A-ED53D79AF1EC}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{AAFA39E9-66A3-4B9A-AFE9-EAF74A85A7F0}" - ProjectSection(SolutionItems) = preProject - tests\e2e\functions\core\payload.json = tests\e2e\functions\core\payload.json - EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestUtils", "tests\e2e\functions\TestUtils\TestUtils.csproj", "{3C6162D7-0162-4BC2-BBF5-0554539A81CD}" EndProject @@ -83,15 +83,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-Function", "tests\e2e\f EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfraAot", "tests\e2e\infra-aot\InfraAot.csproj", "{24AC34AD-AEC9-4CFB-BB01-C3C81938AB95}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Idempotency", "Idempotency", "{9D292892-EF57-4930-8472-24BBBE02EBF4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfraShared", "tests\e2e\InfraShared\InfraShared.csproj", "{D303B458-9D84-4DDF-8781-2C0211672329}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Function", "tests\e2e\functions\core\idempotency\Function\src\Function\Function.csproj", "{6696859B-685D-4482-9237-22FD649357F3}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Idempotency", "Idempotency", "{FB2C7DA3-6FCE-429D-86F9-5775D0231EC6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-Function", "tests\e2e\functions\core\idempotency\AOT-Function\src\AOT-Function\AOT-Function.csproj", "{222BB89F-10D8-4CB0-86BC-17F52FBE588F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Function", "tests\e2e\functions\idempotency\Function\src\Function\Function.csproj", "{9AF99F6D-E8E7-443F-A965-D55B8E388836}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Function.Tests", "tests\e2e\functions\core\idempotency\Function\test\Function.Tests\Function.Tests.csproj", "{96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Function.Tests", "tests\e2e\functions\idempotency\Function\test\Function.Tests\Function.Tests.csproj", "{FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfraShared", "tests\e2e\InfraShared\InfraShared.csproj", "{D303B458-9D84-4DDF-8781-2C0211672329}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-Function", "tests\e2e\functions\idempotency\AOT-Function\src\AOT-Function\AOT-Function.csproj", "{56DFC68A-3994-43CD-A17C-323495F1709C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -442,42 +442,6 @@ Global {24AC34AD-AEC9-4CFB-BB01-C3C81938AB95}.Release|x64.Build.0 = Release|Any CPU {24AC34AD-AEC9-4CFB-BB01-C3C81938AB95}.Release|x86.ActiveCfg = Release|Any CPU {24AC34AD-AEC9-4CFB-BB01-C3C81938AB95}.Release|x86.Build.0 = Release|Any CPU - {6696859B-685D-4482-9237-22FD649357F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6696859B-685D-4482-9237-22FD649357F3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6696859B-685D-4482-9237-22FD649357F3}.Debug|x64.ActiveCfg = Debug|Any CPU - {6696859B-685D-4482-9237-22FD649357F3}.Debug|x64.Build.0 = Debug|Any CPU - {6696859B-685D-4482-9237-22FD649357F3}.Debug|x86.ActiveCfg = Debug|Any CPU - {6696859B-685D-4482-9237-22FD649357F3}.Debug|x86.Build.0 = Debug|Any CPU - {6696859B-685D-4482-9237-22FD649357F3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6696859B-685D-4482-9237-22FD649357F3}.Release|Any CPU.Build.0 = Release|Any CPU - {6696859B-685D-4482-9237-22FD649357F3}.Release|x64.ActiveCfg = Release|Any CPU - {6696859B-685D-4482-9237-22FD649357F3}.Release|x64.Build.0 = Release|Any CPU - {6696859B-685D-4482-9237-22FD649357F3}.Release|x86.ActiveCfg = Release|Any CPU - {6696859B-685D-4482-9237-22FD649357F3}.Release|x86.Build.0 = Release|Any CPU - {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Debug|x64.ActiveCfg = Debug|Any CPU - {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Debug|x64.Build.0 = Debug|Any CPU - {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Debug|x86.ActiveCfg = Debug|Any CPU - {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Debug|x86.Build.0 = Debug|Any CPU - {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Release|Any CPU.Build.0 = Release|Any CPU - {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Release|x64.ActiveCfg = Release|Any CPU - {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Release|x64.Build.0 = Release|Any CPU - {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Release|x86.ActiveCfg = Release|Any CPU - {222BB89F-10D8-4CB0-86BC-17F52FBE588F}.Release|x86.Build.0 = Release|Any CPU - {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Debug|Any CPU.Build.0 = Debug|Any CPU - {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Debug|x64.ActiveCfg = Debug|Any CPU - {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Debug|x64.Build.0 = Debug|Any CPU - {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Debug|x86.ActiveCfg = Debug|Any CPU - {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Debug|x86.Build.0 = Debug|Any CPU - {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Release|Any CPU.ActiveCfg = Release|Any CPU - {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Release|Any CPU.Build.0 = Release|Any CPU - {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Release|x64.ActiveCfg = Release|Any CPU - {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Release|x64.Build.0 = Release|Any CPU - {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Release|x86.ActiveCfg = Release|Any CPU - {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164}.Release|x86.Build.0 = Release|Any CPU {D303B458-9D84-4DDF-8781-2C0211672329}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D303B458-9D84-4DDF-8781-2C0211672329}.Debug|Any CPU.Build.0 = Debug|Any CPU {D303B458-9D84-4DDF-8781-2C0211672329}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -490,6 +454,42 @@ Global {D303B458-9D84-4DDF-8781-2C0211672329}.Release|x64.Build.0 = Release|Any CPU {D303B458-9D84-4DDF-8781-2C0211672329}.Release|x86.ActiveCfg = Release|Any CPU {D303B458-9D84-4DDF-8781-2C0211672329}.Release|x86.Build.0 = Release|Any CPU + {9AF99F6D-E8E7-443F-A965-D55B8E388836}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AF99F6D-E8E7-443F-A965-D55B8E388836}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AF99F6D-E8E7-443F-A965-D55B8E388836}.Debug|x64.ActiveCfg = Debug|Any CPU + {9AF99F6D-E8E7-443F-A965-D55B8E388836}.Debug|x64.Build.0 = Debug|Any CPU + {9AF99F6D-E8E7-443F-A965-D55B8E388836}.Debug|x86.ActiveCfg = Debug|Any CPU + {9AF99F6D-E8E7-443F-A965-D55B8E388836}.Debug|x86.Build.0 = Debug|Any CPU + {9AF99F6D-E8E7-443F-A965-D55B8E388836}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AF99F6D-E8E7-443F-A965-D55B8E388836}.Release|Any CPU.Build.0 = Release|Any CPU + {9AF99F6D-E8E7-443F-A965-D55B8E388836}.Release|x64.ActiveCfg = Release|Any CPU + {9AF99F6D-E8E7-443F-A965-D55B8E388836}.Release|x64.Build.0 = Release|Any CPU + {9AF99F6D-E8E7-443F-A965-D55B8E388836}.Release|x86.ActiveCfg = Release|Any CPU + {9AF99F6D-E8E7-443F-A965-D55B8E388836}.Release|x86.Build.0 = Release|Any CPU + {FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC}.Debug|x64.ActiveCfg = Debug|Any CPU + {FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC}.Debug|x64.Build.0 = Debug|Any CPU + {FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC}.Debug|x86.ActiveCfg = Debug|Any CPU + {FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC}.Debug|x86.Build.0 = Debug|Any CPU + {FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC}.Release|Any CPU.Build.0 = Release|Any CPU + {FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC}.Release|x64.ActiveCfg = Release|Any CPU + {FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC}.Release|x64.Build.0 = Release|Any CPU + {FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC}.Release|x86.ActiveCfg = Release|Any CPU + {FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC}.Release|x86.Build.0 = Release|Any CPU + {56DFC68A-3994-43CD-A17C-323495F1709C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56DFC68A-3994-43CD-A17C-323495F1709C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56DFC68A-3994-43CD-A17C-323495F1709C}.Debug|x64.ActiveCfg = Debug|Any CPU + {56DFC68A-3994-43CD-A17C-323495F1709C}.Debug|x64.Build.0 = Debug|Any CPU + {56DFC68A-3994-43CD-A17C-323495F1709C}.Debug|x86.ActiveCfg = Debug|Any CPU + {56DFC68A-3994-43CD-A17C-323495F1709C}.Debug|x86.Build.0 = Debug|Any CPU + {56DFC68A-3994-43CD-A17C-323495F1709C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56DFC68A-3994-43CD-A17C-323495F1709C}.Release|Any CPU.Build.0 = Release|Any CPU + {56DFC68A-3994-43CD-A17C-323495F1709C}.Release|x64.ActiveCfg = Release|Any CPU + {56DFC68A-3994-43CD-A17C-323495F1709C}.Release|x64.Build.0 = Release|Any CPU + {56DFC68A-3994-43CD-A17C-323495F1709C}.Release|x86.ActiveCfg = Release|Any CPU + {56DFC68A-3994-43CD-A17C-323495F1709C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution @@ -528,10 +528,10 @@ Global {8DDAFE37-ED59-4710-9415-8EBA44CC6437} = {3C9FA701-31FF-4747-B324-E0D252EAFD63} {8DDED681-AE8D-45EB-A22E-2FFB88620F9B} = {3C9FA701-31FF-4747-B324-E0D252EAFD63} {24AC34AD-AEC9-4CFB-BB01-C3C81938AB95} = {93DEAC72-245F-4FC9-A7B5-DAE7EF7E1AB7} - {9D292892-EF57-4930-8472-24BBBE02EBF4} = {AAFA39E9-66A3-4B9A-AFE9-EAF74A85A7F0} - {6696859B-685D-4482-9237-22FD649357F3} = {9D292892-EF57-4930-8472-24BBBE02EBF4} - {222BB89F-10D8-4CB0-86BC-17F52FBE588F} = {9D292892-EF57-4930-8472-24BBBE02EBF4} - {96D0D8D7-EC06-44D1-B8D4-BFE02B1D0164} = {9D292892-EF57-4930-8472-24BBBE02EBF4} {D303B458-9D84-4DDF-8781-2C0211672329} = {93DEAC72-245F-4FC9-A7B5-DAE7EF7E1AB7} + {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} = {CDAE55EB-9438-4F54-B7ED-931D64324D5F} + {9AF99F6D-E8E7-443F-A965-D55B8E388836} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} + {FBCE2C8A-2F64-4B62-8CF1-D4A14C19A5CC} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} + {56DFC68A-3994-43CD-A17C-323495F1709C} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} EndGlobalSection EndGlobal diff --git a/libraries/tests/e2e/InfraShared/FunctionConstruct.cs b/libraries/tests/e2e/InfraShared/FunctionConstruct.cs index 1b752de2..3fbe5616 100644 --- a/libraries/tests/e2e/InfraShared/FunctionConstruct.cs +++ b/libraries/tests/e2e/InfraShared/FunctionConstruct.cs @@ -7,7 +7,7 @@ namespace InfraShared; public class FunctionConstruct : Construct { public Function Function { get; set; } - + public FunctionConstruct(Construct scope, string id, FunctionConstructProps props) : base(scope, id) { var framework = props.Runtime == Runtime.DOTNET_6 ? "net6.0" : "net8.0"; @@ -15,6 +15,8 @@ public FunctionConstruct(Construct scope, string id, FunctionConstructProps prop var command = props.IsAot ? $"dotnet-lambda package -pl {props.SourcePath} -cmd ../../../ -o {distPath} -f net8.0 -farch {props.Architecture.Name} -cifb public.ecr.aws/sam/build-dotnet8" : $"dotnet-lambda package -pl {props.SourcePath} -o {distPath} -f {framework} -farch {props.Architecture.Name}"; + + Console.WriteLine(command); Function = new Function(this, id, new FunctionProps { @@ -24,11 +26,11 @@ public FunctionConstruct(Construct scope, string id, FunctionConstructProps prop Handler = props.Handler, Tracing = Tracing.ACTIVE, Timeout = Duration.Seconds(10), + Environment = props.Environment, Code = Code.FromCustomCommand(distPath, - new[] - { + [ command - }, + ], new CustomCommandOptions { CommandOptions = new Dictionary { { "shell", true } } @@ -36,7 +38,7 @@ public FunctionConstruct(Construct scope, string id, FunctionConstructProps prop }); } - + // public FunctionConstruct(Construct scope, string id, FunctionConstructProps props) : base(scope, id) // { // if(props.IsAot) diff --git a/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs b/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs index fc38939d..1fa4cd9a 100644 --- a/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs +++ b/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs @@ -1,8 +1,9 @@ +using Amazon.CDK; using Amazon.CDK.AWS.Lambda; namespace InfraShared; -public class FunctionConstructProps +public class FunctionConstructProps : PowertoolsDefaultStackProps { public required Architecture Architecture { get; set; } public required Runtime Runtime { get; set; } @@ -10,5 +11,11 @@ public class FunctionConstructProps public required string Handler { get; set; } public required string SourcePath { get; set; } public required string DistPath { get; set; } +} + +public class PowertoolsDefaultStackProps : StackProps +{ public bool IsAot { get; set; } = false; + public string? ArchitectureString { get; set; } + public Dictionary? Environment { get; set; } } \ No newline at end of file diff --git a/libraries/tests/e2e/InfraShared/IdempotencyStack.cs b/libraries/tests/e2e/InfraShared/IdempotencyStack.cs index 33a3acb0..af391def 100644 --- a/libraries/tests/e2e/InfraShared/IdempotencyStack.cs +++ b/libraries/tests/e2e/InfraShared/IdempotencyStack.cs @@ -6,16 +6,11 @@ namespace InfraShared; -public class IdempotencyStackProps : IStackProps -{ - public required Runtime Runtime { get; set; } - public required string Name { get; set; } - public required string Handler { get; set; } -} - public class IdempotencyStack : Stack { - public IdempotencyStack(Construct scope, string id, IdempotencyStackProps props) : base(scope, id, props) + public Table Table { get; set; } + + public IdempotencyStack(Construct scope, string id, PowertoolsDefaultStackProps props) : base(scope, id, props) { Table = new Table(this, "Idempotency", new TableProps { @@ -29,47 +24,38 @@ public IdempotencyStack(Construct scope, string id, IdempotencyStackProps props) TimeToLiveAttribute = "expiration" }); - var utility = "Idempotency"; - var basePath = $"../functions/core/{utility}/Function/src/Function"; - var distPath = $"../functions/core/{utility}/Function/dist"; + var utility = "idempotency"; + var basePath = $"../functions/{utility}/Function/src/Function"; + var distPath = $"../functions/{utility}/Function/dist"; CreateFunctionConstruct(this, $"{utility}_X64_net8", Runtime.DOTNET_8, Architecture.X86_64, - $"E2ETestLambda_X64_NET8_{utility}", basePath, distPath); + $"E2ETestLambda_X64_NET8_{utility}", basePath, distPath, props); CreateFunctionConstruct(this, $"{utility}_arm_net8", Runtime.DOTNET_8, Architecture.ARM_64, - $"E2ETestLambda_ARM_NET8_{utility}", basePath, distPath); + $"E2ETestLambda_ARM_NET8_{utility}", basePath, distPath, props); CreateFunctionConstruct(this, $"{utility}_X64_net6", Runtime.DOTNET_6, Architecture.X86_64, - $"E2ETestLambda_X64_NET6_{utility}", basePath, distPath); + $"E2ETestLambda_X64_NET6_{utility}", basePath, distPath, props); CreateFunctionConstruct(this, $"{utility}_arm_net6", Runtime.DOTNET_6, Architecture.ARM_64, - $"E2ETestLambda_ARM_NET6_{utility}", basePath, distPath); + $"E2ETestLambda_ARM_NET6_{utility}", basePath, distPath, props); } - public Table Table { get; set; } - private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime, Architecture architecture, - string name, string sourcePath, string distPath) + string name, string sourcePath, string distPath, PowertoolsDefaultStackProps props) { var lambdaFunction = new FunctionConstruct(scope, id, new FunctionConstructProps { Runtime = runtime, Architecture = architecture, Name = name, - Handler = "AOT-Function", + Handler = "Function::Function.Function::FunctionHandler", SourcePath = sourcePath, DistPath = distPath, - IsAot = true + Environment = new Dictionary + { + { "TABLE_NAME", Table.TableName } + }, + IsAot = props.IsAot }); - - // var lambdaFunction = new Function(this, "IdempotencyFunction", new FunctionProps - // { - // Runtime = props.Runtime, - // Handler = props.Handler, - // Code = Code.FromAsset("path/to/your/lambda/code"), - // Environment = new Dictionary - // { - // { "TABLE_NAME", table.TableName } - // } - // }); - + Table.GrantReadWriteData(lambdaFunction.Function); } } diff --git a/libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj similarity index 92% rename from libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj rename to libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj index 96b79385..b5c6ae5c 100644 --- a/libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj +++ b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj @@ -28,6 +28,6 @@ - + \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/Function.cs b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/Function.cs similarity index 100% rename from libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/Function.cs rename to libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/Function.cs diff --git a/libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/aws-lambda-tools-defaults.json b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/aws-lambda-tools-defaults.json similarity index 100% rename from libraries/tests/e2e/functions/core/idempotency/AOT-Function/src/AOT-Function/aws-lambda-tools-defaults.json rename to libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/aws-lambda-tools-defaults.json diff --git a/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/Function.cs b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs similarity index 100% rename from libraries/tests/e2e/functions/core/idempotency/Function/src/Function/Function.cs rename to libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs diff --git a/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/Function.csproj b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.csproj similarity index 88% rename from libraries/tests/e2e/functions/core/idempotency/Function/src/Function/Function.csproj rename to libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.csproj index 2648fecf..0dedeaea 100644 --- a/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/Function.csproj +++ b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.csproj @@ -16,6 +16,6 @@ - + \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/TestHelper.cs b/libraries/tests/e2e/functions/idempotency/Function/src/Function/TestHelper.cs similarity index 100% rename from libraries/tests/e2e/functions/core/idempotency/Function/src/Function/TestHelper.cs rename to libraries/tests/e2e/functions/idempotency/Function/src/Function/TestHelper.cs diff --git a/libraries/tests/e2e/functions/core/idempotency/Function/src/Function/aws-lambda-tools-defaults.json b/libraries/tests/e2e/functions/idempotency/Function/src/Function/aws-lambda-tools-defaults.json similarity index 100% rename from libraries/tests/e2e/functions/core/idempotency/Function/src/Function/aws-lambda-tools-defaults.json rename to libraries/tests/e2e/functions/idempotency/Function/src/Function/aws-lambda-tools-defaults.json diff --git a/libraries/tests/e2e/functions/core/idempotency/Function/test/Function.Tests/Function.Tests.csproj b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/Function.Tests.csproj similarity index 93% rename from libraries/tests/e2e/functions/core/idempotency/Function/test/Function.Tests/Function.Tests.csproj rename to libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/Function.Tests.csproj index c5eb20fb..06365337 100644 --- a/libraries/tests/e2e/functions/core/idempotency/Function/test/Function.Tests/Function.Tests.csproj +++ b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/Function.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/libraries/tests/e2e/functions/core/idempotency/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs similarity index 100% rename from libraries/tests/e2e/functions/core/idempotency/Function/test/Function.Tests/FunctionTests.cs rename to libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs diff --git a/libraries/tests/e2e/functions/core/payload.json b/libraries/tests/e2e/functions/payload.json similarity index 100% rename from libraries/tests/e2e/functions/core/payload.json rename to libraries/tests/e2e/functions/payload.json diff --git a/libraries/tests/e2e/infra-aot/AOTStackProps.cs b/libraries/tests/e2e/infra-aot/AOTStackProps.cs deleted file mode 100644 index 4e735a8f..00000000 --- a/libraries/tests/e2e/infra-aot/AOTStackProps.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Amazon.CDK; - -namespace InfraAot; - -public class AotStackProps : StackProps -{ - public string Architecture { get; set; } -} \ No newline at end of file diff --git a/libraries/tests/e2e/infra-aot/CoreAotStack.cs b/libraries/tests/e2e/infra-aot/CoreAotStack.cs index b45e513a..fdfd1025 100644 --- a/libraries/tests/e2e/infra-aot/CoreAotStack.cs +++ b/libraries/tests/e2e/infra-aot/CoreAotStack.cs @@ -10,9 +10,9 @@ public class CoreAotStack : Stack { private readonly Architecture _architecture; - internal CoreAotStack(Construct scope, string id, AotStackProps props = null) : base(scope, id, props) + internal CoreAotStack(Construct scope, string id, PowertoolsDefaultStackProps props = null) : base(scope, id, props) { - if (props != null) _architecture = props.Architecture == "arm64" ? Architecture.ARM_64 : Architecture.X86_64; + if (props != null) _architecture = props.ArchitectureString == "arm64" ? Architecture.ARM_64 : Architecture.X86_64; CreateFunctionConstructs("logging"); CreateFunctionConstructs("metrics"); diff --git a/libraries/tests/e2e/infra-aot/Program.cs b/libraries/tests/e2e/infra-aot/Program.cs index 6ee80930..d0b4f5c3 100644 --- a/libraries/tests/e2e/infra-aot/Program.cs +++ b/libraries/tests/e2e/infra-aot/Program.cs @@ -12,7 +12,8 @@ public static void Main(string[] args) var architecture = app.Node.TryGetContext("architecture")?.ToString(); if (architecture == null) { - throw new System.ArgumentException("architecture context is required. Please provide it with --context architecture=arm64|x86_64"); + throw new System.ArgumentException( + "architecture context is required. Please provide it with --context architecture=arm64|x86_64"); } if (architecture != "arm64" && architecture != "x86_64") @@ -21,19 +22,20 @@ public static void Main(string[] args) } var id = "CoreAotStack"; - if(architecture == "arm64") + if (architecture == "arm64") { id = $"CoreAotStack-{architecture}"; } - _ = new CoreAotStack(app, id, new AotStackProps + _ = new CoreAotStack(app, id, new PowertoolsDefaultStackProps { - Architecture = architecture + ArchitectureString = architecture }); - - _ = new IdempotencyStack(app, "IdempotencyStack-AOT", new StackProps { }); - + + _ = new IdempotencyStack(app, $"IdempotencyStack-AOT-{architecture}", + new PowertoolsDefaultStackProps { IsAot = true, ArchitectureString = architecture }); + app.Synth(); } } -} +} \ No newline at end of file diff --git a/libraries/tests/e2e/infra/FunctionConstruct.cs b/libraries/tests/e2e/infra/FunctionConstruct.cs index aeb9d71f..2bc973e8 100644 --- a/libraries/tests/e2e/infra/FunctionConstruct.cs +++ b/libraries/tests/e2e/infra/FunctionConstruct.cs @@ -1,33 +1,33 @@ -using System.Collections.Generic; -using Amazon.CDK; -using Amazon.CDK.AWS.Lambda; -using Constructs; -using InfraShared; - -namespace Infra; - -public class FunctionConstruct : Construct -{ - public FunctionConstruct(Construct scope, string id, FunctionConstructProps props) : base(scope, id) - { - var framework = props.Runtime == Runtime.DOTNET_6 ? "net6.0" : "net8.0"; - var distPath = $"{props.DistPath}/deploy_{props.Architecture.Name}_{props.Runtime.Name}.zip"; - _ = new Function(this, id, new FunctionProps - { - Runtime = props.Runtime, - Architecture = props.Architecture, - FunctionName = props.Name, - Handler = props.Handler, - Tracing = Tracing.ACTIVE, - Timeout = Duration.Seconds(10), - Code = Code.FromCustomCommand(distPath, - [ - $"dotnet-lambda package -pl {props.SourcePath} -o {distPath} -f {framework} -farch {props.Architecture.Name}" - ], - new CustomCommandOptions - { - CommandOptions = new Dictionary {{"shell", true }} - }) - }); - } -} \ No newline at end of file +// using System.Collections.Generic; +// using Amazon.CDK; +// using Amazon.CDK.AWS.Lambda; +// using Constructs; +// using InfraShared; +// +// namespace Infra; +// +// public class FunctionConstruct : Construct +// { +// public FunctionConstruct(Construct scope, string id, FunctionConstructProps props) : base(scope, id) +// { +// var framework = props.Runtime == Runtime.DOTNET_6 ? "net6.0" : "net8.0"; +// var distPath = $"{props.DistPath}/deploy_{props.Architecture.Name}_{props.Runtime.Name}.zip"; +// _ = new Function(this, id, new FunctionProps +// { +// Runtime = props.Runtime, +// Architecture = props.Architecture, +// FunctionName = props.Name, +// Handler = props.Handler, +// Tracing = Tracing.ACTIVE, +// Timeout = Duration.Seconds(10), +// Code = Code.FromCustomCommand(distPath, +// [ +// $"dotnet-lambda package -pl {props.SourcePath} -o {distPath} -f {framework} -farch {props.Architecture.Name}" +// ], +// new CustomCommandOptions +// { +// CommandOptions = new Dictionary {{"shell", true }} +// }) +// }); +// } +// } \ No newline at end of file diff --git a/libraries/tests/e2e/infra/Program.cs b/libraries/tests/e2e/infra/Program.cs index 208a6182..399049d8 100644 --- a/libraries/tests/e2e/infra/Program.cs +++ b/libraries/tests/e2e/infra/Program.cs @@ -11,7 +11,7 @@ public static void Main(string[] args) _ = new CoreStack(app, "CoreStack", new StackProps { }); - _ = new IdempotencyStack(app, "IdempotencyStack", new StackProps { }); + _ = new IdempotencyStack(app, "IdempotencyStack", new PowertoolsDefaultStackProps { }); app.Synth(); } From dae99f0db6ef2c05e079349d7e0a891ace6710de Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:14:52 +0000 Subject: [PATCH 03/12] refactor and simple function --- .../e2e/InfraShared/FunctionConstruct.cs | 48 ------------------- .../tests/e2e/InfraShared/IdempotencyStack.cs | 9 ++-- .../Function/src/Function/Function.cs | 16 ++++--- .../Function/src/Function/TestHelper.cs | 40 +++++++++++++++- .../tests/e2e/infra-aot/FunctionConstruct.cs | 32 ------------- .../tests/e2e/infra/FunctionConstruct.cs | 33 ------------- 6 files changed, 53 insertions(+), 125 deletions(-) delete mode 100644 libraries/tests/e2e/infra-aot/FunctionConstruct.cs delete mode 100644 libraries/tests/e2e/infra/FunctionConstruct.cs diff --git a/libraries/tests/e2e/InfraShared/FunctionConstruct.cs b/libraries/tests/e2e/InfraShared/FunctionConstruct.cs index 3fbe5616..63fabbd3 100644 --- a/libraries/tests/e2e/InfraShared/FunctionConstruct.cs +++ b/libraries/tests/e2e/InfraShared/FunctionConstruct.cs @@ -37,52 +37,4 @@ public FunctionConstruct(Construct scope, string id, FunctionConstructProps prop }) }); } - - - // public FunctionConstruct(Construct scope, string id, FunctionConstructProps props) : base(scope, id) - // { - // if(props.IsAot) - // { - // var distPath = $"{props.DistPath}/deploy_{props.Architecture.Name}_{props.Runtime.Name}.zip"; - // _ = new Function(this, id, new FunctionProps - // { - // Runtime = props.Runtime, - // Architecture = props.Architecture, - // FunctionName = props.Name, - // Handler = props.Handler, - // Tracing = Tracing.ACTIVE, - // Timeout = Duration.Seconds(10), - // Code = Code.FromCustomCommand(distPath, - // [ - // $"dotnet-lambda package -pl {props.SourcePath} -cmd ../../../ -o {distPath} -f net8.0 -farch {props.Architecture.Name} -cifb public.ecr.aws/sam/build-dotnet8" - // ], - // new CustomCommandOptions - // { - // CommandOptions = new Dictionary {{"shell", true }} - // }) - // }); - // } - // else - // { - // var framework = props.Runtime == Runtime.DOTNET_6 ? "net6.0" : "net8.0"; - // var distPath = $"{props.DistPath}/deploy_{props.Architecture.Name}_{props.Runtime.Name}.zip"; - // _ = new Function(this, id, new FunctionProps - // { - // Runtime = props.Runtime, - // Architecture = props.Architecture, - // FunctionName = props.Name, - // Handler = props.Handler, - // Tracing = Tracing.ACTIVE, - // Timeout = Duration.Seconds(10), - // Code = Code.FromCustomCommand(distPath, - // [ - // $"dotnet-lambda package -pl {props.SourcePath} -o {distPath} -f {framework} -farch {props.Architecture.Name}" - // ], - // new CustomCommandOptions - // { - // CommandOptions = new Dictionary { { "shell", true } } - // }) - // }); - // } - // } } \ No newline at end of file diff --git a/libraries/tests/e2e/InfraShared/IdempotencyStack.cs b/libraries/tests/e2e/InfraShared/IdempotencyStack.cs index af391def..5f708c75 100644 --- a/libraries/tests/e2e/InfraShared/IdempotencyStack.cs +++ b/libraries/tests/e2e/InfraShared/IdempotencyStack.cs @@ -1,5 +1,6 @@ using Amazon.CDK; using Amazon.CDK.AWS.DynamoDB; +using Amazon.CDK.AWS.IAM; using Amazon.CDK.AWS.Lambda; using Constructs; using Attribute = Amazon.CDK.AWS.DynamoDB.Attribute; @@ -16,12 +17,13 @@ public IdempotencyStack(Construct scope, string id, PowertoolsDefaultStackProps { PartitionKey = new Attribute { - Name = "Id", + Name = "id", Type = AttributeType.STRING }, TableName = "IdempotencyTable", BillingMode = BillingMode.PAY_PER_REQUEST, - TimeToLiveAttribute = "expiration" + TimeToLiveAttribute = "expiration", + DeletionProtection = false }); var utility = "idempotency"; @@ -51,11 +53,12 @@ private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime DistPath = distPath, Environment = new Dictionary { - { "TABLE_NAME", Table.TableName } + { "IDEMPOTENCY_TABLE_NAME", Table.TableName } }, IsAot = props.IsAot }); + // Grant the Lambda function permissions to perform all actions on the DynamoDB table Table.GrantReadWriteData(lambdaFunction.Function); } } diff --git a/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs index 04b2d5b2..868cca18 100644 --- a/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs +++ b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs @@ -1,5 +1,6 @@ using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.Core; +using AWS.Lambda.Powertools.Idempotency; using Helpers; // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. @@ -9,14 +10,15 @@ namespace Function; public class Function { + public Function() + { + var tableName = Environment.GetEnvironmentVariable("IDEMPOTENCY_TABLE_NAME"); + Idempotency.Configure(builder => builder.UseDynamoDb(tableName)); + } + + [Idempotent] public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) { - TestHelper.TestMethod(apigwProxyEvent); - - return new APIGatewayProxyResponse() - { - StatusCode = 200, - Body = apigwProxyEvent.Body.ToUpper() - }; + return TestHelper.TestMethod(apigwProxyEvent); } } \ No newline at end of file diff --git a/libraries/tests/e2e/functions/idempotency/Function/src/Function/TestHelper.cs b/libraries/tests/e2e/functions/idempotency/Function/src/Function/TestHelper.cs index 9d2766a4..8caa694a 100644 --- a/libraries/tests/e2e/functions/idempotency/Function/src/Function/TestHelper.cs +++ b/libraries/tests/e2e/functions/idempotency/Function/src/Function/TestHelper.cs @@ -1,11 +1,47 @@ +using System.Text.Json; using Amazon.Lambda.APIGatewayEvents; namespace Helpers; public static class TestHelper { - public static void TestMethod(APIGatewayProxyRequest apigwProxyEvent) + public static APIGatewayProxyResponse TestMethod(APIGatewayProxyRequest apigwProxyEvent) { - + var requestContextRequestId = apigwProxyEvent.RequestContext.RequestId; + var response = new + { + RequestId = requestContextRequestId, + Greeting = "Hello Powertools for AWS Lambda (.NET)", + MethodGuid = GenerateGuid(), // Guid generated by the GenerateGuid method. used to compare Method output + HandlerGuid = Guid.NewGuid().ToString() // Guid generated in the Handler. used to compare Handler output + }; + + try + { + return new APIGatewayProxyResponse + { + Body = JsonSerializer.Serialize(response), + StatusCode = 200, + Headers = new Dictionary { { "Content-Type", "application/json" } } + }; + } + catch (Exception e) + { + return new APIGatewayProxyResponse + { + Body = e.Message, + StatusCode = 500, + Headers = new Dictionary { { "Content-Type", "application/json" } } + }; + } + } + + /// + /// Generates a new Guid to check if value is the same between calls (should be when idempotency enabled) + /// + /// GUID + private static string GenerateGuid() + { + return Guid.NewGuid().ToString(); } } \ No newline at end of file diff --git a/libraries/tests/e2e/infra-aot/FunctionConstruct.cs b/libraries/tests/e2e/infra-aot/FunctionConstruct.cs deleted file mode 100644 index 4e80d066..00000000 --- a/libraries/tests/e2e/infra-aot/FunctionConstruct.cs +++ /dev/null @@ -1,32 +0,0 @@ -// using System.Collections.Generic; -// using Amazon.CDK; -// using Amazon.CDK.AWS.Lambda; -// using Constructs; -// using InfraShared; -// -// namespace InfraAot; -// -// public class FunctionConstruct : Construct -// { -// public FunctionConstruct(Construct scope, string id, FunctionConstructProps props) : base(scope, id) -// { -// var distPath = $"{props.DistPath}/deploy_{props.Architecture.Name}_{props.Runtime.Name}.zip"; -// _ = new Function(this, id, new FunctionProps -// { -// Runtime = props.Runtime, -// Architecture = props.Architecture, -// FunctionName = props.Name, -// Handler = props.Handler, -// Tracing = Tracing.ACTIVE, -// Timeout = Duration.Seconds(10), -// Code = Code.FromCustomCommand(distPath, -// [ -// $"dotnet-lambda package -pl {props.SourcePath} -cmd ../../../ -o {distPath} -f net8.0 -farch {props.Architecture.Name} -cifb public.ecr.aws/sam/build-dotnet8" -// ], -// new CustomCommandOptions -// { -// CommandOptions = new Dictionary {{"shell", true }} -// }) -// }); -// } -// } \ No newline at end of file diff --git a/libraries/tests/e2e/infra/FunctionConstruct.cs b/libraries/tests/e2e/infra/FunctionConstruct.cs deleted file mode 100644 index 2bc973e8..00000000 --- a/libraries/tests/e2e/infra/FunctionConstruct.cs +++ /dev/null @@ -1,33 +0,0 @@ -// using System.Collections.Generic; -// using Amazon.CDK; -// using Amazon.CDK.AWS.Lambda; -// using Constructs; -// using InfraShared; -// -// namespace Infra; -// -// public class FunctionConstruct : Construct -// { -// public FunctionConstruct(Construct scope, string id, FunctionConstructProps props) : base(scope, id) -// { -// var framework = props.Runtime == Runtime.DOTNET_6 ? "net6.0" : "net8.0"; -// var distPath = $"{props.DistPath}/deploy_{props.Architecture.Name}_{props.Runtime.Name}.zip"; -// _ = new Function(this, id, new FunctionProps -// { -// Runtime = props.Runtime, -// Architecture = props.Architecture, -// FunctionName = props.Name, -// Handler = props.Handler, -// Tracing = Tracing.ACTIVE, -// Timeout = Duration.Seconds(10), -// Code = Code.FromCustomCommand(distPath, -// [ -// $"dotnet-lambda package -pl {props.SourcePath} -o {distPath} -f {framework} -farch {props.Architecture.Name}" -// ], -// new CustomCommandOptions -// { -// CommandOptions = new Dictionary {{"shell", true }} -// }) -// }); -// } -// } \ No newline at end of file From e7291e6b4237a45a6a85c0d946cbcc4bf5b191f5 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Mon, 27 Jan 2025 20:02:12 +0000 Subject: [PATCH 04/12] refactor and aot infra --- .../e2e/InfraShared/FunctionConstructProps.cs | 5 ++ .../tests/e2e/InfraShared/IdempotencyStack.cs | 47 ++++++++++++------- .../test/Function.Tests/FunctionTests.cs | 4 +- .../test/Function.Tests/FunctionTests.cs | 4 +- .../test/Function.Tests/FunctionTests.cs | 4 +- .../test/Function.Tests/FunctionTests.cs | 2 +- libraries/tests/e2e/infra-aot/CoreAotStack.cs | 2 +- libraries/tests/e2e/infra-aot/Program.cs | 2 +- libraries/tests/e2e/infra/Program.cs | 2 +- 9 files changed, 45 insertions(+), 27 deletions(-) diff --git a/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs b/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs index 1fa4cd9a..2ae564c5 100644 --- a/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs +++ b/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs @@ -18,4 +18,9 @@ public class PowertoolsDefaultStackProps : StackProps public bool IsAot { get; set; } = false; public string? ArchitectureString { get; set; } public Dictionary? Environment { get; set; } +} + +public class IdempotencyStackProps : PowertoolsDefaultStackProps +{ + public required string TableName { get; set; } } \ No newline at end of file diff --git a/libraries/tests/e2e/InfraShared/IdempotencyStack.cs b/libraries/tests/e2e/InfraShared/IdempotencyStack.cs index 5f708c75..69f07e35 100644 --- a/libraries/tests/e2e/InfraShared/IdempotencyStack.cs +++ b/libraries/tests/e2e/InfraShared/IdempotencyStack.cs @@ -1,6 +1,5 @@ using Amazon.CDK; using Amazon.CDK.AWS.DynamoDB; -using Amazon.CDK.AWS.IAM; using Amazon.CDK.AWS.Lambda; using Constructs; using Attribute = Amazon.CDK.AWS.DynamoDB.Attribute; @@ -10,8 +9,8 @@ namespace InfraShared; public class IdempotencyStack : Stack { public Table Table { get; set; } - - public IdempotencyStack(Construct scope, string id, PowertoolsDefaultStackProps props) : base(scope, id, props) + + public IdempotencyStack(Construct scope, string id, IdempotencyStackProps props) : base(scope, id, props) { Table = new Table(this, "Idempotency", new TableProps { @@ -20,24 +19,38 @@ public IdempotencyStack(Construct scope, string id, PowertoolsDefaultStackProps Name = "id", Type = AttributeType.STRING }, - TableName = "IdempotencyTable", + TableName = props.TableName, BillingMode = BillingMode.PAY_PER_REQUEST, TimeToLiveAttribute = "expiration", - DeletionProtection = false + RemovalPolicy = RemovalPolicy.DESTROY }); - + var utility = "idempotency"; - var basePath = $"../functions/{utility}/Function/src/Function"; - var distPath = $"../functions/{utility}/Function/dist"; - CreateFunctionConstruct(this, $"{utility}_X64_net8", Runtime.DOTNET_8, Architecture.X86_64, - $"E2ETestLambda_X64_NET8_{utility}", basePath, distPath, props); - CreateFunctionConstruct(this, $"{utility}_arm_net8", Runtime.DOTNET_8, Architecture.ARM_64, - $"E2ETestLambda_ARM_NET8_{utility}", basePath, distPath, props); - CreateFunctionConstruct(this, $"{utility}_X64_net6", Runtime.DOTNET_6, Architecture.X86_64, - $"E2ETestLambda_X64_NET6_{utility}", basePath, distPath, props); - CreateFunctionConstruct(this, $"{utility}_arm_net6", Runtime.DOTNET_6, Architecture.ARM_64, - $"E2ETestLambda_ARM_NET6_{utility}", basePath, distPath, props); + if (props.IsAot) + { + var baseAotPath = $"../functions/{utility}/AOT-Function/src/AOT-Function"; + var distAotPath = $"../functions/{utility}/AOT-Function/dist"; + + var architecture = props.ArchitectureString == "arm64" ? Architecture.ARM_64 : Architecture.X86_64; + var arch = architecture == Architecture.X86_64 ? "X64" : "ARM"; + CreateFunctionConstruct(this, $"{utility}_{arch}_aot_net8", Runtime.DOTNET_8, architecture, + $"E2ETestLambda_{arch}_AOT_NET8_{utility}", baseAotPath, distAotPath, props); + } + else + { + var basePath = $"../functions/{utility}/Function/src/Function"; + var distPath = $"../functions/{utility}/Function/dist"; + + CreateFunctionConstruct(this, $"{utility}_X64_net8", Runtime.DOTNET_8, Architecture.X86_64, + $"E2ETestLambda_X64_NET8_{utility}", basePath, distPath, props); + CreateFunctionConstruct(this, $"{utility}_arm_net8", Runtime.DOTNET_8, Architecture.ARM_64, + $"E2ETestLambda_ARM_NET8_{utility}", basePath, distPath, props); + CreateFunctionConstruct(this, $"{utility}_X64_net6", Runtime.DOTNET_6, Architecture.X86_64, + $"E2ETestLambda_X64_NET6_{utility}", basePath, distPath, props); + CreateFunctionConstruct(this, $"{utility}_arm_net6", Runtime.DOTNET_6, Architecture.ARM_64, + $"E2ETestLambda_ARM_NET6_{utility}", basePath, distPath, props); + } } private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime, Architecture architecture, @@ -48,7 +61,7 @@ private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime Runtime = runtime, Architecture = architecture, Name = name, - Handler = "Function::Function.Function::FunctionHandler", + Handler = props.IsAot ? "AOT-Function" : "Function::Function.Function::FunctionHandler", SourcePath = sourcePath, DistPath = distPath, Environment = new Dictionary diff --git a/libraries/tests/e2e/functions/core/logging/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/core/logging/Function/test/Function.Tests/FunctionTests.cs index 8678d68e..ca3a857a 100644 --- a/libraries/tests/e2e/functions/core/logging/Function/test/Function.Tests/FunctionTests.cs +++ b/libraries/tests/e2e/functions/core/logging/Function/test/Function.Tests/FunctionTests.cs @@ -23,7 +23,7 @@ public FunctionTests(ITestOutputHelper testOutputHelper) [Trait("Category", "AOT")] [Theory] [InlineData("E2ETestLambda_X64_AOT_NET8_logging")] - // [InlineData("E2ETestLambda_ARM_AOT_NET8_metrics")] + [InlineData("E2ETestLambda_ARM_AOT_NET8_logging")] public async Task AotFunctionTest(string functionName) { await TestFunction(functionName); @@ -45,7 +45,7 @@ internal async Task TestFunction(string functionName) { FunctionName = functionName, InvocationType = InvocationType.RequestResponse, - Payload = await File.ReadAllTextAsync("../../../../../../../payload.json"), + Payload = await File.ReadAllTextAsync("../../../../../../../../payload.json"), LogType = LogType.Tail }; diff --git a/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/FunctionTests.cs index 647d3669..1670dceb 100644 --- a/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/FunctionTests.cs +++ b/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/FunctionTests.cs @@ -23,7 +23,7 @@ public FunctionTests(ITestOutputHelper testOutputHelper) [Trait("Category", "AOT")] [Theory] [InlineData("E2ETestLambda_X64_AOT_NET8_metrics")] - // [InlineData("E2ETestLambda_ARM_AOT_NET8_metrics")] + [InlineData("E2ETestLambda_ARM_AOT_NET8_metrics")] public async Task AotFunctionTest(string functionName) { await TestFunction(functionName); @@ -45,7 +45,7 @@ internal async Task TestFunction(string functionName) { FunctionName = functionName, InvocationType = InvocationType.RequestResponse, - Payload = await File.ReadAllTextAsync("../../../../../../../payload.json"), + Payload = await File.ReadAllTextAsync("../../../../../../../../payload.json"), LogType = LogType.Tail }; diff --git a/libraries/tests/e2e/functions/core/tracing/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/core/tracing/Function/test/Function.Tests/FunctionTests.cs index 9203a1d5..aa1c0b39 100644 --- a/libraries/tests/e2e/functions/core/tracing/Function/test/Function.Tests/FunctionTests.cs +++ b/libraries/tests/e2e/functions/core/tracing/Function/test/Function.Tests/FunctionTests.cs @@ -26,7 +26,7 @@ public FunctionTests(ITestOutputHelper testOutputHelper) [Trait("Category", "AOT")] [Theory] [InlineData("E2ETestLambda_X64_AOT_NET8_tracing")] - // [InlineData("E2ETestLambda_ARM_AOT_NET8_tracing")] + [InlineData("E2ETestLambda_ARM_AOT_NET8_tracing")] public async Task AotFunctionTest(string functionName) { await TestFunction(functionName); @@ -48,7 +48,7 @@ internal async Task TestFunction(string functionName) { FunctionName = functionName, InvocationType = InvocationType.RequestResponse, - Payload = await File.ReadAllTextAsync("../../../../../../../payload.json"), + Payload = await File.ReadAllTextAsync("../../../../../../../../payload.json"), LogType = LogType.Tail }; diff --git a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs index bbae4b4a..e37aae7f 100644 --- a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs +++ b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs @@ -23,7 +23,7 @@ public FunctionTests(ITestOutputHelper testOutputHelper) [Trait("Category", "AOT")] [Theory] [InlineData("E2ETestLambda_X64_AOT_NET8_idempotency")] - // [InlineData("E2ETestLambda_ARM_AOT_NET8_idempotency")] + [InlineData("E2ETestLambda_ARM_AOT_NET8_idempotency")] public async Task AotFunctionTest(string functionName) { await TestFunction(functionName); diff --git a/libraries/tests/e2e/infra-aot/CoreAotStack.cs b/libraries/tests/e2e/infra-aot/CoreAotStack.cs index fdfd1025..4387892c 100644 --- a/libraries/tests/e2e/infra-aot/CoreAotStack.cs +++ b/libraries/tests/e2e/infra-aot/CoreAotStack.cs @@ -25,7 +25,7 @@ private void CreateFunctionConstructs(string utility) var distAotPath = $"../functions/core/{utility}/AOT-Function/dist"; var arch = _architecture == Architecture.X86_64 ? "X64" : "ARM"; - CreateFunctionConstruct(this, $"{utility}_ARM_aot_net8", Runtime.DOTNET_8, _architecture, + CreateFunctionConstruct(this, $"{utility}_{arch}_aot_net8", Runtime.DOTNET_8, _architecture, $"E2ETestLambda_{arch}_AOT_NET8_{utility}", baseAotPath, distAotPath); } diff --git a/libraries/tests/e2e/infra-aot/Program.cs b/libraries/tests/e2e/infra-aot/Program.cs index d0b4f5c3..db8952a0 100644 --- a/libraries/tests/e2e/infra-aot/Program.cs +++ b/libraries/tests/e2e/infra-aot/Program.cs @@ -33,7 +33,7 @@ public static void Main(string[] args) }); _ = new IdempotencyStack(app, $"IdempotencyStack-AOT-{architecture}", - new PowertoolsDefaultStackProps { IsAot = true, ArchitectureString = architecture }); + new IdempotencyStackProps { IsAot = true, ArchitectureString = architecture, TableName = $"IdempotencyTable-AOT-{architecture}" }); app.Synth(); } diff --git a/libraries/tests/e2e/infra/Program.cs b/libraries/tests/e2e/infra/Program.cs index 399049d8..d56d83b2 100644 --- a/libraries/tests/e2e/infra/Program.cs +++ b/libraries/tests/e2e/infra/Program.cs @@ -11,7 +11,7 @@ public static void Main(string[] args) _ = new CoreStack(app, "CoreStack", new StackProps { }); - _ = new IdempotencyStack(app, "IdempotencyStack", new PowertoolsDefaultStackProps { }); + _ = new IdempotencyStack(app, "IdempotencyStack", new IdempotencyStackProps { TableName = "IdempotencyTable" }); app.Synth(); } From dfa912f0307b388dfc93cd37a2e4a4af6cb905af Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Mon, 27 Jan 2025 20:04:10 +0000 Subject: [PATCH 05/12] pipeline update to run all stacks --- .github/workflows/e2e-tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 98ead3e5..b18e43fb 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -53,7 +53,7 @@ jobs: - name: Deploy Core Stack run: | cd libraries/tests/e2e/infra - cdk deploy --require-approval never + cdk deploy --all --require-approval never deploy-aot-stack: strategy: @@ -90,7 +90,7 @@ jobs: - name: Deploy AOT Stack run: | cd libraries/tests/e2e/infra-aot - cdk deploy -c architecture=${{ matrix.arch }} --require-approval never + cdk deploy --all -c architecture=${{ matrix.arch }} --require-approval never run-tests: runs-on: ubuntu-latest @@ -145,7 +145,7 @@ jobs: - name: Destroy Core Stack run: | cd libraries/tests/e2e/infra - cdk destroy --force + cdk destroy --all --force destroy-aot-stack: strategy: @@ -179,5 +179,5 @@ jobs: - name: Destroy arm64 AOT Core Stack run: | cd libraries/tests/e2e/infra-aot - cdk destroy -c architecture=${{ matrix.arch }} --force + cdk destroy --all -c architecture=${{ matrix.arch }} --force From 6070d0be95b40aaad9a2a58d1d5c70d7039dfad9 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Tue, 28 Jan 2025 09:37:51 +0000 Subject: [PATCH 06/12] fix stack name --- libraries/tests/e2e/infra-aot/Program.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/tests/e2e/infra-aot/Program.cs b/libraries/tests/e2e/infra-aot/Program.cs index db8952a0..db4d533a 100644 --- a/libraries/tests/e2e/infra-aot/Program.cs +++ b/libraries/tests/e2e/infra-aot/Program.cs @@ -22,9 +22,11 @@ public static void Main(string[] args) } var id = "CoreAotStack"; + var idempotencystackAotId = "IdempotencyStack-AOT"; if (architecture == "arm64") { id = $"CoreAotStack-{architecture}"; + idempotencystackAotId = $"IdempotencyStack-AOT-{architecture}"; } _ = new CoreAotStack(app, id, new PowertoolsDefaultStackProps @@ -32,7 +34,7 @@ public static void Main(string[] args) ArchitectureString = architecture }); - _ = new IdempotencyStack(app, $"IdempotencyStack-AOT-{architecture}", + _ = new IdempotencyStack(app, idempotencystackAotId, new IdempotencyStackProps { IsAot = true, ArchitectureString = architecture, TableName = $"IdempotencyTable-AOT-{architecture}" }); app.Synth(); From 262e7f530d24f518355ed26af781e4f73a4d87bf Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:06:58 +0000 Subject: [PATCH 07/12] add idempotency and refactor pipeline --- .github/workflows/e2e-tests.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index b18e43fb..5280ebc7 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -1,6 +1,6 @@ # PROCESS # -# 1. Deploy the core and AOT stacks using the infra deployment workflow. +# 1. Deploy the E2E stacks using the infra deployment workflow for non-aot and aot. # 2. Run the E2E tests after the infrastructure is deployed. # 3. Destroy the CDK stacks after the tests are completed. @@ -50,7 +50,7 @@ jobs: - name: Install AWS Lambda .NET CLI Tools run: dotnet tool install -g Amazon.Lambda.Tools - - name: Deploy Core Stack + - name: Deploy Stack run: | cd libraries/tests/e2e/infra cdk deploy --all --require-approval never @@ -93,8 +93,12 @@ jobs: cdk deploy --all -c architecture=${{ matrix.arch }} --require-approval never run-tests: + strategy: + matrix: + utility: [core, idempotency] runs-on: ubuntu-latest needs: [deploy-stack,deploy-aot-stack] + steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 @@ -111,14 +115,14 @@ jobs: with: dotnet-version: '8.x' - - name: Run Core Tests + - name: Run Tests run: | - cd libraries/tests/e2e/functions/core + cd libraries/tests/e2e/functions/${{ matrix.utility }} dotnet test --filter Category!=AOT - - name: Run Core AOT Tests + - name: Run AOT Tests run: | - cd libraries/tests/e2e/functions/core + cd libraries/tests/e2e/functions/${{ matrix.utility }} dotnet test --filter Category=AOT destroy-stack: @@ -142,7 +146,7 @@ jobs: - name: Install AWS Lambda .NET CLI Tools run: dotnet tool install -g Amazon.Lambda.Tools - - name: Destroy Core Stack + - name: Destroy Stack run: | cd libraries/tests/e2e/infra cdk destroy --all --force @@ -176,7 +180,7 @@ jobs: - name: Install AWS Lambda .NET CLI Tools run: dotnet tool install -g Amazon.Lambda.Tools - - name: Destroy arm64 AOT Core Stack + - name: Destroy arm64 AOT Stack run: | cd libraries/tests/e2e/infra-aot cdk destroy --all -c architecture=${{ matrix.arch }} --force From cce2d69877a584c433880c1550abf99f22fe2012 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:52:19 +0000 Subject: [PATCH 08/12] add solution to idempotency tests root folder --- .../idempotency/IdempotencyTests.sln | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 libraries/tests/e2e/functions/idempotency/IdempotencyTests.sln diff --git a/libraries/tests/e2e/functions/idempotency/IdempotencyTests.sln b/libraries/tests/e2e/functions/idempotency/IdempotencyTests.sln new file mode 100644 index 00000000..add4ad93 --- /dev/null +++ b/libraries/tests/e2e/functions/idempotency/IdempotencyTests.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Function", "Function", "{FE3A26C9-5A8D-4DD3-A87B-2D7FC5BC15A8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{28C61FF3-B4F5-44AC-9375-A4C6FC8579C8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Function.Tests", "Function\test\Function.Tests\Function.Tests.csproj", "{8959B0AC-3B85-4E30-9C48-CAD5F72AD5BB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8959B0AC-3B85-4E30-9C48-CAD5F72AD5BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8959B0AC-3B85-4E30-9C48-CAD5F72AD5BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8959B0AC-3B85-4E30-9C48-CAD5F72AD5BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8959B0AC-3B85-4E30-9C48-CAD5F72AD5BB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {28C61FF3-B4F5-44AC-9375-A4C6FC8579C8} = {FE3A26C9-5A8D-4DD3-A87B-2D7FC5BC15A8} + {8959B0AC-3B85-4E30-9C48-CAD5F72AD5BB} = {28C61FF3-B4F5-44AC-9375-A4C6FC8579C8} + EndGlobalSection +EndGlobal From 9eb5298b1e3aab8d3101ef75eb356d46d41b2e8a Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Tue, 28 Jan 2025 16:53:55 +0000 Subject: [PATCH 09/12] refactor and dynamodb tests --- .../Function/src/Function/Function.cs | 42 ++- .../test/Function.Tests/Function.Tests.csproj | 1 + .../test/Function.Tests/FunctionTests.cs | 258 ++++++------------ 3 files changed, 116 insertions(+), 185 deletions(-) diff --git a/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs index 868cca18..9aa4d5a5 100644 --- a/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs +++ b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs @@ -6,19 +6,39 @@ // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -namespace Function; - -public class Function +namespace Function { - public Function() + public class Function { - var tableName = Environment.GetEnvironmentVariable("IDEMPOTENCY_TABLE_NAME"); - Idempotency.Configure(builder => builder.UseDynamoDb(tableName)); - } + public Function() + { + var tableName = Environment.GetEnvironmentVariable("IDEMPOTENCY_TABLE_NAME"); + Idempotency.Configure(builder => builder.UseDynamoDb(tableName)); + } - [Idempotent] - public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) + [Idempotent] + public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) + { + return TestHelper.TestMethod(apigwProxyEvent); + } + } +} + + +namespace Function2 +{ + public class Function { - return TestHelper.TestMethod(apigwProxyEvent); + public Function() + { + // var tableName = Environment.GetEnvironmentVariable("IDEMPOTENCY_TABLE_NAME"); + // Idempotency.Configure(builder => builder.UseDynamoDb(tableName)); + } + + // [Idempotent] + public string FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) + { + return "Hello, World!"; + } } -} \ No newline at end of file +} diff --git a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/Function.Tests.csproj b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/Function.Tests.csproj index 06365337..ed863946 100644 --- a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/Function.Tests.csproj +++ b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/Function.Tests.csproj @@ -10,6 +10,7 @@ + diff --git a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs index e37aae7f..378b8273 100644 --- a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs +++ b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs @@ -1,4 +1,6 @@ using System.Text.Json; +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.Model; using Amazon.Lambda; using Amazon.Lambda.APIGatewayEvents; using Xunit; @@ -13,44 +15,52 @@ public class FunctionTests { private readonly ITestOutputHelper _testOutputHelper; private readonly AmazonLambdaClient _lambdaClient; + private readonly AmazonDynamoDBClient _dynamoDbClient; + private string _tableName = null!; public FunctionTests(ITestOutputHelper testOutputHelper) { _testOutputHelper = testOutputHelper; _lambdaClient = new AmazonLambdaClient(); + _dynamoDbClient = new AmazonDynamoDBClient(); } [Trait("Category", "AOT")] [Theory] - [InlineData("E2ETestLambda_X64_AOT_NET8_idempotency")] - [InlineData("E2ETestLambda_ARM_AOT_NET8_idempotency")] - public async Task AotFunctionTest(string functionName) + [InlineData("E2ETestLambda_X64_AOT_NET8_idempotency", "IdempotencyTable-AOT-x86_64")] + [InlineData("E2ETestLambda_ARM_AOT_NET8_idempotency", "IdempotencyTable-AOT-arm64")] + public async Task IdempotencyHandlerAotTest(string functionName, string tableName) { - await TestFunction(functionName); + _tableName = tableName; + await TestIdempotencyHandler(functionName); } [Theory] - [InlineData("E2ETestLambda_X64_NET6_idempotency")] - [InlineData("E2ETestLambda_ARM_NET6_idempotency")] - [InlineData("E2ETestLambda_X64_NET8_idempotency")] - [InlineData("E2ETestLambda_ARM_NET8_idempotency")] - public async Task FunctionTest(string functionName) + [InlineData("E2ETestLambda_X64_NET6_idempotency", "IdempotencyTable")] + [InlineData("E2ETestLambda_ARM_NET6_idempotency", "IdempotencyTable")] + [InlineData("E2ETestLambda_X64_NET8_idempotency", "IdempotencyTable")] + [InlineData("E2ETestLambda_ARM_NET8_idempotency", "IdempotencyTable")] + public async Task IdempotencyHandlerTest(string functionName, string tableName) { - await TestFunction(functionName); + _tableName = tableName; + await TestIdempotencyHandler(functionName); } - internal async Task TestFunction(string functionName) + internal async Task TestIdempotencyHandler(string functionName) { var request = new InvokeRequest { FunctionName = functionName, InvocationType = InvocationType.RequestResponse, Payload = await File.ReadAllTextAsync("../../../../../../../payload.json"), - LogType = LogType.Tail + LogType = LogType.Tail, }; - // run twice for cold and warm start - for (int i = 0; i < 2; i++) + var initialGuid = string.Empty; + var initialRequestId = string.Empty; + + // run three times to test idempotency + for (int i = 0; i < 3; i++) { var response = await _lambdaClient.InvokeAsync(request); @@ -66,181 +76,81 @@ internal async Task TestFunction(string functionName) { Assert.Fail("Failed to parse payload."); } - + Assert.Equal(200, parsedPayload.StatusCode); - Assert.Equal("HELLO WORLD", parsedPayload.Body); + + var parsedResponse = JsonSerializer.Deserialize(parsedPayload.Body); - // Assert Output log from Lambda execution - AssertOutputLog(functionName, response); - } - } + if (parsedResponse == null) + { + Assert.Fail("Failed to parse response."); + } + + if(i == 0) + { + initialGuid = parsedResponse.MethodGuid; + initialRequestId = parsedResponse.RequestId; + } - private void AssertOutputLog(string functionName, InvokeResponse response) - { - // Extract and parse log - var logResult = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(response.LogResult)); - _testOutputHelper.WriteLine(logResult); - var output = OutputLogParser.ParseLogSegments(logResult, out var report); - var isColdStart = report.initDuration != "N/A"; + Assert.Equal(initialGuid, parsedResponse.MethodGuid); + Assert.Equal(initialRequestId, parsedResponse.RequestId); + } - // Assert Logging utility - // AssertEventLog(functionName, isColdStart, output[0]); - // AssertInformationLog(functionName, isColdStart, output[1]); - // AssertWarningLog(functionName, isColdStart, output[2]); - // AssertExceptionLog(functionName, isColdStart, output[3]); + // Query DynamoDB and assert results + await AssertDynamoDbData(functionName, initialGuid, initialRequestId); } - - private void AssertEventLog(string functionName, bool isColdStart, string output) + + private async Task AssertDynamoDbData(string functionName, string initialGuid, string initialRequestId) { - using JsonDocument doc = JsonDocument.Parse(output); - JsonElement root = doc.RootElement; + var id = $"{functionName}.FunctionHandler#35973cf447e6cc11008d603c791a232f"; + _testOutputHelper.WriteLine($"Querying DynamoDB with id: {id}"); - AssertDefaultLoggingProperties.ArePresent(functionName, isColdStart, output); - - if (!isColdStart) + var queryRequest = new QueryRequest { - Assert.True(root.TryGetProperty("LookupInfo", out JsonElement lookupInfoElement)); - Assert.True(lookupInfoElement.TryGetProperty("LookupId", out JsonElement lookupIdElement)); - Assert.Equal("c6af9ac6-7b61-11e6-9a41-93e8deadbeef", lookupIdElement.GetString()); - } - - Assert.True(root.TryGetProperty("Level", out JsonElement levelElement)); - Assert.Equal("Information", levelElement.GetString()); - - Assert.True(root.TryGetProperty("Message", out JsonElement messageElement)); - Assert.True(messageElement.TryGetProperty("Resource", out JsonElement resourceElement)); - Assert.Equal("/{proxy+}", resourceElement.GetString()); - - Assert.True(messageElement.TryGetProperty("Path", out JsonElement pathElement)); - Assert.Equal("/path/to/resource", pathElement.GetString()); - - Assert.True(messageElement.TryGetProperty("HttpMethod", out JsonElement httpMethodElement)); - Assert.Equal("POST", httpMethodElement.GetString()); - - Assert.True(messageElement.TryGetProperty("Headers", out JsonElement headersElement)); - Assert.True(headersElement.TryGetProperty("Accept-Encoding", out JsonElement acceptEncodingElement)); - Assert.Equal("gzip, deflate, sdch", acceptEncodingElement.GetString()); - - Assert.True(headersElement.TryGetProperty("Accept-Language", out JsonElement acceptLanguageElement)); - Assert.Equal("en-US,en;q=0.8", acceptLanguageElement.GetString()); - - Assert.True(headersElement.TryGetProperty("Cache-Control", out JsonElement cacheControlElement)); - Assert.Equal("max-age=0", cacheControlElement.GetString()); - - Assert.True( - messageElement.TryGetProperty("QueryStringParameters", out JsonElement queryStringParametersElement)); - Assert.True(queryStringParametersElement.TryGetProperty("Foo", out JsonElement fooElement)); - Assert.Equal("bar", fooElement.GetString()); - - Assert.True(messageElement.TryGetProperty("RequestContext", out JsonElement requestContextElement)); - Assert.True(requestContextElement.TryGetProperty("Path", out JsonElement requestContextPathElement)); - Assert.Equal("/prod/path/to/resource", requestContextPathElement.GetString()); - - Assert.True(requestContextElement.TryGetProperty("AccountId", out JsonElement accountIdElement)); - Assert.Equal("123456789012", accountIdElement.GetString()); - - Assert.True(requestContextElement.TryGetProperty("ResourceId", out JsonElement resourceIdElement)); - Assert.Equal("123456", resourceIdElement.GetString()); - - Assert.True(requestContextElement.TryGetProperty("Stage", out JsonElement stageElement)); - Assert.Equal("prod", stageElement.GetString()); - - Assert.True(requestContextElement.TryGetProperty("RequestId", out JsonElement requestIdElement)); - Assert.Equal("c6af9ac6-7b61-11e6-9a41-93e8deadbeef", requestIdElement.GetString()); - - Assert.True(requestContextElement.TryGetProperty("ResourcePath", out JsonElement resourcePathElement)); - Assert.Equal("/{proxy+}", resourcePathElement.GetString()); - - Assert.True( - requestContextElement.TryGetProperty("HttpMethod", out JsonElement requestContextHttpMethodElement)); - Assert.Equal("POST", requestContextHttpMethodElement.GetString()); - - Assert.True(requestContextElement.TryGetProperty("ApiId", out JsonElement apiIdElement)); - Assert.Equal("1234567890", apiIdElement.GetString()); - - Assert.True(requestContextElement.TryGetProperty("RequestTime", out JsonElement requestTimeElement)); - Assert.Equal("09/Apr/2015:12:34:56 +0000", requestTimeElement.GetString()); - - Assert.True(requestContextElement.TryGetProperty("RequestTimeEpoch", out JsonElement requestTimeEpochElement)); - Assert.Equal(1428582896000, requestTimeEpochElement.GetInt64()); - - Assert.True(messageElement.TryGetProperty("Body", out JsonElement bodyElement)); - Assert.Equal("hello world", bodyElement.GetString()); - - Assert.True(messageElement.TryGetProperty("IsBase64Encoded", out JsonElement isBase64EncodedElement)); - Assert.False(isBase64EncodedElement.GetBoolean()); - } - - private void AssertInformationLog(string functionName, bool isColdStart, string output) - { - using JsonDocument doc = JsonDocument.Parse(output); - JsonElement root = doc.RootElement; + TableName = _tableName, + KeyConditionExpression = "id = :v_id", + ExpressionAttributeValues = new Dictionary + { + { ":v_id", new AttributeValue { S = id } } + } + }; + + _testOutputHelper.WriteLine($"QueryRequest: {JsonSerializer.Serialize(queryRequest)}"); - AssertDefaultLoggingProperties.ArePresent(functionName, isColdStart, output); + var queryResponse = await _dynamoDbClient.QueryAsync(queryRequest); - if (!isColdStart) + _testOutputHelper.WriteLine($"QueryResponse: {JsonSerializer.Serialize(queryResponse)}"); + + if (queryResponse.Items.Count == 0) { - Assert.True(root.TryGetProperty("LookupInfo", out JsonElement lookupInfoElement)); - Assert.True(lookupInfoElement.TryGetProperty("LookupId", out JsonElement lookupIdElement)); - Assert.Equal("c6af9ac6-7b61-11e6-9a41-93e8deadbeef", lookupIdElement.GetString()); + Assert.Fail("No items found in DynamoDB for the given id."); } - Assert.True(root.TryGetProperty("Level", out JsonElement levelElement)); - Assert.Equal("Information", levelElement.GetString()); - - Assert.True(root.TryGetProperty("Message", out JsonElement messageElement)); - Assert.Equal("Processing request started", messageElement.GetString()); - } - - private static void AssertWarningLog(string functionName, bool isColdStart, string output) - { - using JsonDocument doc = JsonDocument.Parse(output); - JsonElement root = doc.RootElement; - - AssertDefaultLoggingProperties.ArePresent(functionName, isColdStart, output); + foreach (var item in queryResponse.Items) + { + var data = item["data"].S; + var status = item["status"].S; - Assert.True(root.TryGetProperty("LookupInfo", out JsonElement lookupInfoElement)); - Assert.True(lookupInfoElement.TryGetProperty("LookupId", out JsonElement lookupIdElement)); - Assert.Equal("c6af9ac6-7b61-11e6-9a41-93e8deadbeef", lookupIdElement.GetString()); + Assert.Equal("COMPLETED", status); - Assert.True(root.TryGetProperty("Level", out JsonElement levelElement)); - Assert.Equal("Warning", levelElement.GetString()); + var parsedData = JsonSerializer.Deserialize(data); - Assert.True(root.TryGetProperty("Test1", out JsonElement test1Element)); - Assert.Equal("value1", test1Element.GetString()); - - Assert.True(root.TryGetProperty("Test2", out JsonElement test2Element)); - Assert.Equal("value2", test2Element.GetString()); - - Assert.True(root.TryGetProperty("Message", out JsonElement messageElement)); - Assert.Equal("Warn with additional keys", messageElement.GetString()); + if (parsedData == null) + { + Assert.Fail("Failed to parse data field."); + } + + var parsedResponse = JsonSerializer.Deserialize(parsedData.Body); + + if (parsedResponse == null) + { + Assert.Fail("Failed to parse response."); + } + + Assert.Equal(initialGuid, parsedResponse.MethodGuid); + Assert.Equal(initialRequestId, parsedResponse.RequestId); + } } +} - private void AssertExceptionLog(string functionName, bool isColdStart, string output) - { - using JsonDocument doc = JsonDocument.Parse(output); - JsonElement root = doc.RootElement; - - AssertDefaultLoggingProperties.ArePresent(functionName, isColdStart, output); - - Assert.True(root.TryGetProperty("LookupInfo", out JsonElement lookupInfoElement)); - Assert.True(lookupInfoElement.TryGetProperty("LookupId", out JsonElement lookupIdElement)); - Assert.Equal("c6af9ac6-7b61-11e6-9a41-93e8deadbeef", lookupIdElement.GetString()); - - Assert.True(root.TryGetProperty("Level", out JsonElement levelElement)); - Assert.Equal("Error", levelElement.GetString()); - - Assert.True(root.TryGetProperty("Message", out JsonElement messageElement)); - Assert.Equal("Oops something went wrong", messageElement.GetString()); - - Assert.True(root.TryGetProperty("Exception", out JsonElement exceptionElement)); - Assert.True(exceptionElement.TryGetProperty("Type", out JsonElement exceptionTypeElement)); - Assert.Equal("System.InvalidOperationException", exceptionTypeElement.GetString()); - - Assert.True(exceptionElement.TryGetProperty("Message", out JsonElement exceptionMessageElement)); - Assert.Equal("Parent exception message", exceptionMessageElement.GetString()); - - Assert.False(root.TryGetProperty("Test1", out JsonElement _)); - Assert.False(root.TryGetProperty("Test2", out JsonElement _)); - } -} \ No newline at end of file +public record Response(string RequestId, string Greeting, string MethodGuid, string HandlerGuid); \ No newline at end of file From d2e762bc8dff9c8bae98a6ecfa279cf28fe3abc1 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:50:35 +0000 Subject: [PATCH 10/12] add test functions. refactor and add more tests. not aot yet. --- .../Function/src/Function/Function.cs | 41 ++- .../Function/src/Function/TestHelper.cs | 14 +- .../test/Function.Tests/FunctionTests.cs | 271 +++++++++++++++--- .../Function/test/Function.Tests/Helpers.cs | 25 ++ .../Function/test/Function.Tests/TestData.cs | 23 ++ 5 files changed, 311 insertions(+), 63 deletions(-) create mode 100644 libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/Helpers.cs create mode 100644 libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/TestData.cs diff --git a/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs index 9aa4d5a5..672ffdfb 100644 --- a/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs +++ b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs @@ -24,21 +24,50 @@ public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxy } } +namespace IdempotencyAttributeTest +{ + public class Function + { + public Function() + { + var tableName = Environment.GetEnvironmentVariable("IDEMPOTENCY_TABLE_NAME"); + Idempotency.Configure(builder => builder.UseDynamoDb(tableName)); + } + + public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) + { + return new APIGatewayProxyResponse + { + Body = MyInternalMethod("dummy", apigwProxyEvent.RequestContext.RequestId), + StatusCode = 200 + }; + } + + [Idempotent] + private string MyInternalMethod(string argOne, [IdempotencyKey] string argTwo) { + return Guid.NewGuid().ToString(); + } + } +} -namespace Function2 +namespace IdempotencyPayloadSubsetTest { public class Function { public Function() { - // var tableName = Environment.GetEnvironmentVariable("IDEMPOTENCY_TABLE_NAME"); - // Idempotency.Configure(builder => builder.UseDynamoDb(tableName)); + var tableName = Environment.GetEnvironmentVariable("IDEMPOTENCY_TABLE_NAME"); + Idempotency.Configure(builder => + builder + .WithOptions(optionsBuilder => + optionsBuilder.WithEventKeyJmesPath("powertools_json(Body).[\"user_id\", \"product_id\"]")) + .UseDynamoDb(tableName)); } - // [Idempotent] - public string FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) + [Idempotent] + public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) { - return "Hello, World!"; + return TestHelper.TestMethod(apigwProxyEvent); } } } diff --git a/libraries/tests/e2e/functions/idempotency/Function/src/Function/TestHelper.cs b/libraries/tests/e2e/functions/idempotency/Function/src/Function/TestHelper.cs index 8caa694a..4708d225 100644 --- a/libraries/tests/e2e/functions/idempotency/Function/src/Function/TestHelper.cs +++ b/libraries/tests/e2e/functions/idempotency/Function/src/Function/TestHelper.cs @@ -7,13 +7,10 @@ public static class TestHelper { public static APIGatewayProxyResponse TestMethod(APIGatewayProxyRequest apigwProxyEvent) { - var requestContextRequestId = apigwProxyEvent.RequestContext.RequestId; var response = new { - RequestId = requestContextRequestId, Greeting = "Hello Powertools for AWS Lambda (.NET)", - MethodGuid = GenerateGuid(), // Guid generated by the GenerateGuid method. used to compare Method output - HandlerGuid = Guid.NewGuid().ToString() // Guid generated in the Handler. used to compare Handler output + Guid = Guid.NewGuid().ToString() // Guid generated in the Handler. used to compare Handler output }; try @@ -35,13 +32,4 @@ public static APIGatewayProxyResponse TestMethod(APIGatewayProxyRequest apigwPro }; } } - - /// - /// Generates a new Guid to check if value is the same between calls (should be when idempotency enabled) - /// - /// GUID - private static string GenerateGuid() - { - return Guid.NewGuid().ToString(); - } } \ No newline at end of file diff --git a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs index 378b8273..440a137f 100644 --- a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs +++ b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs @@ -5,7 +5,6 @@ using Amazon.Lambda.APIGatewayEvents; using Xunit; using Amazon.Lambda.Model; -using TestUtils; using Xunit.Abstractions; namespace Function.Tests; @@ -17,6 +16,7 @@ public class FunctionTests private readonly AmazonLambdaClient _lambdaClient; private readonly AmazonDynamoDBClient _dynamoDbClient; private string _tableName = null!; + private string _handler; public FunctionTests(ITestOutputHelper testOutputHelper) { @@ -24,30 +24,187 @@ public FunctionTests(ITestOutputHelper testOutputHelper) _lambdaClient = new AmazonLambdaClient(); _dynamoDbClient = new AmazonDynamoDBClient(); } - - [Trait("Category", "AOT")] + + // [Trait("Category", "AOT")] + // [Theory] + // [MemberData(nameof(TestDataAot.Inline), MemberType = typeof(TestDataAot))] + // public async Task IdempotencyHandlerAotTest(string functionName, string tableName) + // { + // _tableName = tableName; + // await TestIdempotencyHandler(functionName); + // } + [Theory] - [InlineData("E2ETestLambda_X64_AOT_NET8_idempotency", "IdempotencyTable-AOT-x86_64")] - [InlineData("E2ETestLambda_ARM_AOT_NET8_idempotency", "IdempotencyTable-AOT-arm64")] - public async Task IdempotencyHandlerAotTest(string functionName, string tableName) + [MemberData(nameof(TestData.Inline), MemberType = typeof(TestData))] + public async Task IdempotencyHandlerTest(string functionName, string tableName) { _tableName = tableName; await TestIdempotencyHandler(functionName); } - + [Theory] - [InlineData("E2ETestLambda_X64_NET6_idempotency", "IdempotencyTable")] - [InlineData("E2ETestLambda_ARM_NET6_idempotency", "IdempotencyTable")] - [InlineData("E2ETestLambda_X64_NET8_idempotency", "IdempotencyTable")] - [InlineData("E2ETestLambda_ARM_NET8_idempotency", "IdempotencyTable")] - public async Task IdempotencyHandlerTest(string functionName, string tableName) + [MemberData(nameof(TestData.Inline), MemberType = typeof(TestData))] + public async Task IdempotencyAttributeTest(string functionName, string tableName) { _tableName = tableName; - await TestIdempotencyHandler(functionName); + await TestIdempotencyAttribute(functionName); + } + + [Theory] + [MemberData(nameof(TestData.Inline), MemberType = typeof(TestData))] + public async Task IdempotencyPayloadSubsetTest(string functionName, string tableName) + { + _tableName = tableName; + await TestIdempotencyPayloadSubset(functionName); + } + + private async Task TestIdempotencyPayloadSubset(string functionName) + { + await UpdateFunctionHandler(functionName, "Function::IdempotencyPayloadSubsetTest.Function::FunctionHandler"); + + var initialGuid = string.Empty; + + for (int i = 0; i < 2; i++) + { + var productId = Guid.NewGuid().ToString(); + var apiGatewayRequest = new APIGatewayProxyRequest + { + Body = $"{{\"user_id\":\"xyz\",\"product_id\":\"{productId}\"}}" + }; + + var payload = JsonSerializer.Serialize(apiGatewayRequest); + + var request = new InvokeRequest + { + FunctionName = functionName, + InvocationType = InvocationType.RequestResponse, + Payload = payload, + LogType = LogType.Tail, + }; + + // run two times with the same request + for (int j = 0; j < 2; j++) + { + var response = await _lambdaClient.InvokeAsync(request); + + if (string.IsNullOrEmpty(response.LogResult)) + { + Assert.Fail("No LogResult field returned in the response of Lambda invocation."); + } + + var responsePayload = System.Text.Encoding.UTF8.GetString(response.Payload.ToArray()); + var parsedPayload = JsonSerializer.Deserialize(responsePayload); + + if (parsedPayload == null) + { + Assert.Fail("Failed to parse payload."); + } + + Assert.Equal(200, parsedPayload.StatusCode); + + var parsedResponse = JsonSerializer.Deserialize(parsedPayload.Body); + + if (parsedResponse == null) + { + Assert.Fail("Failed to parse response."); + } + + if (j == 0) + { + // first call should return a new guid + if (parsedResponse.Guid == initialGuid) + { + Assert.Fail("Idempotency failed to clear cache."); + } + + initialGuid = parsedResponse.Guid; + } + + Assert.Equal(initialGuid, parsedResponse.Guid); + + // Query DynamoDB and assert results + var hashRequest = Helpers.HashRequest($"[\"xyz\",\"{productId}\"]"); + + var id = $"{functionName}.FunctionHandler#{hashRequest}"; + await AssertDynamoDbData(id, initialGuid); + } + } + } + + private async Task TestIdempotencyAttribute(string functionName) + { + await UpdateFunctionHandler(functionName, "Function::IdempotencyAttributeTest.Function::FunctionHandler"); + + var initialGuid = string.Empty; + + for (int i = 0; i < 2; i++) + { + var requestId = Guid.NewGuid().ToString(); + var apiGatewayRequest = new APIGatewayProxyRequest + { + Body = "{\"user_id\":\"xyz\",\"product_id\":\"123456789\"}", + RequestContext = new APIGatewayProxyRequest.ProxyRequestContext + { + AccountId = "123456789012", + RequestId = requestId, // requestId is used to invalidate the cache + } + }; + + var payload = JsonSerializer.Serialize(apiGatewayRequest); + + var request = new InvokeRequest + { + FunctionName = functionName, + InvocationType = InvocationType.RequestResponse, + Payload = payload, + LogType = LogType.Tail, + }; + + // run two times with the same request + for (int j = 0; j < 2; j++) + { + var response = await _lambdaClient.InvokeAsync(request); + + if (string.IsNullOrEmpty(response.LogResult)) + { + Assert.Fail("No LogResult field returned in the response of Lambda invocation."); + } + + var responsePayload = System.Text.Encoding.UTF8.GetString(response.Payload.ToArray()); + var parsedPayload = JsonSerializer.Deserialize(responsePayload); + + if (parsedPayload == null) + { + Assert.Fail("Failed to parse payload."); + } + + Assert.Equal(200, parsedPayload.StatusCode); + + if (j == 0) + { + // first call should return a new guid + if (parsedPayload.Body == initialGuid) + { + Assert.Fail("Idempotency failed to clear cache."); + } + + initialGuid = parsedPayload.Body; + } + + Assert.Equal(initialGuid, parsedPayload.Body); + + // Query DynamoDB and assert results + var hashRequestId = Helpers.HashRequest(requestId); + var id = $"{functionName}.MyInternalMethod#{hashRequestId}"; + await AssertDynamoDbData(id, initialGuid, true); + } + } } internal async Task TestIdempotencyHandler(string functionName) { + await UpdateFunctionHandler(functionName, "Function::Function.Function::FunctionHandler"); + var request = new InvokeRequest { FunctionName = functionName, @@ -57,8 +214,7 @@ internal async Task TestIdempotencyHandler(string functionName) }; var initialGuid = string.Empty; - var initialRequestId = string.Empty; - + // run three times to test idempotency for (int i = 0; i < 3; i++) { @@ -76,35 +232,57 @@ internal async Task TestIdempotencyHandler(string functionName) { Assert.Fail("Failed to parse payload."); } - + Assert.Equal(200, parsedPayload.StatusCode); - + var parsedResponse = JsonSerializer.Deserialize(parsedPayload.Body); if (parsedResponse == null) { Assert.Fail("Failed to parse response."); } - - if(i == 0) + + if (i == 0) { - initialGuid = parsedResponse.MethodGuid; - initialRequestId = parsedResponse.RequestId; + initialGuid = parsedResponse.Guid; } - Assert.Equal(initialGuid, parsedResponse.MethodGuid); - Assert.Equal(initialRequestId, parsedResponse.RequestId); + Assert.Equal(initialGuid, parsedResponse.Guid); } - + // Query DynamoDB and assert results - await AssertDynamoDbData(functionName, initialGuid, initialRequestId); + var id = $"{functionName}.FunctionHandler#35973cf447e6cc11008d603c791a232f"; + await AssertDynamoDbData(id, initialGuid); } - - private async Task AssertDynamoDbData(string functionName, string initialGuid, string initialRequestId) + + private async Task UpdateFunctionHandler(string functionName, string handler) { - var id = $"{functionName}.FunctionHandler#35973cf447e6cc11008d603c791a232f"; - _testOutputHelper.WriteLine($"Querying DynamoDB with id: {id}"); + var updateRequest = new UpdateFunctionConfigurationRequest + { + FunctionName = functionName, + Handler = handler + }; + + var updateResponse = await _lambdaClient.UpdateFunctionConfigurationAsync(updateRequest); + + if (updateResponse.HttpStatusCode == System.Net.HttpStatusCode.OK) + { + Console.WriteLine($"Successfully updated the handler for function {functionName} to {handler}"); + } + else + { + Assert.Fail( + $"Failed to update the handler for function {functionName}. Status code: {updateResponse.HttpStatusCode}"); + } + //wait a few seconds for the changes to take effect + await Task.Delay(5000); + } + + private async Task AssertDynamoDbData(string id, string requestId, bool isSavedDataString = false) + { + _testOutputHelper.WriteLine($"Querying DynamoDB with id: {id}"); + var queryRequest = new QueryRequest { TableName = _tableName, @@ -114,11 +292,11 @@ private async Task AssertDynamoDbData(string functionName, string initialGuid, s { ":v_id", new AttributeValue { S = id } } } }; - + _testOutputHelper.WriteLine($"QueryRequest: {JsonSerializer.Serialize(queryRequest)}"); var queryResponse = await _dynamoDbClient.QueryAsync(queryRequest); - + _testOutputHelper.WriteLine($"QueryResponse: {JsonSerializer.Serialize(queryResponse)}"); if (queryResponse.Items.Count == 0) @@ -133,24 +311,29 @@ private async Task AssertDynamoDbData(string functionName, string initialGuid, s Assert.Equal("COMPLETED", status); - var parsedData = JsonSerializer.Deserialize(data); - - if (parsedData == null) + if (!isSavedDataString) { - Assert.Fail("Failed to parse data field."); + var parsedData = JsonSerializer.Deserialize(data); + + if (parsedData == null) + { + Assert.Fail("Failed to parse data field."); + } + + var parsedResponse = JsonSerializer.Deserialize(parsedData.Body); + if (parsedResponse == null) + { + Assert.Fail("Failed to parse response."); + } + + Assert.Equal(requestId, parsedResponse.Guid); } - - var parsedResponse = JsonSerializer.Deserialize(parsedData.Body); - - if (parsedResponse == null) + else { - Assert.Fail("Failed to parse response."); + Assert.Equal(requestId, data.Trim('"')); } - - Assert.Equal(initialGuid, parsedResponse.MethodGuid); - Assert.Equal(initialRequestId, parsedResponse.RequestId); } } } -public record Response(string RequestId, string Greeting, string MethodGuid, string HandlerGuid); \ No newline at end of file +public record Response(string Greeting, string Guid); \ No newline at end of file diff --git a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/Helpers.cs b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/Helpers.cs new file mode 100644 index 00000000..ca270bbf --- /dev/null +++ b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/Helpers.cs @@ -0,0 +1,25 @@ +using System.Security.Cryptography; +using System.Text; + +namespace Function.Tests; + +public static class Helpers +{ + public static string HashRequest(string input) + { + using var hashAlgorithm = MD5.Create(); + if (hashAlgorithm == null) + { + throw new ArgumentException("Invalid HashAlgorithm"); + } + + var data = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(input)); + var sBuilder = new StringBuilder(); + for (var i = 0; i < data.Length; i++) + { + sBuilder.Append(data[i].ToString("x2")); + } + + return sBuilder.ToString(); + } +} \ No newline at end of file diff --git a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/TestData.cs b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/TestData.cs new file mode 100644 index 00000000..f4967b7d --- /dev/null +++ b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/TestData.cs @@ -0,0 +1,23 @@ +namespace Function.Tests; + +public static class TestData +{ + public static IEnumerable Inline => + new List + { + new object[] { "E2ETestLambda_X64_NET6_idempotency", "IdempotencyTable" }, + new object[] { "E2ETestLambda_ARM_NET6_idempotency", "IdempotencyTable" }, + new object[] { "E2ETestLambda_X64_NET8_idempotency", "IdempotencyTable" }, + new object[] { "E2ETestLambda_ARM_NET8_idempotency", "IdempotencyTable" } + }; +} + +public static class TestDataAot +{ + public static IEnumerable Inline => + new List + { + new object[] { "E2ETestLambda_X64_AOT_NET8_idempotency", "IdempotencyTable-AOT-x86_64" }, + new object[] { "E2ETestLambda_ARM_AOT_NET8_idempotency", "IdempotencyTable-AOT-arm64" } + }; +} \ No newline at end of file From 3dbb8ad22baa2ed71705b06a74df862553b520d1 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Thu, 30 Jan 2025 15:26:32 +0000 Subject: [PATCH 11/12] fix sonar issue with number of parameters --- .../tests/e2e/InfraShared/IdempotencyStack.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/libraries/tests/e2e/InfraShared/IdempotencyStack.cs b/libraries/tests/e2e/InfraShared/IdempotencyStack.cs index 69f07e35..5c66b252 100644 --- a/libraries/tests/e2e/InfraShared/IdempotencyStack.cs +++ b/libraries/tests/e2e/InfraShared/IdempotencyStack.cs @@ -31,30 +31,32 @@ public IdempotencyStack(Construct scope, string id, IdempotencyStackProps props) { var baseAotPath = $"../functions/{utility}/AOT-Function/src/AOT-Function"; var distAotPath = $"../functions/{utility}/AOT-Function/dist"; + var path = new Path(baseAotPath, distAotPath); var architecture = props.ArchitectureString == "arm64" ? Architecture.ARM_64 : Architecture.X86_64; var arch = architecture == Architecture.X86_64 ? "X64" : "ARM"; CreateFunctionConstruct(this, $"{utility}_{arch}_aot_net8", Runtime.DOTNET_8, architecture, - $"E2ETestLambda_{arch}_AOT_NET8_{utility}", baseAotPath, distAotPath, props); + $"E2ETestLambda_{arch}_AOT_NET8_{utility}", path, props); } else { var basePath = $"../functions/{utility}/Function/src/Function"; var distPath = $"../functions/{utility}/Function/dist"; + var path = new Path(basePath, distPath); CreateFunctionConstruct(this, $"{utility}_X64_net8", Runtime.DOTNET_8, Architecture.X86_64, - $"E2ETestLambda_X64_NET8_{utility}", basePath, distPath, props); + $"E2ETestLambda_X64_NET8_{utility}", path, props); CreateFunctionConstruct(this, $"{utility}_arm_net8", Runtime.DOTNET_8, Architecture.ARM_64, - $"E2ETestLambda_ARM_NET8_{utility}", basePath, distPath, props); + $"E2ETestLambda_ARM_NET8_{utility}", path, props); CreateFunctionConstruct(this, $"{utility}_X64_net6", Runtime.DOTNET_6, Architecture.X86_64, - $"E2ETestLambda_X64_NET6_{utility}", basePath, distPath, props); + $"E2ETestLambda_X64_NET6_{utility}", path, props); CreateFunctionConstruct(this, $"{utility}_arm_net6", Runtime.DOTNET_6, Architecture.ARM_64, - $"E2ETestLambda_ARM_NET6_{utility}", basePath, distPath, props); + $"E2ETestLambda_ARM_NET6_{utility}", path, props); } } private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime, Architecture architecture, - string name, string sourcePath, string distPath, PowertoolsDefaultStackProps props) + string name,Path path, PowertoolsDefaultStackProps props) { var lambdaFunction = new FunctionConstruct(scope, id, new FunctionConstructProps { @@ -62,8 +64,8 @@ private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime Architecture = architecture, Name = name, Handler = props.IsAot ? "AOT-Function" : "Function::Function.Function::FunctionHandler", - SourcePath = sourcePath, - DistPath = distPath, + SourcePath = path.SourcePath, + DistPath = path.DistPath, Environment = new Dictionary { { "IDEMPOTENCY_TABLE_NAME", Table.TableName } @@ -75,3 +77,5 @@ private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime Table.GrantReadWriteData(lambdaFunction.Function); } } + +public record Path(string SourcePath, string DistPath); From 2c1f19390ea6edf67a62f51a9c5558034bb8dc5f Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Mon, 3 Feb 2025 12:53:02 +0000 Subject: [PATCH 12/12] Removed nested for loops and replaced them with explicit test cases. Created helper methods for executing requests to reduce code duplication.Made the test flow more linear and easier to follow --- .../test/Function.Tests/FunctionTests.cs | 365 ++++++++---------- 1 file changed, 163 insertions(+), 202 deletions(-) diff --git a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs index 440a137f..e1c4957d 100644 --- a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs +++ b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs @@ -34,225 +34,105 @@ public FunctionTests(ITestOutputHelper testOutputHelper) // await TestIdempotencyHandler(functionName); // } - [Theory] - [MemberData(nameof(TestData.Inline), MemberType = typeof(TestData))] - public async Task IdempotencyHandlerTest(string functionName, string tableName) - { - _tableName = tableName; - await TestIdempotencyHandler(functionName); - } - - [Theory] - [MemberData(nameof(TestData.Inline), MemberType = typeof(TestData))] - public async Task IdempotencyAttributeTest(string functionName, string tableName) - { - _tableName = tableName; - await TestIdempotencyAttribute(functionName); - } - [Theory] [MemberData(nameof(TestData.Inline), MemberType = typeof(TestData))] public async Task IdempotencyPayloadSubsetTest(string functionName, string tableName) { _tableName = tableName; - await TestIdempotencyPayloadSubset(functionName); - } - - private async Task TestIdempotencyPayloadSubset(string functionName) - { await UpdateFunctionHandler(functionName, "Function::IdempotencyPayloadSubsetTest.Function::FunctionHandler"); - var initialGuid = string.Empty; - - for (int i = 0; i < 2; i++) - { - var productId = Guid.NewGuid().ToString(); - var apiGatewayRequest = new APIGatewayProxyRequest - { - Body = $"{{\"user_id\":\"xyz\",\"product_id\":\"{productId}\"}}" - }; - - var payload = JsonSerializer.Serialize(apiGatewayRequest); - - var request = new InvokeRequest - { - FunctionName = functionName, - InvocationType = InvocationType.RequestResponse, - Payload = payload, - LogType = LogType.Tail, - }; - - // run two times with the same request - for (int j = 0; j < 2; j++) - { - var response = await _lambdaClient.InvokeAsync(request); - - if (string.IsNullOrEmpty(response.LogResult)) - { - Assert.Fail("No LogResult field returned in the response of Lambda invocation."); - } - - var responsePayload = System.Text.Encoding.UTF8.GetString(response.Payload.ToArray()); - var parsedPayload = JsonSerializer.Deserialize(responsePayload); - - if (parsedPayload == null) - { - Assert.Fail("Failed to parse payload."); - } - - Assert.Equal(200, parsedPayload.StatusCode); - - var parsedResponse = JsonSerializer.Deserialize(parsedPayload.Body); - - if (parsedResponse == null) - { - Assert.Fail("Failed to parse response."); - } - - if (j == 0) - { - // first call should return a new guid - if (parsedResponse.Guid == initialGuid) - { - Assert.Fail("Idempotency failed to clear cache."); - } - - initialGuid = parsedResponse.Guid; - } - - Assert.Equal(initialGuid, parsedResponse.Guid); - - // Query DynamoDB and assert results - var hashRequest = Helpers.HashRequest($"[\"xyz\",\"{productId}\"]"); - - var id = $"{functionName}.FunctionHandler#{hashRequest}"; - await AssertDynamoDbData(id, initialGuid); - } - } + // First unique request + var firstProductId = Guid.NewGuid().ToString(); + var (firstResponse1, firstGuid1) = await ExecutePayloadSubsetRequest(functionName, "xyz", firstProductId); + var (firstResponse2, firstGuid2) = await ExecutePayloadSubsetRequest(functionName, "xyz", firstProductId); + + // Assert first request pair + Assert.Equal(200, firstResponse1.StatusCode); + Assert.Equal(200, firstResponse2.StatusCode); + Assert.Equal(firstGuid1, firstGuid2); // Idempotency check + await AssertDynamoDbData( + $"{functionName}.FunctionHandler#{Helpers.HashRequest($"[\"xyz\",\"{firstProductId}\"]")}", + firstGuid1); + + // Second unique request + var secondProductId = Guid.NewGuid().ToString(); + var (secondResponse1, secondGuid1) = await ExecutePayloadSubsetRequest(functionName, "xyz", secondProductId); + var (secondResponse2, secondGuid2) = await ExecutePayloadSubsetRequest(functionName, "xyz", secondProductId); + + // Assert second request pair + Assert.Equal(200, secondResponse1.StatusCode); + Assert.Equal(200, secondResponse2.StatusCode); + Assert.Equal(secondGuid1, secondGuid2); // Idempotency check + Assert.NotEqual(firstGuid1, secondGuid1); // Different requests should have different GUIDs + await AssertDynamoDbData( + $"{functionName}.FunctionHandler#{Helpers.HashRequest($"[\"xyz\",\"{secondProductId}\"]")}", + secondGuid1); } - private async Task TestIdempotencyAttribute(string functionName) + [Theory] + [MemberData(nameof(TestData.Inline), MemberType = typeof(TestData))] + public async Task IdempotencyAttributeTest(string functionName, string tableName) { + _tableName = tableName; await UpdateFunctionHandler(functionName, "Function::IdempotencyAttributeTest.Function::FunctionHandler"); - var initialGuid = string.Empty; - - for (int i = 0; i < 2; i++) - { - var requestId = Guid.NewGuid().ToString(); - var apiGatewayRequest = new APIGatewayProxyRequest - { - Body = "{\"user_id\":\"xyz\",\"product_id\":\"123456789\"}", - RequestContext = new APIGatewayProxyRequest.ProxyRequestContext - { - AccountId = "123456789012", - RequestId = requestId, // requestId is used to invalidate the cache - } - }; - - var payload = JsonSerializer.Serialize(apiGatewayRequest); - - var request = new InvokeRequest - { - FunctionName = functionName, - InvocationType = InvocationType.RequestResponse, - Payload = payload, - LogType = LogType.Tail, - }; - - // run two times with the same request - for (int j = 0; j < 2; j++) - { - var response = await _lambdaClient.InvokeAsync(request); - - if (string.IsNullOrEmpty(response.LogResult)) - { - Assert.Fail("No LogResult field returned in the response of Lambda invocation."); - } - - var responsePayload = System.Text.Encoding.UTF8.GetString(response.Payload.ToArray()); - var parsedPayload = JsonSerializer.Deserialize(responsePayload); - - if (parsedPayload == null) - { - Assert.Fail("Failed to parse payload."); - } - - Assert.Equal(200, parsedPayload.StatusCode); - - if (j == 0) - { - // first call should return a new guid - if (parsedPayload.Body == initialGuid) - { - Assert.Fail("Idempotency failed to clear cache."); - } - - initialGuid = parsedPayload.Body; - } - - Assert.Equal(initialGuid, parsedPayload.Body); - - // Query DynamoDB and assert results - var hashRequestId = Helpers.HashRequest(requestId); - var id = $"{functionName}.MyInternalMethod#{hashRequestId}"; - await AssertDynamoDbData(id, initialGuid, true); - } - } + // First unique request + var requestId1 = Guid.NewGuid().ToString(); + var (firstResponse1, firstGuid1) = await ExecuteAttributeRequest(functionName, requestId1); + var (firstResponse2, firstGuid2) = await ExecuteAttributeRequest(functionName, requestId1); + + // Assert first request pair + Assert.Equal(200, firstResponse1.StatusCode); + Assert.Equal(200, firstResponse2.StatusCode); + Assert.Equal(firstGuid1, firstGuid2); // Idempotency check + await AssertDynamoDbData( + $"{functionName}.MyInternalMethod#{Helpers.HashRequest(requestId1)}", + firstGuid1, + true); + + // Second unique request + var requestId2 = Guid.NewGuid().ToString(); + var (secondResponse1, secondGuid1) = await ExecuteAttributeRequest(functionName, requestId2); + var (secondResponse2, secondGuid2) = await ExecuteAttributeRequest(functionName, requestId2); + + // Assert second request pair + Assert.Equal(200, secondResponse1.StatusCode); + Assert.Equal(200, secondResponse2.StatusCode); + Assert.Equal(secondGuid1, secondGuid2); // Idempotency check + Assert.NotEqual(firstGuid1, secondGuid1); // Different requests should have different GUIDs + await AssertDynamoDbData( + $"{functionName}.MyInternalMethod#{Helpers.HashRequest(requestId2)}", + secondGuid1, + true); } - internal async Task TestIdempotencyHandler(string functionName) + [Theory] + [MemberData(nameof(TestData.Inline), MemberType = typeof(TestData))] + public async Task IdempotencyHandlerTest(string functionName, string tableName) { + _tableName = tableName; await UpdateFunctionHandler(functionName, "Function::Function.Function::FunctionHandler"); - var request = new InvokeRequest - { - FunctionName = functionName, - InvocationType = InvocationType.RequestResponse, - Payload = await File.ReadAllTextAsync("../../../../../../../payload.json"), - LogType = LogType.Tail, - }; - - var initialGuid = string.Empty; - - // run three times to test idempotency - for (int i = 0; i < 3; i++) - { - var response = await _lambdaClient.InvokeAsync(request); - - if (string.IsNullOrEmpty(response.LogResult)) - { - Assert.Fail("No LogResult field returned in the response of Lambda invocation."); - } - - var payload = System.Text.Encoding.UTF8.GetString(response.Payload.ToArray()); - var parsedPayload = JsonSerializer.Deserialize(payload); - - if (parsedPayload == null) - { - Assert.Fail("Failed to parse payload."); - } - - Assert.Equal(200, parsedPayload.StatusCode); - - var parsedResponse = JsonSerializer.Deserialize(parsedPayload.Body); - - if (parsedResponse == null) - { - Assert.Fail("Failed to parse response."); - } - - if (i == 0) - { - initialGuid = parsedResponse.Guid; - } - - Assert.Equal(initialGuid, parsedResponse.Guid); - } - - // Query DynamoDB and assert results - var id = $"{functionName}.FunctionHandler#35973cf447e6cc11008d603c791a232f"; - await AssertDynamoDbData(id, initialGuid); + var payload = await File.ReadAllTextAsync("../../../../../../../payload.json"); + + // Execute three identical requests + var (response1, guid1) = await ExecuteHandlerRequest(functionName, payload); + var (response2, guid2) = await ExecuteHandlerRequest(functionName, payload); + var (response3, guid3) = await ExecuteHandlerRequest(functionName, payload); + + // Assert all responses + Assert.Equal(200, response1.StatusCode); + Assert.Equal(200, response2.StatusCode); + Assert.Equal(200, response3.StatusCode); + + // Assert idempotency + Assert.Equal(guid1, guid2); + Assert.Equal(guid2, guid3); + + // Assert DynamoDB + await AssertDynamoDbData( + $"{functionName}.FunctionHandler#35973cf447e6cc11008d603c791a232f", + guid1); } private async Task UpdateFunctionHandler(string functionName, string handler) @@ -334,6 +214,87 @@ private async Task AssertDynamoDbData(string id, string requestId, bool isSavedD } } } + + // Helper methods for executing requests + private async Task<(APIGatewayProxyResponse Response, string Guid)> ExecutePayloadSubsetRequest( + string functionName, string userId, string productId) + { + var request = new InvokeRequest + { + FunctionName = functionName, + InvocationType = InvocationType.RequestResponse, + Payload = JsonSerializer.Serialize(new APIGatewayProxyRequest + { + Body = $"{{\"user_id\":\"{userId}\",\"product_id\":\"{productId}\"}}" + }), + LogType = LogType.Tail + }; + + return await ExecuteRequest(request); + } + + private async Task<(APIGatewayProxyResponse Response, string Guid)> ExecuteAttributeRequest( + string functionName, string requestId) + { + var request = new InvokeRequest + { + FunctionName = functionName, + InvocationType = InvocationType.RequestResponse, + Payload = JsonSerializer.Serialize(new APIGatewayProxyRequest + { + Body = "{\"user_id\":\"***\",\"product_id\":\"123456789\"}", + RequestContext = new APIGatewayProxyRequest.ProxyRequestContext + { + AccountId = "123456789012", + RequestId = requestId + } + }), + LogType = LogType.Tail + }; + + return await ExecuteRequest(request); + } + + private async Task<(APIGatewayProxyResponse Response, string Guid)> ExecuteHandlerRequest( + string functionName, string payload) + { + var request = new InvokeRequest + { + FunctionName = functionName, + InvocationType = InvocationType.RequestResponse, + Payload = payload, + LogType = LogType.Tail + }; + + return await ExecuteRequest(request); + } + + private async Task<(APIGatewayProxyResponse Response, string Guid)> ExecuteRequest(InvokeRequest request) + { + var response = await _lambdaClient.InvokeAsync(request); + + if (string.IsNullOrEmpty(response.LogResult)) + Assert.Fail("No LogResult field returned in the response of Lambda invocation."); + + var responsePayload = System.Text.Encoding.UTF8.GetString(response.Payload.ToArray()); + var parsedResponse = JsonSerializer.Deserialize(responsePayload) + ?? throw new Exception("Failed to parse payload."); + + string guid; + try + { + // The GUID is inside the Response object + var parsedBody = JsonSerializer.Deserialize(parsedResponse.Body); + guid = parsedBody?.Guid ?? parsedResponse.Body; + } + catch (JsonException) + { + // For scenarios where the Body is already the GUID + guid = parsedResponse.Body; + } + + return (parsedResponse, guid); + } } public record Response(string Greeting, string Guid); \ No newline at end of file