diff --git a/.autover/changes/12b85f20-8478-422b-a662-2f4aff4b6509.json b/.autover/changes/12b85f20-8478-422b-a662-2f4aff4b6509.json
new file mode 100644
index 000000000..b0d9e04ac
--- /dev/null
+++ b/.autover/changes/12b85f20-8478-422b-a662-2f4aff4b6509.json
@@ -0,0 +1,11 @@
+{
+ "Projects": [
+ {
+ "Name": "Amazon.Lambda.AspNetCoreServer.Hosting",
+ "Type": "Minor",
+ "ChangelogMessages": [
+ "Exposing the extensions points from Amazon.Lambda.AspNetCoreServer onto the HostingOptions. For example customizing the serialization by adding a callback for PostMarshallResponseFeature."
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj
index ae1acc56e..f6369c692 100644
--- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj
+++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj
@@ -27,6 +27,10 @@
+
+
+
+
diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs
index b9e58e7bc..d4fd7937c 100644
--- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs
+++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs
@@ -1,4 +1,6 @@
-using Amazon.Lambda.Core;
+using Amazon.Lambda.Core;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Http.Features.Authentication;
namespace Amazon.Lambda.AspNetCoreServer.Hosting;
@@ -12,4 +14,218 @@ public class HostingOptions
/// back to JSON to return to Lambda.
///
public ILambdaSerializer Serializer { get; set; }
-}
\ No newline at end of file
+
+ ///
+ /// The default response content encoding to use when no explicit content type or content encoding mapping is registered.
+ /// Defaults to ResponseContentEncoding.Default (UTF-8 text).
+ ///
+ public ResponseContentEncoding DefaultResponseContentEncoding { get; set; } = ResponseContentEncoding.Default;
+
+ ///
+ /// Controls whether unhandled exception details are included in responses.
+ /// Defaults to false for security.
+ ///
+ public bool IncludeUnhandledExceptionDetailInResponse { get; set; } = false;
+
+ ///
+ /// Callback invoked after request marshalling to customize the HTTP request feature.
+ /// Receives the IHttpRequestFeature, Lambda request object, and ILambdaContext.
+ /// The Lambda request object will need to be cast to the appropriate type based on the event source.
+ ///
+ ///
+ ///
+ /// API Type
+ /// Event Type
+ ///
+ /// -
+ /// HttpApi
+ /// APIGatewayHttpApiV2ProxyRequest
+ ///
+ /// -
+ /// RestApi
+ /// APIGatewayProxyRequest
+ ///
+ /// -
+ /// ApplicationLoadBalancer
+ /// ApplicationLoadBalancerRequest
+ ///
+ ///
+ ///
+ ///
+ public Action? PostMarshallRequestFeature { get; set; }
+
+ ///
+ /// Callback invoked after response marshalling to customize the HTTP response feature.
+ /// Receives the IHttpResponseFeature, Lambda response object, and ILambdaContext.
+ /// The Lambda response object will need to be cast to the appropriate type based on the event source.
+ ///
+ ///
+ ///
+ /// API Type
+ /// Event Type
+ ///
+ /// -
+ /// HttpApi
+ /// APIGatewayHttpApiV2ProxyResponse
+ ///
+ /// -
+ /// RestApi
+ /// APIGatewayProxyResponse
+ ///
+ /// -
+ /// ApplicationLoadBalancer
+ /// ApplicationLoadBalancerResponse
+ ///
+ ///
+ ///
+ ///
+ public Action? PostMarshallResponseFeature { get; set; }
+
+ ///
+ /// Callback invoked after connection marshalling to customize the HTTP connection feature.
+ /// Receives the IHttpConnectionFeature, Lambda request object, and ILambdaContext.
+ /// The Lambda request object will need to be cast to the appropriate type based on the event source.
+ ///
+ ///
+ ///
+ /// API Type
+ /// Event Type
+ ///
+ /// -
+ /// HttpApi
+ /// APIGatewayHttpApiV2ProxyRequest
+ ///
+ /// -
+ /// RestApi
+ /// APIGatewayProxyRequest
+ ///
+ /// -
+ /// ApplicationLoadBalancer
+ /// ApplicationLoadBalancerRequest
+ ///
+ ///
+ ///
+ ///
+ public Action? PostMarshallConnectionFeature { get; set; }
+
+ ///
+ /// Callback invoked after authentication marshalling to customize the HTTP authentication feature.
+ /// Receives the IHttpAuthenticationFeature, Lambda request object, and ILambdaContext.
+ /// The Lambda request object will need to be cast to the appropriate type based on the event source.
+ ///
+ ///
+ ///
+ /// API Type
+ /// Event Type
+ ///
+ /// -
+ /// HttpApi
+ /// APIGatewayHttpApiV2ProxyRequest
+ ///
+ /// -
+ /// RestApi
+ /// APIGatewayProxyRequest
+ ///
+ /// -
+ /// ApplicationLoadBalancer
+ /// ApplicationLoadBalancerRequest
+ ///
+ ///
+ ///
+ ///
+ public Action? PostMarshallHttpAuthenticationFeature { get; set; }
+
+ ///
+ /// Callback invoked after TLS connection marshalling to customize the TLS connection feature.
+ /// Receives the ITlsConnectionFeature, Lambda request object, and ILambdaContext.
+ /// The Lambda request object will need to be cast to the appropriate type based on the event source.
+ ///
+ ///
+ ///
+ /// API Type
+ /// Event Type
+ ///
+ /// -
+ /// HttpApi
+ /// APIGatewayHttpApiV2ProxyRequest
+ ///
+ /// -
+ /// RestApi
+ /// APIGatewayProxyRequest
+ ///
+ /// -
+ /// ApplicationLoadBalancer
+ /// ApplicationLoadBalancerRequest
+ ///
+ ///
+ ///
+ ///
+ public Action? PostMarshallTlsConnectionFeature { get; set; }
+
+ ///
+ /// Callback invoked after items marshalling to customize the items feature.
+ /// Receives the IItemsFeature, Lambda request object, and ILambdaContext.
+ /// The Lambda request object will need to be cast to the appropriate type based on the event source.
+ ///
+ ///
+ ///
+ /// API Type
+ /// Event Type
+ ///
+ /// -
+ /// HttpApi
+ /// APIGatewayHttpApiV2ProxyRequest
+ ///
+ /// -
+ /// RestApi
+ /// APIGatewayProxyRequest
+ ///
+ /// -
+ /// ApplicationLoadBalancer
+ /// ApplicationLoadBalancerRequest
+ ///
+ ///
+ ///
+ ///
+ public Action? PostMarshallItemsFeature { get; set; }
+
+ ///
+ /// Internal storage for content type to response content encoding mappings.
+ ///
+ internal Dictionary ContentTypeEncodings { get; } = new();
+
+ ///
+ /// Internal storage for content encoding to response content encoding mappings.
+ ///
+ internal Dictionary ContentEncodingEncodings { get; } = new();
+
+ ///
+ /// Registers a response content encoding for a specific content type.
+ ///
+ /// The content type (e.g., "application/json", "image/png")
+ /// The response content encoding to use for this content type
+ public void RegisterResponseContentEncodingForContentType(string contentType, ResponseContentEncoding encoding)
+ {
+ if (string.IsNullOrEmpty(contentType))
+ {
+ return;
+ }
+
+ ContentTypeEncodings[contentType] = encoding;
+ }
+
+ ///
+ /// Registers a response content encoding for a specific content encoding.
+ ///
+ /// The content encoding (e.g., "gzip", "deflate", "br")
+ /// The response content encoding to use for this content encoding
+ public void RegisterResponseContentEncodingForContentEncoding(string contentEncoding, ResponseContentEncoding encoding)
+ {
+ if (string.IsNullOrEmpty(contentEncoding))
+ {
+ return;
+ }
+
+ ContentEncodingEncodings[contentEncoding] = encoding;
+ }
+}
diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/LambdaRuntimeSupportServer.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/LambdaRuntimeSupportServer.cs
index c862b790c..f50a37f7b 100644
--- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/LambdaRuntimeSupportServer.cs
+++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/LambdaRuntimeSupportServer.cs
@@ -3,6 +3,8 @@
using Amazon.Lambda.Core;
using Amazon.Lambda.RuntimeSupport;
using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.Extensions.DependencyInjection;
namespace Amazon.Lambda.AspNetCoreServer.Hosting.Internal
@@ -88,6 +90,7 @@ public class APIGatewayHttpApiV2MinimalApi : APIGatewayHttpApiV2ProxyFunction
#if NET8_0_OR_GREATER
private readonly IEnumerable _beforeSnapshotRequestsCollectors;
#endif
+ private readonly HostingOptions? _hostingOptions;
///
/// Create instances
@@ -99,6 +102,29 @@ public APIGatewayHttpApiV2MinimalApi(IServiceProvider serviceProvider)
#if NET8_0_OR_GREATER
_beforeSnapshotRequestsCollectors = serviceProvider.GetServices();
#endif
+
+ // Retrieve HostingOptions from service provider (may be null for backward compatibility)
+ _hostingOptions = serviceProvider.GetService();
+
+ // Apply configuration from HostingOptions if available
+ if (_hostingOptions != null)
+ {
+ // Apply binary response configuration
+ foreach (var kvp in _hostingOptions.ContentTypeEncodings)
+ {
+ RegisterResponseContentEncodingForContentType(kvp.Key, kvp.Value);
+ }
+
+ foreach (var kvp in _hostingOptions.ContentEncodingEncodings)
+ {
+ RegisterResponseContentEncodingForContentEncoding(kvp.Key, kvp.Value);
+ }
+
+ DefaultResponseContentEncoding = _hostingOptions.DefaultResponseContentEncoding;
+
+ // Apply exception handling configuration
+ IncludeUnhandledExceptionDetailInResponse = _hostingOptions.IncludeUnhandledExceptionDetailInResponse;
+ }
}
#if NET8_0_OR_GREATER
@@ -109,6 +135,55 @@ protected override IEnumerable GetBeforeSnapshotRequests()
yield return collector.Request;
}
#endif
+
+ protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallRequestFeature(aspNetCoreRequestFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallRequestFeature?.Invoke(aspNetCoreRequestFeature, lambdaRequest, lambdaContext);
+ }
+
+ protected override void PostMarshallResponseFeature(IHttpResponseFeature aspNetCoreResponseFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse lambdaResponse, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallResponseFeature(aspNetCoreResponseFeature, lambdaResponse, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallResponseFeature?.Invoke(aspNetCoreResponseFeature, lambdaResponse, lambdaContext);
+ }
+
+ protected override void PostMarshallConnectionFeature(IHttpConnectionFeature aspNetCoreConnectionFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallConnectionFeature(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallConnectionFeature?.Invoke(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext);
+ }
+
+ protected override void PostMarshallHttpAuthenticationFeature(IHttpAuthenticationFeature aspNetCoreHttpAuthenticationFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallHttpAuthenticationFeature(aspNetCoreHttpAuthenticationFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallHttpAuthenticationFeature?.Invoke(aspNetCoreHttpAuthenticationFeature, lambdaRequest, lambdaContext);
+ }
+
+ protected override void PostMarshallTlsConnectionFeature(ITlsConnectionFeature aspNetCoreConnectionFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallTlsConnectionFeature(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallTlsConnectionFeature?.Invoke(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext);
+ }
+
+ protected override void PostMarshallItemsFeatureFeature(IItemsFeature aspNetCoreItemFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallItemsFeatureFeature(aspNetCoreItemFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ // Note: LAMBDA_CONTEXT and LAMBDA_REQUEST_OBJECT are preserved by the base implementation
+ _hostingOptions?.PostMarshallItemsFeature?.Invoke(aspNetCoreItemFeature, lambdaRequest, lambdaContext);
+ }
}
}
@@ -145,6 +220,7 @@ public class APIGatewayRestApiMinimalApi : APIGatewayProxyFunction
#if NET8_0_OR_GREATER
private readonly IEnumerable _beforeSnapshotRequestsCollectors;
#endif
+ private readonly HostingOptions? _hostingOptions;
///
/// Create instances
@@ -156,6 +232,29 @@ public APIGatewayRestApiMinimalApi(IServiceProvider serviceProvider)
#if NET8_0_OR_GREATER
_beforeSnapshotRequestsCollectors = serviceProvider.GetServices();
#endif
+
+ // Retrieve HostingOptions from service provider (may be null for backward compatibility)
+ _hostingOptions = serviceProvider.GetService();
+
+ // Apply configuration from HostingOptions if available
+ if (_hostingOptions != null)
+ {
+ // Apply binary response configuration
+ foreach (var kvp in _hostingOptions.ContentTypeEncodings)
+ {
+ RegisterResponseContentEncodingForContentType(kvp.Key, kvp.Value);
+ }
+
+ foreach (var kvp in _hostingOptions.ContentEncodingEncodings)
+ {
+ RegisterResponseContentEncodingForContentEncoding(kvp.Key, kvp.Value);
+ }
+
+ DefaultResponseContentEncoding = _hostingOptions.DefaultResponseContentEncoding;
+
+ // Apply exception handling configuration
+ IncludeUnhandledExceptionDetailInResponse = _hostingOptions.IncludeUnhandledExceptionDetailInResponse;
+ }
}
#if NET8_0_OR_GREATER
@@ -166,6 +265,55 @@ protected override IEnumerable GetBeforeSnapshotRequests()
yield return collector.Request;
}
#endif
+
+ protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, APIGatewayEvents.APIGatewayProxyRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallRequestFeature(aspNetCoreRequestFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallRequestFeature?.Invoke(aspNetCoreRequestFeature, lambdaRequest, lambdaContext);
+ }
+
+ protected override void PostMarshallResponseFeature(IHttpResponseFeature aspNetCoreResponseFeature, APIGatewayEvents.APIGatewayProxyResponse lambdaResponse, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallResponseFeature(aspNetCoreResponseFeature, lambdaResponse, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallResponseFeature?.Invoke(aspNetCoreResponseFeature, lambdaResponse, lambdaContext);
+ }
+
+ protected override void PostMarshallConnectionFeature(IHttpConnectionFeature aspNetCoreConnectionFeature, APIGatewayEvents.APIGatewayProxyRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallConnectionFeature(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallConnectionFeature?.Invoke(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext);
+ }
+
+ protected override void PostMarshallHttpAuthenticationFeature(IHttpAuthenticationFeature aspNetCoreHttpAuthenticationFeature, APIGatewayEvents.APIGatewayProxyRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallHttpAuthenticationFeature(aspNetCoreHttpAuthenticationFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallHttpAuthenticationFeature?.Invoke(aspNetCoreHttpAuthenticationFeature, lambdaRequest, lambdaContext);
+ }
+
+ protected override void PostMarshallTlsConnectionFeature(ITlsConnectionFeature aspNetCoreConnectionFeature, APIGatewayEvents.APIGatewayProxyRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallTlsConnectionFeature(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallTlsConnectionFeature?.Invoke(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext);
+ }
+
+ protected override void PostMarshallItemsFeatureFeature(IItemsFeature aspNetCoreItemFeature, APIGatewayEvents.APIGatewayProxyRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallItemsFeatureFeature(aspNetCoreItemFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ // Note: LAMBDA_CONTEXT and LAMBDA_REQUEST_OBJECT are preserved by the base implementation
+ _hostingOptions?.PostMarshallItemsFeature?.Invoke(aspNetCoreItemFeature, lambdaRequest, lambdaContext);
+ }
}
}
@@ -202,6 +350,7 @@ public class ApplicationLoadBalancerMinimalApi : ApplicationLoadBalancerFunction
#if NET8_0_OR_GREATER
private readonly IEnumerable _beforeSnapshotRequestsCollectors;
#endif
+ private readonly HostingOptions? _hostingOptions;
///
/// Create instances
@@ -213,6 +362,29 @@ public ApplicationLoadBalancerMinimalApi(IServiceProvider serviceProvider)
#if NET8_0_OR_GREATER
_beforeSnapshotRequestsCollectors = serviceProvider.GetServices();
#endif
+
+ // Retrieve HostingOptions from service provider (may be null for backward compatibility)
+ _hostingOptions = serviceProvider.GetService();
+
+ // Apply configuration from HostingOptions if available
+ if (_hostingOptions != null)
+ {
+ // Apply binary response configuration
+ foreach (var kvp in _hostingOptions.ContentTypeEncodings)
+ {
+ RegisterResponseContentEncodingForContentType(kvp.Key, kvp.Value);
+ }
+
+ foreach (var kvp in _hostingOptions.ContentEncodingEncodings)
+ {
+ RegisterResponseContentEncodingForContentEncoding(kvp.Key, kvp.Value);
+ }
+
+ DefaultResponseContentEncoding = _hostingOptions.DefaultResponseContentEncoding;
+
+ // Apply exception handling configuration
+ IncludeUnhandledExceptionDetailInResponse = _hostingOptions.IncludeUnhandledExceptionDetailInResponse;
+ }
}
#if NET8_0_OR_GREATER
@@ -223,6 +395,55 @@ protected override IEnumerable GetBeforeSnapshotRequests()
yield return collector.Request;
}
#endif
+
+ protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallRequestFeature(aspNetCoreRequestFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallRequestFeature?.Invoke(aspNetCoreRequestFeature, lambdaRequest, lambdaContext);
+ }
+
+ protected override void PostMarshallResponseFeature(IHttpResponseFeature aspNetCoreResponseFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerResponse lambdaResponse, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallResponseFeature(aspNetCoreResponseFeature, lambdaResponse, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallResponseFeature?.Invoke(aspNetCoreResponseFeature, lambdaResponse, lambdaContext);
+ }
+
+ protected override void PostMarshallConnectionFeature(IHttpConnectionFeature aspNetCoreConnectionFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallConnectionFeature(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallConnectionFeature?.Invoke(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext);
+ }
+
+ protected override void PostMarshallHttpAuthenticationFeature(IHttpAuthenticationFeature aspNetCoreHttpAuthenticationFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallHttpAuthenticationFeature(aspNetCoreHttpAuthenticationFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallHttpAuthenticationFeature?.Invoke(aspNetCoreHttpAuthenticationFeature, lambdaRequest, lambdaContext);
+ }
+
+ protected override void PostMarshallTlsConnectionFeature(ITlsConnectionFeature aspNetCoreConnectionFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallTlsConnectionFeature(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ _hostingOptions?.PostMarshallTlsConnectionFeature?.Invoke(aspNetCoreConnectionFeature, lambdaRequest, lambdaContext);
+ }
+
+ protected override void PostMarshallItemsFeatureFeature(IItemsFeature aspNetCoreItemFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext)
+ {
+ base.PostMarshallItemsFeatureFeature(aspNetCoreItemFeature, lambdaRequest, lambdaContext);
+
+ // Invoke configured callback if available
+ // Note: LAMBDA_CONTEXT and LAMBDA_REQUEST_OBJECT are preserved by the base implementation
+ _hostingOptions?.PostMarshallItemsFeature?.Invoke(aspNetCoreItemFeature, lambdaRequest, lambdaContext);
+ }
}
}
}
diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/ServiceCollectionExtensions.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/ServiceCollectionExtensions.cs
index 645b5ba91..aa952bc54 100644
--- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/ServiceCollectionExtensions.cs
+++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/ServiceCollectionExtensions.cs
@@ -157,6 +157,9 @@ private static bool TryLambdaSetup(IServiceCollection services, LambdaEventSourc
if (configure != null)
configure.Invoke(hostingOptions);
+ // Register HostingOptions as singleton in service provider
+ services.TryAddSingleton(hostingOptions);
+
var serverType = eventSource switch
{
LambdaEventSource.HttpApi => typeof(APIGatewayHttpApiV2LambdaRuntimeSupportServer),
diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/APIGatewayHttpApiV2MinimalApiTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/APIGatewayHttpApiV2MinimalApiTests.cs
new file mode 100644
index 000000000..6f1b93562
--- /dev/null
+++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/APIGatewayHttpApiV2MinimalApiTests.cs
@@ -0,0 +1,898 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Lambda.APIGatewayEvents;
+using Amazon.Lambda.AspNetCoreServer;
+using Amazon.Lambda.AspNetCoreServer.Hosting;
+using Amazon.Lambda.AspNetCoreServer.Hosting.Internal;
+using Amazon.Lambda.Core;
+using Amazon.Lambda.Serialization.SystemTextJson;
+using Amazon.Lambda.TestUtilities;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using System.Reflection;
+using Xunit;
+
+namespace Amazon.Lambda.AspNetCoreServer.Hosting.Tests;
+
+///
+/// Tests for APIGatewayHttpApiV2MinimalApi configuration
+///
+public class APIGatewayHttpApiV2MinimalApiTests
+{
+ ///
+ /// Helper method to create a service provider with required services
+ ///
+ private IServiceProvider CreateServiceProvider(HostingOptions? hostingOptions = null)
+ {
+ var services = new ServiceCollection();
+
+ if (hostingOptions != null)
+ {
+ services.AddSingleton(hostingOptions);
+ }
+
+ services.AddSingleton(new DefaultLambdaJsonSerializer());
+ services.AddLogging();
+
+ return services.BuildServiceProvider();
+ }
+
+ ///
+ /// Test that MinimalApi reads HostingOptions from service provider
+ ///
+ [Fact]
+ public void MinimalApi_ReadsHostingOptionsFromServiceProvider()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64,
+ IncludeUnhandledExceptionDetailInResponse = true
+ };
+ hostingOptions.RegisterResponseContentEncodingForContentType("application/json", ResponseContentEncoding.Default);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayHttpApiV2LambdaRuntimeSupportServer.APIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ // Assert - Verify HostingOptions was retrieved and applied
+ Assert.Equal(ResponseContentEncoding.Base64, minimalApi.DefaultResponseContentEncoding);
+ Assert.True(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ ///
+ /// Test that binary response content type configurations are applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesBinaryResponseContentTypeConfiguration()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+ hostingOptions.RegisterResponseContentEncodingForContentType("image/png", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentType("application/pdf", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentType("text/plain", ResponseContentEncoding.Default);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayHttpApiV2LambdaRuntimeSupportServer.APIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ // Assert - Verify the mappings were applied
+ var pngEncoding = minimalApi.GetResponseContentEncodingForContentType("image/png");
+ var pdfEncoding = minimalApi.GetResponseContentEncodingForContentType("application/pdf");
+ var textEncoding = minimalApi.GetResponseContentEncodingForContentType("text/plain");
+
+ Assert.Equal(ResponseContentEncoding.Base64, pngEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, pdfEncoding);
+ Assert.Equal(ResponseContentEncoding.Default, textEncoding);
+ }
+
+ ///
+ /// Test that binary response content encoding configurations are applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesBinaryResponseContentEncodingConfiguration()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("deflate", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("br", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayHttpApiV2LambdaRuntimeSupportServer.APIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ // Assert - Verify the mappings were applied
+ var gzipEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("gzip");
+ var deflateEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("deflate");
+ var brEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("br");
+
+ Assert.Equal(ResponseContentEncoding.Base64, gzipEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, deflateEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, brEncoding);
+ }
+
+ ///
+ /// Test that default response content encoding is applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesDefaultResponseContentEncoding()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayHttpApiV2LambdaRuntimeSupportServer.APIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ // Assert
+ Assert.Equal(ResponseContentEncoding.Base64, minimalApi.DefaultResponseContentEncoding);
+ }
+
+ ///
+ /// Test that exception handling configuration is applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesExceptionHandlingConfiguration()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ IncludeUnhandledExceptionDetailInResponse = true
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayHttpApiV2LambdaRuntimeSupportServer.APIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ // Assert
+ Assert.True(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ ///
+ /// Test that exception handling defaults to false when not configured
+ ///
+ [Fact]
+ public void MinimalApi_ExceptionHandlingDefaultsToFalse()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayHttpApiV2LambdaRuntimeSupportServer.APIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ // Assert
+ Assert.False(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ ///
+ /// Test that MinimalApi works when HostingOptions is not registered (backward compatibility)
+ ///
+ [Fact]
+ public void MinimalApi_WorksWhenHostingOptionsNotRegistered()
+ {
+ // Arrange
+ var serviceProvider = CreateServiceProvider(hostingOptions: null);
+
+ // Act - Should not throw exception
+ var minimalApi = new APIGatewayHttpApiV2LambdaRuntimeSupportServer.APIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ // Assert - Should use default values
+ Assert.Equal(ResponseContentEncoding.Default, minimalApi.DefaultResponseContentEncoding);
+ Assert.False(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ ///
+ /// Test that all configurations are applied together
+ ///
+ [Fact]
+ public void MinimalApi_AppliesAllConfigurationsTogether()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64,
+ IncludeUnhandledExceptionDetailInResponse = true
+ };
+ hostingOptions.RegisterResponseContentEncodingForContentType("application/json", ResponseContentEncoding.Default);
+ hostingOptions.RegisterResponseContentEncodingForContentType("image/png", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("deflate", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayHttpApiV2LambdaRuntimeSupportServer.APIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ // Assert - Verify all configurations were applied
+ Assert.Equal(ResponseContentEncoding.Base64, minimalApi.DefaultResponseContentEncoding);
+ Assert.True(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+
+ // Verify content type mappings
+ var jsonEncoding = minimalApi.GetResponseContentEncodingForContentType("application/json");
+ var pngEncoding = minimalApi.GetResponseContentEncodingForContentType("image/png");
+
+ Assert.Equal(ResponseContentEncoding.Default, jsonEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, pngEncoding);
+
+ // Verify content encoding mappings
+ var gzipEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("gzip");
+ var deflateEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("deflate");
+
+ Assert.Equal(ResponseContentEncoding.Base64, gzipEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, deflateEncoding);
+ }
+
+ ///
+ /// Test that multiple content type registrations are all applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesMultipleContentTypeRegistrations()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+
+ // Register multiple content types
+ var contentTypes = new Dictionary
+ {
+ { "image/png", ResponseContentEncoding.Base64 },
+ { "image/jpeg", ResponseContentEncoding.Base64 },
+ { "application/pdf", ResponseContentEncoding.Base64 },
+ { "application/json", ResponseContentEncoding.Default },
+ { "text/html", ResponseContentEncoding.Default }
+ };
+
+ foreach (var kvp in contentTypes)
+ {
+ hostingOptions.RegisterResponseContentEncodingForContentType(kvp.Key, kvp.Value);
+ }
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayHttpApiV2LambdaRuntimeSupportServer.APIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ // Assert - Verify all mappings were applied
+ foreach (var kvp in contentTypes)
+ {
+ var encoding = minimalApi.GetResponseContentEncodingForContentType(kvp.Key);
+ Assert.Equal(kvp.Value, encoding);
+ }
+ }
+
+ ///
+ /// Test that multiple content encoding registrations are all applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesMultipleContentEncodingRegistrations()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+
+ // Register multiple content encodings
+ var contentEncodings = new Dictionary
+ {
+ { "gzip", ResponseContentEncoding.Base64 },
+ { "deflate", ResponseContentEncoding.Base64 },
+ { "br", ResponseContentEncoding.Base64 },
+ { "compress", ResponseContentEncoding.Base64 }
+ };
+
+ foreach (var kvp in contentEncodings)
+ {
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding(kvp.Key, kvp.Value);
+ }
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayHttpApiV2LambdaRuntimeSupportServer.APIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ // Assert - Verify all mappings were applied
+ foreach (var kvp in contentEncodings)
+ {
+ var encoding = minimalApi.GetResponseContentEncodingForContentEncoding(kvp.Key);
+ Assert.Equal(kvp.Value, encoding);
+ }
+ }
+
+ ///
+ /// Test that unmapped content types fall back to default encoding
+ ///
+ [Fact]
+ public void MinimalApi_UnmappedContentTypesFallbackToDefault()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64
+ };
+ hostingOptions.RegisterResponseContentEncodingForContentType("image/png", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayHttpApiV2LambdaRuntimeSupportServer.APIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ // Assert - Unmapped content type should use default
+ var unmappedEncoding = minimalApi.GetResponseContentEncodingForContentType("application/bin");
+ Assert.Equal(ResponseContentEncoding.Base64, unmappedEncoding);
+ }
+
+ ///
+ /// Test that unmapped content encodings fall back to default encoding
+ ///
+ [Fact]
+ public void MinimalApi_UnmappedContentEncodingsFallbackToDefault()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64
+ };
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayHttpApiV2LambdaRuntimeSupportServer.APIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ // Assert - Unmapped content encoding should use default
+ var unmappedEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("identity");
+ Assert.Equal(ResponseContentEncoding.Base64, unmappedEncoding);
+ }
+
+
+ #region Callback Invocation Tests
+
+ ///
+ /// Test wrapper class that exposes protected PostMarshall methods for testing
+ ///
+ private class TestableAPIGatewayHttpApiV2MinimalApi : APIGatewayHttpApiV2LambdaRuntimeSupportServer.APIGatewayHttpApiV2MinimalApi
+ {
+ public TestableAPIGatewayHttpApiV2MinimalApi(IServiceProvider serviceProvider) : base(serviceProvider)
+ {
+ }
+
+ public void TestPostMarshallRequestFeature(IHttpRequestFeature feature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context)
+ {
+ PostMarshallRequestFeature(feature, request, context);
+ }
+
+ public void TestPostMarshallResponseFeature(IHttpResponseFeature feature, APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse response, ILambdaContext context)
+ {
+ PostMarshallResponseFeature(feature, response, context);
+ }
+
+ public void TestPostMarshallConnectionFeature(IHttpConnectionFeature feature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context)
+ {
+ PostMarshallConnectionFeature(feature, request, context);
+ }
+
+ public void TestPostMarshallHttpAuthenticationFeature(IHttpAuthenticationFeature feature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context)
+ {
+ PostMarshallHttpAuthenticationFeature(feature, request, context);
+ }
+
+ public void TestPostMarshallTlsConnectionFeature(ITlsConnectionFeature feature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context)
+ {
+ PostMarshallTlsConnectionFeature(feature, request, context);
+ }
+
+ public void TestPostMarshallItemsFeature(IItemsFeature feature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context)
+ {
+ PostMarshallItemsFeatureFeature(feature, request, context);
+ }
+ }
+
+ ///
+ /// Test that PostMarshallRequestFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallRequestFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IHttpRequestFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallRequestFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testRequest = new APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallRequestFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+ ///
+ /// Test that PostMarshallResponseFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallResponseFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IHttpResponseFeature? capturedFeature = null;
+ object? capturedResponse = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallResponseFeature = (feature, response, context) =>
+ {
+ capturedFeature = feature;
+ capturedResponse = response;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testResponse = new APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallResponseFeature(testFeature, testResponse, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedResponse);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testResponse, capturedResponse);
+ Assert.Same(testContext, capturedContext);
+ }
+
+
+ ///
+ /// Test that PostMarshallConnectionFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallConnectionFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IHttpConnectionFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallConnectionFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testRequest = new APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallConnectionFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+ ///
+ /// Test that PostMarshallHttpAuthenticationFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallHttpAuthenticationFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IHttpAuthenticationFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallHttpAuthenticationFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.Authentication.HttpAuthenticationFeature();
+ var testRequest = new APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallHttpAuthenticationFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+ ///
+ /// Test that PostMarshallTlsConnectionFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallTlsConnectionFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ ITlsConnectionFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallTlsConnectionFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.TlsConnectionFeature();
+ var testRequest = new APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallTlsConnectionFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+ ///
+ /// Test that PostMarshallItemsFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallItemsFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IItemsFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallItemsFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallItemsFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+
+ ///
+ /// Test that null callbacks are handled gracefully (no exception thrown)
+ ///
+ [Fact]
+ public void MinimalApi_NullCallbacks_HandledGracefully()
+ {
+ // Arrange - HostingOptions with no callbacks configured
+ var hostingOptions = new HostingOptions();
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ var testRequestFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testResponseFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testConnectionFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testAuthFeature = new Microsoft.AspNetCore.Http.Features.Authentication.HttpAuthenticationFeature();
+ var testTlsFeature = new Microsoft.AspNetCore.Http.Features.TlsConnectionFeature();
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest();
+ var testResponse = new APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act & Assert - Should not throw exceptions
+ minimalApi.TestPostMarshallRequestFeature(testRequestFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallResponseFeature(testResponseFeature, testResponse, testContext);
+ minimalApi.TestPostMarshallConnectionFeature(testConnectionFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallHttpAuthenticationFeature(testAuthFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallTlsConnectionFeature(testTlsFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // If we reach here without exceptions, the test passes
+ Assert.True(true);
+ }
+
+ ///
+ /// Test that callbacks can modify features and modifications are preserved
+ ///
+ [Fact]
+ public void MinimalApi_Callbacks_CanModifyFeatures()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallRequestFeature = (feature, request, context) =>
+ {
+ feature.Path = "/modified-path";
+ feature.Method = "POST";
+ },
+ PostMarshallResponseFeature = (feature, response, context) =>
+ {
+ feature.StatusCode = 201;
+ feature.ReasonPhrase = "Created";
+ },
+ PostMarshallConnectionFeature = (feature, request, context) =>
+ {
+ feature.RemoteIpAddress = System.Net.IPAddress.Parse("192.168.1.100");
+ },
+ PostMarshallItemsFeature = (feature, request, context) =>
+ {
+ feature.Items["CustomKey"] = "CustomValue";
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ var testRequestFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testResponseFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testConnectionFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest();
+ var testResponse = new APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallRequestFeature(testRequestFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallResponseFeature(testResponseFeature, testResponse, testContext);
+ minimalApi.TestPostMarshallConnectionFeature(testConnectionFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // Assert - Verify modifications were applied
+ Assert.Equal("/modified-path", testRequestFeature.Path);
+ Assert.Equal("POST", testRequestFeature.Method);
+ Assert.Equal(201, testResponseFeature.StatusCode);
+ Assert.Equal("Created", testResponseFeature.ReasonPhrase);
+ Assert.Equal(System.Net.IPAddress.Parse("192.168.1.100"), testConnectionFeature.RemoteIpAddress);
+ Assert.True(testItemsFeature.Items.TryGetValue("CustomKey", out var customValue));
+ Assert.Equal("CustomValue", customValue);
+ }
+
+ ///
+ /// Test that LAMBDA_CONTEXT and LAMBDA_REQUEST_OBJECT are preserved in ItemsFeature
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallItemsFeature_PreservesLambdaContextAndRequestObject()
+ {
+ // Arrange
+ var callbackInvoked = false;
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallItemsFeature = (feature, request, context) =>
+ {
+ callbackInvoked = true;
+ // Verify that LAMBDA_CONTEXT and LAMBDA_REQUEST_OBJECT exist
+ // These should be set by the base implementation before the callback
+ Assert.True(feature.Items.ContainsKey("LAMBDA_CONTEXT") || feature.Items.Count >= 0);
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ // Pre-populate with LAMBDA_CONTEXT and LAMBDA_REQUEST_OBJECT to simulate base implementation
+ var testContext = new TestLambdaContext();
+ var testRequest = new APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest();
+ testItemsFeature.Items["LAMBDA_CONTEXT"] = testContext;
+ testItemsFeature.Items["LAMBDA_REQUEST_OBJECT"] = testRequest;
+
+ // Act
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // Assert - Verify callback was invoked and items are still present
+ Assert.True(callbackInvoked);
+ Assert.True(testItemsFeature.Items.TryGetValue("LAMBDA_CONTEXT", out var lambdaContextObj));
+ Assert.True(testItemsFeature.Items.TryGetValue("LAMBDA_REQUEST_OBJECT", out var lambdaRequestObj));
+ Assert.Same(testContext, lambdaContextObj);
+ Assert.Same(testRequest, lambdaRequestObj);
+ }
+
+
+ ///
+ /// Test that callbacks are invoked in the correct order (after base implementation)
+ ///
+ [Fact]
+ public void MinimalApi_Callbacks_InvokedAfterBaseImplementation()
+ {
+ // Arrange
+ var invocationOrder = new List();
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallRequestFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("RequestCallback");
+ },
+ PostMarshallResponseFeature = (feature, response, context) =>
+ {
+ invocationOrder.Add("ResponseCallback");
+ },
+ PostMarshallConnectionFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("ConnectionCallback");
+ },
+ PostMarshallHttpAuthenticationFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("AuthCallback");
+ },
+ PostMarshallTlsConnectionFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("TlsCallback");
+ },
+ PostMarshallItemsFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("ItemsCallback");
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ var testRequestFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testResponseFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testConnectionFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testAuthFeature = new Microsoft.AspNetCore.Http.Features.Authentication.HttpAuthenticationFeature();
+ var testTlsFeature = new Microsoft.AspNetCore.Http.Features.TlsConnectionFeature();
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest();
+ var testResponse = new APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallRequestFeature(testRequestFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallResponseFeature(testResponseFeature, testResponse, testContext);
+ minimalApi.TestPostMarshallConnectionFeature(testConnectionFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallHttpAuthenticationFeature(testAuthFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallTlsConnectionFeature(testTlsFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // Assert - Verify all callbacks were invoked
+ Assert.Equal(6, invocationOrder.Count);
+ Assert.Contains("RequestCallback", invocationOrder);
+ Assert.Contains("ResponseCallback", invocationOrder);
+ Assert.Contains("ConnectionCallback", invocationOrder);
+ Assert.Contains("AuthCallback", invocationOrder);
+ Assert.Contains("TlsCallback", invocationOrder);
+ Assert.Contains("ItemsCallback", invocationOrder);
+ }
+
+ ///
+ /// Test that multiple callbacks can be configured and all are invoked
+ ///
+ [Fact]
+ public void MinimalApi_MultipleCallbacks_AllInvoked()
+ {
+ // Arrange
+ var requestCallbackInvoked = false;
+ var responseCallbackInvoked = false;
+ var connectionCallbackInvoked = false;
+ var authCallbackInvoked = false;
+ var tlsCallbackInvoked = false;
+ var itemsCallbackInvoked = false;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallRequestFeature = (feature, request, context) => { requestCallbackInvoked = true; },
+ PostMarshallResponseFeature = (feature, response, context) => { responseCallbackInvoked = true; },
+ PostMarshallConnectionFeature = (feature, request, context) => { connectionCallbackInvoked = true; },
+ PostMarshallHttpAuthenticationFeature = (feature, request, context) => { authCallbackInvoked = true; },
+ PostMarshallTlsConnectionFeature = (feature, request, context) => { tlsCallbackInvoked = true; },
+ PostMarshallItemsFeature = (feature, request, context) => { itemsCallbackInvoked = true; }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayHttpApiV2MinimalApi(serviceProvider);
+
+ var testRequestFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testResponseFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testConnectionFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testAuthFeature = new Microsoft.AspNetCore.Http.Features.Authentication.HttpAuthenticationFeature();
+ var testTlsFeature = new Microsoft.AspNetCore.Http.Features.TlsConnectionFeature();
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest();
+ var testResponse = new APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallRequestFeature(testRequestFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallResponseFeature(testResponseFeature, testResponse, testContext);
+ minimalApi.TestPostMarshallConnectionFeature(testConnectionFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallHttpAuthenticationFeature(testAuthFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallTlsConnectionFeature(testTlsFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // Assert
+ Assert.True(requestCallbackInvoked);
+ Assert.True(responseCallbackInvoked);
+ Assert.True(connectionCallbackInvoked);
+ Assert.True(authCallbackInvoked);
+ Assert.True(tlsCallbackInvoked);
+ Assert.True(itemsCallbackInvoked);
+ }
+
+ #endregion
+}
diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/APIGatewayRestApiMinimalApiTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/APIGatewayRestApiMinimalApiTests.cs
new file mode 100644
index 000000000..57bc8d2e4
--- /dev/null
+++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/APIGatewayRestApiMinimalApiTests.cs
@@ -0,0 +1,899 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Lambda.APIGatewayEvents;
+using Amazon.Lambda.AspNetCoreServer;
+using Amazon.Lambda.AspNetCoreServer.Hosting;
+using Amazon.Lambda.AspNetCoreServer.Hosting.Internal;
+using Amazon.Lambda.Core;
+using Amazon.Lambda.Serialization.SystemTextJson;
+using Amazon.Lambda.TestUtilities;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using System.Reflection;
+using Xunit;
+
+namespace Amazon.Lambda.AspNetCoreServer.Hosting.Tests;
+
+///
+/// Tests for APIGatewayRestApiMinimalApi configuration
+///
+public class APIGatewayRestApiMinimalApiTests
+{
+ ///
+ /// Helper method to create a service provider with required services
+ ///
+ private IServiceProvider CreateServiceProvider(HostingOptions? hostingOptions = null)
+ {
+ var services = new ServiceCollection();
+
+ if (hostingOptions != null)
+ {
+ services.AddSingleton(hostingOptions);
+ }
+
+ services.AddSingleton(new DefaultLambdaJsonSerializer());
+ services.AddLogging();
+
+ return services.BuildServiceProvider();
+ }
+
+ ///
+ /// Test that MinimalApi reads HostingOptions from service provider
+ ///
+ [Fact]
+ public void MinimalApi_ReadsHostingOptionsFromServiceProvider()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64,
+ IncludeUnhandledExceptionDetailInResponse = true
+ };
+ hostingOptions.RegisterResponseContentEncodingForContentType("application/json", ResponseContentEncoding.Default);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayRestApiLambdaRuntimeSupportServer.APIGatewayRestApiMinimalApi(serviceProvider);
+
+ // Assert - Verify HostingOptions was retrieved and applied
+ Assert.Equal(ResponseContentEncoding.Base64, minimalApi.DefaultResponseContentEncoding);
+ Assert.True(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ ///
+ /// Test that binary response content type configurations are applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesBinaryResponseContentTypeConfiguration()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+ hostingOptions.RegisterResponseContentEncodingForContentType("image/png", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentType("application/pdf", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentType("text/plain", ResponseContentEncoding.Default);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayRestApiLambdaRuntimeSupportServer.APIGatewayRestApiMinimalApi(serviceProvider);
+
+ // Assert - Verify the mappings were applied
+ var pngEncoding = minimalApi.GetResponseContentEncodingForContentType("image/png");
+ var pdfEncoding = minimalApi.GetResponseContentEncodingForContentType("application/pdf");
+ var textEncoding = minimalApi.GetResponseContentEncodingForContentType("text/plain");
+
+ Assert.Equal(ResponseContentEncoding.Base64, pngEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, pdfEncoding);
+ Assert.Equal(ResponseContentEncoding.Default, textEncoding);
+ }
+
+ ///
+ /// Test that binary response content encoding configurations are applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesBinaryResponseContentEncodingConfiguration()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("deflate", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("br", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayRestApiLambdaRuntimeSupportServer.APIGatewayRestApiMinimalApi(serviceProvider);
+
+ // Assert - Verify the mappings were applied
+ var gzipEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("gzip");
+ var deflateEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("deflate");
+ var brEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("br");
+
+ Assert.Equal(ResponseContentEncoding.Base64, gzipEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, deflateEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, brEncoding);
+ }
+
+ ///
+ /// Test that default response content encoding is applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesDefaultResponseContentEncoding()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayRestApiLambdaRuntimeSupportServer.APIGatewayRestApiMinimalApi(serviceProvider);
+
+ // Assert
+ Assert.Equal(ResponseContentEncoding.Base64, minimalApi.DefaultResponseContentEncoding);
+ }
+
+ ///
+ /// Test that exception handling configuration is applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesExceptionHandlingConfiguration()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ IncludeUnhandledExceptionDetailInResponse = true
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayRestApiLambdaRuntimeSupportServer.APIGatewayRestApiMinimalApi(serviceProvider);
+
+ // Assert
+ Assert.True(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ ///
+ /// Test that exception handling defaults to false when not configured
+ ///
+ [Fact]
+ public void MinimalApi_ExceptionHandlingDefaultsToFalse()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayRestApiLambdaRuntimeSupportServer.APIGatewayRestApiMinimalApi(serviceProvider);
+
+ // Assert
+ Assert.False(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ ///
+ /// Test that MinimalApi works when HostingOptions is not registered (backward compatibility)
+ ///
+ [Fact]
+ public void MinimalApi_WorksWhenHostingOptionsNotRegistered()
+ {
+ // Arrange
+ var serviceProvider = CreateServiceProvider(hostingOptions: null);
+
+ // Act - Should not throw exception
+ var minimalApi = new APIGatewayRestApiLambdaRuntimeSupportServer.APIGatewayRestApiMinimalApi(serviceProvider);
+
+ // Assert - Should use default values
+ Assert.Equal(ResponseContentEncoding.Default, minimalApi.DefaultResponseContentEncoding);
+ Assert.False(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ ///
+ /// Test that all configurations are applied together
+ ///
+ [Fact]
+ public void MinimalApi_AppliesAllConfigurationsTogether()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64,
+ IncludeUnhandledExceptionDetailInResponse = true
+ };
+ hostingOptions.RegisterResponseContentEncodingForContentType("application/json", ResponseContentEncoding.Default);
+ hostingOptions.RegisterResponseContentEncodingForContentType("image/png", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("deflate", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayRestApiLambdaRuntimeSupportServer.APIGatewayRestApiMinimalApi(serviceProvider);
+
+ // Assert - Verify all configurations were applied
+ Assert.Equal(ResponseContentEncoding.Base64, minimalApi.DefaultResponseContentEncoding);
+ Assert.True(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+
+ // Verify content type mappings
+ var jsonEncoding = minimalApi.GetResponseContentEncodingForContentType("application/json");
+ var pngEncoding = minimalApi.GetResponseContentEncodingForContentType("image/png");
+
+ Assert.Equal(ResponseContentEncoding.Default, jsonEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, pngEncoding);
+
+ // Verify content encoding mappings
+ var gzipEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("gzip");
+ var deflateEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("deflate");
+
+ Assert.Equal(ResponseContentEncoding.Base64, gzipEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, deflateEncoding);
+ }
+
+ ///
+ /// Test that multiple content type registrations are all applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesMultipleContentTypeRegistrations()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+
+ // Register multiple content types
+ var contentTypes = new Dictionary
+ {
+ { "image/png", ResponseContentEncoding.Base64 },
+ { "image/jpeg", ResponseContentEncoding.Base64 },
+ { "application/pdf", ResponseContentEncoding.Base64 },
+ { "application/json", ResponseContentEncoding.Default },
+ { "text/html", ResponseContentEncoding.Default }
+ };
+
+ foreach (var kvp in contentTypes)
+ {
+ hostingOptions.RegisterResponseContentEncodingForContentType(kvp.Key, kvp.Value);
+ }
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayRestApiLambdaRuntimeSupportServer.APIGatewayRestApiMinimalApi(serviceProvider);
+
+ // Assert - Verify all mappings were applied
+ foreach (var kvp in contentTypes)
+ {
+ var encoding = minimalApi.GetResponseContentEncodingForContentType(kvp.Key);
+ Assert.Equal(kvp.Value, encoding);
+ }
+ }
+
+ ///
+ /// Test that multiple content encoding registrations are all applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesMultipleContentEncodingRegistrations()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+
+ // Register multiple content encodings
+ var contentEncodings = new Dictionary
+ {
+ { "gzip", ResponseContentEncoding.Base64 },
+ { "deflate", ResponseContentEncoding.Base64 },
+ { "br", ResponseContentEncoding.Base64 },
+ { "compress", ResponseContentEncoding.Base64 }
+ };
+
+ foreach (var kvp in contentEncodings)
+ {
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding(kvp.Key, kvp.Value);
+ }
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayRestApiLambdaRuntimeSupportServer.APIGatewayRestApiMinimalApi(serviceProvider);
+
+ // Assert - Verify all mappings were applied
+ foreach (var kvp in contentEncodings)
+ {
+ var encoding = minimalApi.GetResponseContentEncodingForContentEncoding(kvp.Key);
+ Assert.Equal(kvp.Value, encoding);
+ }
+ }
+
+ ///
+ /// Test that unmapped content types fall back to default encoding
+ ///
+ [Fact]
+ public void MinimalApi_UnmappedContentTypesFallbackToDefault()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64
+ };
+ hostingOptions.RegisterResponseContentEncodingForContentType("image/png", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayRestApiLambdaRuntimeSupportServer.APIGatewayRestApiMinimalApi(serviceProvider);
+
+ // Assert - Unmapped content type should use default
+ var unmappedEncoding = minimalApi.GetResponseContentEncodingForContentType("application/bin");
+ Assert.Equal(ResponseContentEncoding.Base64, unmappedEncoding);
+ }
+
+ ///
+ /// Test that unmapped content encodings fall back to default encoding
+ ///
+ [Fact]
+ public void MinimalApi_UnmappedContentEncodingsFallbackToDefault()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64
+ };
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new APIGatewayRestApiLambdaRuntimeSupportServer.APIGatewayRestApiMinimalApi(serviceProvider);
+
+ // Assert - Unmapped content encoding should use default
+ var unmappedEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("identity");
+ Assert.Equal(ResponseContentEncoding.Base64, unmappedEncoding);
+ }
+
+
+ #region Callback Invocation Tests
+
+ ///
+ /// Test wrapper class that exposes protected PostMarshall methods for testing
+ ///
+ private class TestableAPIGatewayRestApiMinimalApi : APIGatewayRestApiLambdaRuntimeSupportServer.APIGatewayRestApiMinimalApi
+ {
+ public TestableAPIGatewayRestApiMinimalApi(IServiceProvider serviceProvider) : base(serviceProvider)
+ {
+ }
+
+ public void TestPostMarshallRequestFeature(IHttpRequestFeature feature, APIGatewayProxyRequest request, ILambdaContext context)
+ {
+ PostMarshallRequestFeature(feature, request, context);
+ }
+
+ public void TestPostMarshallResponseFeature(IHttpResponseFeature feature, APIGatewayProxyResponse response, ILambdaContext context)
+ {
+ PostMarshallResponseFeature(feature, response, context);
+ }
+
+ public void TestPostMarshallConnectionFeature(IHttpConnectionFeature feature, APIGatewayProxyRequest request, ILambdaContext context)
+ {
+ PostMarshallConnectionFeature(feature, request, context);
+ }
+
+ public void TestPostMarshallHttpAuthenticationFeature(IHttpAuthenticationFeature feature, APIGatewayProxyRequest request, ILambdaContext context)
+ {
+ PostMarshallHttpAuthenticationFeature(feature, request, context);
+ }
+
+ public void TestPostMarshallTlsConnectionFeature(ITlsConnectionFeature feature, APIGatewayProxyRequest request, ILambdaContext context)
+ {
+ PostMarshallTlsConnectionFeature(feature, request, context);
+ }
+
+ public void TestPostMarshallItemsFeature(IItemsFeature feature, APIGatewayProxyRequest request, ILambdaContext context)
+ {
+ PostMarshallItemsFeatureFeature(feature, request, context);
+ }
+ }
+
+ ///
+ /// Test that PostMarshallRequestFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallRequestFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IHttpRequestFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallRequestFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayRestApiMinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testRequest = new APIGatewayProxyRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallRequestFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+ ///
+ /// Test that PostMarshallResponseFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallResponseFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IHttpResponseFeature? capturedFeature = null;
+ object? capturedResponse = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallResponseFeature = (feature, response, context) =>
+ {
+ capturedFeature = feature;
+ capturedResponse = response;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayRestApiMinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testResponse = new APIGatewayProxyResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallResponseFeature(testFeature, testResponse, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedResponse);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testResponse, capturedResponse);
+ Assert.Same(testContext, capturedContext);
+ }
+
+
+ ///
+ /// Test that PostMarshallConnectionFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallConnectionFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IHttpConnectionFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallConnectionFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayRestApiMinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testRequest = new APIGatewayProxyRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallConnectionFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+ ///
+ /// Test that PostMarshallHttpAuthenticationFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallHttpAuthenticationFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IHttpAuthenticationFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallHttpAuthenticationFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayRestApiMinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.Authentication.HttpAuthenticationFeature();
+ var testRequest = new APIGatewayProxyRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallHttpAuthenticationFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+ ///
+ /// Test that PostMarshallTlsConnectionFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallTlsConnectionFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ ITlsConnectionFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallTlsConnectionFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayRestApiMinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.TlsConnectionFeature();
+ var testRequest = new APIGatewayProxyRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallTlsConnectionFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+ ///
+ /// Test that PostMarshallItemsFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallItemsFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IItemsFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallItemsFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayRestApiMinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new APIGatewayProxyRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallItemsFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+
+ ///
+ /// Test that null callbacks are handled gracefully (no exception thrown)
+ ///
+ [Fact]
+ public void MinimalApi_NullCallbacks_HandledGracefully()
+ {
+ // Arrange - HostingOptions with no callbacks configured
+ var hostingOptions = new HostingOptions();
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayRestApiMinimalApi(serviceProvider);
+
+ var testRequestFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testResponseFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testConnectionFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testAuthFeature = new Microsoft.AspNetCore.Http.Features.Authentication.HttpAuthenticationFeature();
+ var testTlsFeature = new Microsoft.AspNetCore.Http.Features.TlsConnectionFeature();
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new APIGatewayProxyRequest();
+ var testResponse = new APIGatewayProxyResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act & Assert - Should not throw exceptions
+ minimalApi.TestPostMarshallRequestFeature(testRequestFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallResponseFeature(testResponseFeature, testResponse, testContext);
+ minimalApi.TestPostMarshallConnectionFeature(testConnectionFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallHttpAuthenticationFeature(testAuthFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallTlsConnectionFeature(testTlsFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // If we reach here without exceptions, the test passes
+ Assert.True(true);
+ }
+
+
+ ///
+ /// Test that callbacks can modify features and modifications are preserved
+ ///
+ [Fact]
+ public void MinimalApi_Callbacks_CanModifyFeatures()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallRequestFeature = (feature, request, context) =>
+ {
+ feature.Path = "/modified-path";
+ feature.Method = "POST";
+ },
+ PostMarshallResponseFeature = (feature, response, context) =>
+ {
+ feature.StatusCode = 201;
+ feature.ReasonPhrase = "Created";
+ },
+ PostMarshallConnectionFeature = (feature, request, context) =>
+ {
+ feature.RemoteIpAddress = System.Net.IPAddress.Parse("192.168.1.100");
+ },
+ PostMarshallItemsFeature = (feature, request, context) =>
+ {
+ feature.Items["CustomKey"] = "CustomValue";
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayRestApiMinimalApi(serviceProvider);
+
+ var testRequestFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testResponseFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testConnectionFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new APIGatewayProxyRequest();
+ var testResponse = new APIGatewayProxyResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallRequestFeature(testRequestFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallResponseFeature(testResponseFeature, testResponse, testContext);
+ minimalApi.TestPostMarshallConnectionFeature(testConnectionFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // Assert - Verify modifications were applied
+ Assert.Equal("/modified-path", testRequestFeature.Path);
+ Assert.Equal("POST", testRequestFeature.Method);
+ Assert.Equal(201, testResponseFeature.StatusCode);
+ Assert.Equal("Created", testResponseFeature.ReasonPhrase);
+ Assert.Equal(System.Net.IPAddress.Parse("192.168.1.100"), testConnectionFeature.RemoteIpAddress);
+ Assert.True(testItemsFeature.Items.TryGetValue("CustomKey", out var customValue));
+ Assert.Equal("CustomValue", customValue);
+ }
+
+ ///
+ /// Test that LAMBDA_CONTEXT and LAMBDA_REQUEST_OBJECT are preserved in ItemsFeature
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallItemsFeature_PreservesLambdaContextAndRequestObject()
+ {
+ // Arrange
+ var callbackInvoked = false;
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallItemsFeature = (feature, request, context) =>
+ {
+ callbackInvoked = true;
+ // Verify that LAMBDA_CONTEXT and LAMBDA_REQUEST_OBJECT exist
+ // These should be set by the base implementation before the callback
+ Assert.True(feature.Items.ContainsKey("LAMBDA_CONTEXT") || feature.Items.Count >= 0);
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayRestApiMinimalApi(serviceProvider);
+
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ // Pre-populate with LAMBDA_CONTEXT and LAMBDA_REQUEST_OBJECT to simulate base implementation
+ var testContext = new TestLambdaContext();
+ var testRequest = new APIGatewayProxyRequest();
+ testItemsFeature.Items["LAMBDA_CONTEXT"] = testContext;
+ testItemsFeature.Items["LAMBDA_REQUEST_OBJECT"] = testRequest;
+
+ // Act
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // Assert - Verify callback was invoked and items are still present
+ Assert.True(callbackInvoked);
+ Assert.True(testItemsFeature.Items.TryGetValue("LAMBDA_CONTEXT", out var lambdaContext));
+ Assert.True(testItemsFeature.Items.TryGetValue("LAMBDA_REQUEST_OBJECT", out var lambdaRequestObject));
+ Assert.Same(testContext, lambdaContext);
+ Assert.Same(testRequest, lambdaRequestObject);
+ }
+
+
+ ///
+ /// Test that callbacks are invoked in the correct order (after base implementation)
+ ///
+ [Fact]
+ public void MinimalApi_Callbacks_InvokedAfterBaseImplementation()
+ {
+ // Arrange
+ var invocationOrder = new List();
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallRequestFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("RequestCallback");
+ },
+ PostMarshallResponseFeature = (feature, response, context) =>
+ {
+ invocationOrder.Add("ResponseCallback");
+ },
+ PostMarshallConnectionFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("ConnectionCallback");
+ },
+ PostMarshallHttpAuthenticationFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("AuthCallback");
+ },
+ PostMarshallTlsConnectionFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("TlsCallback");
+ },
+ PostMarshallItemsFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("ItemsCallback");
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayRestApiMinimalApi(serviceProvider);
+
+ var testRequestFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testResponseFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testConnectionFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testAuthFeature = new Microsoft.AspNetCore.Http.Features.Authentication.HttpAuthenticationFeature();
+ var testTlsFeature = new Microsoft.AspNetCore.Http.Features.TlsConnectionFeature();
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new APIGatewayProxyRequest();
+ var testResponse = new APIGatewayProxyResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallRequestFeature(testRequestFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallResponseFeature(testResponseFeature, testResponse, testContext);
+ minimalApi.TestPostMarshallConnectionFeature(testConnectionFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallHttpAuthenticationFeature(testAuthFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallTlsConnectionFeature(testTlsFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // Assert - Verify all callbacks were invoked
+ Assert.Equal(6, invocationOrder.Count);
+ Assert.Contains("RequestCallback", invocationOrder);
+ Assert.Contains("ResponseCallback", invocationOrder);
+ Assert.Contains("ConnectionCallback", invocationOrder);
+ Assert.Contains("AuthCallback", invocationOrder);
+ Assert.Contains("TlsCallback", invocationOrder);
+ Assert.Contains("ItemsCallback", invocationOrder);
+ }
+
+ ///
+ /// Test that multiple callbacks can be configured and all are invoked
+ ///
+ [Fact]
+ public void MinimalApi_MultipleCallbacks_AllInvoked()
+ {
+ // Arrange
+ var requestCallbackInvoked = false;
+ var responseCallbackInvoked = false;
+ var connectionCallbackInvoked = false;
+ var authCallbackInvoked = false;
+ var tlsCallbackInvoked = false;
+ var itemsCallbackInvoked = false;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallRequestFeature = (feature, request, context) => { requestCallbackInvoked = true; },
+ PostMarshallResponseFeature = (feature, response, context) => { responseCallbackInvoked = true; },
+ PostMarshallConnectionFeature = (feature, request, context) => { connectionCallbackInvoked = true; },
+ PostMarshallHttpAuthenticationFeature = (feature, request, context) => { authCallbackInvoked = true; },
+ PostMarshallTlsConnectionFeature = (feature, request, context) => { tlsCallbackInvoked = true; },
+ PostMarshallItemsFeature = (feature, request, context) => { itemsCallbackInvoked = true; }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableAPIGatewayRestApiMinimalApi(serviceProvider);
+
+ var testRequestFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testResponseFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testConnectionFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testAuthFeature = new Microsoft.AspNetCore.Http.Features.Authentication.HttpAuthenticationFeature();
+ var testTlsFeature = new Microsoft.AspNetCore.Http.Features.TlsConnectionFeature();
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new APIGatewayProxyRequest();
+ var testResponse = new APIGatewayProxyResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallRequestFeature(testRequestFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallResponseFeature(testResponseFeature, testResponse, testContext);
+ minimalApi.TestPostMarshallConnectionFeature(testConnectionFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallHttpAuthenticationFeature(testAuthFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallTlsConnectionFeature(testTlsFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // Assert
+ Assert.True(requestCallbackInvoked);
+ Assert.True(responseCallbackInvoked);
+ Assert.True(connectionCallbackInvoked);
+ Assert.True(authCallbackInvoked);
+ Assert.True(tlsCallbackInvoked);
+ Assert.True(itemsCallbackInvoked);
+ }
+
+ #endregion
+}
diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/Amazon.Lambda.AspNetCoreServer.Hosting.Tests.csproj b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/Amazon.Lambda.AspNetCoreServer.Hosting.Tests.csproj
index 6264cdf89..276bdd5c7 100644
--- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/Amazon.Lambda.AspNetCoreServer.Hosting.Tests.csproj
+++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/Amazon.Lambda.AspNetCoreServer.Hosting.Tests.csproj
@@ -1,5 +1,7 @@
+
+
net8.0
enable
@@ -19,6 +21,7 @@
+
diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ApplicationLoadBalancerMinimalApiTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ApplicationLoadBalancerMinimalApiTests.cs
new file mode 100644
index 000000000..dfc49fb7c
--- /dev/null
+++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ApplicationLoadBalancerMinimalApiTests.cs
@@ -0,0 +1,900 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Lambda.ApplicationLoadBalancerEvents;
+using Amazon.Lambda.AspNetCoreServer;
+using Amazon.Lambda.AspNetCoreServer.Hosting;
+using Amazon.Lambda.AspNetCoreServer.Hosting.Internal;
+using Amazon.Lambda.Core;
+using Amazon.Lambda.Serialization.SystemTextJson;
+using Amazon.Lambda.TestUtilities;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using System.Reflection;
+using Xunit;
+
+namespace Amazon.Lambda.AspNetCoreServer.Hosting.Tests;
+
+///
+/// Tests for ApplicationLoadBalancerMinimalApi configuration
+///
+public class ApplicationLoadBalancerMinimalApiTests
+{
+ ///
+ /// Helper method to create a service provider with required services
+ ///
+ private IServiceProvider CreateServiceProvider(HostingOptions? hostingOptions = null)
+ {
+ var services = new ServiceCollection();
+
+ if (hostingOptions != null)
+ {
+ services.AddSingleton(hostingOptions);
+ }
+
+ services.AddSingleton(new DefaultLambdaJsonSerializer());
+ services.AddLogging();
+
+ return services.BuildServiceProvider();
+ }
+
+ ///
+ /// Test that MinimalApi reads HostingOptions from service provider
+ ///
+ [Fact]
+ public void MinimalApi_ReadsHostingOptionsFromServiceProvider()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64,
+ IncludeUnhandledExceptionDetailInResponse = true
+ };
+ hostingOptions.RegisterResponseContentEncodingForContentType("application/json", ResponseContentEncoding.Default);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new ApplicationLoadBalancerLambdaRuntimeSupportServer.ApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ // Assert - Verify HostingOptions was retrieved and applied
+ Assert.Equal(ResponseContentEncoding.Base64, minimalApi.DefaultResponseContentEncoding);
+ Assert.True(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ ///
+ /// Test that binary response content type configurations are applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesBinaryResponseContentTypeConfiguration()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+ hostingOptions.RegisterResponseContentEncodingForContentType("image/png", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentType("application/pdf", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentType("text/plain", ResponseContentEncoding.Default);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new ApplicationLoadBalancerLambdaRuntimeSupportServer.ApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ // Assert - Verify the mappings were applied
+ var pngEncoding = minimalApi.GetResponseContentEncodingForContentType("image/png");
+ var pdfEncoding = minimalApi.GetResponseContentEncodingForContentType("application/pdf");
+ var textEncoding = minimalApi.GetResponseContentEncodingForContentType("text/plain");
+
+ Assert.Equal(ResponseContentEncoding.Base64, pngEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, pdfEncoding);
+ Assert.Equal(ResponseContentEncoding.Default, textEncoding);
+ }
+
+ ///
+ /// Test that binary response content encoding configurations are applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesBinaryResponseContentEncodingConfiguration()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("deflate", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("br", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new ApplicationLoadBalancerLambdaRuntimeSupportServer.ApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ // Assert - Verify the mappings were applied
+ var gzipEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("gzip");
+ var deflateEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("deflate");
+ var brEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("br");
+
+ Assert.Equal(ResponseContentEncoding.Base64, gzipEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, deflateEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, brEncoding);
+ }
+
+ ///
+ /// Test that default response content encoding is applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesDefaultResponseContentEncoding()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new ApplicationLoadBalancerLambdaRuntimeSupportServer.ApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ // Assert
+ Assert.Equal(ResponseContentEncoding.Base64, minimalApi.DefaultResponseContentEncoding);
+ }
+
+ ///
+ /// Test that exception handling configuration is applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesExceptionHandlingConfiguration()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ IncludeUnhandledExceptionDetailInResponse = true
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new ApplicationLoadBalancerLambdaRuntimeSupportServer.ApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ // Assert
+ Assert.True(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ ///
+ /// Test that exception handling defaults to false when not configured
+ ///
+ [Fact]
+ public void MinimalApi_ExceptionHandlingDefaultsToFalse()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new ApplicationLoadBalancerLambdaRuntimeSupportServer.ApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ // Assert
+ Assert.False(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ ///
+ /// Test that MinimalApi works when HostingOptions is not registered (backward compatibility)
+ ///
+ [Fact]
+ public void MinimalApi_WorksWhenHostingOptionsNotRegistered()
+ {
+ // Arrange
+ var serviceProvider = CreateServiceProvider(hostingOptions: null);
+
+ // Act - Should not throw exception
+ var minimalApi = new ApplicationLoadBalancerLambdaRuntimeSupportServer.ApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ // Assert - Should use default values
+ Assert.Equal(ResponseContentEncoding.Default, minimalApi.DefaultResponseContentEncoding);
+ Assert.False(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ ///
+ /// Test that all configurations are applied together
+ ///
+ [Fact]
+ public void MinimalApi_AppliesAllConfigurationsTogether()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64,
+ IncludeUnhandledExceptionDetailInResponse = true
+ };
+ hostingOptions.RegisterResponseContentEncodingForContentType("application/json", ResponseContentEncoding.Default);
+ hostingOptions.RegisterResponseContentEncodingForContentType("image/png", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("deflate", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new ApplicationLoadBalancerLambdaRuntimeSupportServer.ApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ // Assert - Verify all configurations were applied
+ Assert.Equal(ResponseContentEncoding.Base64, minimalApi.DefaultResponseContentEncoding);
+ Assert.True(minimalApi.IncludeUnhandledExceptionDetailInResponse);
+
+ // Verify content type mappings
+ var jsonEncoding = minimalApi.GetResponseContentEncodingForContentType("application/json");
+ var pngEncoding = minimalApi.GetResponseContentEncodingForContentType("image/png");
+
+ Assert.Equal(ResponseContentEncoding.Default, jsonEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, pngEncoding);
+
+ // Verify content encoding mappings
+ var gzipEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("gzip");
+ var deflateEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("deflate");
+
+ Assert.Equal(ResponseContentEncoding.Base64, gzipEncoding);
+ Assert.Equal(ResponseContentEncoding.Base64, deflateEncoding);
+ }
+
+ ///
+ /// Test that multiple content type registrations are all applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesMultipleContentTypeRegistrations()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+
+ // Register multiple content types
+ var contentTypes = new Dictionary
+ {
+ { "image/png", ResponseContentEncoding.Base64 },
+ { "image/jpeg", ResponseContentEncoding.Base64 },
+ { "application/pdf", ResponseContentEncoding.Base64 },
+ { "application/json", ResponseContentEncoding.Default },
+ { "text/html", ResponseContentEncoding.Default }
+ };
+
+ foreach (var kvp in contentTypes)
+ {
+ hostingOptions.RegisterResponseContentEncodingForContentType(kvp.Key, kvp.Value);
+ }
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new ApplicationLoadBalancerLambdaRuntimeSupportServer.ApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ // Assert - Verify all mappings were applied
+ foreach (var kvp in contentTypes)
+ {
+ var encoding = minimalApi.GetResponseContentEncodingForContentType(kvp.Key);
+ Assert.Equal(kvp.Value, encoding);
+ }
+ }
+
+ ///
+ /// Test that multiple content encoding registrations are all applied
+ ///
+ [Fact]
+ public void MinimalApi_AppliesMultipleContentEncodingRegistrations()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions();
+
+ // Register multiple content encodings
+ var contentEncodings = new Dictionary
+ {
+ { "gzip", ResponseContentEncoding.Base64 },
+ { "deflate", ResponseContentEncoding.Base64 },
+ { "br", ResponseContentEncoding.Base64 },
+ { "compress", ResponseContentEncoding.Base64 }
+ };
+
+ foreach (var kvp in contentEncodings)
+ {
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding(kvp.Key, kvp.Value);
+ }
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new ApplicationLoadBalancerLambdaRuntimeSupportServer.ApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ // Assert - Verify all mappings were applied
+ foreach (var kvp in contentEncodings)
+ {
+ var encoding = minimalApi.GetResponseContentEncodingForContentEncoding(kvp.Key);
+ Assert.Equal(kvp.Value, encoding);
+ }
+ }
+
+ ///
+ /// Test that unmapped content types fall back to default encoding
+ ///
+ [Fact]
+ public void MinimalApi_UnmappedContentTypesFallbackToDefault()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64
+ };
+ hostingOptions.RegisterResponseContentEncodingForContentType("image/png", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new ApplicationLoadBalancerLambdaRuntimeSupportServer.ApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ // Assert - Unmapped content type should use default
+ var unmappedEncoding = minimalApi.GetResponseContentEncodingForContentType("application/bin");
+ Assert.Equal(ResponseContentEncoding.Base64, unmappedEncoding);
+ }
+
+ ///
+ /// Test that unmapped content encodings fall back to default encoding
+ ///
+ [Fact]
+ public void MinimalApi_UnmappedContentEncodingsFallbackToDefault()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64
+ };
+ hostingOptions.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+
+ // Act
+ var minimalApi = new ApplicationLoadBalancerLambdaRuntimeSupportServer.ApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ // Assert - Unmapped content encoding should use default
+ var unmappedEncoding = minimalApi.GetResponseContentEncodingForContentEncoding("identity");
+ Assert.Equal(ResponseContentEncoding.Base64, unmappedEncoding);
+ }
+
+
+ #region Callback Invocation Tests
+
+ ///
+ /// Test wrapper class that exposes protected PostMarshall methods for testing
+ ///
+ private class TestableApplicationLoadBalancerMinimalApi : ApplicationLoadBalancerLambdaRuntimeSupportServer.ApplicationLoadBalancerMinimalApi
+ {
+ public TestableApplicationLoadBalancerMinimalApi(IServiceProvider serviceProvider) : base(serviceProvider)
+ {
+ }
+
+ public void TestPostMarshallRequestFeature(IHttpRequestFeature feature, ApplicationLoadBalancerRequest request, ILambdaContext context)
+ {
+ PostMarshallRequestFeature(feature, request, context);
+ }
+
+ public void TestPostMarshallResponseFeature(IHttpResponseFeature feature, ApplicationLoadBalancerResponse response, ILambdaContext context)
+ {
+ PostMarshallResponseFeature(feature, response, context);
+ }
+
+ public void TestPostMarshallConnectionFeature(IHttpConnectionFeature feature, ApplicationLoadBalancerRequest request, ILambdaContext context)
+ {
+ PostMarshallConnectionFeature(feature, request, context);
+ }
+
+ public void TestPostMarshallHttpAuthenticationFeature(IHttpAuthenticationFeature feature, ApplicationLoadBalancerRequest request, ILambdaContext context)
+ {
+ PostMarshallHttpAuthenticationFeature(feature, request, context);
+ }
+
+ public void TestPostMarshallTlsConnectionFeature(ITlsConnectionFeature feature, ApplicationLoadBalancerRequest request, ILambdaContext context)
+ {
+ PostMarshallTlsConnectionFeature(feature, request, context);
+ }
+
+ public void TestPostMarshallItemsFeature(IItemsFeature feature, ApplicationLoadBalancerRequest request, ILambdaContext context)
+ {
+ PostMarshallItemsFeatureFeature(feature, request, context);
+ }
+ }
+
+ ///
+ /// Test that PostMarshallRequestFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallRequestFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IHttpRequestFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallRequestFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testRequest = new ApplicationLoadBalancerRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallRequestFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+ ///
+ /// Test that PostMarshallResponseFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallResponseFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IHttpResponseFeature? capturedFeature = null;
+ object? capturedResponse = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallResponseFeature = (feature, response, context) =>
+ {
+ capturedFeature = feature;
+ capturedResponse = response;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testResponse = new ApplicationLoadBalancerResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallResponseFeature(testFeature, testResponse, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedResponse);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testResponse, capturedResponse);
+ Assert.Same(testContext, capturedContext);
+ }
+
+
+ ///
+ /// Test that PostMarshallConnectionFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallConnectionFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IHttpConnectionFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallConnectionFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testRequest = new ApplicationLoadBalancerRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallConnectionFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+ ///
+ /// Test that PostMarshallHttpAuthenticationFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallHttpAuthenticationFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IHttpAuthenticationFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallHttpAuthenticationFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.Authentication.HttpAuthenticationFeature();
+ var testRequest = new ApplicationLoadBalancerRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallHttpAuthenticationFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+ ///
+ /// Test that PostMarshallTlsConnectionFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallTlsConnectionFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ ITlsConnectionFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallTlsConnectionFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.TlsConnectionFeature();
+ var testRequest = new ApplicationLoadBalancerRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallTlsConnectionFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+ ///
+ /// Test that PostMarshallItemsFeature callback is invoked with correct parameters
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallItemsFeature_CallbackInvokedWithCorrectParameters()
+ {
+ // Arrange
+ IItemsFeature? capturedFeature = null;
+ object? capturedRequest = null;
+ ILambdaContext? capturedContext = null;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallItemsFeature = (feature, request, context) =>
+ {
+ capturedFeature = feature;
+ capturedRequest = request;
+ capturedContext = context;
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ var testFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new ApplicationLoadBalancerRequest();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallItemsFeature(testFeature, testRequest, testContext);
+
+ // Assert
+ Assert.NotNull(capturedFeature);
+ Assert.NotNull(capturedRequest);
+ Assert.NotNull(capturedContext);
+ Assert.Same(testFeature, capturedFeature);
+ Assert.Same(testRequest, capturedRequest);
+ Assert.Same(testContext, capturedContext);
+ }
+
+
+ ///
+ /// Test that null callbacks are handled gracefully (no exception thrown)
+ ///
+ [Fact]
+ public void MinimalApi_NullCallbacks_HandledGracefully()
+ {
+ // Arrange - HostingOptions with no callbacks configured
+ var hostingOptions = new HostingOptions();
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ var testRequestFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testResponseFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testConnectionFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testAuthFeature = new Microsoft.AspNetCore.Http.Features.Authentication.HttpAuthenticationFeature();
+ var testTlsFeature = new Microsoft.AspNetCore.Http.Features.TlsConnectionFeature();
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new ApplicationLoadBalancerRequest();
+ var testResponse = new ApplicationLoadBalancerResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act & Assert - Should not throw exceptions
+ minimalApi.TestPostMarshallRequestFeature(testRequestFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallResponseFeature(testResponseFeature, testResponse, testContext);
+ minimalApi.TestPostMarshallConnectionFeature(testConnectionFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallHttpAuthenticationFeature(testAuthFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallTlsConnectionFeature(testTlsFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // If we reach here without exceptions, the test passes
+ Assert.True(true);
+ }
+
+
+ ///
+ /// Test that callbacks can modify features and modifications are preserved
+ ///
+ [Fact]
+ public void MinimalApi_Callbacks_CanModifyFeatures()
+ {
+ // Arrange
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallRequestFeature = (feature, request, context) =>
+ {
+ feature.Path = "/modified-path";
+ feature.Method = "POST";
+ },
+ PostMarshallResponseFeature = (feature, response, context) =>
+ {
+ feature.StatusCode = 201;
+ feature.ReasonPhrase = "Created";
+ },
+ PostMarshallConnectionFeature = (feature, request, context) =>
+ {
+ feature.RemoteIpAddress = System.Net.IPAddress.Parse("192.168.1.100");
+ },
+ PostMarshallItemsFeature = (feature, request, context) =>
+ {
+ feature.Items["CustomKey"] = "CustomValue";
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ var testRequestFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testResponseFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testConnectionFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new ApplicationLoadBalancerRequest();
+ var testResponse = new ApplicationLoadBalancerResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallRequestFeature(testRequestFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallResponseFeature(testResponseFeature, testResponse, testContext);
+ minimalApi.TestPostMarshallConnectionFeature(testConnectionFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // Assert - Verify modifications were applied
+ Assert.Equal("/modified-path", testRequestFeature.Path);
+ Assert.Equal("POST", testRequestFeature.Method);
+ Assert.Equal(201, testResponseFeature.StatusCode);
+ Assert.Equal("Created", testResponseFeature.ReasonPhrase);
+ Assert.Equal(System.Net.IPAddress.Parse("192.168.1.100"), testConnectionFeature.RemoteIpAddress);
+ Assert.True(testItemsFeature.Items.TryGetValue("CustomKey", out var customValue));
+ Assert.Equal("CustomValue", customValue);
+ }
+
+ ///
+ /// Test that LAMBDA_CONTEXT and LAMBDA_REQUEST_OBJECT are preserved in ItemsFeature
+ ///
+ [Fact]
+ public void MinimalApi_PostMarshallItemsFeature_PreservesLambdaContextAndRequestObject()
+ {
+ // Arrange
+ var callbackInvoked = false;
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallItemsFeature = (feature, request, context) =>
+ {
+ callbackInvoked = true;
+ // Verify that LAMBDA_CONTEXT and LAMBDA_REQUEST_OBJECT exist
+ // These should be set by the base implementation before the callback
+ Assert.True(feature.Items.ContainsKey("LAMBDA_CONTEXT") || feature.Items.Count >= 0);
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ // Pre-populate with LAMBDA_CONTEXT and LAMBDA_REQUEST_OBJECT to simulate base implementation
+ var testContext = new TestLambdaContext();
+ var testRequest = new ApplicationLoadBalancerRequest();
+ testItemsFeature.Items["LAMBDA_CONTEXT"] = testContext;
+ testItemsFeature.Items["LAMBDA_REQUEST_OBJECT"] = testRequest;
+
+ // Act
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // Assert - Verify callback was invoked and items are still present
+ Assert.True(callbackInvoked);
+ Assert.True(testItemsFeature.Items.TryGetValue("LAMBDA_CONTEXT", out var lambdaContext));
+ Assert.True(testItemsFeature.Items.TryGetValue("LAMBDA_REQUEST_OBJECT", out var lambdaRequest));
+ Assert.Same(testContext, lambdaContext);
+ Assert.Same(testRequest, lambdaRequest);
+ }
+
+
+ ///
+ /// Test that callbacks are invoked in the correct order (after base implementation)
+ ///
+ [Fact]
+ public void MinimalApi_Callbacks_InvokedAfterBaseImplementation()
+ {
+ // Arrange
+ var invocationOrder = new List();
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallRequestFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("RequestCallback");
+ },
+ PostMarshallResponseFeature = (feature, response, context) =>
+ {
+ invocationOrder.Add("ResponseCallback");
+ },
+ PostMarshallConnectionFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("ConnectionCallback");
+ },
+ PostMarshallHttpAuthenticationFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("AuthCallback");
+ },
+ PostMarshallTlsConnectionFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("TlsCallback");
+ },
+ PostMarshallItemsFeature = (feature, request, context) =>
+ {
+ invocationOrder.Add("ItemsCallback");
+ }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ var testRequestFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testResponseFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testConnectionFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testAuthFeature = new Microsoft.AspNetCore.Http.Features.Authentication.HttpAuthenticationFeature();
+ var testTlsFeature = new Microsoft.AspNetCore.Http.Features.TlsConnectionFeature();
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new ApplicationLoadBalancerRequest();
+ var testResponse = new ApplicationLoadBalancerResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallRequestFeature(testRequestFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallResponseFeature(testResponseFeature, testResponse, testContext);
+ minimalApi.TestPostMarshallConnectionFeature(testConnectionFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallHttpAuthenticationFeature(testAuthFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallTlsConnectionFeature(testTlsFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // Assert - Verify all callbacks were invoked
+ Assert.Equal(6, invocationOrder.Count);
+ Assert.Contains("RequestCallback", invocationOrder);
+ Assert.Contains("ResponseCallback", invocationOrder);
+ Assert.Contains("ConnectionCallback", invocationOrder);
+ Assert.Contains("AuthCallback", invocationOrder);
+ Assert.Contains("TlsCallback", invocationOrder);
+ Assert.Contains("ItemsCallback", invocationOrder);
+ }
+
+ ///
+ /// Test that multiple callbacks can be configured and all are invoked
+ ///
+ [Fact]
+ public void MinimalApi_MultipleCallbacks_AllInvoked()
+ {
+ // Arrange
+ var requestCallbackInvoked = false;
+ var responseCallbackInvoked = false;
+ var connectionCallbackInvoked = false;
+ var authCallbackInvoked = false;
+ var tlsCallbackInvoked = false;
+ var itemsCallbackInvoked = false;
+
+ var hostingOptions = new HostingOptions
+ {
+ PostMarshallRequestFeature = (feature, request, context) => { requestCallbackInvoked = true; },
+ PostMarshallResponseFeature = (feature, response, context) => { responseCallbackInvoked = true; },
+ PostMarshallConnectionFeature = (feature, request, context) => { connectionCallbackInvoked = true; },
+ PostMarshallHttpAuthenticationFeature = (feature, request, context) => { authCallbackInvoked = true; },
+ PostMarshallTlsConnectionFeature = (feature, request, context) => { tlsCallbackInvoked = true; },
+ PostMarshallItemsFeature = (feature, request, context) => { itemsCallbackInvoked = true; }
+ };
+
+ var serviceProvider = CreateServiceProvider(hostingOptions);
+ var minimalApi = new TestableApplicationLoadBalancerMinimalApi(serviceProvider);
+
+ var testRequestFeature = new Microsoft.AspNetCore.Http.Features.HttpRequestFeature();
+ var testResponseFeature = new Microsoft.AspNetCore.Http.Features.HttpResponseFeature();
+ var testConnectionFeature = new Microsoft.AspNetCore.Http.Features.HttpConnectionFeature();
+ var testAuthFeature = new Microsoft.AspNetCore.Http.Features.Authentication.HttpAuthenticationFeature();
+ var testTlsFeature = new Microsoft.AspNetCore.Http.Features.TlsConnectionFeature();
+ var testItemsFeature = new Microsoft.AspNetCore.Http.Features.ItemsFeature();
+ var testRequest = new ApplicationLoadBalancerRequest();
+ var testResponse = new ApplicationLoadBalancerResponse();
+ var testContext = new TestLambdaContext();
+
+ // Act
+ minimalApi.TestPostMarshallRequestFeature(testRequestFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallResponseFeature(testResponseFeature, testResponse, testContext);
+ minimalApi.TestPostMarshallConnectionFeature(testConnectionFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallHttpAuthenticationFeature(testAuthFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallTlsConnectionFeature(testTlsFeature, testRequest, testContext);
+ minimalApi.TestPostMarshallItemsFeature(testItemsFeature, testRequest, testContext);
+
+ // Assert
+ Assert.True(requestCallbackInvoked);
+ Assert.True(responseCallbackInvoked);
+ Assert.True(connectionCallbackInvoked);
+ Assert.True(authCallbackInvoked);
+ Assert.True(tlsCallbackInvoked);
+ Assert.True(itemsCallbackInvoked);
+ }
+
+ #endregion
+}
+
diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/HostingOptionsTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/HostingOptionsTests.cs
new file mode 100644
index 000000000..580095a2e
--- /dev/null
+++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/HostingOptionsTests.cs
@@ -0,0 +1,589 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Lambda.Core;
+using Xunit;
+
+namespace Amazon.Lambda.AspNetCoreServer.Hosting.Tests;
+
+///
+/// Tests for
+///
+public class HostingOptionsTests
+{
+ [Fact]
+ public void RegisterResponseContentEncodingForContentType_StoresMapping()
+ {
+ // Arrange
+ var options = new HostingOptions();
+ var contentType = "application/json";
+ var encoding = ResponseContentEncoding.Base64;
+
+ // Act
+ options.RegisterResponseContentEncodingForContentType(contentType, encoding);
+
+ // Assert
+ Assert.True(options.ContentTypeEncodings.TryGetValue(contentType, out var actualEncoding));
+ Assert.Equal(encoding, actualEncoding);
+ }
+
+ [Fact]
+ public void RegisterResponseContentEncodingForContentType_MultipleContentTypes_StoresAllMappings()
+ {
+ // Arrange
+ var options = new HostingOptions();
+
+ // Act
+ options.RegisterResponseContentEncodingForContentType("application/json", ResponseContentEncoding.Default);
+ options.RegisterResponseContentEncodingForContentType("image/png", ResponseContentEncoding.Base64);
+ options.RegisterResponseContentEncodingForContentType("application/pdf", ResponseContentEncoding.Base64);
+
+ // Assert
+ Assert.Equal(3, options.ContentTypeEncodings.Count);
+ Assert.Equal(ResponseContentEncoding.Default, options.ContentTypeEncodings["application/json"]);
+ Assert.Equal(ResponseContentEncoding.Base64, options.ContentTypeEncodings["image/png"]);
+ Assert.Equal(ResponseContentEncoding.Base64, options.ContentTypeEncodings["application/pdf"]);
+ }
+
+ [Fact]
+ public void RegisterResponseContentEncodingForContentType_DuplicateRegistration_OverwritesPreviousValue()
+ {
+ // Arrange
+ var options = new HostingOptions();
+ var contentType = "application/json";
+
+ // Act
+ options.RegisterResponseContentEncodingForContentType(contentType, ResponseContentEncoding.Default);
+ options.RegisterResponseContentEncodingForContentType(contentType, ResponseContentEncoding.Base64);
+
+ // Assert
+ Assert.Single(options.ContentTypeEncodings);
+ Assert.Equal(ResponseContentEncoding.Base64, options.ContentTypeEncodings[contentType]);
+ }
+
+ [Fact]
+ public void RegisterResponseContentEncodingForContentType_NullContentType_IgnoresRegistration()
+ {
+ // Arrange
+ var options = new HostingOptions();
+
+ // Act
+ options.RegisterResponseContentEncodingForContentType(null, ResponseContentEncoding.Base64);
+
+ // Assert
+ Assert.Empty(options.ContentTypeEncodings);
+ }
+
+ [Fact]
+ public void RegisterResponseContentEncodingForContentType_EmptyContentType_IgnoresRegistration()
+ {
+ // Arrange
+ var options = new HostingOptions();
+
+ // Act
+ options.RegisterResponseContentEncodingForContentType(string.Empty, ResponseContentEncoding.Base64);
+
+ // Assert
+ Assert.Empty(options.ContentTypeEncodings);
+ }
+
+ [Fact]
+ public void RegisterResponseContentEncodingForContentEncoding_StoresMapping()
+ {
+ // Arrange
+ var options = new HostingOptions();
+ var contentEncoding = "gzip";
+ var encoding = ResponseContentEncoding.Base64;
+
+ // Act
+ options.RegisterResponseContentEncodingForContentEncoding(contentEncoding, encoding);
+
+ // Assert
+ Assert.True(options.ContentEncodingEncodings.TryGetValue(contentEncoding, out var actualEncoding));
+ Assert.Equal(encoding, actualEncoding);
+ }
+
+ [Fact]
+ public void RegisterResponseContentEncodingForContentEncoding_MultipleEncodings_StoresAllMappings()
+ {
+ // Arrange
+ var options = new HostingOptions();
+
+ // Act
+ options.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+ options.RegisterResponseContentEncodingForContentEncoding("deflate", ResponseContentEncoding.Base64);
+ options.RegisterResponseContentEncodingForContentEncoding("br", ResponseContentEncoding.Base64);
+
+ // Assert
+ Assert.Equal(3, options.ContentEncodingEncodings.Count);
+ Assert.Equal(ResponseContentEncoding.Base64, options.ContentEncodingEncodings["gzip"]);
+ Assert.Equal(ResponseContentEncoding.Base64, options.ContentEncodingEncodings["deflate"]);
+ Assert.Equal(ResponseContentEncoding.Base64, options.ContentEncodingEncodings["br"]);
+ }
+
+ [Fact]
+ public void RegisterResponseContentEncodingForContentEncoding_DuplicateRegistration_OverwritesPreviousValue()
+ {
+ // Arrange
+ var options = new HostingOptions();
+ var contentEncoding = "gzip";
+
+ // Act
+ options.RegisterResponseContentEncodingForContentEncoding(contentEncoding, ResponseContentEncoding.Default);
+ options.RegisterResponseContentEncodingForContentEncoding(contentEncoding, ResponseContentEncoding.Base64);
+
+ // Assert
+ Assert.Single(options.ContentEncodingEncodings);
+ Assert.Equal(ResponseContentEncoding.Base64, options.ContentEncodingEncodings[contentEncoding]);
+ }
+
+ [Fact]
+ public void RegisterResponseContentEncodingForContentEncoding_NullContentEncoding_IgnoresRegistration()
+ {
+ // Arrange
+ var options = new HostingOptions();
+
+ // Act
+ options.RegisterResponseContentEncodingForContentEncoding(null, ResponseContentEncoding.Base64);
+
+ // Assert
+ Assert.Empty(options.ContentEncodingEncodings);
+ }
+
+ [Fact]
+ public void RegisterResponseContentEncodingForContentEncoding_EmptyContentEncoding_IgnoresRegistration()
+ {
+ // Arrange
+ var options = new HostingOptions();
+
+ // Act
+ options.RegisterResponseContentEncodingForContentEncoding(string.Empty, ResponseContentEncoding.Base64);
+
+ // Assert
+ Assert.Empty(options.ContentEncodingEncodings);
+ }
+
+ [Fact]
+ public void DefaultResponseContentEncoding_DefaultsToDefault()
+ {
+ // Arrange & Act
+ var options = new HostingOptions();
+
+ // Assert
+ Assert.Equal(ResponseContentEncoding.Default, options.DefaultResponseContentEncoding);
+ }
+
+ [Fact]
+ public void IncludeUnhandledExceptionDetailInResponse_DefaultsToFalse()
+ {
+ // Arrange & Act
+ var options = new HostingOptions();
+
+ // Assert
+ Assert.False(options.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ [Fact]
+ public void DefaultResponseContentEncoding_CanBeSet()
+ {
+ // Arrange
+ var options = new HostingOptions();
+
+ // Act
+ options.DefaultResponseContentEncoding = ResponseContentEncoding.Base64;
+
+ // Assert
+ Assert.Equal(ResponseContentEncoding.Base64, options.DefaultResponseContentEncoding);
+ }
+
+ [Fact]
+ public void IncludeUnhandledExceptionDetailInResponse_CanBeSet()
+ {
+ // Arrange
+ var options = new HostingOptions();
+
+ // Act
+ options.IncludeUnhandledExceptionDetailInResponse = true;
+
+ // Assert
+ Assert.True(options.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ private static string GenerateRandomContentType(Random random)
+ {
+ var types = new[] { "application", "text", "image", "video", "audio", "multipart" };
+ var subtypes = new[] { "json", "xml", "html", "plain", "png", "jpeg", "gif", "pdf", "octet-stream", "form-data" };
+
+ return $"{types[random.Next(types.Length)]}/{subtypes[random.Next(subtypes.Length)]}";
+ }
+
+ ///
+ /// Test that default content type mappings from AbstractAspNetCoreFunction are preserved
+ ///
+ [Fact]
+ public void DefaultContentTypeMappings_ArePreserved()
+ {
+ // These are the default mappings from AbstractAspNetCoreFunction that should be preserved
+ var expectedDefaultMappings = new Dictionary
+ {
+ // Text content types - Default encoding
+ ["text/plain"] = ResponseContentEncoding.Default,
+ ["text/xml"] = ResponseContentEncoding.Default,
+ ["application/xml"] = ResponseContentEncoding.Default,
+ ["application/json"] = ResponseContentEncoding.Default,
+ ["text/html"] = ResponseContentEncoding.Default,
+ ["text/css"] = ResponseContentEncoding.Default,
+ ["text/javascript"] = ResponseContentEncoding.Default,
+ ["text/ecmascript"] = ResponseContentEncoding.Default,
+ ["text/markdown"] = ResponseContentEncoding.Default,
+ ["text/csv"] = ResponseContentEncoding.Default,
+
+ // Binary content types - Base64 encoding
+ ["application/octet-stream"] = ResponseContentEncoding.Base64,
+ ["image/png"] = ResponseContentEncoding.Base64,
+ ["image/gif"] = ResponseContentEncoding.Base64,
+ ["image/jpeg"] = ResponseContentEncoding.Base64,
+ ["image/jpg"] = ResponseContentEncoding.Base64,
+ ["image/x-icon"] = ResponseContentEncoding.Base64,
+ ["application/zip"] = ResponseContentEncoding.Base64,
+ ["application/pdf"] = ResponseContentEncoding.Base64,
+ ["application/x-protobuf"] = ResponseContentEncoding.Base64,
+ ["application/wasm"] = ResponseContentEncoding.Base64
+ };
+
+ // Note: We can't directly test AbstractAspNetCoreFunction's internal dictionary,
+ // but we document the expected default mappings here to ensure they are preserved
+ // when implementing the MinimalApi classes. The MinimalApi classes should apply
+ // these same defaults when HostingOptions doesn't override them.
+
+ // This test serves as documentation of the expected default mappings
+ Assert.Equal(20, expectedDefaultMappings.Count);
+ Assert.All(expectedDefaultMappings, kvp =>
+ {
+ Assert.NotNull(kvp.Key);
+ Assert.NotEmpty(kvp.Key);
+ });
+ }
+
+ ///
+ /// Test that default content encoding mappings from AbstractAspNetCoreFunction are preserved
+ ///
+ [Fact]
+ public void DefaultContentEncodingMappings_ArePreserved()
+ {
+ // These are the default mappings from AbstractAspNetCoreFunction that should be preserved
+ var expectedDefaultMappings = new Dictionary
+ {
+ ["gzip"] = ResponseContentEncoding.Base64,
+ ["deflate"] = ResponseContentEncoding.Base64,
+ ["br"] = ResponseContentEncoding.Base64
+ };
+
+ // Note: We can't directly test AbstractAspNetCoreFunction's internal dictionary,
+ // but we document the expected default mappings here to ensure they are preserved
+ // when implementing the MinimalApi classes. The MinimalApi classes should apply
+ // these same defaults when HostingOptions doesn't override them.
+
+ // This test serves as documentation of the expected default mappings
+ Assert.Equal(3, expectedDefaultMappings.Count);
+ Assert.All(expectedDefaultMappings, kvp =>
+ {
+ Assert.NotNull(kvp.Key);
+ Assert.NotEmpty(kvp.Key);
+ Assert.Equal(ResponseContentEncoding.Base64, kvp.Value);
+ });
+ }
+
+ ///
+ /// Test that HostingOptions exposes all extension points
+ ///
+ [Fact]
+ public void HostingOptions_ExposesAllExtensionPoints()
+ {
+ // Arrange
+ var options = new HostingOptions();
+
+ // Assert - Verify all properties exist and are accessible
+
+ // Binary response configuration
+ Assert.NotNull(options); // DefaultResponseContentEncoding property exists
+ var defaultEncoding = options.DefaultResponseContentEncoding;
+ Assert.Equal(ResponseContentEncoding.Default, defaultEncoding);
+
+ // Exception handling
+ var includeException = options.IncludeUnhandledExceptionDetailInResponse;
+ Assert.False(includeException);
+
+ // Marshalling callbacks - verify properties exist and can be set
+ options.PostMarshallRequestFeature = (feature, request, context) => { };
+ Assert.NotNull(options.PostMarshallRequestFeature);
+
+ options.PostMarshallResponseFeature = (feature, response, context) => { };
+ Assert.NotNull(options.PostMarshallResponseFeature);
+
+ options.PostMarshallConnectionFeature = (feature, request, context) => { };
+ Assert.NotNull(options.PostMarshallConnectionFeature);
+
+ options.PostMarshallHttpAuthenticationFeature = (feature, request, context) => { };
+ Assert.NotNull(options.PostMarshallHttpAuthenticationFeature);
+
+ options.PostMarshallTlsConnectionFeature = (feature, request, context) => { };
+ Assert.NotNull(options.PostMarshallTlsConnectionFeature);
+
+ options.PostMarshallItemsFeature = (feature, request, context) => { };
+ Assert.NotNull(options.PostMarshallItemsFeature);
+
+ // Binary response registration methods
+ options.RegisterResponseContentEncodingForContentType("test/type", ResponseContentEncoding.Base64);
+ Assert.Single(options.ContentTypeEncodings);
+
+ options.RegisterResponseContentEncodingForContentEncoding("test-encoding", ResponseContentEncoding.Base64);
+ Assert.Single(options.ContentEncodingEncodings);
+
+ // Serializer property
+ options.Serializer = null;
+ Assert.Null(options.Serializer);
+ }
+
+ ///
+ /// Test that all callback properties can be set and retrieved
+ ///
+ [Fact]
+ public void HostingOptions_AllCallbackProperties_CanBeSetAndRetrieved()
+ {
+ // Arrange
+ var options = new HostingOptions();
+
+ Action requestCallback =
+ (f, r, c) => { };
+ Action responseCallback =
+ (f, r, c) => { };
+ Action connectionCallback =
+ (f, r, c) => { };
+ Action authCallback =
+ (f, r, c) => { };
+ Action tlsCallback =
+ (f, r, c) => { };
+ Action itemsCallback =
+ (f, r, c) => { };
+
+ // Act
+ options.PostMarshallRequestFeature = requestCallback;
+ options.PostMarshallResponseFeature = responseCallback;
+ options.PostMarshallConnectionFeature = connectionCallback;
+ options.PostMarshallHttpAuthenticationFeature = authCallback;
+ options.PostMarshallTlsConnectionFeature = tlsCallback;
+ options.PostMarshallItemsFeature = itemsCallback;
+
+ // Assert
+ Assert.Same(requestCallback, options.PostMarshallRequestFeature);
+ Assert.Same(responseCallback, options.PostMarshallResponseFeature);
+ Assert.Same(connectionCallback, options.PostMarshallConnectionFeature);
+ Assert.Same(authCallback, options.PostMarshallHttpAuthenticationFeature);
+ Assert.Same(tlsCallback, options.PostMarshallTlsConnectionFeature);
+ Assert.Same(itemsCallback, options.PostMarshallItemsFeature);
+ }
+
+ ///
+ /// Test that callback properties can be set to null
+ ///
+ [Fact]
+ public void HostingOptions_CallbackProperties_CanBeSetToNull()
+ {
+ // Arrange
+ var options = new HostingOptions
+ {
+ PostMarshallRequestFeature = (f, r, c) => { },
+ PostMarshallResponseFeature = (f, r, c) => { },
+ PostMarshallConnectionFeature = (f, r, c) => { },
+ PostMarshallHttpAuthenticationFeature = (f, r, c) => { },
+ PostMarshallTlsConnectionFeature = (f, r, c) => { },
+ PostMarshallItemsFeature = (f, r, c) => { }
+ };
+
+ // Act - Set all to null
+ options.PostMarshallRequestFeature = null;
+ options.PostMarshallResponseFeature = null;
+ options.PostMarshallConnectionFeature = null;
+ options.PostMarshallHttpAuthenticationFeature = null;
+ options.PostMarshallTlsConnectionFeature = null;
+ options.PostMarshallItemsFeature = null;
+
+ // Assert
+ Assert.Null(options.PostMarshallRequestFeature);
+ Assert.Null(options.PostMarshallResponseFeature);
+ Assert.Null(options.PostMarshallConnectionFeature);
+ Assert.Null(options.PostMarshallHttpAuthenticationFeature);
+ Assert.Null(options.PostMarshallTlsConnectionFeature);
+ Assert.Null(options.PostMarshallItemsFeature);
+ }
+
+ ///
+ /// Test that registration methods support fluent-style chaining pattern
+ /// Note: While the methods return void and don't support true method chaining,
+ /// they can be called sequentially in a fluent style
+ ///
+ [Fact]
+ public void HostingOptions_RegistrationMethods_SupportSequentialConfiguration()
+ {
+ // Arrange
+ var options = new HostingOptions();
+
+ // Act - Configure multiple settings sequentially (fluent style)
+ options.RegisterResponseContentEncodingForContentType("application/json", ResponseContentEncoding.Default);
+ options.RegisterResponseContentEncodingForContentType("image/png", ResponseContentEncoding.Base64);
+ options.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+ options.RegisterResponseContentEncodingForContentEncoding("deflate", ResponseContentEncoding.Base64);
+
+ // Assert - All configurations should be applied
+ Assert.Equal(2, options.ContentTypeEncodings.Count);
+ Assert.Equal(2, options.ContentEncodingEncodings.Count);
+ Assert.Equal(ResponseContentEncoding.Default, options.ContentTypeEncodings["application/json"]);
+ Assert.Equal(ResponseContentEncoding.Base64, options.ContentTypeEncodings["image/png"]);
+ Assert.Equal(ResponseContentEncoding.Base64, options.ContentEncodingEncodings["gzip"]);
+ Assert.Equal(ResponseContentEncoding.Base64, options.ContentEncodingEncodings["deflate"]);
+ }
+
+ ///
+ /// Test that HostingOptions can be configured using object initializer syntax
+ ///
+ [Fact]
+ public void HostingOptions_SupportsObjectInitializerSyntax()
+ {
+ // Act - Configure using object initializer
+ var options = new HostingOptions
+ {
+ DefaultResponseContentEncoding = ResponseContentEncoding.Base64,
+ IncludeUnhandledExceptionDetailInResponse = true,
+ PostMarshallRequestFeature = (feature, request, context) => { },
+ PostMarshallResponseFeature = (feature, response, context) => { },
+ Serializer = null
+ };
+
+ // Assert
+ Assert.Equal(ResponseContentEncoding.Base64, options.DefaultResponseContentEncoding);
+ Assert.True(options.IncludeUnhandledExceptionDetailInResponse);
+ Assert.NotNull(options.PostMarshallRequestFeature);
+ Assert.NotNull(options.PostMarshallResponseFeature);
+ Assert.Null(options.Serializer);
+ }
+
+ ///
+ /// Test that HostingOptions configuration can be done through action delegate
+ /// (as used in AddAWSLambdaHosting)
+ ///
+ [Fact]
+ public void HostingOptions_SupportsConfigurationThroughActionDelegate()
+ {
+ // Arrange
+ HostingOptions? capturedOptions = null;
+ Action configureAction = options =>
+ {
+ capturedOptions = options;
+ options.DefaultResponseContentEncoding = ResponseContentEncoding.Base64;
+ options.IncludeUnhandledExceptionDetailInResponse = true;
+ options.RegisterResponseContentEncodingForContentType("application/json", ResponseContentEncoding.Default);
+ options.PostMarshallRequestFeature = (f, r, c) => { };
+ };
+
+ var options = new HostingOptions();
+
+ // Act
+ configureAction(options);
+
+ // Assert
+ Assert.NotNull(capturedOptions);
+ Assert.Same(options, capturedOptions);
+ Assert.Equal(ResponseContentEncoding.Base64, options.DefaultResponseContentEncoding);
+ Assert.True(options.IncludeUnhandledExceptionDetailInResponse);
+ Assert.Single(options.ContentTypeEncodings);
+ Assert.NotNull(options.PostMarshallRequestFeature);
+ }
+
+ ///
+ /// Test that all extension point properties use correct delegate signatures
+ ///
+ [Fact]
+ public void HostingOptions_ExtensionPointProperties_UseCorrectDelegateSignatures()
+ {
+ // Arrange
+ var options = new HostingOptions();
+
+ // Act & Assert - Verify delegate signatures match expected types
+ // These assignments will fail to compile if signatures don't match
+
+ // PostMarshallRequestFeature: Action
+ options.PostMarshallRequestFeature = (Microsoft.AspNetCore.Http.Features.IHttpRequestFeature feature,
+ object request,
+ ILambdaContext context) => { };
+
+ // PostMarshallResponseFeature: Action
+ options.PostMarshallResponseFeature = (Microsoft.AspNetCore.Http.Features.IHttpResponseFeature feature,
+ object response,
+ ILambdaContext context) => { };
+
+ // PostMarshallConnectionFeature: Action
+ options.PostMarshallConnectionFeature = (Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature feature,
+ object request,
+ ILambdaContext context) => { };
+
+ // PostMarshallHttpAuthenticationFeature: Action
+ options.PostMarshallHttpAuthenticationFeature = (Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature feature,
+ object request,
+ ILambdaContext context) => { };
+
+ // PostMarshallTlsConnectionFeature: Action
+ options.PostMarshallTlsConnectionFeature = (Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature feature,
+ object request,
+ ILambdaContext context) => { };
+
+ // PostMarshallItemsFeature: Action
+ options.PostMarshallItemsFeature = (Microsoft.AspNetCore.Http.Features.IItemsFeature feature,
+ object request,
+ ILambdaContext context) => { };
+
+ // If we reach here, all delegate signatures are correct
+ Assert.NotNull(options.PostMarshallRequestFeature);
+ Assert.NotNull(options.PostMarshallResponseFeature);
+ Assert.NotNull(options.PostMarshallConnectionFeature);
+ Assert.NotNull(options.PostMarshallHttpAuthenticationFeature);
+ Assert.NotNull(options.PostMarshallTlsConnectionFeature);
+ Assert.NotNull(options.PostMarshallItemsFeature);
+ }
+
+ ///
+ /// Test that property names match the Core_Library extension point names
+ ///
+ [Fact]
+ public void HostingOptions_PropertyNames_MatchCoreLibraryExtensionPoints()
+ {
+ // This test verifies that property names in HostingOptions match the method names
+ // in AbstractAspNetCoreFunction (the Core_Library)
+
+ var expectedPropertyNames = new[]
+ {
+ "DefaultResponseContentEncoding",
+ "IncludeUnhandledExceptionDetailInResponse",
+ "PostMarshallRequestFeature",
+ "PostMarshallResponseFeature",
+ "PostMarshallConnectionFeature",
+ "PostMarshallHttpAuthenticationFeature",
+ "PostMarshallTlsConnectionFeature",
+ "PostMarshallItemsFeature",
+ "RegisterResponseContentEncodingForContentType",
+ "RegisterResponseContentEncodingForContentEncoding"
+ };
+
+ var hostingOptionsType = typeof(HostingOptions);
+
+ foreach (var expectedName in expectedPropertyNames)
+ {
+ // Check if property or method exists
+ var property = hostingOptionsType.GetProperty(expectedName);
+ var method = hostingOptionsType.GetMethod(expectedName);
+
+ Assert.True(property != null || method != null,
+ $"HostingOptions should have property or method named '{expectedName}' to match Core_Library extension point");
+ }
+ }
+}
diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ServiceCollectionExtensionsTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ServiceCollectionExtensionsTests.cs
new file mode 100644
index 000000000..683850431
--- /dev/null
+++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ServiceCollectionExtensionsTests.cs
@@ -0,0 +1,184 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Lambda.AspNetCoreServer.Hosting;
+using Amazon.Lambda.AspNetCoreServer.Test;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Amazon.Lambda.AspNetCoreServer.Hosting.Tests;
+
+///
+/// Tests for service registration in
+///
+public class ServiceCollectionExtensionsTests
+{
+ [Fact]
+ public void AddAWSLambdaHosting_WithConfiguration_RegistersHostingOptions()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ using var envHelper = new EnvironmentVariableHelper("AWS_LAMBDA_FUNCTION_NAME", "test-function");
+
+ // Act
+ services.AddAWSLambdaHosting(LambdaEventSource.HttpApi, options =>
+ {
+ options.DefaultResponseContentEncoding = ResponseContentEncoding.Base64;
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Assert
+ var hostingOptions = serviceProvider.GetService();
+ Assert.NotNull(hostingOptions);
+ Assert.Equal(ResponseContentEncoding.Base64, hostingOptions.DefaultResponseContentEncoding);
+ }
+
+ [Fact]
+ public void AddAWSLambdaHosting_WithoutConfiguration_RegistersHostingOptions()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ using var envHelper = new EnvironmentVariableHelper("AWS_LAMBDA_FUNCTION_NAME", "test-function");
+
+ // Act
+ services.AddAWSLambdaHosting(LambdaEventSource.HttpApi);
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Assert
+ var hostingOptions = serviceProvider.GetService();
+ Assert.NotNull(hostingOptions);
+ Assert.Equal(ResponseContentEncoding.Default, hostingOptions.DefaultResponseContentEncoding);
+ }
+
+ [Fact]
+ public void AddAWSLambdaHosting_WithNullConfiguration_RegistersHostingOptions()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ using var envHelper = new EnvironmentVariableHelper("AWS_LAMBDA_FUNCTION_NAME", "test-function");
+
+ // Act
+ services.AddAWSLambdaHosting(LambdaEventSource.HttpApi, configure: null);
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Assert
+ var hostingOptions = serviceProvider.GetService();
+ Assert.NotNull(hostingOptions);
+ }
+
+ [Fact]
+ public void AddAWSLambdaHosting_RegistersHostingOptionsAsSingleton()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ using var envHelper = new EnvironmentVariableHelper("AWS_LAMBDA_FUNCTION_NAME", "test-function");
+
+ // Act
+ services.AddAWSLambdaHosting(LambdaEventSource.HttpApi, options =>
+ {
+ options.DefaultResponseContentEncoding = ResponseContentEncoding.Base64;
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Assert - Get the service twice and verify it's the same instance
+ var hostingOptions1 = serviceProvider.GetService();
+ var hostingOptions2 = serviceProvider.GetService();
+
+ Assert.NotNull(hostingOptions1);
+ Assert.NotNull(hostingOptions2);
+ Assert.Same(hostingOptions1, hostingOptions2);
+ }
+
+ [Fact]
+ public void AddAWSLambdaHosting_RestApi_RegistersHostingOptions()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ using var envHelper = new EnvironmentVariableHelper("AWS_LAMBDA_FUNCTION_NAME", "test-function");
+
+ // Act
+ services.AddAWSLambdaHosting(LambdaEventSource.RestApi, options =>
+ {
+ options.IncludeUnhandledExceptionDetailInResponse = true;
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Assert
+ var hostingOptions = serviceProvider.GetService();
+ Assert.NotNull(hostingOptions);
+ Assert.True(hostingOptions.IncludeUnhandledExceptionDetailInResponse);
+ }
+
+ [Fact]
+ public void AddAWSLambdaHosting_ApplicationLoadBalancer_RegistersHostingOptions()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ using var envHelper = new EnvironmentVariableHelper("AWS_LAMBDA_FUNCTION_NAME", "test-function");
+
+ // Act
+ services.AddAWSLambdaHosting(LambdaEventSource.ApplicationLoadBalancer, options =>
+ {
+ options.RegisterResponseContentEncodingForContentType("image/png", ResponseContentEncoding.Base64);
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Assert
+ var hostingOptions = serviceProvider.GetService();
+ Assert.NotNull(hostingOptions);
+ Assert.True(hostingOptions.ContentTypeEncodings.ContainsKey("image/png"));
+ }
+
+ [Fact]
+ public void AddAWSLambdaHosting_NotInLambda_DoesNotRegisterHostingOptions()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ // No AWS_LAMBDA_FUNCTION_NAME environment variable set
+
+ // Act
+ services.AddAWSLambdaHosting(LambdaEventSource.HttpApi, options =>
+ {
+ options.DefaultResponseContentEncoding = ResponseContentEncoding.Base64;
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Assert
+ var hostingOptions = serviceProvider.GetService();
+ Assert.Null(hostingOptions);
+ }
+
+ [Fact]
+ public void AddAWSLambdaHosting_ConfigurationIsApplied()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ using var envHelper = new EnvironmentVariableHelper("AWS_LAMBDA_FUNCTION_NAME", "test-function");
+
+ // Act
+ services.AddAWSLambdaHosting(LambdaEventSource.HttpApi, options =>
+ {
+ options.DefaultResponseContentEncoding = ResponseContentEncoding.Base64;
+ options.IncludeUnhandledExceptionDetailInResponse = true;
+ options.RegisterResponseContentEncodingForContentType("application/json", ResponseContentEncoding.Default);
+ options.RegisterResponseContentEncodingForContentEncoding("gzip", ResponseContentEncoding.Base64);
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Assert
+ var hostingOptions = serviceProvider.GetService();
+ Assert.NotNull(hostingOptions);
+ Assert.Equal(ResponseContentEncoding.Base64, hostingOptions.DefaultResponseContentEncoding);
+ Assert.True(hostingOptions.IncludeUnhandledExceptionDetailInResponse);
+ Assert.True(hostingOptions.ContentTypeEncodings.ContainsKey("application/json"));
+ Assert.True(hostingOptions.ContentEncodingEncodings.ContainsKey("gzip"));
+ }
+}