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/README.md b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/README.md
index bf3455a65..68d287fa5 100644
--- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/README.md
+++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/README.md
@@ -48,4 +48,67 @@ app.MapControllers();
app.Run();
+```
+
+## Extension Points
+
+`AddAWSLambdaHosting` accepts an optional `HostingOptions` configuration action that exposes the same customization hooks available in the traditional `AbstractAspNetCoreFunction` base class approach.
+
+### Binary response handling
+
+By default, common binary content types like `image/png` and `application/pdf` are already configured for Base64 encoding. You can register additional types or override the default encoding:
+
+```csharp
+builder.Services.AddAWSLambdaHosting(LambdaEventSource.HttpApi, options =>
+{
+ // Register a custom binary content type
+ options.RegisterResponseContentEncodingForContentType("application/x-custom-binary", ResponseContentEncoding.Base64);
+
+ // Ensure compressed responses are Base64-encoded (gzip, deflate, br are already defaults)
+ options.RegisterResponseContentEncodingForContentEncoding("zstd", ResponseContentEncoding.Base64);
+
+ // Change the fallback encoding for any unregistered content type
+ options.DefaultResponseContentEncoding = ResponseContentEncoding.Base64;
+});
+```
+
+### Exception details in responses
+
+Useful during development to surface unhandled exception details in the HTTP response body:
+
+```csharp
+builder.Services.AddAWSLambdaHosting(LambdaEventSource.HttpApi, options =>
+{
+ options.IncludeUnhandledExceptionDetailInResponse = app.Environment.IsDevelopment();
+});
+```
+
+### Customizing request and response marshalling
+
+Callbacks let you inspect or modify the ASP.NET Core feature objects after the Lambda event has been marshalled into them. The second parameter is the raw Lambda request or response object — cast it to the appropriate type for your event source (`APIGatewayHttpApiV2ProxyRequest` for `HttpApi`, `APIGatewayProxyRequest` for `RestApi`, `ApplicationLoadBalancerRequest` for `ApplicationLoadBalancer`).
+
+```csharp
+builder.Services.AddAWSLambdaHosting(LambdaEventSource.HttpApi, options =>
+{
+ // Add a custom header derived from the raw Lambda request
+ options.PostMarshallRequestFeature = (requestFeature, lambdaRequest, context) =>
+ {
+ var apiRequest = (APIGatewayHttpApiV2ProxyRequest)lambdaRequest;
+ requestFeature.Headers["X-Stage"] = apiRequest.RequestContext.Stage;
+ };
+
+ // Inject the Lambda context into HttpContext.Items for use in middleware or controllers
+ options.PostMarshallItemsFeature = (itemsFeature, lambdaRequest, context) =>
+ {
+ itemsFeature.Items["MyCustomKey"] = context.FunctionName;
+ };
+
+ // Modify the response after it has been marshalled back to a Lambda response
+ options.PostMarshallResponseFeature = (responseFeature, lambdaResponse, context) =>
+ {
+ var apiResponse = (APIGatewayHttpApiV2ProxyResponse)lambdaResponse;
+ apiResponse.Headers ??= new Dictionary();
+ apiResponse.Headers["X-Request-Id"] = context.AwsRequestId;
+ };
+});
```
\ No newline at end of file
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"));
+ }
+}