Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
321d4d2
refactor(testing): implement transaction-based architecture with acti…
j-d-ha Dec 6, 2025
8f29a4b
refactor(models): reorder properties and clean up XML comments in mod…
j-d-ha Dec 6, 2025
881a203
refactor(testing): change LambdaTestServer to use IAsyncDisposable in…
j-d-ha Dec 6, 2025
b64b58e
feat(testing): buffer request content for re-readability in LambdaTes…
j-d-ha Dec 6, 2025
9045db7
refactor(testing): improve cleanup and transaction handling in Lambda…
j-d-ha Dec 6, 2025
5acf082
feat(testing): handle shutdown gracefully when transaction queue is full
j-d-ha Dec 6, 2025
583c3ce
feat(testing): add return values to transaction completion methods
j-d-ha Dec 6, 2025
21e94a3
feat(testing): preserve headers when buffering request content in Lam…
j-d-ha Dec 6, 2025
5407f34
refactor(testing): simplify LambdaTestServer and factory client creation
j-d-ha Dec 6, 2025
dc1b1b8
feat(testing): enhance invocation handling with metadata and response…
j-d-ha Dec 6, 2025
429c1cc
feat(testing): add invocation timeout and enhance pending invocation …
j-d-ha Dec 6, 2025
041a79c
feat(testing): add new tests for invocation error and cancellation sc…
j-d-ha Dec 6, 2025
82b4e20
refactor(testing): centralize invocation completion creation in Lambd…
j-d-ha Dec 6, 2025
8dd442f
fix(testing): address disposal and exception type in LambdaTestServer…
j-d-ha Dec 6, 2025
f14b9cb
refactor(examples): restructure AwsLambda.Host example and testing pr…
j-d-ha Dec 7, 2025
da199ec
test(examples): uncomment and include `LambdaHostTest` in the project
j-d-ha Dec 7, 2025
5aedb88
fix(testing): update exception type in LambdaHostTest assertion
j-d-ha Dec 7, 2025
2f9140b
refactor(testing): simplify async handling and invocation completion …
j-d-ha Dec 7, 2025
69b0d72
refactor(models): enforce required properties and initialize collecti…
j-d-ha Dec 7, 2025
eea2435
refactor(testing): replace `InvocationTimeout` with `ClientWaitTimeou…
j-d-ha Dec 7, 2025
70d7e93
refactor(testing): simplify `InvokeAsync_WithZeroTimeout_CancelsInvoc…
j-d-ha Dec 7, 2025
966e98c
refactor(testing): replace `WebApplicationFactory` with `LambdaApplic…
j-d-ha Dec 7, 2025
4cc2e54
fix(testing): throw exception after setting `TrySetException` in `Def…
j-d-ha Dec 7, 2025
c2709d5
refactor(testing): simplify `DeferredHost` by consolidating parameters
j-d-ha Dec 7, 2025
11bc45e
test(lambda-host): add new test for handling bad configuration
j-d-ha Dec 7, 2025
1af3130
fix(testing): handle deferred host task completion consistently
j-d-ha Dec 7, 2025
39cf563
fix(testing): throw exception for invalid or unexpected transactions …
j-d-ha Dec 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 35 additions & 14 deletions AwsLambda.Host.sln
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AwsLambda.Host.Envelopes.Cl
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AwsLambda.Host.Testing", "src\AwsLambda.Host.Testing\AwsLambda.Host.Testing.csproj", "{9FA188D7-CF5F-4F87-B292-2AF69994FF12}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AwsLambda.Host.Examples.Testing", "examples\AwsLambda.Host.Examples.Testing\AwsLambda.Host.Examples.Testing.csproj", "{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AwsLambda.Host.Example.Testing", "AwsLambda.Host.Example.Testing", "{26B446C5-76B0-4190-9022-1BF514737E2E}"
ProjectSection(SolutionItems) = preProject
examples\AwsLambda.Host.Example.Testing\Properties\launchSettings.json = examples\AwsLambda.Host.Example.Testing\Properties\launchSettings.json
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "examples\AwsLambda.Host.Example.Testing\Tests\Tests.csproj", "{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lambda", "examples\AwsLambda.Host.Example.Testing\Lambda\Lambda.csproj", "{6DB827E6-C534-4C6A-9128-05F6A05DD6C5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -366,18 +373,30 @@ Global
{9FA188D7-CF5F-4F87-B292-2AF69994FF12}.Release|x64.Build.0 = Release|Any CPU
{9FA188D7-CF5F-4F87-B292-2AF69994FF12}.Release|x86.ActiveCfg = Release|Any CPU
{9FA188D7-CF5F-4F87-B292-2AF69994FF12}.Release|x86.Build.0 = Release|Any CPU
{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B}.Debug|x64.ActiveCfg = Debug|Any CPU
{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B}.Debug|x64.Build.0 = Debug|Any CPU
{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B}.Debug|x86.ActiveCfg = Debug|Any CPU
{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B}.Debug|x86.Build.0 = Debug|Any CPU
{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B}.Release|Any CPU.Build.0 = Release|Any CPU
{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B}.Release|x64.ActiveCfg = Release|Any CPU
{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B}.Release|x64.Build.0 = Release|Any CPU
{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B}.Release|x86.ActiveCfg = Release|Any CPU
{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B}.Release|x86.Build.0 = Release|Any CPU
{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818}.Debug|x64.ActiveCfg = Debug|Any CPU
{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818}.Debug|x64.Build.0 = Debug|Any CPU
{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818}.Debug|x86.ActiveCfg = Debug|Any CPU
{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818}.Debug|x86.Build.0 = Debug|Any CPU
{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818}.Release|Any CPU.Build.0 = Release|Any CPU
{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818}.Release|x64.ActiveCfg = Release|Any CPU
{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818}.Release|x64.Build.0 = Release|Any CPU
{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818}.Release|x86.ActiveCfg = Release|Any CPU
{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818}.Release|x86.Build.0 = Release|Any CPU
{6DB827E6-C534-4C6A-9128-05F6A05DD6C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6DB827E6-C534-4C6A-9128-05F6A05DD6C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6DB827E6-C534-4C6A-9128-05F6A05DD6C5}.Debug|x64.ActiveCfg = Debug|Any CPU
{6DB827E6-C534-4C6A-9128-05F6A05DD6C5}.Debug|x64.Build.0 = Debug|Any CPU
{6DB827E6-C534-4C6A-9128-05F6A05DD6C5}.Debug|x86.ActiveCfg = Debug|Any CPU
{6DB827E6-C534-4C6A-9128-05F6A05DD6C5}.Debug|x86.Build.0 = Debug|Any CPU
{6DB827E6-C534-4C6A-9128-05F6A05DD6C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6DB827E6-C534-4C6A-9128-05F6A05DD6C5}.Release|Any CPU.Build.0 = Release|Any CPU
{6DB827E6-C534-4C6A-9128-05F6A05DD6C5}.Release|x64.ActiveCfg = Release|Any CPU
{6DB827E6-C534-4C6A-9128-05F6A05DD6C5}.Release|x64.Build.0 = Release|Any CPU
{6DB827E6-C534-4C6A-9128-05F6A05DD6C5}.Release|x86.ActiveCfg = Release|Any CPU
{6DB827E6-C534-4C6A-9128-05F6A05DD6C5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -407,6 +426,8 @@ Global
{DA647025-1B7B-425A-9405-8E015F6DA723} = {1C3C52D9-2936-4A0C-A9C8-F330F22B8359}
{6D40345B-6CCF-4A6C-8FA2-5BE6C693E3F7} = {1C3C52D9-2936-4A0C-A9C8-F330F22B8359}
{9FA188D7-CF5F-4F87-B292-2AF69994FF12} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{884FD7E2-72D3-422B-82B4-FF5E7F92CE6B} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
{26B446C5-76B0-4190-9022-1BF514737E2E} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
{0EDDAE60-A252-4CB0-85E2-FEA0CA0C5818} = {26B446C5-76B0-4190-9022-1BF514737E2E}
{6DB827E6-C534-4C6A-9128-05F6A05DD6C5} = {26B446C5-76B0-4190-9022-1BF514737E2E}
EndGlobalSection
EndGlobal
31 changes: 31 additions & 0 deletions examples/AwsLambda.Host.Example.Testing/Lambda/Lambda.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<AWSProjectType>Lambda</AWSProjectType>
<!-- This property makes the build directory similar to a publish directory and helps the AWS .NET Lambda Mock Test Tool find project dependencies. -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<!-- Generate ready to run images during publishing to improve cold start time. -->
<PublishReadyToRun>true</PublishReadyToRun>
<InterceptorsNamespaces>$(InterceptorsNamespaces);AwsLambda.Host</InterceptorsNamespaces>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\src\AwsLambda.Host\AwsLambda.Host.csproj" />
<ProjectReference
Include="..\..\..\src\AwsLambda.Host.SourceGenerators\AwsLambda.Host.SourceGenerators.csproj"
ReferenceOutputAssembly="false"
OutputItemType="Analyzer"
/>
</ItemGroup>
<ItemGroup>
<None Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
22 changes: 22 additions & 0 deletions examples/AwsLambda.Host.Example.Testing/Lambda/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using AwsLambda.Host.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

// Create the application builder
var builder = LambdaApplication.CreateBuilder();

builder.Services.ConfigureLambdaHostOptions(options =>
{
options.ClearLambdaOutputFormatting = true;
});

// Build the Lambda application
var lambda = builder.Build();

// Map your handler - the event is automatically injected
lambda.MapHandler(([Event] string name) => $"Hello {name}!");

// Run the Lambda
await lambda.RunAsync();

public partial class Program;
139 changes: 139 additions & 0 deletions examples/AwsLambda.Host.Example.Testing/Tests/LambdaHostTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using AwsLambda.Host.Options;
using AwsLambda.Host.Testing;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace Lambda.Host.Example.HelloWorld;

[TestSubject(typeof(Program))]
public class LambdaHostTest
{
[Fact]
public async Task LambdaHost_CanStartWithoutError()
{
await using var factory = new LambdaApplicationFactory<Program>();

var client = factory.CreateClient();
// No need to wait for next request - server handles this automatically
var response = await client.InvokeAsync<string, string>(
"Jonas",
TestContext.Current.CancellationToken
);
Assert.True(response.WasSuccess);
Assert.NotNull(response);
Assert.Equal("Hello Jonas!", response.Response);
}

[Fact]
public async Task LambdaHost_CrashesWithBadConfiguration_ThrowsException()
{
await using var factory = new LambdaApplicationFactory<Program>().WithWebHostBuilder(
builder =>
{
builder.ConfigureServices(
(_, services) =>
{
services.Configure<LambdaHostOptions>(options =>
{
options.BootstrapOptions.RuntimeApiEndpoint = "http://localhost:3002";
});
}
);
}
);

var client = factory.CreateClient();
// No need to wait for next request - server handles this automatically
var response = await client.InvokeAsync<string, string>(
"Jonas",
TestContext.Current.CancellationToken
);
Assert.True(response.WasSuccess);
Assert.NotNull(response);
Assert.Equal("Hello Jonas!", response.Response);
}

[Fact]
public async Task LambdaHost_ProcessesConcurrentInvocationsInFifoOrder()
{
await using var factory = new LambdaApplicationFactory<Program>();
var client = factory.CreateClient();

// Launch 5 concurrent invocations
var tasks = Enumerable
.Range(1, 5)
.Select(i =>
client.InvokeAsync<string, string>(
$"User{i}",
TestContext.Current.CancellationToken
)
)
.ToArray();

var responses = await Task.WhenAll(tasks);

// All should complete successfully
Assert.All(responses, r => Assert.True(r.WasSuccess));
Assert.Equal("Hello User1!", responses[0].Response);
Assert.Equal("Hello User2!", responses[1].Response);
Assert.Equal("Hello User3!", responses[2].Response);
Assert.Equal("Hello User4!", responses[3].Response);
Assert.Equal("Hello User5!", responses[4].Response);
}

[Fact]
public async Task InvokeAsync_WithInvalidPayload_ReturnsError()
{
await using var factory = new LambdaApplicationFactory<Program>();
var client = factory.CreateClient();

var response = await client.InvokeAsync<string, int>(
123,
TestContext.Current.CancellationToken
);

Assert.False(response.WasSuccess);
Assert.NotNull(response.Error);
Assert.Contains("Json", response.Error!.ErrorType, StringComparison.OrdinalIgnoreCase);
}

[Fact]
public async Task InvokeAsync_WithPreCanceledToken_CancelsInvocation()
{
await using var factory = new LambdaApplicationFactory<Program>();
var client = factory.CreateClient();

using var cts = new CancellationTokenSource();
await cts.CancelAsync();

await Assert.ThrowsAsync<TaskCanceledException>(() =>
client.InvokeAsync<string, string>("Jonas", cts.Token)
);
}

[Fact]
public async Task InvokeAsync_WithZeroTimeout_CancelsInvocation() =>
await Assert.ThrowsAsync<AggregateException>(async () =>
{
try
{
await using var factory = new LambdaApplicationFactory<Program>();
var client = factory
.CreateClient()
.ConfigureOptions(options =>
options.InvocationHeaderOptions.ClientWaitTimeout = TimeSpan.Zero
);

await client.InvokeAsync<string, string>(
"Jonas",
TestContext.Current.CancellationToken
);
}
catch (Exception e)
{
Console.WriteLine(e.GetType().FullName);
throw;
}
});
}
32 changes: 32 additions & 0 deletions examples/AwsLambda.Host.Example.Testing/Tests/Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NSubstitute" />
<PackageReference Include="AutoFixture" />
<PackageReference Include="AwesomeAssertions" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="NSubstitute.Analyzers.CSharp">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Text.RegularExpressions" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.v3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\AwsLambda.Host.Testing\AwsLambda.Host.Testing.csproj" />
<ProjectReference Include="..\Lambda\Lambda.csproj" />
</ItemGroup>
</Project>

This file was deleted.

23 changes: 0 additions & 23 deletions examples/AwsLambda.Host.Examples.Testing/LambdaHostTest.cs

This file was deleted.

Loading
Loading