From 9b29f26a0b7e5adfbe695f5039aa0a38671716e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jul 2023 11:58:27 +0000 Subject: [PATCH 01/41] chore(deps): bump pygments from 2.13.0 to 2.15.0 Bumps [pygments](https://github.com/pygments/pygments) from 2.13.0 to 2.15.0. - [Release notes](https://github.com/pygments/pygments/releases) - [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES) - [Commits](https://github.com/pygments/pygments/compare/2.13.0...2.15.0) --- updated-dependencies: - dependency-name: pygments dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/poetry.lock b/poetry.lock index 66ac82b9..6ea62c2b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -19,7 +18,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -31,7 +29,6 @@ files = [ name = "ghp-import" version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." -category = "main" optional = false python-versions = "*" files = [ @@ -49,7 +46,6 @@ dev = ["flake8", "markdown", "twine", "wheel"] name = "gitdb" version = "4.0.10" description = "Git Object Database" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -64,7 +60,6 @@ smmap = ">=3.0.1,<6" name = "gitpython" version = "3.1.30" description = "GitPython is a python library used to interact with Git repositories" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -79,7 +74,6 @@ gitdb = ">=4.0.1,<5" name = "importlib-metadata" version = "5.2.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -99,7 +93,6 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -117,7 +110,6 @@ i18n = ["Babel (>=2.7)"] name = "markdown" version = "3.3.7" description = "Python implementation of Markdown." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -135,7 +127,6 @@ testing = ["coverage", "pyyaml"] name = "markupsafe" version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -185,7 +176,6 @@ files = [ name = "mergedeep" version = "1.3.4" description = "A deep merge function for 🐍." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -197,7 +187,6 @@ files = [ name = "mike" version = "1.1.2" description = "Manage multiple versions of your MkDocs-powered documentation" -category = "main" optional = false python-versions = "*" files = [ @@ -219,7 +208,6 @@ test = ["coverage", "flake8 (>=3.0)", "shtab"] name = "mkdocs" version = "1.4.2" description = "Project documentation with Markdown." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -248,7 +236,6 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp name = "mkdocs-git-revision-date-plugin" version = "0.3.2" description = "MkDocs plugin for setting revision date from git per markdown file." -category = "main" optional = false python-versions = ">=3.4" files = [ @@ -264,7 +251,6 @@ mkdocs = ">=0.17" name = "mkdocs-material" version = "7.3.6" description = "A Material Design theme for MkDocs" -category = "main" optional = false python-versions = "*" files = [ @@ -284,7 +270,6 @@ pymdown-extensions = ">=9.0" name = "mkdocs-material-extensions" version = "1.1.1" description = "Extension pack for Python Markdown and MkDocs Material." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -296,7 +281,6 @@ files = [ name = "packaging" version = "22.0" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -306,14 +290,13 @@ files = [ [[package]] name = "pygments" -version = "2.13.0" +version = "2.15.0" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, - {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, + {file = "Pygments-2.15.0-py3-none-any.whl", hash = "sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094"}, + {file = "Pygments-2.15.0.tar.gz", hash = "sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500"}, ] [package.extras] @@ -323,7 +306,6 @@ plugins = ["importlib-metadata"] name = "pymdown-extensions" version = "10.0" description = "Extension pack for Python Markdown." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -339,7 +321,6 @@ pyyaml = "*" name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -354,7 +335,6 @@ six = ">=1.5" name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -404,7 +384,6 @@ files = [ name = "pyyaml-env-tag" version = "0.1" description = "A custom YAML tag for referencing environment variables in YAML files. " -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -419,7 +398,6 @@ pyyaml = "*" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -431,7 +409,6 @@ files = [ name = "smmap" version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -443,7 +420,6 @@ files = [ name = "verspec" version = "0.1.0" description = "Flexible version handling" -category = "main" optional = false python-versions = "*" files = [ @@ -458,7 +434,6 @@ test = ["coverage", "flake8 (>=3.7)", "mypy", "pretend", "pytest"] name = "watchdog" version = "2.2.0" description = "Filesystem events monitoring" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -499,7 +474,6 @@ watchmedo = ["PyYAML (>=3.10)"] name = "zipp" version = "3.11.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.7" files = [ From 8a889ca6dd9bed3155dbee9e7663162fdf02c777 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:22:49 +0100 Subject: [PATCH 02/41] Add JMESPathSerializationContext.cs and Serializer. Add Idempotency IdempotencySerializationContext.cs and Serializer. Update code to use both serializers --- .../Idempotency.cs | 26 ++- .../IdempotencyOptionsBuilder.cs | 14 +- .../IdempotentAttribute.cs | 5 +- .../Internal/IdempotencyAspectHandler.cs | 3 +- .../IdempotencySerializationContext.cs | 32 +++ .../Serializers/IdempotencySerializer.cs | 111 ++++++++++ .../Persistence/BasePersistenceStore.cs | 9 +- .../JMESPathSerializationContext.cs | 36 ++++ .../Serializers/JMESPathSerializer.cs | 57 +++++ .../Values/DecimalValue.cs | 5 +- .../Values/DoubleValue.cs | 4 +- .../Values/JsonElementValue.cs | 26 ++- .../Values/ObjectValue.cs | 7 +- .../Values/StringValue.cs | 5 +- .../Handlers/IdempotencyFunction.cs | 4 + .../IdempotencyFunctionMethodDecorated.cs | 4 + .../IdempotencyTest.cs | 53 +++-- .../Internal/IdempotentAspectTests.cs | 120 ++++++----- .../Internal/LRUCacheTests.cs | 2 +- .../Model/TestJsonSerializerContext.cs | 28 +++ .../Persistence/BasePersistenceStoreTests.cs | 201 +++++++++--------- .../TestSetup.cs | 18 ++ 22 files changed, 559 insertions(+), 211 deletions(-) create mode 100644 libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializationContext.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs create mode 100644 libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Model/TestJsonSerializerContext.cs create mode 100644 libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/TestSetup.cs diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs index e8de9f52..0646e056 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs @@ -1,12 +1,12 @@ /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at - * + * * http://aws.amazon.com/apache2.0 - * + * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing @@ -14,8 +14,10 @@ */ using System; +using System.Text.Json.Serialization; using Amazon.Lambda.Core; using AWS.Lambda.Powertools.Common; +using AWS.Lambda.Powertools.Idempotency.Internal.Serializers; using AWS.Lambda.Powertools.Idempotency.Persistence; namespace AWS.Lambda.Powertools.Idempotency; @@ -27,7 +29,7 @@ namespace AWS.Lambda.Powertools.Idempotency; /// Use it before the function handler get called. /// Example: Idempotency.Configure(builder => builder.WithPersistenceStore(...)); /// -public sealed class Idempotency +public sealed class Idempotency { /// /// The general configurations for the idempotency @@ -47,6 +49,7 @@ internal Idempotency(IPowertoolsConfigurations powertoolsConfigurations) { powertoolsConfigurations.SetExecutionEnvironment(this); } + /// /// Set Idempotency options /// @@ -90,7 +93,7 @@ public static void Configure(Action configurationAction) /// Holds ILambdaContext /// public ILambdaContext LambdaContext { get; private set; } - + /// /// Can be used in a method which is not the handler to capture the Lambda context, /// to calculate the remaining time before the invocation times out. @@ -177,5 +180,18 @@ public IdempotencyBuilder WithOptions(IdempotencyOptions options) Options = options; return this; } + +#if NET8_0_OR_GREATER + /// + /// Set Customer JsonSerializerContext to append to IdempotencySerializationContext + /// + /// + /// IdempotencyBuilder + public IdempotencyBuilder WithJsonSerializationContext(JsonSerializerContext context) + { + IdempotencySerializer.AddTypeInfoResolver(context); + return this; + } +#endif } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptionsBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptionsBuilder.cs index 721ebf29..1371f8e7 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptionsBuilder.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptionsBuilder.cs @@ -11,26 +11,32 @@ public class IdempotencyOptionsBuilder /// Default maximum number of items in the local cache. /// private readonly int _localCacheMaxItems = 256; + /// /// Local cache enabled /// private bool _useLocalCache; + /// /// Default expiration in seconds. /// private long _expirationInSeconds = 60 * 60; // 1 hour + /// /// Event key JMESPath expression. /// private string _eventKeyJmesPath; + /// /// Payload validation JMESPath expression. /// private string _payloadValidationJmesPath; + /// /// Throw exception if no idempotency key is found. /// private bool _throwOnNoIdempotencyKey; + /// /// Default Hash function /// @@ -107,7 +113,7 @@ public IdempotencyOptionsBuilder WithThrowOnNoIdempotencyKey(bool throwOnNoIdemp /// the instance of the builder (to chain operations) public IdempotencyOptionsBuilder WithExpiration(TimeSpan duration) { - _expirationInSeconds = (long) duration.TotalSeconds; + _expirationInSeconds = (long)duration.TotalSeconds; return this; } @@ -116,9 +122,15 @@ public IdempotencyOptionsBuilder WithExpiration(TimeSpan duration) /// /// Can be any algorithm supported by HashAlgorithm.Create /// the instance of the builder (to chain operations) +#if NET8_0_OR_GREATER + [Obsolete("Idempotency uses MD5 and does not support other hash algorithms.")] +#endif public IdempotencyOptionsBuilder WithHashFunction(string hashFunction) { +#if NET6_0 + // for backward compability keep this code in .net 6 _hashFunction = hashFunction; +#endif return this; } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotentAttribute.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotentAttribute.cs index 9f1683fa..0204ee1a 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotentAttribute.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotentAttribute.cs @@ -23,6 +23,7 @@ using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Idempotency.Exceptions; using AWS.Lambda.Powertools.Idempotency.Internal; +using AWS.Lambda.Powertools.Idempotency.Internal.Serializers; namespace AWS.Lambda.Powertools.Idempotency; @@ -151,7 +152,7 @@ private static JsonDocument GetPayload(AspectEventArgs eventArgs) // Use the first argument if IdempotentAttribute placed on handler or number of arguments is 1 if (isPlacedOnRequestHandler || args.Count == 1) { - payload = args is not null && args.Any() ? JsonDocument.Parse(JsonSerializer.Serialize(args[0])) : null; + payload = args is not null && args.Any() ? JsonDocument.Parse(IdempotencySerializer.Serialize(args[0], typeof(object))) : null; } else { @@ -160,7 +161,7 @@ private static JsonDocument GetPayload(AspectEventArgs eventArgs) if (parameter != null) { // set payload to the value of the parameter - payload = JsonDocument.Parse(JsonSerializer.Serialize(args[Array.IndexOf(eventArgsMethod.GetParameters(), parameter)])); + payload = JsonDocument.Parse(IdempotencySerializer.Serialize(args[Array.IndexOf(eventArgsMethod.GetParameters(), parameter)], typeof(object))); } } diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs index a78ce309..ba1bf80a 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs @@ -18,6 +18,7 @@ using System.Threading.Tasks; using Amazon.Lambda.Core; using AWS.Lambda.Powertools.Idempotency.Exceptions; +using AWS.Lambda.Powertools.Idempotency.Internal.Serializers; using AWS.Lambda.Powertools.Idempotency.Persistence; namespace AWS.Lambda.Powertools.Idempotency.Internal; @@ -184,7 +185,7 @@ private Task HandleForStatus(DataRecord record) default: try { - var result = JsonSerializer.Deserialize(record.ResponseData!); + var result = IdempotencySerializer.Deserialize(record.ResponseData!); if (result is null) { throw new IdempotencyPersistenceLayerException("Unable to cast function response as " + typeof(T).Name); diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializationContext.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializationContext.cs new file mode 100644 index 00000000..6f77b9cf --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializationContext.cs @@ -0,0 +1,32 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Text.Json.Serialization; + +namespace AWS.Lambda.Powertools.Idempotency.Internal.Serializers; + +#if NET8_0_OR_GREATER + + +/// +/// The source generated JsonSerializerContext to be used to Serialize Idempotency types +/// +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(object))] +public partial class IdempotencySerializationContext : JsonSerializerContext +{ + +} +#endif \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs new file mode 100644 index 00000000..247c77de --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs @@ -0,0 +1,111 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; + +namespace AWS.Lambda.Powertools.Idempotency.Internal.Serializers; + +/// +/// Serializer for Idempotency. +/// +internal static class IdempotencySerializer +{ + private static JsonSerializerOptions _jsonOptions; + + static IdempotencySerializer() + { + BuildDefaultOptions(); + } + + /// + /// Builds the default JsonSerializerOptions. + /// + private static void BuildDefaultOptions() + { + _jsonOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; +#if NET8_0_OR_GREATER + _jsonOptions.TypeInfoResolverChain.Add(IdempotencySerializationContext.Default); +#endif + } + +#if NET8_0_OR_GREATER + + /// + /// Adds a JsonTypeInfoResolver to the JsonSerializerOptions. + /// + /// The JsonTypeInfoResolver to add. + /// + /// This method is only available in .NET 8.0 and later versions. + /// + internal static void AddTypeInfoResolver(JsonSerializerContext context) + { + BuildDefaultOptions(); + _jsonOptions.TypeInfoResolverChain.Add(context); + } + + /// + /// Gets the JsonTypeInfo for a given type. + /// + /// The type to get information for. + /// The JsonTypeInfo for the specified type, or null if not found. + internal static JsonTypeInfo GetTypeInfo(Type type) + { + var typeInfo = _jsonOptions.TypeInfoResolver?.GetTypeInfo(type, _jsonOptions); + if (typeInfo == null) + { + throw new Exception( + $"Type {type} is not known to the serializer. Ensure it's included in the JsonSerializerContext."); + } + + return typeInfo; + } +#endif + + /// + /// Serializes the specified object to a JSON string. + /// + /// The object to serialize. + /// The type of the object to serialize. + /// A JSON string representation of the object. + internal static string Serialize(object value, Type inputType) + { +#if NET6_0 + return JsonSerializer.Serialize(value, _jsonOptions); +#else + return JsonSerializer.Serialize(value, GetTypeInfo(inputType)); +#endif + } + + /// + /// Deserializes the specified JSON string to an object of type T. + /// + /// The type of the object to deserialize to. + /// The JSON string to deserialize. + /// An object of type T represented by the JSON string. + internal static T Deserialize(string value) + { +#if NET6_0 + return JsonSerializer.Deserialize(value,_jsonOptions); +#else + return (T)JsonSerializer.Deserialize(value, GetTypeInfo(typeof(T))); +#endif + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs index c71dc9d9..fc8604a2 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs @@ -21,6 +21,7 @@ using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Idempotency.Exceptions; using AWS.Lambda.Powertools.Idempotency.Internal; +using AWS.Lambda.Powertools.Idempotency.Internal.Serializers; using AWS.Lambda.Powertools.JMESPath; namespace AWS.Lambda.Powertools.Idempotency.Persistence; @@ -95,7 +96,7 @@ internal void Configure(IdempotencyOptions options, string functionName, LRUCach /// The current date time public virtual async Task SaveSuccess(JsonDocument data, object result, DateTimeOffset now) { - var responseJson = JsonSerializer.Serialize(result); + var responseJson = IdempotencySerializer.Serialize(result, typeof(object)); var record = new DataRecord( GetHashedIdempotencyKey(data), DataRecord.DataRecordStatus.COMPLETED, @@ -323,7 +324,13 @@ private static bool IsMissingIdempotencyKey(JsonElement data) /// internal string GenerateHash(JsonElement data) { +#if NET8_0_OR_GREATER + // starting .NET 8 no option to change hash algorithm + using var hashAlgorithm = MD5.Create(); +#else + using var hashAlgorithm = HashAlgorithm.Create(_idempotencyOptions.HashFunction); +#endif if (hashAlgorithm == null) { throw new ArgumentException("Invalid HashAlgorithm"); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs new file mode 100644 index 00000000..e031916e --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs @@ -0,0 +1,36 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AWS.Lambda.Powertools.JMESPath.Serializers; + +#if NET8_0_OR_GREATER + +/// +/// The source generated JsonSerializerContext to be used to Serialize JMESPath types +/// +[JsonSourceGenerationOptions(WriteIndented = false)] +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(object))] +[JsonSerializable(typeof(decimal))] +[JsonSerializable(typeof(double))] +[JsonSerializable(typeof(JsonElement))] +public partial class JMESPathSerializationContext : JsonSerializerContext +{ +} + +#endif \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs new file mode 100644 index 00000000..8e716125 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs @@ -0,0 +1,57 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Text.Json; + +namespace AWS.Lambda.Powertools.JMESPath.Serializers; + +/// +/// Class JMESPathSerializer. +/// +internal static class JMESPathSerializer +{ + /// + /// Serializes the specified value. + /// + /// The value. + /// Type of the input. + /// System.String. + internal static string Serialize(object value, Type inputType) + { +#if NET6_0 + return JsonSerializer.Serialize(value); +#else + + return JsonSerializer.Serialize(value, inputType, JMESPathSerializationContext.Default); +#endif + } + + /// + /// Deserializes the specified value. + /// + /// + /// The value. + /// T. + internal static T Deserialize(string value) + { +#if NET6_0 + return JsonSerializer.Deserialize(value); +#else + + return (T)JsonSerializer.Deserialize(value, typeof(T), JMESPathSerializationContext.Default); +#endif + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs index b03ff5db..26a7d06c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs @@ -14,8 +14,8 @@ */ using System; -using System.Text.Json; using AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Serializers; namespace AWS.Lambda.Powertools.JMESPath.Values; @@ -98,7 +98,6 @@ public IExpression GetExpression() public override string ToString() { - var s = JsonSerializer.Serialize(_value); - return s; + return JMESPathSerializer.Serialize(_value, typeof(decimal)); } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs index 2962f28d..9dd037e5 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs @@ -16,6 +16,7 @@ using System; using System.Text.Json; using AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Serializers; namespace AWS.Lambda.Powertools.JMESPath.Values; @@ -103,7 +104,6 @@ public IExpression GetExpression() public override string ToString() { - var s = JsonSerializer.Serialize(_value); - return s; + return JMESPathSerializer.Serialize(_value, typeof(double)); } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs index b215d339..f22f5bd6 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs @@ -18,6 +18,7 @@ using System.Text.Json; using System.Text.Json.Nodes; using AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Serializers; namespace AWS.Lambda.Powertools.JMESPath.Values; @@ -204,12 +205,28 @@ public bool TryGetProperty(string propertyName, out IValue property) { var r = _element.TryGetProperty(propertyName, out var prop); - property = prop.ValueKind == JsonValueKind.String && IsJsonValid(prop.GetString()) - ? new JsonElementValue(JsonNode.Parse(prop.GetString() ?? string.Empty).Deserialize()) - : new JsonElementValue(prop); + // property = prop.ValueKind == JsonValueKind.String && IsJsonValid(prop.GetString()) + // ? new JsonElementValue(JsonNode.Parse(prop.GetString() ?? string.Empty).Deserialize()) + // : new JsonElementValue(prop); + + property = CreateJsonElementValue(prop); return r; } + + JsonElementValue CreateJsonElementValue(JsonElement prop) + { + if (prop.ValueKind == JsonValueKind.String && IsJsonValid(prop.GetString())) + { + var jsonString = prop.GetString() ?? string.Empty; + // var jsonNode = JsonNode.Parse(jsonString); + var jsonElement = JMESPathSerializer.Deserialize(jsonString); + return new JsonElementValue(jsonElement); + } + + return new JsonElementValue(prop); + } + private static bool IsJsonValid(string json) { @@ -244,7 +261,6 @@ public IExpression GetExpression() public override string ToString() { - var s = JsonSerializer.Serialize(_element); - return s; + return JMESPathSerializer.Serialize(_element, typeof(JsonElement)); } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs index 86153435..211abb78 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs @@ -16,8 +16,8 @@ using System; using System.Collections.Generic; using System.Text; -using System.Text.Json; using AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Serializers; namespace AWS.Lambda.Powertools.JMESPath.Values; @@ -44,7 +44,8 @@ private sealed class ObjectEnumerator : IObjectValueEnumerator public ObjectEnumerator(IDictionary value) { _value = value; - _enumerator = value.GetEnumerator(); + using var enumerator = value.GetEnumerator(); + _enumerator = enumerator; } public bool MoveNext() @@ -162,7 +163,7 @@ public override string ToString() first = false; } - buffer.Append(JsonSerializer.Serialize(property.Key)); + buffer.Append(JMESPathSerializer.Serialize(property.Key, property.Key.GetType())); buffer.Append(':'); buffer.Append(property.Value); } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs index 02fb2890..75bb4638 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs @@ -14,8 +14,8 @@ */ using System; -using System.Text.Json; using AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Serializers; namespace AWS.Lambda.Powertools.JMESPath.Values; @@ -90,7 +90,6 @@ public IExpression GetExpression() public override string ToString() { - var s = JsonSerializer.Serialize(_value); - return s; + return JMESPathSerializer.Serialize(_value, typeof(string)); } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunction.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunction.cs index 5c500f05..6e546fee 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunction.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunction.cs @@ -21,6 +21,7 @@ using System.Threading.Tasks; using Amazon.DynamoDBv2; using Amazon.Lambda.APIGatewayEvents; +using AWS.Lambda.Powertools.Idempotency.Tests.Model; namespace AWS.Lambda.Powertools.Idempotency.Tests.Handlers; @@ -32,6 +33,9 @@ public IdempotencyFunction(AmazonDynamoDBClient client) { Idempotency.Configure(builder => builder +#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +#endif .WithOptions(optionsBuilder => optionsBuilder .WithEventKeyJmesPath("powertools_json(Body).address") diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunctionMethodDecorated.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunctionMethodDecorated.cs index 4a404588..ae4f0d0d 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunctionMethodDecorated.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunctionMethodDecorated.cs @@ -22,6 +22,7 @@ using Amazon.DynamoDBv2; using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.Core; +using AWS.Lambda.Powertools.Idempotency.Tests.Model; namespace AWS.Lambda.Powertools.Idempotency.Tests.Handlers; @@ -33,6 +34,9 @@ public IdempotencyFunctionMethodDecorated(AmazonDynamoDBClient client) { Idempotency.Configure(builder => builder +#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +#endif .UseDynamoDb(storeBuilder => storeBuilder .WithTableName("idempotency_table") diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs index 018e672a..31530d33 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs @@ -22,6 +22,7 @@ using Amazon.DynamoDBv2.Model; using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.TestUtilities; +using AWS.Lambda.Powertools.Idempotency.Internal.Serializers; using AWS.Lambda.Powertools.Idempotency.Tests.Handlers; using AWS.Lambda.Powertools.Idempotency.Tests.Persistence; using FluentAssertions; @@ -39,20 +40,17 @@ public IdempotencyTest(DynamoDbFixture fixture) _client = fixture.Client; _tableName = fixture.TableName; } - + [Fact] [Trait("Category", "Integration")] - public async Task EndToEndTest() + public async Task EndToEndTest() { var function = new IdempotencyFunction(_client); - - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; - - var request = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./resources/apigw_event2.json"),options); - + + var request = + IdempotencySerializer.Deserialize( + await File.ReadAllTextAsync("./resources/apigw_event2.json")); + var response = await function.Handle(request); function.HandlerExecuted.Should().BeTrue(); @@ -61,7 +59,10 @@ public async Task EndToEndTest() var response2 = await function.Handle(request); function.HandlerExecuted.Should().BeFalse(); - JsonSerializer.Serialize(response).Should().Be(JsonSerializer.Serialize(response)); + IdempotencySerializer.Serialize(response, typeof(APIGatewayProxyResponse)).Should() + .Be( + "{\"statusCode\":200,\"headers\":{\"Content-Type\":\"application/json\",\"Access-Control-Allow-Origin\":\"*\",\"Access-Control-Allow-Methods\":\"GET, OPTIONS\",\"Access-Control-Allow-Headers\":\"*\"},\"multiValueHeaders\":null,\"body\":\"{ \\u0022message\\u0022: \\u0022hello world\\u0022, \\u0022location\\u0022: \\u002295.92.53.22\\n\\u0022 }\",\"isBase64Encoded\":false}"); + response2.Body.Should().Contain("hello world"); var scanResponse = await _client.ScanAsync(new ScanRequest @@ -69,33 +70,30 @@ public async Task EndToEndTest() TableName = _tableName }); scanResponse.Count.Should().Be(1); - + // delete row dynamo var key = new Dictionary { ["id"] = new AttributeValue { S = "testFunction.GetPageContents#ff323c6f0c5ceb97eed49121babcec0f" } }; - await _client.DeleteItemAsync(new DeleteItemRequest{TableName = _tableName, Key = key}); + await _client.DeleteItemAsync(new DeleteItemRequest { TableName = _tableName, Key = key }); } - + [Fact] [Trait("Category", "Integration")] - public async Task EndToEndTestMethod() + public async Task EndToEndTestMethod() { var function = new IdempotencyFunctionMethodDecorated(_client); - - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; - + var context = new TestLambdaContext { RemainingTime = TimeSpan.FromSeconds(30) }; - - var request = JsonSerializer.Deserialize(await File.ReadAllTextAsync("./resources/apigw_event2.json"),options); - + + var request = + IdempotencySerializer.Deserialize( + await File.ReadAllTextAsync("./resources/apigw_event2.json")); + var response = await function.Handle(request, context); function.MethodCalled.Should().BeTrue(); @@ -105,7 +103,8 @@ public async Task EndToEndTestMethod() function.MethodCalled.Should().BeFalse(); // Assert - JsonSerializer.Serialize(response).Should().Be(JsonSerializer.Serialize(response2)); + IdempotencySerializer.Serialize(response, typeof(APIGatewayProxyResponse)).Should() + .Be(IdempotencySerializer.Serialize(response2, typeof(APIGatewayProxyResponse))); response.Body.Should().Contain("hello world"); response2.Body.Should().Contain("hello world"); @@ -114,12 +113,12 @@ public async Task EndToEndTestMethod() TableName = _tableName }); scanResponse.Count.Should().Be(1); - + // delete row dynamo var key = new Dictionary { ["id"] = new AttributeValue { S = "testFunction.GetPageContents#ff323c6f0c5ceb97eed49121babcec0f" } }; - await _client.DeleteItemAsync(new DeleteItemRequest{TableName = _tableName, Key = key}); + await _client.DeleteItemAsync(new DeleteItemRequest { TableName = _tableName, Key = key }); } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs index d9836854..7a67a4d9 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs @@ -1,12 +1,12 @@ /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at - * + * * http://aws.amazon.com/apache2.0 - * + * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing @@ -25,11 +25,7 @@ using AWS.Lambda.Powertools.Idempotency.Tests.Model; using FluentAssertions; using NSubstitute; -using NSubstitute.ExceptionExtensions; using Xunit; -// ReSharper disable CompareOfFloatsByEqualityOperator - -[assembly: CollectionBehavior(DisableTestParallelization = true)] namespace AWS.Lambda.Powertools.Idempotency.Tests.Internal; @@ -45,25 +41,28 @@ public async Task Handle_WhenFirstCall_ShouldPutInStore(Type type) var store = Substitute.For(); Idempotency.Configure(builder => builder +#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +#endif .WithPersistenceStore(store) .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) - ); - + ); + var context = new TestLambdaContext { RemainingTime = TimeSpan.FromSeconds(30) }; - + var function = Activator.CreateInstance(type) as IIdempotencyEnabledFunction; var product = new Product(42, "fake product", 12); - + //Act var basket = await function!.HandleTest(product, context); - + //Assert basket.Products.Count.Should().Be(1); function.HandlerExecuted.Should().BeTrue(); - + await store.Received().SaveInProgress( Arg.Is(t => t.ToString() == JsonSerializer.SerializeToDocument(product, new JsonSerializerOptions()).ToString()), @@ -86,14 +85,17 @@ public async Task Handle_WhenSecondCall_AndNotExpired_ShouldGetFromStore(Type ty var store = Substitute.For(); store.SaveInProgress(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(_ => throw new IdempotencyItemAlreadyExistsException()); - + // GIVEN Idempotency.Configure(builder => builder +#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +#endif .WithPersistenceStore(store) .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) - ); - + ); + var product = new Product(42, "fake product", 12); var basket = new Basket(product); var record = new DataRecord( @@ -105,19 +107,20 @@ public async Task Handle_WhenSecondCall_AndNotExpired_ShouldGetFromStore(Type ty store.GetRecord(Arg.Any(), Arg.Any()).Returns(record); var function = Activator.CreateInstance(type) as IIdempotencyEnabledFunction; - + // Act var resultBasket = await function!.HandleTest(product, new TestLambdaContext()); - + // Assert resultBasket.Should().Be(basket); function.HandlerExecuted.Should().BeFalse(); } - + [Theory] [InlineData(typeof(IdempotencyEnabledFunction))] [InlineData(typeof(IdempotencyEnabledSyncFunction))] - public async Task Handle_WhenSecondCall_AndStatusInProgress_ShouldThrowIdempotencyAlreadyInProgressException(Type type) + public async Task Handle_WhenSecondCall_AndStatusInProgress_ShouldThrowIdempotencyAlreadyInProgressException( + Type type) { // Arrange var store = Substitute.For(); @@ -126,11 +129,11 @@ public async Task Handle_WhenSecondCall_AndStatusInProgress_ShouldThrowIdempoten builder .WithPersistenceStore(store) .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) - ); - + ); + store.SaveInProgress(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(_ => throw new IdempotencyItemAlreadyExistsException()); - + var product = new Product(42, "fake product", 12); var basket = new Basket(product); var record = new DataRecord( @@ -145,30 +148,32 @@ public async Task Handle_WhenSecondCall_AndStatusInProgress_ShouldThrowIdempoten // Act var function = Activator.CreateInstance(type) as IIdempotencyEnabledFunction; Func act = async () => await function!.HandleTest(product, new TestLambdaContext()); - + // Assert await act.Should().ThrowAsync(); } - + [Theory] [InlineData(typeof(IdempotencyEnabledFunction))] [InlineData(typeof(IdempotencyEnabledSyncFunction))] - public async Task Handle_WhenSecondCall_InProgress_LambdaTimeout_Expired_ShouldThrowIdempotencyInconsistentStateException(Type type) + public async Task + Handle_WhenSecondCall_InProgress_LambdaTimeout_Expired_ShouldThrowIdempotencyInconsistentStateException( + Type type) { // Arrange var store = Substitute.For(); - + Idempotency.Configure(builder => builder .WithPersistenceStore(store) .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); - + store.SaveInProgress(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(_ => throw new IdempotencyItemAlreadyExistsException()); var timestampInThePast = DateTimeOffset.Now.AddSeconds(-30).ToUnixTimeMilliseconds(); - + var product = new Product(42, "fake product", 12); var basket = new Basket(product); var record = new DataRecord( @@ -178,14 +183,14 @@ public async Task Handle_WhenSecondCall_InProgress_LambdaTimeout_Expired_ShouldT JsonSerializer.SerializeToNode(basket)!.ToString(), null, timestampInThePast); - + store.GetRecord(Arg.Any(), Arg.Any()) .Returns(record); - + // Act var function = Activator.CreateInstance(type) as IIdempotencyEnabledFunction; Func act = async () => await function!.HandleTest(product, new TestLambdaContext()); - + // Assert await act.Should().ThrowAsync(); } @@ -193,28 +198,28 @@ public async Task Handle_WhenSecondCall_InProgress_LambdaTimeout_Expired_ShouldT [Theory] [InlineData(typeof(IdempotencyWithErrorFunction))] [InlineData(typeof(IdempotencyWithErrorSyncFunction))] - public async Task Handle_WhenThrowException_ShouldDeleteRecord_AndThrowFunctionException(Type type) + public async Task Handle_WhenThrowException_ShouldDeleteRecord_AndThrowFunctionException(Type type) { // Arrange var store = Substitute.For(); - + Idempotency.Configure(builder => builder .WithPersistenceStore(store) .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) - ); - + ); + var function = Activator.CreateInstance(type) as IIdempotencyWithErrorFunction; var product = new Product(42, "fake product", 12); - + // Act Func act = async () => await function!.HandleTest(product, new TestLambdaContext()); - + // Assert await act.Should().ThrowAsync(); await store.Received().DeleteRecord(Arg.Any(), Arg.Any()); } - + [Theory] [InlineData(typeof(IdempotencyEnabledFunction))] [InlineData(typeof(IdempotencyEnabledSyncFunction))] @@ -222,18 +227,18 @@ public async Task Handle_WhenIdempotencyDisabled_ShouldJustRunTheFunction(Type t { // Arrange var store = Substitute.For(); - + Environment.SetEnvironmentVariable(Constants.IdempotencyDisabledEnv, "true"); - + Idempotency.Configure(builder => builder .WithPersistenceStore(store) .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) - ); - + ); + var function = Activator.CreateInstance(type) as IIdempotencyEnabledFunction; var product = new Product(42, "fake product", 12); - + // Act var basket = await function!.HandleTest(product, new TestLambdaContext()); @@ -249,13 +254,13 @@ public void Idempotency_Set_Execution_Environment_Context() // Arrange var assemblyName = "AWS.Lambda.Powertools.Idempotency"; var assemblyVersion = "1.0.0"; - + var env = Substitute.For(); env.GetAssemblyName(Arg.Any()).Returns(assemblyName); env.GetAssemblyVersion(Arg.Any()).Returns(assemblyVersion); var conf = new PowertoolsConfigurations(new SystemWrapper(env)); - + // Act var xRayRecorder = new Idempotency(conf); @@ -266,7 +271,7 @@ public void Idempotency_Set_Execution_Environment_Context() ); env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); - + Assert.NotNull(xRayRecorder); } @@ -281,16 +286,16 @@ public async Task Handle_WhenIdempotencyOnSubMethodAnnotated_AndFirstCall_Should { RemainingTime = TimeSpan.FromSeconds(30) }; - + // Act IdempotencyInternalFunction function = new IdempotencyInternalFunction(true); Product product = new Product(42, "fake product", 12); Basket resultBasket = function.HandleRequest(product, context); - + // Assert resultBasket.Products.Count.Should().Be(2); function.IsSubMethodCalled.Should().BeTrue(); - + await store .Received(1) .SaveInProgress( @@ -334,9 +339,9 @@ public void Handle_WhenIdempotencyOnSubMethodAnnotated_AndSecondCall_AndNotExpir resultBasket.Should().Be(basket); function.IsSubMethodCalled.Should().BeFalse(); } - + [Fact] - public void Handle_WhenIdempotencyOnSubMethodAnnotated_AndKeyJMESPath_ShouldPutInStoreWithKey() + public void Handle_WhenIdempotencyOnSubMethodAnnotated_AndKeyJMESPath_ShouldPutInStoreWithKey() { // Arrange var store = new InMemoryPersistenceStore(); @@ -345,7 +350,7 @@ public void Handle_WhenIdempotencyOnSubMethodAnnotated_AndKeyJMESPath_ShouldPutI .WithPersistenceStore(store) .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); - + // Act IdempotencyInternalFunctionInternalKey function = new IdempotencyInternalFunctionInternalKey(); Product product = new Product(42, "fake product", 12); @@ -355,8 +360,9 @@ public void Handle_WhenIdempotencyOnSubMethodAnnotated_AndKeyJMESPath_ShouldPutI // a1d0c6e83f027327d8461063f4ac58a6 = MD5(42) store.GetRecord("testFunction.createBasket#a1d0c6e83f027327d8461063f4ac58a6").Should().NotBeNull(); } + [Fact] - public void Handle_WhenIdempotencyOnSubMethodNotAnnotated_ShouldThrowException() + public void Handle_WhenIdempotencyOnSubMethodNotAnnotated_ShouldThrowException() { // Arrange var store = Substitute.For(); @@ -369,13 +375,13 @@ public void Handle_WhenIdempotencyOnSubMethodNotAnnotated_ShouldThrowException() IdempotencyInternalFunctionInvalid function = new IdempotencyInternalFunctionInvalid(); Product product = new Product(42, "fake product", 12); Action act = () => function!.HandleRequest(product, new TestLambdaContext()); - + // Assert act.Should().Throw(); } - + [Fact] - public void Handle_WhenIdempotencyOnSubMethodVoid_ShouldThrowException() + public void Handle_WhenIdempotencyOnSubMethodVoid_ShouldThrowException() { // Arrange var store = Substitute.For(); diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/LRUCacheTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/LRUCacheTests.cs index 54ec5388..a34f5474 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/LRUCacheTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/LRUCacheTests.cs @@ -127,7 +127,7 @@ public async Task TestMultiThreadingAsync() tasks.Add(Task.Run(() => StoreElement(cache, numOfOps))); } - await Task.WhenAll(tasks).ConfigureAwait(false); + await Task.WhenAll(tasks); for (var i = numOfOps - numOfThreads; i < numOfOps; i++) { diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Model/TestJsonSerializerContext.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Model/TestJsonSerializerContext.cs new file mode 100644 index 00000000..c9c1d262 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Model/TestJsonSerializerContext.cs @@ -0,0 +1,28 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Text.Json.Serialization; +using Amazon.Lambda.APIGatewayEvents; + +namespace AWS.Lambda.Powertools.Idempotency.Tests.Model; + +[JsonSerializable(typeof(Basket))] +[JsonSerializable(typeof(APIGatewayProxyRequest))] +[JsonSerializable(typeof(APIGatewayProxyResponse))] +[JsonSerializable(typeof(Product))] +public partial class TestJsonSerializerContext : JsonSerializerContext +{ + +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs index 366aac77..0b71f51b 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs @@ -1,12 +1,12 @@ /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at - * + * * http://aws.amazon.com/apache2.0 - * + * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing @@ -21,6 +21,7 @@ using Amazon.Lambda.APIGatewayEvents; using AWS.Lambda.Powertools.Idempotency.Exceptions; using AWS.Lambda.Powertools.Idempotency.Internal; +using AWS.Lambda.Powertools.Idempotency.Internal.Serializers; using AWS.Lambda.Powertools.Idempotency.Persistence; using AWS.Lambda.Powertools.Idempotency.Tests.Model; using FluentAssertions; @@ -35,6 +36,7 @@ class InMemoryPersistenceStore : BasePersistenceStore private string _validationHash = null; public DataRecord DataRecord; public int Status = -1; + public override Task GetRecord(string idempotencyKey) { Status = 0; @@ -68,18 +70,18 @@ public override Task DeleteRecord(string idempotencyKey) return Task.CompletedTask; } } - + [Fact] public async Task SaveInProgress_WhenDefaultConfig_ShouldSaveRecordInStore() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - + persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null); - + var now = DateTimeOffset.UtcNow; - + // Act await persistenceStore.SaveInProgress(JsonSerializer.SerializeToDocument(request)!, now, null); @@ -92,19 +94,19 @@ public async Task SaveInProgress_WhenDefaultConfig_ShouldSaveRecordInStore() dr.PayloadHash.Should().BeEmpty(); persistenceStore.Status.Should().Be(1); } - + [Fact] public async Task SaveInProgress_WhenRemainingTime_ShouldSaveRecordInStore() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - + persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null); - + var now = DateTimeOffset.UtcNow; - var lambdaTimeoutMs = 30000; - + var lambdaTimeoutMs = 30000; + // Act await persistenceStore.SaveInProgress(JsonSerializer.SerializeToDocument(request)!, now, lambdaTimeoutMs); @@ -125,16 +127,16 @@ public async Task SaveInProgress_WhenKeyJmesPathIsSet_ShouldSaveRecordInStore_Wi // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - + persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithEventKeyJmesPath("powertools_json(Body).id") .Build(), "myfunc"); var now = DateTimeOffset.UtcNow; - + // Act await persistenceStore.SaveInProgress(JsonSerializer.SerializeToDocument(request)!, now, null); - + // Assert var dr = persistenceStore.DataRecord; dr.Status.Should().Be(DataRecord.DataRecordStatus.INPROGRESS); @@ -144,23 +146,24 @@ public async Task SaveInProgress_WhenKeyJmesPathIsSet_ShouldSaveRecordInStore_Wi dr.PayloadHash.Should().BeEmpty(); persistenceStore.Status.Should().Be(1); } - + [Fact] - public async Task SaveInProgress_WhenKeyJmesPathIsSetToMultipleFields_ShouldSaveRecordInStore_WithIdempotencyKeyEqualsKeyJmesPath() + public async Task + SaveInProgress_WhenKeyJmesPathIsSetToMultipleFields_ShouldSaveRecordInStore_WithIdempotencyKeyEqualsKeyJmesPath() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - + persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithEventKeyJmesPath("powertools_json(Body).[id, message]") //[43876123454654,"Lambda rocks"] .Build(), "myfunc"); var now = DateTimeOffset.UtcNow; - + // Act await persistenceStore.SaveInProgress(JsonSerializer.SerializeToDocument(request)!, now, null); - + // Assert var dr = persistenceStore.DataRecord; dr.Status.Should().Be(DataRecord.DataRecordStatus.INPROGRESS); @@ -170,45 +173,46 @@ public async Task SaveInProgress_WhenKeyJmesPathIsSetToMultipleFields_ShouldSave dr.PayloadHash.Should().BeEmpty(); persistenceStore.Status.Should().Be(1); } - - + + [Fact] public async Task SaveInProgress_WhenJMESPath_NotFound_ShouldThrowException() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - + persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithEventKeyJmesPath("unavailable") .WithThrowOnNoIdempotencyKey(true) // should throw .Build(), ""); var now = DateTimeOffset.UtcNow; - + // Act - var act = async () => await persistenceStore.SaveInProgress(JsonSerializer.SerializeToDocument(request)!, now, null); - + var act = async () => + await persistenceStore.SaveInProgress(JsonSerializer.SerializeToDocument(request)!, now, null); + // Assert await act.Should() .ThrowAsync() .WithMessage("No data found to create a hashed idempotency key"); - + persistenceStore.Status.Should().Be(-1); } - + [Fact] public async Task SaveInProgress_WhenJMESpath_NotFound_ShouldNotThrowException() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - + persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithEventKeyJmesPath("unavailable") .Build(), ""); - + var now = DateTimeOffset.UtcNow; - + // Act await persistenceStore.SaveInProgress(JsonSerializer.SerializeToDocument(request)!, now, null); @@ -217,20 +221,20 @@ public async Task SaveInProgress_WhenJMESpath_NotFound_ShouldNotThrowException() dr.Status.Should().Be(DataRecord.DataRecordStatus.INPROGRESS); persistenceStore.Status.Should().Be(1); } - + [Fact] public async Task SaveInProgress_WhenLocalCacheIsSet_AndNotExpired_ShouldThrowException() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - - LRUCache cache = new (2); + + LRUCache cache = new(2); persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithUseLocalCache(true) .WithEventKeyJmesPath("powertools_json(Body).id") .Build(), null, cache); - + var now = DateTimeOffset.UtcNow; cache.Set("testFunction#2fef178cc82be5ce3da6c5e0466a6182", new DataRecord( @@ -239,7 +243,7 @@ public async Task SaveInProgress_WhenLocalCacheIsSet_AndNotExpired_ShouldThrowEx now.AddSeconds(3600).ToUnixTimeSeconds(), null, null) ); - + // Act var act = () => persistenceStore.SaveInProgress(JsonSerializer.SerializeToDocument(request)!, now, null); @@ -249,21 +253,21 @@ await act.Should() persistenceStore.Status.Should().Be(-1); } - + [Fact] public async Task SaveInProgress_WhenLocalCacheIsSetButExpired_ShouldRemoveFromCache() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - - LRUCache cache = new (2); + + LRUCache cache = new(2); persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithEventKeyJmesPath("powertools_json(Body).id") .WithUseLocalCache(true) .WithExpiration(TimeSpan.FromSeconds(2)) .Build(), null, cache); - + var now = DateTimeOffset.UtcNow; cache.Set("testFunction#2fef178cc82be5ce3da6c5e0466a6182", new DataRecord( @@ -272,7 +276,7 @@ public async Task SaveInProgress_WhenLocalCacheIsSetButExpired_ShouldRemoveFromC now.AddSeconds(-3).ToUnixTimeSeconds(), null, null) ); - + // Act await persistenceStore.SaveInProgress(JsonSerializer.SerializeToDocument(request)!, now, null); @@ -282,22 +286,22 @@ public async Task SaveInProgress_WhenLocalCacheIsSetButExpired_ShouldRemoveFromC cache.Count.Should().Be(0); persistenceStore.Status.Should().Be(1); } - + ////// Save Success - + [Fact] - public async Task SaveSuccess_WhenDefaultConfig_ShouldUpdateRecord() + public async Task SaveSuccess_WhenDefaultConfig_ShouldUpdateRecord() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - LRUCache cache = new (2); + LRUCache cache = new(2); persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null, cache); var product = new Product(34543, "product", 42); - + var now = DateTimeOffset.UtcNow; - + // Act await persistenceStore.SaveSuccess(JsonSerializer.SerializeToDocument(request)!, product, now); @@ -305,75 +309,74 @@ public async Task SaveSuccess_WhenDefaultConfig_ShouldUpdateRecord() var dr = persistenceStore.DataRecord; dr.Status.Should().Be(DataRecord.DataRecordStatus.COMPLETED); dr.ExpiryTimestamp.Should().Be(now.AddSeconds(3600).ToUnixTimeSeconds()); - dr.ResponseData.Should().Be(JsonSerializer.Serialize(product)); + dr.ResponseData.Should().Be(IdempotencySerializer.Serialize(product, typeof(Product))); dr.IdempotencyKey.Should().Be("testFunction#5eff007a9ed2789a9f9f6bc182fc6ae6"); dr.PayloadHash.Should().BeEmpty(); persistenceStore.Status.Should().Be(2); cache.Count.Should().Be(0); } - + [Fact] public async Task SaveSuccess_WhenCacheEnabled_ShouldSaveInCache() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - LRUCache cache = new (2); - + LRUCache cache = new(2); + persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithUseLocalCache(true).Build(), null, cache); var product = new Product(34543, "product", 42); var now = DateTimeOffset.UtcNow; - + // Act await persistenceStore.SaveSuccess(JsonSerializer.SerializeToDocument(request)!, product, now); // Assert persistenceStore.Status.Should().Be(2); cache.Count.Should().Be(1); - + var foundDataRecord = cache.TryGet("testFunction#5eff007a9ed2789a9f9f6bc182fc6ae6", out var record); foundDataRecord.Should().BeTrue(); record.Status.Should().Be(DataRecord.DataRecordStatus.COMPLETED); record.ExpiryTimestamp.Should().Be(now.AddSeconds(3600).ToUnixTimeSeconds()); - record.ResponseData.Should().Be(JsonSerializer.Serialize(product)); + record.ResponseData.Should().Be(IdempotencySerializer.Serialize(product, typeof(Product))); record.IdempotencyKey.Should().Be("testFunction#5eff007a9ed2789a9f9f6bc182fc6ae6"); record.PayloadHash.Should().BeEmpty(); } - + /// Get Record - [Fact] - public async Task GetRecord_WhenRecordIsInStore_ShouldReturnRecordFromPersistence() + public async Task GetRecord_WhenRecordIsInStore_ShouldReturnRecordFromPersistence() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - + LRUCache cache = new(2); persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), "myfunc", cache); var now = DateTimeOffset.UtcNow; - + // Act var record = await persistenceStore.GetRecord(JsonSerializer.SerializeToDocument(request)!, now); - + // Assert record.IdempotencyKey.Should().Be("testFunction.myfunc#5eff007a9ed2789a9f9f6bc182fc6ae6"); record.Status.Should().Be(DataRecord.DataRecordStatus.INPROGRESS); record.ResponseData.Should().Be("Response"); persistenceStore.Status.Should().Be(0); } - + [Fact] - public async Task GetRecord_WhenCacheEnabledNotExpired_ShouldReturnRecordFromCache() + public async Task GetRecord_WhenCacheEnabledNotExpired_ShouldReturnRecordFromCache() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); LRUCache cache = new(2); - + persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithUseLocalCache(true).Build(), "myfunc", cache); @@ -388,16 +391,16 @@ public async Task GetRecord_WhenCacheEnabledNotExpired_ShouldReturnRecordFromCac // Act var record = await persistenceStore.GetRecord(JsonSerializer.SerializeToDocument(request)!, now); - + // Assert record.IdempotencyKey.Should().Be("testFunction.myfunc#5eff007a9ed2789a9f9f6bc182fc6ae6"); record.Status.Should().Be(DataRecord.DataRecordStatus.COMPLETED); record.ResponseData.Should().Be("result of the function"); persistenceStore.Status.Should().Be(-1); } - + [Fact] - public async Task GetRecord_WhenLocalCacheEnabledButRecordExpired_ShouldReturnRecordFromPersistence() + public async Task GetRecord_WhenLocalCacheEnabledButRecordExpired_ShouldReturnRecordFromPersistence() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); @@ -417,7 +420,7 @@ public async Task GetRecord_WhenLocalCacheEnabledButRecordExpired_ShouldReturnRe // Act var record = await persistenceStore.GetRecord(JsonSerializer.SerializeToDocument(request)!, now); - + // Assert record.IdempotencyKey.Should().Be("testFunction.myfunc#5eff007a9ed2789a9f9f6bc182fc6ae6"); record.Status.Should().Be(DataRecord.DataRecordStatus.INPROGRESS); @@ -425,86 +428,86 @@ public async Task GetRecord_WhenLocalCacheEnabledButRecordExpired_ShouldReturnRe persistenceStore.Status.Should().Be(0); cache.Count.Should().Be(0); } - + [Fact] public async Task GetRecord_WhenInvalidPayload_ShouldThrowValidationException() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - + persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithEventKeyJmesPath("powertools_json(Body).id") .WithPayloadValidationJmesPath("powertools_json(Body).message") .Build(), "myfunc"); - + var now = DateTimeOffset.UtcNow; - + // Act Func act = () => persistenceStore.GetRecord(JsonSerializer.SerializeToDocument(request)!, now); - + // Assert await act.Should().ThrowAsync(); } - + // Delete Record [Fact] - public async Task DeleteRecord_WhenRecordExist_ShouldDeleteRecordFromPersistence() + public async Task DeleteRecord_WhenRecordExist_ShouldDeleteRecordFromPersistence() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - + persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null); // Act await persistenceStore.DeleteRecord(JsonSerializer.SerializeToDocument(request)!, new ArithmeticException()); - + // Assert persistenceStore.Status.Should().Be(3); } - + [Fact] - public async Task DeleteRecord_WhenLocalCacheEnabled_ShouldDeleteRecordFromCache() + public async Task DeleteRecord_WhenLocalCacheEnabled_ShouldDeleteRecordFromCache() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - LRUCache cache = new (2); + LRUCache cache = new(2); persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithUseLocalCache(true).Build(), null, cache); cache.Set("testFunction#5eff007a9ed2789a9f9f6bc182fc6ae6", - new DataRecord("testFunction#5eff007a9ed2789a9f9f6bc182fc6ae6", + new DataRecord("testFunction#5eff007a9ed2789a9f9f6bc182fc6ae6", DataRecord.DataRecordStatus.COMPLETED, 123, null, null)); - + // Act await persistenceStore.DeleteRecord(JsonSerializer.SerializeToDocument(request)!, new ArithmeticException()); - + // Assert persistenceStore.Status.Should().Be(3); - cache.Count.Should().Be(0); + cache.Count.Should().Be(0); } - + [Fact] - public void GenerateHash_WhenInputIsString_ShouldGenerateMd5ofString() + public void GenerateHash_WhenInputIsString_ShouldGenerateMd5ofString() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null); var expectedHash = "70c24d88041893f7fbab4105b76fd9e1"; // MD5(Lambda rocks) - + // Act var jsonValue = JsonValue.Create("Lambda rocks"); var generatedHash = persistenceStore.GenerateHash(JsonDocument.Parse(jsonValue!.ToJsonString()).RootElement); - + // Assert generatedHash.Should().Be(expectedHash); } - + [Fact] public void GenerateHash_WhenInputIsObject_ShouldGenerateMd5ofJsonObject() { @@ -513,41 +516,39 @@ public void GenerateHash_WhenInputIsObject_ShouldGenerateMd5ofJsonObject() persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null); var product = new Product(42, "Product", 12); var expectedHash = "c83e720b399b3b4898c8734af177c53a"; // MD5({"Id":42,"Name":"Product","Price":12}) - + // Act var jsonValue = JsonValue.Create(product); var generatedHash = persistenceStore.GenerateHash(JsonDocument.Parse(jsonValue!.ToJsonString()).RootElement); - + // Assert generatedHash.Should().Be(expectedHash); } [Fact] - public void GenerateHash_WhenInputIsDouble_ShouldGenerateMd5ofDouble() + public void GenerateHash_WhenInputIsDouble_ShouldGenerateMd5ofDouble() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null); var expectedHash = "bb84c94278119c8838649706df4db42b"; // MD5(256.42) - + // Act var generatedHash = persistenceStore.GenerateHash(JsonDocument.Parse("256.42").RootElement); - + // Assert generatedHash.Should().Be(expectedHash); } - + private static APIGatewayProxyRequest LoadApiGatewayProxyRequest() { - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; - var eventJson = File.ReadAllText("./resources/apigw_event.json"); try { - var request = JsonSerializer.Deserialize(eventJson, options); +#if NET8_0_OR_GREATER + IdempotencySerializer.AddTypeInfoResolver(TestJsonSerializerContext.Default); +#endif + var request = IdempotencySerializer.Deserialize(eventJson); return request!; } catch (Exception e) diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/TestSetup.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/TestSetup.cs new file mode 100644 index 00000000..26f0e213 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/TestSetup.cs @@ -0,0 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file From 2c3b7ba6960eed910de491bdad34b84ea53c915b Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:12:26 +0100 Subject: [PATCH 03/41] fix failing tests --- .../IdempotencyTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs index 31530d33..8e85d616 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs @@ -60,9 +60,9 @@ public async Task EndToEndTest() function.HandlerExecuted.Should().BeFalse(); IdempotencySerializer.Serialize(response, typeof(APIGatewayProxyResponse)).Should() - .Be( - "{\"statusCode\":200,\"headers\":{\"Content-Type\":\"application/json\",\"Access-Control-Allow-Origin\":\"*\",\"Access-Control-Allow-Methods\":\"GET, OPTIONS\",\"Access-Control-Allow-Headers\":\"*\"},\"multiValueHeaders\":null,\"body\":\"{ \\u0022message\\u0022: \\u0022hello world\\u0022, \\u0022location\\u0022: \\u002295.92.53.22\\n\\u0022 }\",\"isBase64Encoded\":false}"); - + .Be(IdempotencySerializer.Serialize(response2, typeof(APIGatewayProxyResponse))); + + response.Body.Should().Contain("hello world"); response2.Body.Should().Contain("hello world"); var scanResponse = await _client.ScanAsync(new ScanRequest From a4606e83165d6cc77962b2e857ddbcb259cd8af7 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:56:42 +0100 Subject: [PATCH 04/41] add tests --- .../Internal/IdempotencySerializerTests.cs | 89 +++++++++++++++++++ .../Model/TestClass.cs | 7 ++ .../Model/TestJsonSerializerContext.cs | 1 + 3 files changed, 97 insertions(+) create mode 100644 libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs create mode 100644 libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Model/TestClass.cs diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs new file mode 100644 index 00000000..f9903aab --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs @@ -0,0 +1,89 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using AWS.Lambda.Powertools.Idempotency.Internal.Serializers; +using AWS.Lambda.Powertools.Idempotency.Tests.Model; +using NSubstitute; +using Xunit; + +namespace AWS.Lambda.Powertools.Idempotency.Tests.Internal; + +public class IdempotencySerializerTests +{ + public IdempotencySerializerTests() + { +#if NET8_0_OR_GREATER + IdempotencySerializer.AddTypeInfoResolver(TestJsonSerializerContext.Default); +#endif + } + + [Fact] + public void Serialize_ValidObject_ReturnsJsonString() + { + // Arrange + var testObject = new TestClass { Id = 1, Name = "Test" }; + + // Act + var result = IdempotencySerializer.Serialize(testObject, typeof(TestClass)); + + // Assert + Assert.Contains("\"Id\":1", result); + Assert.Contains("\"Name\":\"Test\"", result); + } + + [Fact] + public void Deserialize_ValidJsonString_ReturnsObject() + { + // Arrange + var json = "{\"Id\":1,\"Name\":\"Test\"}"; + + // Act + var result = IdempotencySerializer.Deserialize(json); + + // Assert + Assert.Equal(1, result.Id); + Assert.Equal("Test", result.Name); + } + +#if NET8_0_OR_GREATER + + [Fact] + public void GetTypeInfo_UnknownType_ThrowsException() + { + // Arrange + var mockResolver = Substitute.For(); + mockResolver.GetTypeInfo(typeof(TestClass), Arg.Any()) + .Returns((JsonTypeInfo)null); + + var options = new JsonSerializerOptions(); + options.TypeInfoResolver = mockResolver; + + // Use reflection to set the private _jsonOptions field + var field = typeof(IdempotencySerializer).GetField("_jsonOptions", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + field!.SetValue(null, options); + + // Act & Assert + var exception = Assert.Throws(() => IdempotencySerializer.GetTypeInfo(typeof(TestClass))); + Assert.Equal("Type AWS.Lambda.Powertools.Idempotency.Tests.Model.TestClass is not known to the serializer. Ensure it's included in the JsonSerializerContext.", exception.Message); + } + + [Fact] + public void AddTypeInfoResolver_AddsResolverToChain() + { + // Arrange + var mockContext = new TestJsonSerializerContext(); + + // Act + IdempotencySerializer.AddTypeInfoResolver(mockContext); + + // Assert + // Use reflection to get the private _jsonOptions field + var field = typeof(IdempotencySerializer).GetField("_jsonOptions", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + var options = (JsonSerializerOptions)field!.GetValue(null); + + Assert.Contains(mockContext, options!.TypeInfoResolverChain); + } +#endif +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Model/TestClass.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Model/TestClass.cs new file mode 100644 index 00000000..9f74a2a7 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Model/TestClass.cs @@ -0,0 +1,7 @@ +namespace AWS.Lambda.Powertools.Idempotency.Tests.Model; + +public class TestClass +{ + public int Id { get; set; } + public string Name { get; set; } +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Model/TestJsonSerializerContext.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Model/TestJsonSerializerContext.cs index c9c1d262..600c5be1 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Model/TestJsonSerializerContext.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Model/TestJsonSerializerContext.cs @@ -22,6 +22,7 @@ namespace AWS.Lambda.Powertools.Idempotency.Tests.Model; [JsonSerializable(typeof(APIGatewayProxyRequest))] [JsonSerializable(typeof(APIGatewayProxyResponse))] [JsonSerializable(typeof(Product))] +[JsonSerializable(typeof(TestClass))] public partial class TestJsonSerializerContext : JsonSerializerContext { From d6f7b43a09b5d625088859cb75e0805270021586 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:21:11 +0100 Subject: [PATCH 05/41] tackle sonar rules --- .../Internal/Serializers/IdempotencySerializer.cs | 3 ++- .../Serializers/JMESPathSerializationContext.cs | 2 +- .../Serializers/JMESPathSerializer.cs | 8 ++++---- .../Values/DecimalValue.cs | 2 +- .../Values/DoubleValue.cs | 2 +- .../Values/JsonElementValue.cs | 11 ++--------- .../Values/ObjectValue.cs | 2 +- .../Values/StringValue.cs | 2 +- .../Internal/IdempotencySerializerTests.cs | 3 ++- 9 files changed, 15 insertions(+), 20 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs index 247c77de..53aeaa7d 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs @@ -14,6 +14,7 @@ */ using System; +using System.Runtime.Serialization; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; @@ -71,7 +72,7 @@ internal static JsonTypeInfo GetTypeInfo(Type type) var typeInfo = _jsonOptions.TypeInfoResolver?.GetTypeInfo(type, _jsonOptions); if (typeInfo == null) { - throw new Exception( + throw new SerializationException( $"Type {type} is not known to the serializer. Ensure it's included in the JsonSerializerContext."); } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs index e031916e..96611cde 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs @@ -29,7 +29,7 @@ namespace AWS.Lambda.Powertools.JMESPath.Serializers; [JsonSerializable(typeof(decimal))] [JsonSerializable(typeof(double))] [JsonSerializable(typeof(JsonElement))] -public partial class JMESPathSerializationContext : JsonSerializerContext +public partial class JmesPathSerializationContext : JsonSerializerContext { } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs index 8e716125..b599145f 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs @@ -19,9 +19,9 @@ namespace AWS.Lambda.Powertools.JMESPath.Serializers; /// -/// Class JMESPathSerializer. +/// Class used to serialize JMESPath types /// -internal static class JMESPathSerializer +internal static class JmesPathSerializer { /// /// Serializes the specified value. @@ -35,7 +35,7 @@ internal static string Serialize(object value, Type inputType) return JsonSerializer.Serialize(value); #else - return JsonSerializer.Serialize(value, inputType, JMESPathSerializationContext.Default); + return JsonSerializer.Serialize(value, inputType, JmesPathSerializationContext.Default); #endif } @@ -51,7 +51,7 @@ internal static T Deserialize(string value) return JsonSerializer.Deserialize(value); #else - return (T)JsonSerializer.Deserialize(value, typeof(T), JMESPathSerializationContext.Default); + return (T)JsonSerializer.Deserialize(value, typeof(T), JmesPathSerializationContext.Default); #endif } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs index 26a7d06c..563fe015 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs @@ -98,6 +98,6 @@ public IExpression GetExpression() public override string ToString() { - return JMESPathSerializer.Serialize(_value, typeof(decimal)); + return JmesPathSerializer.Serialize(_value, typeof(decimal)); } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs index 9dd037e5..0d999eb9 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs @@ -104,6 +104,6 @@ public IExpression GetExpression() public override string ToString() { - return JMESPathSerializer.Serialize(_value, typeof(double)); + return JmesPathSerializer.Serialize(_value, typeof(double)); } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs index f22f5bd6..0137a87f 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs @@ -204,13 +204,7 @@ public bool TryGetDouble(out double value) public bool TryGetProperty(string propertyName, out IValue property) { var r = _element.TryGetProperty(propertyName, out var prop); - - // property = prop.ValueKind == JsonValueKind.String && IsJsonValid(prop.GetString()) - // ? new JsonElementValue(JsonNode.Parse(prop.GetString() ?? string.Empty).Deserialize()) - // : new JsonElementValue(prop); - property = CreateJsonElementValue(prop); - return r; } @@ -219,8 +213,7 @@ JsonElementValue CreateJsonElementValue(JsonElement prop) if (prop.ValueKind == JsonValueKind.String && IsJsonValid(prop.GetString())) { var jsonString = prop.GetString() ?? string.Empty; - // var jsonNode = JsonNode.Parse(jsonString); - var jsonElement = JMESPathSerializer.Deserialize(jsonString); + var jsonElement = JmesPathSerializer.Deserialize(jsonString); return new JsonElementValue(jsonElement); } @@ -261,6 +254,6 @@ public IExpression GetExpression() public override string ToString() { - return JMESPathSerializer.Serialize(_element, typeof(JsonElement)); + return JmesPathSerializer.Serialize(_element, typeof(JsonElement)); } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs index 211abb78..cba85f1a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs @@ -163,7 +163,7 @@ public override string ToString() first = false; } - buffer.Append(JMESPathSerializer.Serialize(property.Key, property.Key.GetType())); + buffer.Append(JmesPathSerializer.Serialize(property.Key, property.Key.GetType())); buffer.Append(':'); buffer.Append(property.Value); } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs index 75bb4638..d9d1d721 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs @@ -90,6 +90,6 @@ public IExpression GetExpression() public override string ToString() { - return JMESPathSerializer.Serialize(_value, typeof(string)); + return JmesPathSerializer.Serialize(_value, typeof(string)); } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs index f9903aab..caa19108 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.Serialization; using System.Text.Json; using System.Text.Json.Serialization.Metadata; using AWS.Lambda.Powertools.Idempotency.Internal.Serializers; @@ -64,7 +65,7 @@ public void GetTypeInfo_UnknownType_ThrowsException() field!.SetValue(null, options); // Act & Assert - var exception = Assert.Throws(() => IdempotencySerializer.GetTypeInfo(typeof(TestClass))); + var exception = Assert.Throws(() => IdempotencySerializer.GetTypeInfo(typeof(TestClass))); Assert.Equal("Type AWS.Lambda.Powertools.Idempotency.Tests.Model.TestClass is not known to the serializer. Ensure it's included in the JsonSerializerContext.", exception.Message); } From 7f5bd665a219525ab6864900825a6604136509be Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Mon, 30 Sep 2024 11:21:41 +0100 Subject: [PATCH 06/41] update tests. Instance is now internal --- .../Idempotency.cs | 2 +- .../Internal/IdempotentAspectTests.cs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs index 0646e056..0685cb16 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs @@ -71,7 +71,7 @@ private void SetPersistenceStore(BasePersistenceStore persistenceStore) /// /// Holds the idempotency Instance: /// - public static Idempotency Instance { get; } = new(PowertoolsConfigurations.Instance); + internal static Idempotency Instance { get; } = new(PowertoolsConfigurations.Instance); /// /// Use this method to configure persistence layer (mandatory) and idempotency options (optional) diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs index 7a67a4d9..9a084271 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs @@ -128,6 +128,9 @@ public async Task Handle_WhenSecondCall_AndStatusInProgress_ShouldThrowIdempoten Idempotency.Configure(builder => builder .WithPersistenceStore(store) +#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +#endif .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); @@ -166,6 +169,9 @@ public async Task Idempotency.Configure(builder => builder .WithPersistenceStore(store) +#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +#endif .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); @@ -206,6 +212,9 @@ public async Task Handle_WhenThrowException_ShouldDeleteRecord_AndThrowFunctionE Idempotency.Configure(builder => builder .WithPersistenceStore(store) +#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +#endif .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); @@ -233,6 +242,9 @@ public async Task Handle_WhenIdempotencyDisabled_ShouldJustRunTheFunction(Type t Idempotency.Configure(builder => builder .WithPersistenceStore(store) +#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +#endif .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); @@ -348,6 +360,9 @@ public void Handle_WhenIdempotencyOnSubMethodAnnotated_AndKeyJMESPath_ShouldPutI Idempotency.Configure(builder => builder .WithPersistenceStore(store) +#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +#endif .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); From 1c2c997b81b17f3be6c4ee62f73b8ce5974f10ae Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Thu, 3 Oct 2024 13:13:19 +0100 Subject: [PATCH 07/41] check IsDynamicCodeSupported to avoid TypeInfo in non AOT --- .../Serializers/IdempotencySerializer.cs | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs index 53aeaa7d..5582b40e 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs @@ -1,12 +1,12 @@ /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at - * + * * http://aws.amazon.com/apache2.0 - * + * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing @@ -18,6 +18,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; +using AWS.Lambda.Powertools.Common.Utils; namespace AWS.Lambda.Powertools.Idempotency.Internal.Serializers; @@ -43,12 +44,15 @@ private static void BuildDefaultOptions() PropertyNameCaseInsensitive = true }; #if NET8_0_OR_GREATER - _jsonOptions.TypeInfoResolverChain.Add(IdempotencySerializationContext.Default); + if (!RuntimeFeatureWrapper.IsDynamicCodeSupported) + { + _jsonOptions.TypeInfoResolverChain.Add(IdempotencySerializationContext.Default); + } #endif } #if NET8_0_OR_GREATER - + /// /// Adds a JsonTypeInfoResolver to the JsonSerializerOptions. /// @@ -61,7 +65,7 @@ internal static void AddTypeInfoResolver(JsonSerializerContext context) BuildDefaultOptions(); _jsonOptions.TypeInfoResolverChain.Add(context); } - + /// /// Gets the JsonTypeInfo for a given type. /// @@ -91,6 +95,12 @@ internal static string Serialize(object value, Type inputType) #if NET6_0 return JsonSerializer.Serialize(value, _jsonOptions); #else + if (RuntimeFeatureWrapper.IsDynamicCodeSupported) + { +#pragma warning disable + return JsonSerializer.Serialize(value, _jsonOptions); + } + return JsonSerializer.Serialize(value, GetTypeInfo(inputType)); #endif } @@ -106,6 +116,12 @@ internal static T Deserialize(string value) #if NET6_0 return JsonSerializer.Deserialize(value,_jsonOptions); #else + if (RuntimeFeatureWrapper.IsDynamicCodeSupported) + { +#pragma warning disable + return JsonSerializer.Deserialize(value, _jsonOptions); + } + return (T)JsonSerializer.Deserialize(value, GetTypeInfo(typeof(T))); #endif } From e7f53aaf73d76dfaf72502f2de155e1a6e5aa2d9 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:03:02 +0100 Subject: [PATCH 08/41] ignore warnings --- .../Internal/Serializers/IdempotencySerializer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs index 5582b40e..7a430879 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs @@ -14,6 +14,7 @@ */ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; using System.Text.Json; using System.Text.Json.Serialization; @@ -97,8 +98,7 @@ internal static string Serialize(object value, Type inputType) #else if (RuntimeFeatureWrapper.IsDynamicCodeSupported) { -#pragma warning disable - return JsonSerializer.Serialize(value, _jsonOptions); + return JsonSerializer.Serialize(value, _jsonOptions.GetTypeInfo(inputType)); } return JsonSerializer.Serialize(value, GetTypeInfo(inputType)); @@ -111,6 +111,8 @@ internal static string Serialize(object value, Type inputType) /// The type of the object to deserialize to. /// The JSON string to deserialize. /// An object of type T represented by the JSON string. + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "False positive")] + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "False positive")] internal static T Deserialize(string value) { #if NET6_0 @@ -118,7 +120,6 @@ internal static T Deserialize(string value) #else if (RuntimeFeatureWrapper.IsDynamicCodeSupported) { -#pragma warning disable return JsonSerializer.Deserialize(value, _jsonOptions); } 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 09/41] 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 10/41] 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 11/41] 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 12/41] 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 13/41] 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 9438406e5fb9fbe770a9472fc191431bad788447 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 09:34:59 +0000 Subject: [PATCH 14/41] chore(deps): bump aws-actions/configure-aws-credentials Bumps [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) from 4.0.2 to 4.0.3. - [Release notes](https://github.com/aws-actions/configure-aws-credentials/releases) - [Changelog](https://github.com/aws-actions/configure-aws-credentials/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws-actions/configure-aws-credentials/compare/e3dd6a429d7300a6a4c196c26e071d42e0343502...4fc4975a852c8cd99761e2de1f4ba73402e44dd9) --- updated-dependencies: - dependency-name: aws-actions/configure-aws-credentials dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/dispatch_analytics.yml | 2 +- .github/workflows/docs.yml | 4 ++-- .github/workflows/e2e-tests.yml | 10 +++++----- .github/workflows/reusable_publish_docs.yml | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/dispatch_analytics.yml b/.github/workflows/dispatch_analytics.yml index a1b0f3bb..2b240cdd 100644 --- a/.github/workflows/dispatch_analytics.yml +++ b/.github/workflows/dispatch_analytics.yml @@ -30,7 +30,7 @@ jobs: environment: analytics steps: - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 + uses: aws-actions/configure-aws-credentials@4fc4975a852c8cd99761e2de1f4ba73402e44dd9 with: aws-region: eu-central-1 role-to-assume: ${{ secrets.AWS_ANALYTICS_ROLE_ARN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 870b10d1..b92804aa 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -35,7 +35,7 @@ jobs: - name: Build docs website run: make build-docs-website - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + uses: aws-actions/configure-aws-credentials@4fc4975a852c8cd99761e2de1f4ba73402e44dd9 # v4.0.3 with: aws-region: us-east-1 role-to-assume: ${{ secrets.AWS_DOCS_ROLE_ARN }} @@ -65,7 +65,7 @@ jobs: docfx apidocs/docfx.json - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + uses: aws-actions/configure-aws-credentials@4fc4975a852c8cd99761e2de1f4ba73402e44dd9 # v4.0.3 with: aws-region: us-east-1 role-to-assume: ${{ secrets.AWS_DOCS_ROLE_ARN }} diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 98ead3e5..50a8b552 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -33,7 +33,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + uses: aws-actions/configure-aws-credentials@4fc4975a852c8cd99761e2de1f4ba73402e44dd9 # v4.0.3 with: role-to-assume: ${{ secrets.E2E_DEPLOY_ROLE }} aws-region: us-east-1 @@ -70,7 +70,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + uses: aws-actions/configure-aws-credentials@4fc4975a852c8cd99761e2de1f4ba73402e44dd9 # v4.0.3 with: role-to-assume: ${{ secrets.E2E_DEPLOY_ROLE }} aws-region: us-east-1 @@ -100,7 +100,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + uses: aws-actions/configure-aws-credentials@4fc4975a852c8cd99761e2de1f4ba73402e44dd9 # v4.0.3 with: role-to-assume: ${{ secrets.E2E_DEPLOY_ROLE }} aws-region: us-east-1 @@ -130,7 +130,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + uses: aws-actions/configure-aws-credentials@4fc4975a852c8cd99761e2de1f4ba73402e44dd9 # v4.0.3 with: role-to-assume: ${{ secrets.E2E_DEPLOY_ROLE }} aws-region: us-east-1 @@ -164,7 +164,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + uses: aws-actions/configure-aws-credentials@4fc4975a852c8cd99761e2de1f4ba73402e44dd9 # v4.0.3 with: role-to-assume: ${{ secrets.E2E_DEPLOY_ROLE }} aws-region: us-east-1 diff --git a/.github/workflows/reusable_publish_docs.yml b/.github/workflows/reusable_publish_docs.yml index 0cfe197a..7a33c317 100644 --- a/.github/workflows/reusable_publish_docs.yml +++ b/.github/workflows/reusable_publish_docs.yml @@ -67,7 +67,7 @@ jobs: poetry run mike set-default --push latest - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + uses: aws-actions/configure-aws-credentials@4fc4975a852c8cd99761e2de1f4ba73402e44dd9 # v4.0.3 with: aws-region: us-east-1 role-to-assume: ${{ secrets.AWS_DOCS_ROLE_ARN }} @@ -95,7 +95,7 @@ jobs: brew install -f docfx --skip-cask-deps --ignore-dependencies docfx apidocs/docfx.json - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + uses: aws-actions/configure-aws-credentials@4fc4975a852c8cd99761e2de1f4ba73402e44dd9 # v4.0.3 with: aws-region: us-east-1 role-to-assume: ${{ secrets.AWS_DOCS_ROLE_ARN }} From 03fc79aae7390b7f5ac0379f70aab0a7532fcc35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 09:35:03 +0000 Subject: [PATCH 15/41] chore(deps): bump actions/setup-python from 5.3.0 to 5.4.0 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.3.0 to 5.4.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/0b93645e9fea7318ecaed2b359559ac225c90a2b...42375524e23c412d93fb67b49958b491fce71c38) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/docs.yml | 2 +- .github/workflows/reusable_publish_docs.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 870b10d1..bd019ad0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,7 +24,7 @@ jobs: with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: "3.12" - name: Capture branch and tag diff --git a/.github/workflows/reusable_publish_docs.yml b/.github/workflows/reusable_publish_docs.yml index 0cfe197a..d988b70c 100644 --- a/.github/workflows/reusable_publish_docs.yml +++ b/.github/workflows/reusable_publish_docs.yml @@ -41,7 +41,7 @@ jobs: - name: Install poetry run: pipx install poetry - name: Set up Python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: "3.12" cache: "poetry" From e51837080b78dc9bce71afa7ec302ff6223bbd55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 09:35:09 +0000 Subject: [PATCH 16/41] chore(deps): bump github/codeql-action from 3.28.5 to 3.28.6 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.5 to 3.28.6. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4...17a820bf2e43b47be2c72b39cc905417bc1ab6d0) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4d9519b0..22bccad3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -32,14 +32,14 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 #v2 + uses: github/codeql-action/init@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 #v2 with: languages: ${{ matrix.language }} # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 #v2 + uses: github/codeql-action/autobuild@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 #v2 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -52,4 +52,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 #v2 + uses: github/codeql-action/analyze@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 #v2 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 17/41] 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 18/41] 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 19/41] 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 fad56168b4ca1609c58010392e8d081867e981ab Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:54:45 +0000 Subject: [PATCH 20/41] Update to dotnet8 docfx version is only compatible with dotnet 8 or later Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- .github/workflows/docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 870b10d1..fc6d3b7d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -54,10 +54,10 @@ jobs: environment: Docs steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Setup .NET 6.0 + - name: Setup .NET 8.0 uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # 4.2.0 with: - dotnet-version: 6.0.405 + dotnet-version: '8.x' - name: Build Api Docs run: | 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 21/41] 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 e5e94a69b703c2844784febf664594fb5ae5d83d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 09:54:36 +0000 Subject: [PATCH 22/41] chore(deps): bump actions/setup-dotnet from 4.2.0 to 4.3.0 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4.2.0 to 4.3.0. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/87b7050bc53ea08284295505d98d2aa94301e852...3951f0dfe7a07e2313ec93c75700083e2005cbab) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/e2e-tests.yml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e1be4609..cfc6045b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup .NET 6.0 & 8.0 - uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # 4.2.0 + uses: actions/setup-dotnet@3951f0dfe7a07e2313ec93c75700083e2005cbab # 4.3.0 with: dotnet-version: | 6.0.405 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b1ad9b3f..a0e6f418 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -55,7 +55,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup .NET 8.0 - uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # 4.2.0 + uses: actions/setup-dotnet@3951f0dfe7a07e2313ec93c75700083e2005cbab # 4.3.0 with: dotnet-version: '8.x' diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 50a8b552..d42e4ae2 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -40,7 +40,7 @@ jobs: mask-aws-account-id: true - name: Set up .NET - uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # 4.2.0 + uses: actions/setup-dotnet@3951f0dfe7a07e2313ec93c75700083e2005cbab # 4.3.0 with: dotnet-version: '8.x' @@ -77,7 +77,7 @@ jobs: mask-aws-account-id: true - name: Set up .NET - uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # 4.2.0 + uses: actions/setup-dotnet@3951f0dfe7a07e2313ec93c75700083e2005cbab # 4.3.0 with: dotnet-version: '8.x' @@ -107,7 +107,7 @@ jobs: mask-aws-account-id: true - name: Set up .NET - uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # 4.2.0 + uses: actions/setup-dotnet@3951f0dfe7a07e2313ec93c75700083e2005cbab # 4.3.0 with: dotnet-version: '8.x' From 5e5025568f1a30638ac09c3e78581f3cda4b1184 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 09:54:43 +0000 Subject: [PATCH 23/41] chore(deps): bump github/codeql-action from 3.28.6 to 3.28.8 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.6 to 3.28.8. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/17a820bf2e43b47be2c72b39cc905417bc1ab6d0...dd746615b3b9d728a6a37ca2045b68ca76d4841a) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 22bccad3..9e3023ae 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -32,14 +32,14 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 #v2 + uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a #v2 with: languages: ${{ matrix.language }} # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 #v2 + uses: github/codeql-action/autobuild@dd746615b3b9d728a6a37ca2045b68ca76d4841a #v2 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -52,4 +52,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 #v2 + uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a #v2 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 24/41] 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 25/41] 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 510bb0a6cbe5b4ee154c0e4e527e5144e48be156 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:03:27 +0000 Subject: [PATCH 26/41] chore(deps): bump squidfunk/mkdocs-material in /docs Bumps squidfunk/mkdocs-material from `41942f7` to `471695f`. --- updated-dependencies: - dependency-name: squidfunk/mkdocs-material dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docs/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index 5f90260a..64c9e4f6 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,3 +1,3 @@ # v9.1.18 -FROM squidfunk/mkdocs-material@sha256:41942f7a2f5163aacd0e866e076d95db4f26550b97d76c1594c04250cbb580e9 +FROM squidfunk/mkdocs-material@sha256:471695f3e611d9858788ac04e4daa9af961ccab73f1c0f545e90f8cc5d4268b8 RUN pip install mkdocs-git-revision-date-plugin From 668ded8f26af3124b26f4839dec8d7c446927944 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Fri, 31 Jan 2025 15:59:19 +0000 Subject: [PATCH 27/41] Sanitize SegmentName. Show accepted chars in the SegmentName. Add tests --- .../Internal/Helpers.cs | 23 +++++++++++++++++++ .../Internal/XRayRecorder.cs | 4 +++- .../TracingAttribute.cs | 5 ++-- .../Handlers/Handlers.cs | 12 ++++++++++ .../TracingAttributeTest.cs | 22 ++++++++++++++++++ 5 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 libraries/src/AWS.Lambda.Powertools.Tracing/Internal/Helpers.cs diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/Helpers.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/Helpers.cs new file mode 100644 index 00000000..22b53e4b --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/Helpers.cs @@ -0,0 +1,23 @@ +using System.Text.RegularExpressions; + +namespace AWS.Lambda.Powertools.Tracing.Internal; + +/// +/// Helper class +/// +public static class Helpers +{ + /// + /// Sanitize a string by removing any characters that are not alphanumeric, whitespace, or one of the following: _ . : / % & # = + - @ + /// + /// + /// + public static string SanitizeString(string input) + { + // Define a regular expression pattern to match allowed characters + var pattern = @"[^a-zA-Z0-9\s_\.\:/%&#=+\-@]"; + + // Replace any character that does not match the pattern with an empty string + return Regex.Replace(input, pattern, string.Empty); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs index e192036e..53bface3 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs @@ -78,7 +78,9 @@ public XRayRecorder(IAWSXRayRecorder awsxRayRecorder, IPowertoolsConfigurations public void BeginSubsegment(string name) { if (_isLambda) - _awsxRayRecorder.BeginSubsegment(name); + { + _awsxRayRecorder.BeginSubsegment(Helpers.SanitizeString(name)); + } } /// diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs index 1946fb9c..c144d038 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs @@ -15,7 +15,6 @@ using System; using AspectInjector.Broker; -using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Tracing.Internal; namespace AWS.Lambda.Powertools.Tracing; @@ -114,8 +113,10 @@ public class TracingAttribute : Attribute /// /// Set custom segment name for the operation. /// The default is '## {MethodName}'. + /// + /// The logical name of the service that handled the request, up to 200 characters. + /// Names can contain Unicode letters, numbers, and whitespace, and the following symbols: \_, ., :, /, %, &, #, =, +, \\, -, @ /// - /// The name of the segment. public string SegmentName { get; set; } = ""; /// diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs index 385ce96c..38210ac9 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs @@ -31,6 +31,18 @@ public void HandleWithSegmentName() } + [Tracing(SegmentName = "## <
$>g__Handler|0_0")] + public void HandleWithInvalidSegmentName() + { + MethodWithInvalidSegmentName(); + } + + [Tracing(SegmentName = "Inval$#id | ")] + private void MethodWithInvalidSegmentName() + { + + } + [Tracing(Namespace = "Namespace Defined")] public void HandleWithNamespace() { diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs index 58252d67..f02619c1 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs @@ -256,6 +256,28 @@ public void OnEntry_WhenSegmentNameHasValue_BeginSubsegmentWithValue() Assert.Single(segment.Subsegments); Assert.Equal("SegmentName", subSegment.Name); } + + [Fact] + public void OnEntry_WhenSegmentName_Is_Unsupported() + { + // Arrange + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + + // Act + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.HandleWithInvalidSegmentName(); + var subSegment = segment.Subsegments[0]; + var childSegment = subSegment.Subsegments[0]; + + // Assert + Assert.True(segment.IsSubsegmentsAdded); + Assert.True(subSegment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.Single(subSegment.Subsegments); + Assert.Equal("## Maing__Handler0_0", subSegment.Name); + Assert.Equal("Inval#id Segment", childSegment.Name); + } [Fact] public void OnEntry_WhenNamespaceIsNull_SetNamespaceWithService() From 284834c6e58455b4a93dfe6ab2dcd13327d42def Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Fri, 31 Jan 2025 16:22:26 +0000 Subject: [PATCH 28/41] fix sonar, add timeout to regex --- .../src/AWS.Lambda.Powertools.Tracing/Internal/Helpers.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/Helpers.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/Helpers.cs index 22b53e4b..f124852d 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/Helpers.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/Helpers.cs @@ -1,3 +1,4 @@ +using System; using System.Text.RegularExpressions; namespace AWS.Lambda.Powertools.Tracing.Internal; @@ -17,7 +18,7 @@ public static string SanitizeString(string input) // Define a regular expression pattern to match allowed characters var pattern = @"[^a-zA-Z0-9\s_\.\:/%&#=+\-@]"; - // Replace any character that does not match the pattern with an empty string - return Regex.Replace(input, pattern, string.Empty); + // Replace any character that does not match the pattern with an empty string, with a timeout + return Regex.Replace(input, pattern, string.Empty, RegexOptions.None, TimeSpan.FromMilliseconds(100)); } } \ No newline at end of file From 6ee0a4e36cfd6fea950b5ce74e894995672f0693 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Mon, 3 Feb 2025 10:44:09 +0000 Subject: [PATCH 29/41] update tracing to 1.6.1 Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index f94048ab..5f312bb8 100644 --- a/version.json +++ b/version.json @@ -2,7 +2,7 @@ "Core": { "Logging": "1.6.4", "Metrics": "1.8.0", - "Tracing": "1.6.0" + "Tracing": "1.6.1" }, "Utilities": { "Parameters": "1.3.0", 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 30/41] 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 From c450803166e3e7719765c5d2cea9e4ccf72fd643 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:11:12 +0000 Subject: [PATCH 31/41] chore(deps): bump squidfunk/mkdocs-material in /docs Bumps squidfunk/mkdocs-material from `471695f` to `7e841df`. --- updated-dependencies: - dependency-name: squidfunk/mkdocs-material dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docs/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index 64c9e4f6..3a3ed6f0 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,3 +1,3 @@ # v9.1.18 -FROM squidfunk/mkdocs-material@sha256:471695f3e611d9858788ac04e4daa9af961ccab73f1c0f545e90f8cc5d4268b8 +FROM squidfunk/mkdocs-material@sha256:7e841df1cfb6c8c4ff0968f2cfe55127fb1a2f5614e1c9bc23cbc11fe4c96644 RUN pip install mkdocs-git-revision-date-plugin From f36c7ab65d4c0368f8a730cf0d94c1375f771fcf Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:43:01 +0000 Subject: [PATCH 32/41] Add AOT support for Idempotency utility and related tests - Implemented AOT-Function with idempotency configuration and handler. - Created AOT-FunctionHandlerTest and AOT-FunctionPayloadSubsetTest projects. - Updated FunctionTests to include AOT-specific tests for idempotency. - Enhanced documentation to include AOT support details. --- docs/utilities/idempotency.md | 67 ++++++++++++++ libraries/AWS.Lambda.Powertools.sln | 58 +++++++++--- .../e2e/InfraShared/FunctionConstruct.cs | 2 +- .../e2e/InfraShared/FunctionConstructProps.cs | 2 +- .../tests/e2e/InfraShared/IdempotencyStack.cs | 33 ++++--- .../AOT-FunctionHandlerTest.csproj | 34 +++++++ .../Function.cs | 26 +++--- .../aws-lambda-tools-defaults.json | 0 .../AOT-FunctionMethodAttributeTest.csproj | 34 +++++++ .../Function.cs | 51 +++++++++++ .../aws-lambda-tools-defaults.json | 16 ++++ .../AOT-FunctionPayloadSubsetTest.csproj} | 6 +- .../AOT-FunctionPayloadSubsetTest/Function.cs | 45 ++++++++++ .../TestHelperAOT.cs | 41 +++++++++ .../aws-lambda-tools-defaults.json | 16 ++++ .../test/Function.Tests/Function.Tests.csproj | 1 - .../test/Function.Tests/FunctionTests.cs | 89 ++++++++++++++----- .../Function/test/Function.Tests/TestData.cs | 23 ----- 18 files changed, 452 insertions(+), 92 deletions(-) create mode 100644 libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionHandlerTest/AOT-FunctionHandlerTest.csproj rename libraries/tests/e2e/functions/idempotency/AOT-Function/src/{AOT-Function => AOT-FunctionHandlerTest}/Function.cs (55%) rename libraries/tests/e2e/functions/idempotency/AOT-Function/src/{AOT-Function => AOT-FunctionHandlerTest}/aws-lambda-tools-defaults.json (100%) create mode 100644 libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionMethodAttributeTest/AOT-FunctionMethodAttributeTest.csproj create mode 100644 libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionMethodAttributeTest/Function.cs create mode 100644 libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionMethodAttributeTest/aws-lambda-tools-defaults.json rename libraries/tests/e2e/functions/idempotency/AOT-Function/src/{AOT-Function/AOT-Function.csproj => AOT-FunctionPayloadSubsetTest/AOT-FunctionPayloadSubsetTest.csproj} (91%) create mode 100644 libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/Function.cs create mode 100644 libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/TestHelperAOT.cs create mode 100644 libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/aws-lambda-tools-defaults.json delete mode 100644 libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/TestData.cs diff --git a/docs/utilities/idempotency.md b/docs/utilities/idempotency.md index 897702c4..e6cefa3d 100644 --- a/docs/utilities/idempotency.md +++ b/docs/utilities/idempotency.md @@ -14,6 +14,7 @@ The idempotency utility provides a simple solution to convert your Lambda functi * Select a subset of the event as the idempotency key using [JMESPath](https://jmespath.org/) expressions * Set a time window in which records with the same payload should be considered duplicates * Expires in-progress executions if the Lambda function times out halfway through +* Ahead-of-Time compilation to native code support [AOT](https://docs.aws.amazon.com/lambda/latest/dg/dotnet-native-aot.html) from version 1.3.0 ## Terminology @@ -821,10 +822,76 @@ Data would then be stored in DynamoDB like this: | idempotency#MyLambdaFunction | 2b2cdb5f86361e97b4383087c1ffdf27 | 1636549571 | COMPLETED | {"id": 527212, "message": "success"} | | idempotency#MyLambdaFunction | f091d2527ad1c78f05d54cc3f363be80 | 1636549585 | IN_PROGRESS | | + +## AOT Support + +Native AOT trims your application code as part of the compilation to ensure that the binary is as small as possible. .NET 8 for Lambda provides improved trimming support compared to previous versions of .NET. + +### WithJsonSerializationContext() + +To use Idempotency utility with AOT support you first need to add `WithJsonSerializationContext()` to your `Idempotency` configuration. + +This ensures that when serializing your payload, the utility uses the correct serialization context. + +In the example below, we use the default `LambdaFunctionJsonSerializerContext`: + +```csharp +Idempotency.Configure(builder => +builder.WithJsonSerializationContext(LambdaFunctionJsonSerializerContext.Default))); + +``` + +Full example: + +```csharp hl_lines="8" +public static class Function +{ + private static async Task Main() + { + var tableName = Environment.GetEnvironmentVariable("IDEMPOTENCY_TABLE_NAME"); + Idempotency.Configure(builder => + builder + .WithJsonSerializationContext(LambdaFunctionJsonSerializerContext.Default) + .WithOptions(optionsBuilder => optionsBuilder + .WithExpiration(TimeSpan.FromHours(1))) + .UseDynamoDb(storeBuilder => storeBuilder + .WithTableName(tableName) + )); + + Func handler = FunctionHandler; + await LambdaBootstrapBuilder.Create(handler, + new SourceGeneratorLambdaJsonSerializer()) + .Build() + .RunAsync(); + } + + [Idempotent] + public static APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, + ILambdaContext context) + { + return new APIGatewayProxyResponse + { + Body = JsonSerializer.Serialize(response, typeof(Response), LambdaFunctionJsonSerializerContext.Default), + StatusCode = 200, + Headers = new Dictionary { { "Content-Type", "application/json" } } + }; + } +} + +[JsonSerializable(typeof(APIGatewayProxyRequest))] +[JsonSerializable(typeof(APIGatewayProxyResponse))] +[JsonSerializable(typeof(Response))] +public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext +{ +} +``` + ## Testing your code The idempotency utility provides several routes to test your code. +You can check our Integration tests which use [TestContainers](https://testcontainers.com/modules/dynamodb/){:target="_blank"} with a local DynamoDB instance to test the idempotency utility. Or our end-to-end tests which use the AWS SDK to interact with a real DynamoDB table. + ### Disabling the idempotency utility When testing your code, you may wish to disable the idempotency logic altogether and focus on testing your business logic. To do this, you can set the environment variable `POWERTOOLS_IDEMPOTENCY_DISABLED` to true. diff --git a/libraries/AWS.Lambda.Powertools.sln b/libraries/AWS.Lambda.Powertools.sln index ba9f2700..72aea967 100644 --- a/libraries/AWS.Lambda.Powertools.sln +++ b/libraries/AWS.Lambda.Powertools.sln @@ -91,7 +91,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Function", "tests\e2e\funct EndProject 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}") = "AOT-Function", "tests\e2e\functions\idempotency\AOT-Function\src\AOT-Function\AOT-Function.csproj", "{56DFC68A-3994-43CD-A17C-323495F1709C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-FunctionPayloadSubsetTest", "tests\e2e\functions\idempotency\AOT-Function\src\AOT-FunctionPayloadSubsetTest\AOT-FunctionPayloadSubsetTest.csproj", "{ACA789EA-BD38-490B-A7F8-6A3A86985025}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-FunctionHandlerTest", "tests\e2e\functions\idempotency\AOT-Function\src\AOT-FunctionHandlerTest\AOT-FunctionHandlerTest.csproj", "{E71C48D2-AD56-4177-BBD7-6BB859A40C92}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-FunctionMethodAttributeTest", "tests\e2e\functions\idempotency\AOT-Function\src\AOT-FunctionMethodAttributeTest\AOT-FunctionMethodAttributeTest.csproj", "{CC8CFF43-DC72-464C-A42D-55E023DE8500}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -478,18 +482,42 @@ Global {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 + {ACA789EA-BD38-490B-A7F8-6A3A86985025}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACA789EA-BD38-490B-A7F8-6A3A86985025}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACA789EA-BD38-490B-A7F8-6A3A86985025}.Debug|x64.ActiveCfg = Debug|Any CPU + {ACA789EA-BD38-490B-A7F8-6A3A86985025}.Debug|x64.Build.0 = Debug|Any CPU + {ACA789EA-BD38-490B-A7F8-6A3A86985025}.Debug|x86.ActiveCfg = Debug|Any CPU + {ACA789EA-BD38-490B-A7F8-6A3A86985025}.Debug|x86.Build.0 = Debug|Any CPU + {ACA789EA-BD38-490B-A7F8-6A3A86985025}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACA789EA-BD38-490B-A7F8-6A3A86985025}.Release|Any CPU.Build.0 = Release|Any CPU + {ACA789EA-BD38-490B-A7F8-6A3A86985025}.Release|x64.ActiveCfg = Release|Any CPU + {ACA789EA-BD38-490B-A7F8-6A3A86985025}.Release|x64.Build.0 = Release|Any CPU + {ACA789EA-BD38-490B-A7F8-6A3A86985025}.Release|x86.ActiveCfg = Release|Any CPU + {ACA789EA-BD38-490B-A7F8-6A3A86985025}.Release|x86.Build.0 = Release|Any CPU + {E71C48D2-AD56-4177-BBD7-6BB859A40C92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E71C48D2-AD56-4177-BBD7-6BB859A40C92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E71C48D2-AD56-4177-BBD7-6BB859A40C92}.Debug|x64.ActiveCfg = Debug|Any CPU + {E71C48D2-AD56-4177-BBD7-6BB859A40C92}.Debug|x64.Build.0 = Debug|Any CPU + {E71C48D2-AD56-4177-BBD7-6BB859A40C92}.Debug|x86.ActiveCfg = Debug|Any CPU + {E71C48D2-AD56-4177-BBD7-6BB859A40C92}.Debug|x86.Build.0 = Debug|Any CPU + {E71C48D2-AD56-4177-BBD7-6BB859A40C92}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E71C48D2-AD56-4177-BBD7-6BB859A40C92}.Release|Any CPU.Build.0 = Release|Any CPU + {E71C48D2-AD56-4177-BBD7-6BB859A40C92}.Release|x64.ActiveCfg = Release|Any CPU + {E71C48D2-AD56-4177-BBD7-6BB859A40C92}.Release|x64.Build.0 = Release|Any CPU + {E71C48D2-AD56-4177-BBD7-6BB859A40C92}.Release|x86.ActiveCfg = Release|Any CPU + {E71C48D2-AD56-4177-BBD7-6BB859A40C92}.Release|x86.Build.0 = Release|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Debug|x64.ActiveCfg = Debug|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Debug|x64.Build.0 = Debug|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Debug|x86.ActiveCfg = Debug|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Debug|x86.Build.0 = Debug|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Release|Any CPU.Build.0 = Release|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Release|x64.ActiveCfg = Release|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Release|x64.Build.0 = Release|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Release|x86.ActiveCfg = Release|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution @@ -532,6 +560,8 @@ Global {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} + {ACA789EA-BD38-490B-A7F8-6A3A86985025} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} + {E71C48D2-AD56-4177-BBD7-6BB859A40C92} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} + {CC8CFF43-DC72-464C-A42D-55E023DE8500} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} EndGlobalSection EndGlobal diff --git a/libraries/tests/e2e/InfraShared/FunctionConstruct.cs b/libraries/tests/e2e/InfraShared/FunctionConstruct.cs index 63fabbd3..6dfeb84b 100644 --- a/libraries/tests/e2e/InfraShared/FunctionConstruct.cs +++ b/libraries/tests/e2e/InfraShared/FunctionConstruct.cs @@ -23,7 +23,7 @@ public FunctionConstruct(Construct scope, string id, FunctionConstructProps prop Runtime = props.Runtime, Architecture = props.Architecture, FunctionName = props.Name, - Handler = props.Handler, + Handler = props.Handler!, Tracing = Tracing.ACTIVE, Timeout = Duration.Seconds(10), Environment = props.Environment, diff --git a/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs b/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs index 2ae564c5..a5d316b8 100644 --- a/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs +++ b/libraries/tests/e2e/InfraShared/FunctionConstructProps.cs @@ -8,7 +8,6 @@ public class FunctionConstructProps : PowertoolsDefaultStackProps public required Architecture Architecture { get; set; } public required Runtime Runtime { get; set; } public required string Name { get; set; } - public required string Handler { get; set; } public required string SourcePath { get; set; } public required string DistPath { get; set; } } @@ -18,6 +17,7 @@ public class PowertoolsDefaultStackProps : StackProps public bool IsAot { get; set; } = false; public string? ArchitectureString { get; set; } public Dictionary? Environment { get; set; } + public string? Handler { get; set; } } public class IdempotencyStackProps : PowertoolsDefaultStackProps diff --git a/libraries/tests/e2e/InfraShared/IdempotencyStack.cs b/libraries/tests/e2e/InfraShared/IdempotencyStack.cs index 5c66b252..1718e823 100644 --- a/libraries/tests/e2e/InfraShared/IdempotencyStack.cs +++ b/libraries/tests/e2e/InfraShared/IdempotencyStack.cs @@ -29,21 +29,28 @@ public IdempotencyStack(Construct scope, string id, IdempotencyStackProps props) if (props.IsAot) { - 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}", path, props); + var tests = new[] { "HandlerTest", "PayloadSubsetTest", "MethodAttributeTest" }; + + foreach (var test in tests) + { + var baseAotPath = $"../functions/{utility}/AOT-Function/src/AOT-Function{test}"; + var distAotPath = $"../functions/{utility}/AOT-Function/dist/AOT-Function{test}"; + var path = new Path(baseAotPath, distAotPath); + props.Handler = $"AOT-Function{test}"; + + 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__{test}", Runtime.DOTNET_8, architecture, + $"E2ETestLambda_{arch}_AOT_NET8_{utility}_{test}", path, props); + } } else { var basePath = $"../functions/{utility}/Function/src/Function"; var distPath = $"../functions/{utility}/Function/dist"; var path = new Path(basePath, distPath); - + props.Handler = "Function::Function.Function::FunctionHandler"; + CreateFunctionConstruct(this, $"{utility}_X64_net8", Runtime.DOTNET_8, Architecture.X86_64, $"E2ETestLambda_X64_NET8_{utility}", path, props); CreateFunctionConstruct(this, $"{utility}_arm_net8", Runtime.DOTNET_8, Architecture.ARM_64, @@ -56,14 +63,14 @@ public IdempotencyStack(Construct scope, string id, IdempotencyStackProps props) } private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime, Architecture architecture, - string name,Path path, PowertoolsDefaultStackProps props) + string name, Path path, PowertoolsDefaultStackProps props) { var lambdaFunction = new FunctionConstruct(scope, id, new FunctionConstructProps { Runtime = runtime, Architecture = architecture, Name = name, - Handler = props.IsAot ? "AOT-Function" : "Function::Function.Function::FunctionHandler", + Handler = props.Handler!, SourcePath = path.SourcePath, DistPath = path.DistPath, Environment = new Dictionary @@ -72,10 +79,10 @@ private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime }, IsAot = props.IsAot }); - + // Grant the Lambda function permissions to perform all actions on the DynamoDB table Table.GrantReadWriteData(lambdaFunction.Function); } } -public record Path(string SourcePath, string DistPath); +public record Path(string SourcePath, string DistPath); \ No newline at end of file diff --git a/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionHandlerTest/AOT-FunctionHandlerTest.csproj b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionHandlerTest/AOT-FunctionHandlerTest.csproj new file mode 100644 index 00000000..2d080cbc --- /dev/null +++ b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionHandlerTest/AOT-FunctionHandlerTest.csproj @@ -0,0 +1,34 @@ + + + Exe + net8.0 + enable + enable + Lambda + + true + + true + + true + + partial + AOT-Function + + + + + + + + + + + + + TestHelperAOT.cs + + + \ No newline at end of file diff --git a/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/Function.cs b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionHandlerTest/Function.cs similarity index 55% rename from libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/Function.cs rename to libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionHandlerTest/Function.cs index 3a4c9beb..3aea7eba 100644 --- a/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/Function.cs +++ b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionHandlerTest/Function.cs @@ -3,7 +3,7 @@ using System.Text.Json.Serialization; using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.Serialization.SystemTextJson; -using Helpers; +using AWS.Lambda.Powertools.Idempotency; namespace AOT_Function; @@ -11,6 +11,16 @@ public static class Function { private static async Task Main() { + var tableName = Environment.GetEnvironmentVariable("IDEMPOTENCY_TABLE_NAME"); + Idempotency.Configure(builder => + builder + .WithJsonSerializationContext(LambdaFunctionJsonSerializerContext.Default) + .WithOptions(optionsBuilder => optionsBuilder + .WithExpiration(TimeSpan.FromHours(1))) + .UseDynamoDb(storeBuilder => storeBuilder + .WithTableName(tableName) + )); + Func handler = FunctionHandler; await LambdaBootstrapBuilder.Create(handler, new SourceGeneratorLambdaJsonSerializer()) @@ -18,21 +28,17 @@ await LambdaBootstrapBuilder.Create(handler, .RunAsync(); } - public static APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) + [Idempotent] + public static APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, + ILambdaContext context) { - TestHelper.TestMethod(apigwProxyEvent); - - return new APIGatewayProxyResponse() - { - StatusCode = 200, - Body = apigwProxyEvent.Body.ToUpper() - }; + return TestHelperAot.TestMethod(apigwProxyEvent); } } [JsonSerializable(typeof(APIGatewayProxyRequest))] [JsonSerializable(typeof(APIGatewayProxyResponse))] +[JsonSerializable(typeof(Response))] public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext { - } \ No newline at end of file diff --git a/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/aws-lambda-tools-defaults.json b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionHandlerTest/aws-lambda-tools-defaults.json similarity index 100% rename from libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/aws-lambda-tools-defaults.json rename to libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionHandlerTest/aws-lambda-tools-defaults.json diff --git a/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionMethodAttributeTest/AOT-FunctionMethodAttributeTest.csproj b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionMethodAttributeTest/AOT-FunctionMethodAttributeTest.csproj new file mode 100644 index 00000000..2d080cbc --- /dev/null +++ b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionMethodAttributeTest/AOT-FunctionMethodAttributeTest.csproj @@ -0,0 +1,34 @@ + + + Exe + net8.0 + enable + enable + Lambda + + true + + true + + true + + partial + AOT-Function + + + + + + + + + + + + + TestHelperAOT.cs + + + \ No newline at end of file diff --git a/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionMethodAttributeTest/Function.cs b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionMethodAttributeTest/Function.cs new file mode 100644 index 00000000..5233c1f3 --- /dev/null +++ b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionMethodAttributeTest/Function.cs @@ -0,0 +1,51 @@ +using Amazon.Lambda.Core; +using Amazon.Lambda.RuntimeSupport; +using System.Text.Json.Serialization; +using Amazon.Lambda.APIGatewayEvents; +using Amazon.Lambda.Serialization.SystemTextJson; +using AWS.Lambda.Powertools.Idempotency; + +namespace AOT_Function; + +public static class Function +{ + private static async Task Main() + { + var tableName = Environment.GetEnvironmentVariable("IDEMPOTENCY_TABLE_NAME"); + Idempotency.Configure(builder => + builder + .WithJsonSerializationContext(LambdaFunctionJsonSerializerContext.Default) + .WithOptions(optionsBuilder => optionsBuilder + .WithExpiration(TimeSpan.FromHours(1))) + .UseDynamoDb(storeBuilder => storeBuilder + .WithTableName(tableName) + )); + + Func handler = FunctionHandler; + await LambdaBootstrapBuilder.Create(handler, + new SourceGeneratorLambdaJsonSerializer()) + .Build() + .RunAsync(); + } + + public static APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, + ILambdaContext context) + { + return new APIGatewayProxyResponse + { + Body = MyInternalMethod("dummy", apigwProxyEvent.RequestContext.RequestId), + StatusCode = 200 + }; + } + + [Idempotent] + private static string MyInternalMethod(string argOne, [IdempotencyKey] string argTwo) { + return Guid.NewGuid().ToString(); + } +} + +[JsonSerializable(typeof(APIGatewayProxyRequest))] +[JsonSerializable(typeof(APIGatewayProxyResponse))] +public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionMethodAttributeTest/aws-lambda-tools-defaults.json b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionMethodAttributeTest/aws-lambda-tools-defaults.json new file mode 100644 index 00000000..be3c7ec1 --- /dev/null +++ b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionMethodAttributeTest/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/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/AOT-FunctionPayloadSubsetTest.csproj similarity index 91% rename from libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj rename to libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/AOT-FunctionPayloadSubsetTest.csproj index b5c6ae5c..7896b34c 100644 --- a/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-Function/AOT-Function.csproj +++ b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/AOT-FunctionPayloadSubsetTest.csproj @@ -15,6 +15,7 @@ partial + AOT-Function @@ -22,11 +23,6 @@ - - - TestHelper.cs - - diff --git a/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/Function.cs b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/Function.cs new file mode 100644 index 00000000..189250f1 --- /dev/null +++ b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/Function.cs @@ -0,0 +1,45 @@ +using Amazon.Lambda.Core; +using Amazon.Lambda.RuntimeSupport; +using System.Text.Json.Serialization; +using Amazon.Lambda.APIGatewayEvents; +using Amazon.Lambda.Serialization.SystemTextJson; +using AWS.Lambda.Powertools.Idempotency; + +namespace AOT_Function; + +public static class Function +{ + private static async Task Main() + { + var tableName = Environment.GetEnvironmentVariable("IDEMPOTENCY_TABLE_NAME"); + Idempotency.Configure(builder => + builder + .WithJsonSerializationContext(LambdaFunctionJsonSerializerContext.Default) + .WithOptions(optionsBuilder => optionsBuilder + .WithEventKeyJmesPath("powertools_json(Body).[\"user_id\", \"product_id\"]") + .WithExpiration(TimeSpan.FromHours(1))) + .UseDynamoDb(storeBuilder => storeBuilder + .WithTableName(tableName) + )); + + Func handler = FunctionHandler; + await LambdaBootstrapBuilder.Create(handler, + new SourceGeneratorLambdaJsonSerializer()) + .Build() + .RunAsync(); + } + + [Idempotent] + public static APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, + ILambdaContext context) + { + return TestHelperAot.TestMethod(apigwProxyEvent); + } +} + +[JsonSerializable(typeof(APIGatewayProxyRequest))] +[JsonSerializable(typeof(APIGatewayProxyResponse))] +[JsonSerializable(typeof(Response))] +public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/TestHelperAOT.cs b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/TestHelperAOT.cs new file mode 100644 index 00000000..144c7158 --- /dev/null +++ b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/TestHelperAOT.cs @@ -0,0 +1,41 @@ +using System.Text.Json; +using Amazon.Lambda.APIGatewayEvents; + +namespace AOT_Function; + +public static class TestHelperAot +{ + public static APIGatewayProxyResponse TestMethod(APIGatewayProxyRequest apigwProxyEvent) + { + var response = new Response + { + Greeting = "Hello Powertools for AWS Lambda (.NET)", + Guid = Guid.NewGuid().ToString() // Guid generated in the Handler. used to compare Handler output + }; + + try + { + return new APIGatewayProxyResponse + { + Body = JsonSerializer.Serialize(response, typeof(Response), LambdaFunctionJsonSerializerContext.Default), + 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" } } + }; + } + } +} + +public class Response +{ + public string? Greeting { get; set; } + public string? Guid { get; set; } +} \ No newline at end of file diff --git a/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/aws-lambda-tools-defaults.json b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/aws-lambda-tools-defaults.json new file mode 100644 index 00000000..be3c7ec1 --- /dev/null +++ b/libraries/tests/e2e/functions/idempotency/AOT-Function/src/AOT-FunctionPayloadSubsetTest/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/idempotency/Function/test/Function.Tests/Function.Tests.csproj b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/Function.Tests.csproj index ed863946..b4c5f8d3 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 @@ -18,7 +18,6 @@ - \ 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 e1c4957d..2d89a36d 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 @@ -16,7 +16,6 @@ public class FunctionTests private readonly AmazonLambdaClient _lambdaClient; private readonly AmazonDynamoDBClient _dynamoDbClient; private string _tableName = null!; - private string _handler; public FunctionTests(ITestOutputHelper testOutputHelper) { @@ -25,22 +24,74 @@ public FunctionTests(ITestOutputHelper testOutputHelper) _dynamoDbClient = new AmazonDynamoDBClient(); } - // [Trait("Category", "AOT")] - // [Theory] - // [MemberData(nameof(TestDataAot.Inline), MemberType = typeof(TestDataAot))] - // public async Task IdempotencyHandlerAotTest(string functionName, string tableName) - // { - // _tableName = tableName; - // await TestIdempotencyHandler(functionName); - // } - + [Trait("Category", "AOT")] + [Theory] + [InlineData("E2ETestLambda_ARM_AOT_NET8_idempotency_HandlerTest", "IdempotencyTable-AOT-arm64")] + [InlineData("E2ETestLambda_X64_AOT_NET8_idempotency_HandlerTest", "IdempotencyTable-AOT-x86_64")] + public async Task IdempotencyHandlerAotTest(string functionName, string tableName) + { + _tableName = tableName; + await IdempotencyHandler(functionName); + } + + [Trait("Category", "AOT")] + [Theory] + [InlineData("E2ETestLambda_ARM_AOT_NET8_idempotency_MethodAttributeTest", "IdempotencyTable-AOT-arm64")] + [InlineData("E2ETestLambda_X64_AOT_NET8_idempotency_MethodTest", "IdempotencyTable-AOT-x86_64")] + public async Task IdempotencyAttributeAotTest(string functionName, string tableName) + { + _tableName = tableName; + await IdempotencyAttribute(functionName); + } + + [Trait("Category", "AOT")] [Theory] - [MemberData(nameof(TestData.Inline), MemberType = typeof(TestData))] - public async Task IdempotencyPayloadSubsetTest(string functionName, string tableName) + [InlineData("E2ETestLambda_ARM_AOT_NET8_idempotency_PayloadSubsetTest", "IdempotencyTable-AOT-arm64")] + [InlineData("E2ETestLambda_X64_AOT_NET8_idempotency_PayloadSubsetTest", "IdempotencyTable-AOT-x86_64")] + public async Task IdempotencyPayloadSubsetAotTest(string functionName, string tableName) { _tableName = tableName; + await IdempotencyPayloadSubset(functionName); + } + + [Theory] + [InlineData("E2ETestLambda_X64_NET8_idempotency")] + [InlineData("E2ETestLambda_ARM_NET8_idempotency")] + [InlineData("E2ETestLambda_X64_NET6_idempotency")] + [InlineData("E2ETestLambda_ARM_NET6_idempotency")] + public async Task IdempotencyPayloadSubsetTest(string functionName) + { + _tableName = "IdempotencyTable"; await UpdateFunctionHandler(functionName, "Function::IdempotencyPayloadSubsetTest.Function::FunctionHandler"); + await IdempotencyPayloadSubset(functionName); + } + + [Theory] + [InlineData("E2ETestLambda_X64_NET8_idempotency")] + [InlineData("E2ETestLambda_ARM_NET8_idempotency")] + [InlineData("E2ETestLambda_X64_NET6_idempotency")] + [InlineData("E2ETestLambda_ARM_NET6_idempotency")] + public async Task IdempotencyAttributeTest(string functionName) + { + _tableName = "IdempotencyTable"; + await UpdateFunctionHandler(functionName, "Function::IdempotencyAttributeTest.Function::FunctionHandler"); + await IdempotencyAttribute(functionName); + } + + [Theory] + [InlineData("E2ETestLambda_X64_NET8_idempotency")] + [InlineData("E2ETestLambda_ARM_NET8_idempotency")] + [InlineData("E2ETestLambda_X64_NET6_idempotency")] + [InlineData("E2ETestLambda_ARM_NET6_idempotency")] + public async Task IdempotencyHandlerTest(string functionName) + { + _tableName = "IdempotencyTable"; + await UpdateFunctionHandler(functionName, "Function::Function.Function::FunctionHandler"); + await IdempotencyHandler(functionName); + } + private async Task IdempotencyPayloadSubset(string functionName) + { // First unique request var firstProductId = Guid.NewGuid().ToString(); var (firstResponse1, firstGuid1) = await ExecutePayloadSubsetRequest(functionName, "xyz", firstProductId); @@ -69,13 +120,8 @@ await AssertDynamoDbData( secondGuid1); } - [Theory] - [MemberData(nameof(TestData.Inline), MemberType = typeof(TestData))] - public async Task IdempotencyAttributeTest(string functionName, string tableName) + private async Task IdempotencyAttribute(string functionName) { - _tableName = tableName; - await UpdateFunctionHandler(functionName, "Function::IdempotencyAttributeTest.Function::FunctionHandler"); - // First unique request var requestId1 = Guid.NewGuid().ToString(); var (firstResponse1, firstGuid1) = await ExecuteAttributeRequest(functionName, requestId1); @@ -106,13 +152,8 @@ await AssertDynamoDbData( true); } - [Theory] - [MemberData(nameof(TestData.Inline), MemberType = typeof(TestData))] - public async Task IdempotencyHandlerTest(string functionName, string tableName) + private async Task IdempotencyHandler(string functionName) { - _tableName = tableName; - await UpdateFunctionHandler(functionName, "Function::Function.Function::FunctionHandler"); - var payload = await File.ReadAllTextAsync("../../../../../../../payload.json"); // Execute three identical requests 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 deleted file mode 100644 index f4967b7d..00000000 --- a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/TestData.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 41fc2f505df19c0035a9dfebc0fb5a9307e7bb33 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:17:49 +0000 Subject: [PATCH 33/41] feat(tests): add unit tests for IdempotencySerializer and update JSON options handling --- .../Serializers/IdempotencySerializer.cs | 17 ++++- .../Handlers/IdempotencyFunction.cs | 3 - .../Internal/IdempotencySerializerTests.cs | 69 +++++++++++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs index 7a430879..823603ee 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs @@ -83,6 +83,11 @@ internal static JsonTypeInfo GetTypeInfo(Type type) return typeInfo; } + + internal static void SetJsonOptions(JsonSerializerOptions options) + { + _jsonOptions = options; + } #endif /// @@ -98,10 +103,18 @@ internal static string Serialize(object value, Type inputType) #else if (RuntimeFeatureWrapper.IsDynamicCodeSupported) { - return JsonSerializer.Serialize(value, _jsonOptions.GetTypeInfo(inputType)); +#pragma warning disable + return JsonSerializer.Serialize(value, _jsonOptions); + } + + var typeInfo = GetTypeInfo(inputType); + if (typeInfo == null) + { + throw new SerializationException( + $"Type {inputType} is not known to the serializer. Ensure it's included in the JsonSerializerContext."); } - return JsonSerializer.Serialize(value, GetTypeInfo(inputType)); + return JsonSerializer.Serialize(value, typeInfo); #endif } diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunction.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunction.cs index 6e546fee..32485b09 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunction.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunction.cs @@ -33,9 +33,6 @@ public IdempotencyFunction(AmazonDynamoDBClient client) { Idempotency.Configure(builder => builder -#if NET8_0_OR_GREATER - .WithJsonSerializationContext(TestJsonSerializerContext.Default) -#endif .WithOptions(optionsBuilder => optionsBuilder .WithEventKeyJmesPath("powertools_json(Body).address") diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs index caa19108..065c985c 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs @@ -2,6 +2,7 @@ using System.Runtime.Serialization; using System.Text.Json; using System.Text.Json.Serialization.Metadata; +using AWS.Lambda.Powertools.Common.Utils; using AWS.Lambda.Powertools.Idempotency.Internal.Serializers; using AWS.Lambda.Powertools.Idempotency.Tests.Model; using NSubstitute; @@ -45,6 +46,18 @@ public void Deserialize_ValidJsonString_ReturnsObject() Assert.Equal(1, result.Id); Assert.Equal("Test", result.Name); } + + [Fact] + public void BuildDefaultOptions_SetsCorrectProperties() + { + // Arrange & Act + var field = typeof(IdempotencySerializer).GetField("_jsonOptions", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + var options = (JsonSerializerOptions)field!.GetValue(null); + + // Assert + Assert.True(options.PropertyNameCaseInsensitive); + } #if NET8_0_OR_GREATER @@ -86,5 +99,61 @@ public void AddTypeInfoResolver_AddsResolverToChain() Assert.Contains(mockContext, options!.TypeInfoResolverChain); } + + [Fact] + public void Serialize_WhenDynamicCodeNotSupported_UsesDefaultTypeInfoResolver() + { + // Arrange + var testObject = new TestClass { Id = 1, Name = "Test" }; + RuntimeFeatureWrapper.SetIsDynamicCodeSupported(false); + + // Act + var result = IdempotencySerializer.Serialize(testObject, typeof(TestClass)); + + // Assert + Assert.Contains("\"Id\":1", result); + Assert.Contains("\"Name\":\"Test\"", result); + + // Reset + RuntimeFeatureWrapper.Reset(); + } + + [Fact] + public void Serialize_WhenDynamicCodeSupported_UsesTypeInfo() + { + // Arrange + var testObject = new TestClass { Id = 1, Name = "Test" }; + RuntimeFeatureWrapper.SetIsDynamicCodeSupported(true); + + // Act + var result = IdempotencySerializer.Serialize(testObject, typeof(TestClass)); + + // Assert + Assert.Contains("\"Id\":1", result); + Assert.Contains("\"Name\":\"Test\"", result); + + // Reset + RuntimeFeatureWrapper.Reset(); + } + + [Fact] + public void SetJsonOptions_UpdatesOptionsCorrectly() + { + // Arrange + var newOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = false + }; + + // Act + IdempotencySerializer.SetJsonOptions(newOptions); + + // Assert + var field = typeof(IdempotencySerializer).GetField("_jsonOptions", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + var options = (JsonSerializerOptions)field!.GetValue(null); + Assert.Same(newOptions, options); + } + #endif } \ No newline at end of file From af591437025055b568b53b69bc76c95a8752a05e Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:31:05 +0000 Subject: [PATCH 34/41] fix function name --- .../idempotency/Function/test/Function.Tests/FunctionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2d89a36d..f7eba28f 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 @@ -37,7 +37,7 @@ public async Task IdempotencyHandlerAotTest(string functionName, string tableNam [Trait("Category", "AOT")] [Theory] [InlineData("E2ETestLambda_ARM_AOT_NET8_idempotency_MethodAttributeTest", "IdempotencyTable-AOT-arm64")] - [InlineData("E2ETestLambda_X64_AOT_NET8_idempotency_MethodTest", "IdempotencyTable-AOT-x86_64")] + [InlineData("E2ETestLambda_X64_AOT_NET8_idempotency_MethodAttributeTest", "IdempotencyTable-AOT-x86_64")] public async Task IdempotencyAttributeAotTest(string functionName, string tableName) { _tableName = tableName; From 112f2dbf262caf565d27805c4b108a346e29387f Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:13:08 +0000 Subject: [PATCH 35/41] Update idempotency version Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 5f312bb8..d52ea67c 100644 --- a/version.json +++ b/version.json @@ -6,7 +6,7 @@ }, "Utilities": { "Parameters": "1.3.0", - "Idempotency": "1.2.2", + "Idempotency": "1.3.0", "BatchProcessing": "1.2.0" } } From ffda1ec5e27f3761c23f4739dd1f162364f73321 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Wed, 5 Feb 2025 18:40:35 +0000 Subject: [PATCH 36/41] feat(idempotency): add support for custom key prefixes in IdempotencyHandler and related tests --- .../IdempotentAttribute.cs | 9 +- .../Internal/IdempotencyAspectHandler.cs | 4 +- .../Persistence/BasePersistenceStore.cs | 93 +++++++++++-------- ...IdempotencyAttributeWithCustomKeyPrefix.cs | 21 +++++ .../IdempotencyFunctionMethodDecorated.cs | 5 - .../IdempotencyHandlerWithCustomKeyPrefix.cs | 16 ++++ .../Internal/IdempotentAspectTests.cs | 43 ++++++++- .../Persistence/BasePersistenceStoreTests.cs | 59 ++++++++---- .../DynamoDBPersistenceStoreTests.cs | 6 +- .../Function/src/Function/Function.cs | 18 ++++ .../test/Function.Tests/FunctionTests.cs | 18 +++- 11 files changed, 219 insertions(+), 73 deletions(-) create mode 100644 libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyAttributeWithCustomKeyPrefix.cs create mode 100644 libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyHandlerWithCustomKeyPrefix.cs diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotentAttribute.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotentAttribute.cs index 0204ee1a..fcd19f98 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotentAttribute.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotentAttribute.cs @@ -63,6 +63,11 @@ namespace AWS.Lambda.Powertools.Idempotency; [Injection(typeof(UniversalWrapperAspect), Inherited = true)] public class IdempotentAttribute : UniversalWrapperAttribute { + /// + /// Custom prefix for idempotency key: key_prefix#hash + /// + public string KeyPrefix { get; set; } + /// /// Wraps as a synchronous operation, simply throws IdempotencyConfigurationException /// @@ -90,7 +95,7 @@ protected internal sealed override T WrapSync(Func target, objec Task ResultDelegate() => Task.FromResult(target(args)); - var idempotencyHandler = new IdempotencyAspectHandler(ResultDelegate, eventArgs.Method.Name, payload,GetContext(eventArgs)); + var idempotencyHandler = new IdempotencyAspectHandler(ResultDelegate, eventArgs.Method.Name, KeyPrefix, payload,GetContext(eventArgs)); if (idempotencyHandler == null) { throw new Exception("Failed to create an instance of IdempotencyAspectHandler"); @@ -128,7 +133,7 @@ protected internal sealed override async Task WrapAsync( Task ResultDelegate() => target(args); - var idempotencyHandler = new IdempotencyAspectHandler(ResultDelegate, eventArgs.Method.Name, payload, GetContext(eventArgs)); + var idempotencyHandler = new IdempotencyAspectHandler(ResultDelegate, eventArgs.Method.Name, KeyPrefix, payload, GetContext(eventArgs)); if (idempotencyHandler == null) { throw new Exception("Failed to create an instance of IdempotencyAspectHandler"); diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs index ba1bf80a..a8d7da73 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs @@ -50,11 +50,13 @@ internal class IdempotencyAspectHandler /// /// /// + /// /// /// public IdempotencyAspectHandler( Func> target, string functionName, + string keyPrefix, JsonDocument payload, ILambdaContext lambdaContext) { @@ -62,7 +64,7 @@ public IdempotencyAspectHandler( _data = payload; _lambdaContext = lambdaContext; _persistenceStore = Idempotency.Instance.PersistenceStore; - _persistenceStore.Configure(Idempotency.Instance.IdempotencyOptions, functionName); + _persistenceStore.Configure(Idempotency.Instance.IdempotencyOptions, functionName, keyPrefix); } /// diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs index fc8604a2..3cf9b1f6 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs @@ -1,12 +1,12 @@ /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at - * + * * http://aws.amazon.com/apache2.0 - * + * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing @@ -37,16 +37,17 @@ public abstract class BasePersistenceStore : IPersistenceStore /// Idempotency Options /// private IdempotencyOptions _idempotencyOptions = null!; - + /// /// Function name /// private string _functionName; + /// /// Boolean to indicate whether or not payload validation is enabled /// protected bool PayloadValidationEnabled; - + /// /// LRUCache /// @@ -57,34 +58,45 @@ public abstract class BasePersistenceStore : IPersistenceStore ///
/// Idempotency configuration settings /// The name of the function being decorated - public void Configure(IdempotencyOptions idempotencyOptions, string functionName) + /// + public void Configure(IdempotencyOptions idempotencyOptions, string functionName, string keyPrefix) { - var funcEnv = Environment.GetEnvironmentVariable(Constants.LambdaFunctionNameEnv); - _functionName = funcEnv ?? "testFunction"; - if (!string.IsNullOrWhiteSpace(functionName)) + if (!string.IsNullOrEmpty(keyPrefix)) { - _functionName += "." + functionName; + _functionName = keyPrefix; } + else + { + var funcEnv = Environment.GetEnvironmentVariable(Constants.LambdaFunctionNameEnv); + + _functionName = funcEnv ?? "testFunction"; + if (!string.IsNullOrWhiteSpace(functionName)) + { + _functionName += "." + functionName; + } + } + _idempotencyOptions = idempotencyOptions; - + if (!string.IsNullOrWhiteSpace(_idempotencyOptions.PayloadValidationJmesPath)) { PayloadValidationEnabled = true; } - + var useLocalCache = _idempotencyOptions.UseLocalCache; if (useLocalCache) { _cache = new LRUCache(_idempotencyOptions.LocalCacheMaxItems); } } - + /// /// For test purpose only (adding a cache to mock) /// - internal void Configure(IdempotencyOptions options, string functionName, LRUCache cache) + internal void Configure(IdempotencyOptions options, string functionName, string keyPrefix, + LRUCache cache) { - Configure(options, functionName); + Configure(options, functionName, keyPrefix); _cache = cache; } @@ -118,12 +130,12 @@ public virtual async Task SaveSuccess(JsonDocument data, object result, DateTime public virtual async Task SaveInProgress(JsonDocument data, DateTimeOffset now, double? remainingTimeInMs) { var idempotencyKey = GetHashedIdempotencyKey(data); - + if (RetrieveFromCache(idempotencyKey, now) != null) { throw new IdempotencyItemAlreadyExistsException(); } - + long? inProgressExpirationMsTimestamp = null; if (remainingTimeInMs.HasValue) { @@ -137,11 +149,10 @@ public virtual async Task SaveInProgress(JsonDocument data, DateTimeOffset now, null, GetHashedPayload(data), inProgressExpirationMsTimestamp - ); await PutRecord(record, now); } - + /// /// Delete record from the persistence store /// @@ -152,14 +163,14 @@ public virtual async Task DeleteRecord(JsonDocument data, Exception throwable) var idemPotencyKey = GetHashedIdempotencyKey(data); Console.WriteLine("Function raised an exception {0}. " + - "Clearing in progress record in persistence store for idempotency key: {1}", + "Clearing in progress record in persistence store for idempotency key: {1}", throwable.GetType().Name, idemPotencyKey); await DeleteRecord(idemPotencyKey); DeleteFromCache(idemPotencyKey); } - + /// /// Retrieve idempotency key for data provided, fetch from persistence store, and convert to DataRecord. /// @@ -182,7 +193,7 @@ public virtual async Task GetRecord(JsonDocument data, DateTimeOffse ValidatePayload(data, record); return record; } - + /// /// Save data_record to local cache except when status is "INPROGRESS" /// NOTE: We can't cache "INPROGRESS" records as we have no way to reflect updates that can happen outside of the @@ -198,7 +209,7 @@ private void SaveToCache(DataRecord dataRecord) _cache.Set(dataRecord.IdempotencyKey, dataRecord); } - + /// /// Validate that the hashed payload matches data provided and stored data record /// @@ -215,7 +226,7 @@ private void ValidatePayload(JsonDocument data, DataRecord dataRecord) throw new IdempotencyValidationException("Payload does not match stored record for this event key"); } } - + /// /// Retrieve data record from cache /// @@ -228,14 +239,15 @@ private DataRecord RetrieveFromCache(string idempotencyKey, DateTimeOffset now) return null; if (!_cache.TryGet(idempotencyKey, out var record) || record == null) return null; - if (!record.IsExpired(now)) + if (!record.IsExpired(now)) { return record; } + DeleteFromCache(idempotencyKey); return null; } - + /// /// Deletes item from cache /// @@ -244,10 +256,10 @@ private void DeleteFromCache(string idempotencyKey) { if (!_idempotencyOptions.UseLocalCache) return; - + _cache.Delete(idempotencyKey); } - + /// /// Extract payload using validation key jmespath and return a hashed representation /// @@ -259,12 +271,12 @@ private string GetHashedPayload(JsonDocument data) { return ""; } - + var transformer = JsonTransformer.Parse(_idempotencyOptions.PayloadValidationJmesPath); var result = transformer.Transform(data.RootElement); return GenerateHash(result.RootElement); } - + /// /// Calculate unix timestamp of expiry date for idempotency record /// @@ -285,7 +297,7 @@ private string GetHashedIdempotencyKey(JsonDocument data) { var node = data.RootElement; var eventKeyJmesPath = _idempotencyOptions.EventKeyJmesPath; - if (eventKeyJmesPath != null) + if (eventKeyJmesPath != null) { var transformer = JsonTransformer.Parse(eventKeyJmesPath); var result = transformer.Transform(node); @@ -298,7 +310,9 @@ private string GetHashedIdempotencyKey(JsonDocument data) { throw new IdempotencyKeyException("No data found to create a hashed idempotency key"); } - Console.WriteLine("No data found to create a hashed idempotency key. JMESPath: {0}", _idempotencyOptions.EventKeyJmesPath ?? string.Empty); + + Console.WriteLine("No data found to create a hashed idempotency key. JMESPath: {0}", + _idempotencyOptions.EventKeyJmesPath ?? string.Empty); } var hash = GenerateHash(node); @@ -313,9 +327,10 @@ private string GetHashedIdempotencyKey(JsonDocument data) private static bool IsMissingIdempotencyKey(JsonElement data) { return data.ValueKind == JsonValueKind.Null || data.ValueKind == JsonValueKind.Undefined - || (data.ValueKind == JsonValueKind.String && data.ToString() == string.Empty); + || (data.ValueKind == JsonValueKind.String && + data.ToString() == string.Empty); } - + /// /// Generate a hash value from the provided data /// @@ -328,16 +343,16 @@ internal string GenerateHash(JsonElement data) // starting .NET 8 no option to change hash algorithm using var hashAlgorithm = MD5.Create(); #else - using var hashAlgorithm = HashAlgorithm.Create(_idempotencyOptions.HashFunction); #endif if (hashAlgorithm == null) { throw new ArgumentException("Invalid HashAlgorithm"); } + var stringToHash = data.ToString(); var hash = GetHash(hashAlgorithm, stringToHash); - + return hash; } @@ -351,18 +366,18 @@ private static string GetHash(HashAlgorithm hashAlgorithm, string input) { // Convert the input string to a byte array and compute the hash. var data = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(input)); - + // Create a new Stringbuilder to collect the bytes // and create a string. var sBuilder = new StringBuilder(); - + // Loop through each byte of the hashed data // and format each one as a hexadecimal string. for (var i = 0; i < data.Length; i++) { sBuilder.Append(data[i].ToString("x2")); } - + // Return the hexadecimal string. return sBuilder.ToString(); } diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyAttributeWithCustomKeyPrefix.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyAttributeWithCustomKeyPrefix.cs new file mode 100644 index 00000000..f773411e --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyAttributeWithCustomKeyPrefix.cs @@ -0,0 +1,21 @@ +using System; +using Amazon.Lambda.Core; + +namespace AWS.Lambda.Powertools.Idempotency.Tests.Handlers; + +/// +/// Simple Lambda function with Idempotent attribute on a sub method with a custom prefix key +/// +public class IdempotencyAttributeWithCustomKeyPrefix +{ + public string HandleRequest(string input, ILambdaContext context) + { + return ReturnGuid(input); + } + + [Idempotent(KeyPrefix = "MyMethod")] + private string ReturnGuid(string p) + { + return Guid.NewGuid().ToString(); + } +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunctionMethodDecorated.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunctionMethodDecorated.cs index ae4f0d0d..ed752060 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunctionMethodDecorated.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyFunctionMethodDecorated.cs @@ -13,7 +13,6 @@ * permissions and limitations under the License. */ -using System; using System.Collections.Generic; using System.IO; using System.Net.Http; @@ -22,7 +21,6 @@ using Amazon.DynamoDBv2; using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.Core; -using AWS.Lambda.Powertools.Idempotency.Tests.Model; namespace AWS.Lambda.Powertools.Idempotency.Tests.Handlers; @@ -34,9 +32,6 @@ public IdempotencyFunctionMethodDecorated(AmazonDynamoDBClient client) { Idempotency.Configure(builder => builder -#if NET8_0_OR_GREATER - .WithJsonSerializationContext(TestJsonSerializerContext.Default) -#endif .UseDynamoDb(storeBuilder => storeBuilder .WithTableName("idempotency_table") diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyHandlerWithCustomKeyPrefix.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyHandlerWithCustomKeyPrefix.cs new file mode 100644 index 00000000..8c384e65 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Handlers/IdempotencyHandlerWithCustomKeyPrefix.cs @@ -0,0 +1,16 @@ +using System; +using Amazon.Lambda.Core; + +namespace AWS.Lambda.Powertools.Idempotency.Tests.Handlers; + +/// +/// Simple Lambda function with Idempotent on handler with a custom prefix key +/// +public class IdempotencyHandlerWithCustomKeyPrefix +{ + [Idempotent(KeyPrefix = "MyHandler")] + public string HandleRequest(string input, ILambdaContext context) + { + return Guid.NewGuid().ToString(); + } +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs index 9a084271..f83cfe34 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs @@ -353,7 +353,7 @@ public void Handle_WhenIdempotencyOnSubMethodAnnotated_AndSecondCall_AndNotExpir } [Fact] - public void Handle_WhenIdempotencyOnSubMethodAnnotated_AndKeyJMESPath_ShouldPutInStoreWithKey() + public async Task Handle_WhenIdempotencyOnSubMethodAnnotated_AndKeyJMESPath_ShouldPutInStoreWithKey() { // Arrange var store = new InMemoryPersistenceStore(); @@ -373,7 +373,46 @@ public void Handle_WhenIdempotencyOnSubMethodAnnotated_AndKeyJMESPath_ShouldPutI // Assert // a1d0c6e83f027327d8461063f4ac58a6 = MD5(42) - store.GetRecord("testFunction.createBasket#a1d0c6e83f027327d8461063f4ac58a6").Should().NotBeNull(); + var record = await store.GetRecord("testFunction.CreateBasket#a1d0c6e83f027327d8461063f4ac58a6"); + Assert.NotNull(record); + } + + [Fact] + public async Task WhenIdempotency_Custom_Prefix_Key_Handler() + { + // Arrange + var store = new InMemoryPersistenceStore(); + Idempotency.Configure(builder => + builder + .WithPersistenceStore(store)); + + // Act + var function = new IdempotencyHandlerWithCustomKeyPrefix(); + function.HandleRequest("42", new TestLambdaContext()); + + // Assert + // a1d0c6e83f027327d8461063f4ac58a6 = MD5(42) + var record = await store.GetRecord("MyHandler#a1d0c6e83f027327d8461063f4ac58a6"); + Assert.NotNull(record); + } + + [Fact] + public async Task WhenIdempotency_Custom_Prefix_Key_Method() + { + // Arrange + var store = new InMemoryPersistenceStore(); + Idempotency.Configure(builder => + builder + .WithPersistenceStore(store)); + + // Act + var function = new IdempotencyAttributeWithCustomKeyPrefix(); + function.HandleRequest("42", new TestLambdaContext()); + + // Assert + // a1d0c6e83f027327d8461063f4ac58a6 = MD5(42) + var record = await store.GetRecord("MyMethod#a1d0c6e83f027327d8461063f4ac58a6"); + Assert.NotNull(record); } [Fact] diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs index 0b71f51b..0aed1440 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs @@ -78,7 +78,7 @@ public async Task SaveInProgress_WhenDefaultConfig_ShouldSaveRecordInStore() var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null); + persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null, null); var now = DateTimeOffset.UtcNow; @@ -102,7 +102,7 @@ public async Task SaveInProgress_WhenRemainingTime_ShouldSaveRecordInStore() var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null); + persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null, null); var now = DateTimeOffset.UtcNow; var lambdaTimeoutMs = 30000; @@ -130,7 +130,7 @@ public async Task SaveInProgress_WhenKeyJmesPathIsSet_ShouldSaveRecordInStore_Wi persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithEventKeyJmesPath("powertools_json(Body).id") - .Build(), "myfunc"); + .Build(), "myfunc", null); var now = DateTimeOffset.UtcNow; @@ -157,7 +157,7 @@ public async Task persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithEventKeyJmesPath("powertools_json(Body).[id, message]") //[43876123454654,"Lambda rocks"] - .Build(), "myfunc"); + .Build(), "myfunc", null); var now = DateTimeOffset.UtcNow; @@ -185,7 +185,7 @@ public async Task SaveInProgress_WhenJMESPath_NotFound_ShouldThrowException() persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithEventKeyJmesPath("unavailable") .WithThrowOnNoIdempotencyKey(true) // should throw - .Build(), ""); + .Build(), "", null); var now = DateTimeOffset.UtcNow; // Act @@ -209,7 +209,7 @@ public async Task SaveInProgress_WhenJMESpath_NotFound_ShouldNotThrowException() persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithEventKeyJmesPath("unavailable") - .Build(), ""); + .Build(), "", null); var now = DateTimeOffset.UtcNow; @@ -233,7 +233,7 @@ public async Task SaveInProgress_WhenLocalCacheIsSet_AndNotExpired_ShouldThrowEx persistenceStore.Configure(new IdempotencyOptionsBuilder() .WithUseLocalCache(true) .WithEventKeyJmesPath("powertools_json(Body).id") - .Build(), null, cache); + .Build(), null, null, cache); var now = DateTimeOffset.UtcNow; cache.Set("testFunction#2fef178cc82be5ce3da6c5e0466a6182", @@ -266,7 +266,7 @@ public async Task SaveInProgress_WhenLocalCacheIsSetButExpired_ShouldRemoveFromC .WithEventKeyJmesPath("powertools_json(Body).id") .WithUseLocalCache(true) .WithExpiration(TimeSpan.FromSeconds(2)) - .Build(), null, cache); + .Build(), null, null, cache); var now = DateTimeOffset.UtcNow; cache.Set("testFunction#2fef178cc82be5ce3da6c5e0466a6182", @@ -296,7 +296,7 @@ public async Task SaveSuccess_WhenDefaultConfig_ShouldUpdateRecord() var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); LRUCache cache = new(2); - persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null, cache); + persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null, null, cache); var product = new Product(34543, "product", 42); @@ -325,7 +325,7 @@ public async Task SaveSuccess_WhenCacheEnabled_ShouldSaveInCache() LRUCache cache = new(2); persistenceStore.Configure(new IdempotencyOptionsBuilder() - .WithUseLocalCache(true).Build(), null, cache); + .WithUseLocalCache(true).Build(), null, null, cache); var product = new Product(34543, "product", 42); var now = DateTimeOffset.UtcNow; @@ -355,7 +355,7 @@ public async Task GetRecord_WhenRecordIsInStore_ShouldReturnRecordFromPersistenc var request = LoadApiGatewayProxyRequest(); LRUCache cache = new(2); - persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), "myfunc", cache); + persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), "myfunc", null, cache); var now = DateTimeOffset.UtcNow; @@ -378,7 +378,7 @@ public async Task GetRecord_WhenCacheEnabledNotExpired_ShouldReturnRecordFromCac LRUCache cache = new(2); persistenceStore.Configure(new IdempotencyOptionsBuilder() - .WithUseLocalCache(true).Build(), "myfunc", cache); + .WithUseLocalCache(true).Build(), "myfunc", null, cache); var now = DateTimeOffset.UtcNow; var dr = new DataRecord( @@ -407,7 +407,7 @@ public async Task GetRecord_WhenLocalCacheEnabledButRecordExpired_ShouldReturnRe var request = LoadApiGatewayProxyRequest(); LRUCache cache = new(2); persistenceStore.Configure(new IdempotencyOptionsBuilder() - .WithUseLocalCache(true).Build(), "myfunc", cache); + .WithUseLocalCache(true).Build(), "myfunc", null, cache); var now = DateTimeOffset.UtcNow; var dr = new DataRecord( @@ -440,7 +440,7 @@ public async Task GetRecord_WhenInvalidPayload_ShouldThrowValidationException() .WithEventKeyJmesPath("powertools_json(Body).id") .WithPayloadValidationJmesPath("powertools_json(Body).message") .Build(), - "myfunc"); + "myfunc", null); var now = DateTimeOffset.UtcNow; @@ -459,7 +459,7 @@ public async Task DeleteRecord_WhenRecordExist_ShouldDeleteRecordFromPersistence var persistenceStore = new InMemoryPersistenceStore(); var request = LoadApiGatewayProxyRequest(); - persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null); + persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null, null); // Act await persistenceStore.DeleteRecord(JsonSerializer.SerializeToDocument(request)!, new ArithmeticException()); @@ -476,7 +476,7 @@ public async Task DeleteRecord_WhenLocalCacheEnabled_ShouldDeleteRecordFromCache var request = LoadApiGatewayProxyRequest(); LRUCache cache = new(2); persistenceStore.Configure(new IdempotencyOptionsBuilder() - .WithUseLocalCache(true).Build(), null, cache); + .WithUseLocalCache(true).Build(), null, null, cache); cache.Set("testFunction#5eff007a9ed2789a9f9f6bc182fc6ae6", new DataRecord("testFunction#5eff007a9ed2789a9f9f6bc182fc6ae6", @@ -497,7 +497,7 @@ public void GenerateHash_WhenInputIsString_ShouldGenerateMd5ofString() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); - persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null); + persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null, null); var expectedHash = "70c24d88041893f7fbab4105b76fd9e1"; // MD5(Lambda rocks) // Act @@ -513,7 +513,7 @@ public void GenerateHash_WhenInputIsObject_ShouldGenerateMd5ofJsonObject() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); - persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null); + persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null, null); var product = new Product(42, "Product", 12); var expectedHash = "c83e720b399b3b4898c8734af177c53a"; // MD5({"Id":42,"Name":"Product","Price":12}) @@ -530,7 +530,7 @@ public void GenerateHash_WhenInputIsDouble_ShouldGenerateMd5ofDouble() { // Arrange var persistenceStore = new InMemoryPersistenceStore(); - persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null); + persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), null, null); var expectedHash = "bb84c94278119c8838649706df4db42b"; // MD5(256.42) // Act @@ -539,6 +539,27 @@ public void GenerateHash_WhenInputIsDouble_ShouldGenerateMd5ofDouble() // Assert generatedHash.Should().Be(expectedHash); } + + [Fact] + public async Task When_Key_Prefix_Set_Should_Create_With_Prefix() + { + // Arrange + var persistenceStore = new InMemoryPersistenceStore(); + var request = LoadApiGatewayProxyRequest(); + + persistenceStore.Configure(new IdempotencyOptionsBuilder() + .WithEventKeyJmesPath("powertools_json(Body).id") + .Build(), "myfunc", "MyCustomPrefixKey"); + + var now = DateTimeOffset.UtcNow; + + // Act + await persistenceStore.SaveInProgress(JsonSerializer.SerializeToDocument(request)!, now, null); + + // Assert + var dr = persistenceStore.DataRecord; + dr.IdempotencyKey.Should().Be("MyCustomPrefixKey#2fef178cc82be5ce3da6c5e0466a6182"); + } private static APIGatewayProxyRequest LoadApiGatewayProxyRequest() { diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/DynamoDBPersistenceStoreTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/DynamoDBPersistenceStoreTests.cs index 957adc3f..6dc2fb84 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/DynamoDBPersistenceStoreTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/DynamoDBPersistenceStoreTests.cs @@ -42,7 +42,7 @@ public DynamoDbPersistenceStoreTests(DynamoDbFixture fixture) .WithTableName(_tableName) .WithDynamoDBClient(_client) .Build(); - _dynamoDbPersistenceStore.Configure(new IdempotencyOptionsBuilder().Build(),functionName: null); + _dynamoDbPersistenceStore.Configure(new IdempotencyOptionsBuilder().Build(),functionName: null, keyPrefix: null); } //putRecord @@ -281,7 +281,7 @@ await _client.PutItemAsync(new PutItemRequest }); // enable payload validation _dynamoDbPersistenceStore.Configure(new IdempotencyOptionsBuilder().WithPayloadValidationJmesPath("path").Build(), - null); + null, null); // Act expiry = now.AddSeconds(3600).ToUnixTimeSeconds(); @@ -367,7 +367,7 @@ public async Task EndToEndWithCustomAttrNamesAndSortKey() .WithStatusAttr("state") .WithValidationAttr("valid") .Build(); - persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(),functionName: null); + persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(),functionName: null, keyPrefix: null); var now = DateTimeOffset.UtcNow; var record = new DataRecord( 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 672ffdfb..389e414c 100644 --- a/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs +++ b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.cs @@ -71,3 +71,21 @@ public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxy } } } + +namespace CustomKeyPrefixTest +{ + public class Function + { + public Function() + { + var tableName = Environment.GetEnvironmentVariable("IDEMPOTENCY_TABLE_NAME"); + Idempotency.Configure(builder => builder.UseDynamoDb(tableName)); + } + + [Idempotent(KeyPrefix = "MyCustomKeyPrefix")] + public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) + { + return TestHelper.TestMethod(apigwProxyEvent); + } + } +} \ 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 f7eba28f..3f5c7cc7 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 @@ -89,6 +89,18 @@ public async Task IdempotencyHandlerTest(string functionName) await UpdateFunctionHandler(functionName, "Function::Function.Function::FunctionHandler"); await IdempotencyHandler(functionName); } + + [Theory] + [InlineData("E2ETestLambda_X64_NET8_idempotency")] + [InlineData("E2ETestLambda_ARM_NET8_idempotency")] + [InlineData("E2ETestLambda_X64_NET6_idempotency")] + [InlineData("E2ETestLambda_ARM_NET6_idempotency")] + public async Task IdempotencyHandlerCustomKey(string functionName) + { + _tableName = "IdempotencyTable"; + await UpdateFunctionHandler(functionName, "Function::CustomKeyPrefixTest.Function::FunctionHandler"); + await IdempotencyHandler(functionName, "MyCustomKeyPrefix"); + } private async Task IdempotencyPayloadSubset(string functionName) { @@ -152,7 +164,7 @@ await AssertDynamoDbData( true); } - private async Task IdempotencyHandler(string functionName) + private async Task IdempotencyHandler(string functionName, string? keyPrefix = null) { var payload = await File.ReadAllTextAsync("../../../../../../../payload.json"); @@ -170,9 +182,11 @@ private async Task IdempotencyHandler(string functionName) Assert.Equal(guid1, guid2); Assert.Equal(guid2, guid3); + var key = keyPrefix ?? $"{functionName}.FunctionHandler"; + // Assert DynamoDB await AssertDynamoDbData( - $"{functionName}.FunctionHandler#35973cf447e6cc11008d603c791a232f", + $"{key}#35973cf447e6cc11008d603c791a232f", guid1); } From ad6dd5a9eb13b28c564d81525c3d7db6e8011e73 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:07:02 +0000 Subject: [PATCH 37/41] add privacy plugin Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index d62a9ace..9dbf670d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -85,6 +85,7 @@ markdown_extensions: copyright: Copyright © 2024 Amazon Web Services plugins: + - privacy - git-revision-date - search From 181f7c1b45b97af672d00e90c2210839ff1b25c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Feb 2025 09:20:39 +0000 Subject: [PATCH 38/41] chore(deps): bump squidfunk/mkdocs-material in /docs Bumps squidfunk/mkdocs-material from `7e841df` to `c62453b`. --- updated-dependencies: - dependency-name: squidfunk/mkdocs-material dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docs/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index 3a3ed6f0..4f576cc6 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,3 +1,3 @@ # v9.1.18 -FROM squidfunk/mkdocs-material@sha256:7e841df1cfb6c8c4ff0968f2cfe55127fb1a2f5614e1c9bc23cbc11fe4c96644 +FROM squidfunk/mkdocs-material@sha256:c62453b1ba229982c6325a71165c1a3007c11bd3dd470e7a1446c5783bd145b4 RUN pip install mkdocs-git-revision-date-plugin From 72888478bbc6eca7a3d930986464389507889066 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 09:44:35 +0000 Subject: [PATCH 39/41] chore(deps): bump zgosalvez/github-actions-ensure-sha-pinned-actions Bumps [zgosalvez/github-actions-ensure-sha-pinned-actions](https://github.com/zgosalvez/github-actions-ensure-sha-pinned-actions) from 3.0.20 to 3.0.21. - [Release notes](https://github.com/zgosalvez/github-actions-ensure-sha-pinned-actions/releases) - [Commits](https://github.com/zgosalvez/github-actions-ensure-sha-pinned-actions/compare/c3a2b64f69b7a1542a68f44d9edbd9ec3fc1455e...6eb1abde32fed00453b0d03497f4ba4fecba146d) --- updated-dependencies: - dependency-name: zgosalvez/github-actions-ensure-sha-pinned-actions dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/secure_workflows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/secure_workflows.yml b/.github/workflows/secure_workflows.yml index 0b4941da..fff04f91 100644 --- a/.github/workflows/secure_workflows.yml +++ b/.github/workflows/secure_workflows.yml @@ -16,7 +16,7 @@ jobs: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Ensure 3rd party workflows have SHA pinned - uses: zgosalvez/github-actions-ensure-sha-pinned-actions@c3a2b64f69b7a1542a68f44d9edbd9ec3fc1455e # v3.0.20 + uses: zgosalvez/github-actions-ensure-sha-pinned-actions@6eb1abde32fed00453b0d03497f4ba4fecba146d # v3.0.21 with: # Trusted GitHub Actions and/or organizations allowlist: | From b77a3abea003bb70de98ab257fc063ac3e03f1f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 09:44:42 +0000 Subject: [PATCH 40/41] chore(deps): bump github/codeql-action from 3.28.8 to 3.28.9 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.8 to 3.28.9. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/dd746615b3b9d728a6a37ca2045b68ca76d4841a...9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9e3023ae..448c592d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -32,14 +32,14 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a #v2 + uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 #v2 with: languages: ${{ matrix.language }} # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@dd746615b3b9d728a6a37ca2045b68ca76d4841a #v2 + uses: github/codeql-action/autobuild@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 #v2 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -52,4 +52,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a #v2 + uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 #v2 From bcd95dd27916011c1bc5939710c0453f39e31def Mon Sep 17 00:00:00 2001 From: Simon Thulbourn Date: Mon, 10 Feb 2025 12:16:52 +0000 Subject: [PATCH 41/41] chore: add openssf scorecard workflow --- .github/workflows/ossf_scorecard.yml | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/ossf_scorecard.yml diff --git a/.github/workflows/ossf_scorecard.yml b/.github/workflows/ossf_scorecard.yml new file mode 100644 index 00000000..f5667321 --- /dev/null +++ b/.github/workflows/ossf_scorecard.yml @@ -0,0 +1,48 @@ +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + schedule: + - cron: "0 9 * * *" + push: + branches: [main] + workflow_dispatch: + +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + # environment: scorecard + permissions: + security-events: write # update code-scanning dashboard + id-token: write # confirm org+repo identity before publish results + + steps: + - name: "Checkout code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + with: + results_file: results.sarif + results_format: sarif + publish_results: true # publish to OSSF Scorecard REST API + # repo_token: ${{ secrets.SCORECARD_TOKEN }} # read-only fine-grained token to read branch protection settings + + - name: "Upload results" + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + with: + sarif_file: results.sarif