diff --git a/.github/actions/run-dotnet-tests/run-unix.sh b/.github/actions/run-dotnet-tests/run-unix.sh
index 751ba54..8457c26 100755
--- a/.github/actions/run-dotnet-tests/run-unix.sh
+++ b/.github/actions/run-dotnet-tests/run-unix.sh
@@ -30,7 +30,9 @@ for tfm in "${TFMS[@]}"; do
[[ -z "$tfm" ]] && continue
echo "🧪 $tfm ..."
- dotnet test "$PROJECT_PATH" -c "$CONFIGURATION" -f "$tfm" --no-build \
- --logger "trx;LogFileName=testResults-$tfm.trx" \
- --results-directory "$RESULTS_DIR"
+ # --timeout ensures test session exits properly (workaround for MTP Linux shutdown bug)
+ dotnet test --project "$PROJECT_PATH" -c "$CONFIGURATION" -f "$tfm" --no-build \
+ --report-trx --report-trx-filename "testResults-$tfm.trx" \
+ --results-directory "$RESULTS_DIR" \
+ -- --timeout 10m
done
diff --git a/.github/actions/run-dotnet-tests/run-win.ps1 b/.github/actions/run-dotnet-tests/run-win.ps1
index a0bf555..6f1f402 100644
--- a/.github/actions/run-dotnet-tests/run-win.ps1
+++ b/.github/actions/run-dotnet-tests/run-win.ps1
@@ -29,7 +29,9 @@ Write-Host "📋 Target frameworks: $($tfms -join ', ')"
foreach ($tfm in $tfms) {
Write-Host "🧪 $tfm ..."
- dotnet test $ProjectPath -c $Configuration -f $tfm --no-build `
- --logger "trx;LogFileName=testResults-$tfm.trx" `
- --results-directory $ResultsDir
+ # --timeout ensures test session exits properly (workaround for MTP shutdown bug)
+ dotnet test --project $ProjectPath -c $Configuration -f $tfm --no-build `
+ --report-trx --report-trx-filename "testResults-$tfm.trx" `
+ --results-directory $ResultsDir `
+ -- --timeout 10m
}
diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml
index a665284..1fd3f2a 100644
--- a/.github/workflows/ci-cd.yml
+++ b/.github/workflows/ci-cd.yml
@@ -55,6 +55,7 @@ jobs:
dotnet-version: |
8.0.x
9.0.x
+ 10.0.x
- name: "Setup Node.js"
if: runner.os == 'Linux'
@@ -160,6 +161,7 @@ jobs:
dotnet-version: |
8.0.x
9.0.x
+ 10.0.x
- name: "Cache NuGet packages"
uses: actions/cache@v4
@@ -176,7 +178,8 @@ jobs:
run: dotnet build --configuration Release --no-restore
- name: "Run Unit Tests"
- run: dotnet test ./tests/Aspire.Hosting.LocalStack.Unit.Tests/Aspire.Hosting.LocalStack.Unit.Tests.csproj --verbosity normal --configuration Release --no-build
+ # --timeout ensures test session exits properly (workaround for MTP Linux shutdown bug)
+ run: dotnet test --project ./tests/Aspire.Hosting.LocalStack.Unit.Tests/Aspire.Hosting.LocalStack.Unit.Tests.csproj --verbosity normal --configuration Release --no-build -- --timeout 5m
- name: "Setup GitHub Packages Authentication"
run: |
diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml
index d890ca2..446fe52 100644
--- a/.github/workflows/publish-nuget.yml
+++ b/.github/workflows/publish-nuget.yml
@@ -41,6 +41,7 @@ jobs:
dotnet-version: |
8.0.x
9.0.x
+ 10.0.x
- name: "Cache NuGet packages"
uses: actions/cache@v4
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1743046..e9b6844 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [13.1.0] - 2025-12-18
+
+### Added
+
+- **Aspire 13.1 Support**: Full compatibility with .NET Aspire 13.1 and .NET 10.0
+- **Health Check Improvements**: Added log suppression for `localstack_health_client` to reduce console noise during startup
+
+### Changed
+
+- **Dependencies**: Updated `Aspire.Hosting` to `13.1.0`
+- **LocalStack Container**: Updated from `4.10.0` → `4.12.0`
+- **Target Frameworks**: Added `net10.0` support
+- **Testing Framework**: Migrated from xUnit v3 to TUnit v1.5.70
+ - 453 tests migrated (432 unit tests, 21 integration tests)
+ - All tests passing across net8.0, net9.0, and net10.0
+ - Leverages Microsoft.Testing.Platform for improved performance
+ - Source-generated test discovery for faster execution
+ - Async-first assertion API throughout test suite
+
+### Fixed
+- **LocalStack Container Health Check**: Improved cancellation handling during application shutdown
+
## [9.5.3] - 2025-11-04
### Added
diff --git a/Directory.Build.props b/Directory.Build.props
index cf975cb..73c486e 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,8 +1,8 @@
- net8.0
- $(DefaultTargetFramework);net9.0
+ net10.0
+ $(DefaultTargetFramework);net8.0;net9.0
latest
enable
enable
@@ -19,7 +19,7 @@
true
true
- 9.5.2
+ 13.1.0
true
true
diff --git a/Directory.Packages.props b/Directory.Packages.props
index f44e9e8..a5329cb 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,32 +1,31 @@
-
+
-
+
-
-
-
+
+
+
-
-
+
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
@@ -34,27 +33,23 @@
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
-
-
-
+
diff --git a/README.md b/README.md
index 839f05e..601dd10 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ dotnet add package LocalStack.Aspire.Hosting
> **Package Note**: The package is named `LocalStack.Aspire.Hosting` but uses the namespace `Aspire.Hosting.LocalStack` to align with .NET Aspire hosting conventions. This ensures consistency with other Aspire hosting integrations while maintaining a unique package identity.
-**Requirements**: .NET 8.0 or later (supports both .NET 8 and .NET 9)
+**Requirements**: .NET 8.0 or later (supports .NET 8, .NET 9, and .NET 10)
### Development Builds
@@ -37,6 +37,7 @@ This package follows Aspire's **major.minor** versioning but releases **patch ve
- `9.5.x` works with Aspire 9.5.x
- `9.6.x` works with Aspire 9.6.x
+- `13.1.x` works with Aspire 13.1.x
We may ship features and fixes between Aspire releases. When upgrading Aspire's minor version, upgrade this package to match.
diff --git a/global.json b/global.json
index e97d9d4..df32a2e 100644
--- a/global.json
+++ b/global.json
@@ -1,7 +1,10 @@
{
- "sdk": {
- "version": "9.0.302",
- "rollForward": "latestFeature",
- "allowPrerelease": false
- }
+ "sdk": {
+ "version": "10.0.100",
+ "rollForward": "latestFeature",
+ "allowPrerelease": false
+ },
+ "test": {
+ "runner": "Microsoft.Testing.Platform"
+ }
}
diff --git a/playground/LocalStack.Playground.ServiceDefaults/LocalStack.Playground.ServiceDefaults.csproj b/playground/LocalStack.Playground.ServiceDefaults/LocalStack.Playground.ServiceDefaults.csproj
index 4684b76..ff13edd 100644
--- a/playground/LocalStack.Playground.ServiceDefaults/LocalStack.Playground.ServiceDefaults.csproj
+++ b/playground/LocalStack.Playground.ServiceDefaults/LocalStack.Playground.ServiceDefaults.csproj
@@ -11,6 +11,7 @@
+
diff --git a/playground/README.md b/playground/README.md
index dc9a8c6..6da29dc 100644
--- a/playground/README.md
+++ b/playground/README.md
@@ -37,7 +37,7 @@ Shows how LocalStack integrates with AWS Lambda and API Gateway emulators for se
## Prerequisites
-- .NET 9.0 or later
+- .NET 10.0 or later
- Docker Desktop (for LocalStack containers)
- Node.js 18+ and AWS CDK v2 (for CDK examples only)
diff --git a/playground/lambda/LocalStack.Lambda.Analyzer/Properties/launchSettings.json b/playground/lambda/LocalStack.Lambda.Analyzer/Properties/launchSettings.json
index c3f0417..ead2f72 100644
--- a/playground/lambda/LocalStack.Lambda.Analyzer/Properties/launchSettings.json
+++ b/playground/lambda/LocalStack.Lambda.Analyzer/Properties/launchSettings.json
@@ -22,8 +22,8 @@
"Aspire_AnalyzerLambda": {
"commandName": "Executable",
"executablePath": "dotnet",
- "commandLineArgs": "exec --depsfile ./LocalStack.Lambda.Analyzer.deps.json --runtimeconfig ./LocalStack.Lambda.Analyzer.runtimeconfig.json %USERPROFILE%\\.dotnet\\tools\\.store\\amazon.lambda.testtool\\0.11.0\\amazon.lambda.testtool\\0.11.0\\content\\Amazon.Lambda.RuntimeSupport\\net8.0\\Amazon.Lambda.RuntimeSupport.dll LocalStack.Lambda.Analyzer::LocalStack.Lambda.Analyzer.Function::FunctionHandler",
- "workingDirectory": ".\\bin\\$(Configuration)\\net8.0"
+ "commandLineArgs": "exec --depsfile ./LocalStack.Lambda.Analyzer.deps.json --runtimeconfig ./LocalStack.Lambda.Analyzer.runtimeconfig.json %USERPROFILE%\\.dotnet\\tools\\.store\\amazon.lambda.testtool\\0.11.0\\amazon.lambda.testtool\\0.11.0\\content\\Amazon.Lambda.RuntimeSupport\\net10.0\\Amazon.Lambda.RuntimeSupport.dll LocalStack.Lambda.Analyzer::LocalStack.Lambda.Analyzer.Function::FunctionHandler",
+ "workingDirectory": ".\\bin\\$(Configuration)\\net10.0"
}
}
}
\ No newline at end of file
diff --git a/playground/lambda/LocalStack.Lambda.Redirector/Properties/launchSettings.json b/playground/lambda/LocalStack.Lambda.Redirector/Properties/launchSettings.json
index 0c53d7a..806e405 100644
--- a/playground/lambda/LocalStack.Lambda.Redirector/Properties/launchSettings.json
+++ b/playground/lambda/LocalStack.Lambda.Redirector/Properties/launchSettings.json
@@ -22,8 +22,8 @@
"Aspire_RedirectorLambda": {
"commandName": "Executable",
"executablePath": "dotnet",
- "commandLineArgs": "exec --depsfile ./LocalStack.Lambda.Redirector.deps.json --runtimeconfig ./LocalStack.Lambda.Redirector.runtimeconfig.json %USERPROFILE%\\.dotnet\\tools\\.store\\amazon.lambda.testtool\\0.11.0\\amazon.lambda.testtool\\0.11.0\\content\\Amazon.Lambda.RuntimeSupport\\net8.0\\Amazon.Lambda.RuntimeSupport.dll LocalStack.Lambda.Redirector::LocalStack.Lambda.Redirector.Function::FunctionHandler",
- "workingDirectory": ".\\bin\\$(Configuration)\\net8.0"
+ "commandLineArgs": "exec --depsfile ./LocalStack.Lambda.Redirector.deps.json --runtimeconfig ./LocalStack.Lambda.Redirector.runtimeconfig.json %USERPROFILE%\\.dotnet\\tools\\.store\\amazon.lambda.testtool\\0.11.0\\amazon.lambda.testtool\\0.11.0\\content\\Amazon.Lambda.RuntimeSupport\\net10.0\\Amazon.Lambda.RuntimeSupport.dll LocalStack.Lambda.Redirector::LocalStack.Lambda.Redirector.Function::FunctionHandler",
+ "workingDirectory": ".\\bin\\$(Configuration)\\net10.0"
}
}
}
\ No newline at end of file
diff --git a/playground/lambda/LocalStack.Lambda.UrlShortener/Properties/launchSettings.json b/playground/lambda/LocalStack.Lambda.UrlShortener/Properties/launchSettings.json
index e3bdb50..ac8031a 100644
--- a/playground/lambda/LocalStack.Lambda.UrlShortener/Properties/launchSettings.json
+++ b/playground/lambda/LocalStack.Lambda.UrlShortener/Properties/launchSettings.json
@@ -22,8 +22,8 @@
"Aspire_UrlShortenerLambda": {
"commandName": "Executable",
"executablePath": "dotnet",
- "commandLineArgs": "exec --depsfile ./LocalStack.Lambda.UrlShortener.deps.json --runtimeconfig ./LocalStack.Lambda.UrlShortener.runtimeconfig.json %USERPROFILE%\\.dotnet\\tools\\.store\\amazon.lambda.testtool\\0.11.0\\amazon.lambda.testtool\\0.11.0\\content\\Amazon.Lambda.RuntimeSupport\\net8.0\\Amazon.Lambda.RuntimeSupport.dll LocalStack.Lambda.UrlShortener::LocalStack.Lambda.UrlShortener.Function::FunctionHandler",
- "workingDirectory": ".\\bin\\$(Configuration)\\net8.0"
+ "commandLineArgs": "exec --depsfile ./LocalStack.Lambda.UrlShortener.deps.json --runtimeconfig ./LocalStack.Lambda.UrlShortener.runtimeconfig.json %USERPROFILE%\\.dotnet\\tools\\.store\\amazon.lambda.testtool\\0.11.0\\amazon.lambda.testtool\\0.11.0\\content\\Amazon.Lambda.RuntimeSupport\\net10.0\\Amazon.Lambda.RuntimeSupport.dll LocalStack.Lambda.UrlShortener::LocalStack.Lambda.UrlShortener.Function::FunctionHandler",
+ "workingDirectory": ".\\bin\\$(Configuration)\\net10.0"
}
}
}
\ No newline at end of file
diff --git a/playground/provisioning/LocalStack.Provisioning.CDK.AppHost/LocalStack.Provisioning.CDK.AppHost.csproj b/playground/provisioning/LocalStack.Provisioning.CDK.AppHost/LocalStack.Provisioning.CDK.AppHost.csproj
index 5194dcc..546cc43 100644
--- a/playground/provisioning/LocalStack.Provisioning.CDK.AppHost/LocalStack.Provisioning.CDK.AppHost.csproj
+++ b/playground/provisioning/LocalStack.Provisioning.CDK.AppHost/LocalStack.Provisioning.CDK.AppHost.csproj
@@ -12,11 +12,13 @@
+
+
+
+
-
-
diff --git a/playground/provisioning/LocalStack.Provisioning.CloudFormation.AppHost/LocalStack.Provisioning.CloudFormation.AppHost.csproj b/playground/provisioning/LocalStack.Provisioning.CloudFormation.AppHost/LocalStack.Provisioning.CloudFormation.AppHost.csproj
index 8d0bd1d..0dca656 100644
--- a/playground/provisioning/LocalStack.Provisioning.CloudFormation.AppHost/LocalStack.Provisioning.CloudFormation.AppHost.csproj
+++ b/playground/provisioning/LocalStack.Provisioning.CloudFormation.AppHost/LocalStack.Provisioning.CloudFormation.AppHost.csproj
@@ -8,11 +8,13 @@
$(NoWarn);CS8002
+
+
+
+
-
-
diff --git a/src/Aspire.Hosting.LocalStack/Aspire.Hosting.LocalStack.csproj b/src/Aspire.Hosting.LocalStack/Aspire.Hosting.LocalStack.csproj
index 60cccc7..03cd125 100644
--- a/src/Aspire.Hosting.LocalStack/Aspire.Hosting.LocalStack.csproj
+++ b/src/Aspire.Hosting.LocalStack/Aspire.Hosting.LocalStack.csproj
@@ -2,7 +2,7 @@
$(AllTargetFrameworks)
- 9.5.3
+ 13.1.0
true
aspire hosting,aspire,localstack,aws,integration,docker,cloud
README.md
diff --git a/src/Aspire.Hosting.LocalStack/Container/LocalStackContainerImageTags.cs b/src/Aspire.Hosting.LocalStack/Container/LocalStackContainerImageTags.cs
index 3ecb992..68301b5 100644
--- a/src/Aspire.Hosting.LocalStack/Container/LocalStackContainerImageTags.cs
+++ b/src/Aspire.Hosting.LocalStack/Container/LocalStackContainerImageTags.cs
@@ -18,5 +18,5 @@ internal static class LocalStackContainerImageTags
///
/// The default LocalStack container image tag.
///
- internal const string Tag = "4.10.0";
+ internal const string Tag = "4.12.0";
}
diff --git a/src/Aspire.Hosting.LocalStack/Internal/LocalStackHealthCheck.cs b/src/Aspire.Hosting.LocalStack/Internal/LocalStackHealthCheck.cs
index c634dbb..4dcc2ec 100644
--- a/src/Aspire.Hosting.LocalStack/Internal/LocalStackHealthCheck.cs
+++ b/src/Aspire.Hosting.LocalStack/Internal/LocalStackHealthCheck.cs
@@ -54,11 +54,17 @@ public async Task CheckHealthAsync(HealthCheckContext context
}
catch (HttpRequestException ex)
{
+ // Catches network errors including HttpIOException (response ended prematurely during startup)
return HealthCheckResult.Unhealthy("LocalStack health check failed: network error", ex);
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
return HealthCheckResult.Unhealthy("LocalStack health check timed out", ex);
}
+ catch (IOException ex)
+ {
+ // Catches IO errors (e.g., response ended prematurely) during startup
+ return HealthCheckResult.Unhealthy("LocalStack is starting up", ex);
+ }
}
}
diff --git a/src/Aspire.Hosting.LocalStack/LocalStackResourceBuilderExtensions.cs b/src/Aspire.Hosting.LocalStack/LocalStackResourceBuilderExtensions.cs
index 0c75497..e894e64 100644
--- a/src/Aspire.Hosting.LocalStack/LocalStackResourceBuilderExtensions.cs
+++ b/src/Aspire.Hosting.LocalStack/LocalStackResourceBuilderExtensions.cs
@@ -19,6 +19,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Logging;
namespace Aspire.Hosting;
@@ -308,6 +309,13 @@ private static IResourceBuilder ConfigureHealthCheck(
{
builder.Services.AddHttpClient(Constants.LocalStackHealthClientName, client => client.Timeout = TimeSpan.FromSeconds(5));
+ // Suppress noisy logs from the health check client during startup
+ builder.Services.AddLogging(logging =>
+ {
+ logging.AddFilter($"System.Net.Http.HttpClient.{Constants.LocalStackHealthClientName}.ClientHandler", LogLevel.Warning);
+ logging.AddFilter($"System.Net.Http.HttpClient.{Constants.LocalStackHealthClientName}.LogicalHandler", LogLevel.Warning);
+ });
+
EndpointReference endpoint = resourceBuilder.Resource.GetEndpoint(LocalStackResource.PrimaryEndpointName);
builder.Services.AddHealthChecks().Add(new HealthCheckRegistration(
diff --git a/tests/Aspire.Hosting.LocalStack.Integration.Tests/Aspire.Hosting.LocalStack.Integration.Tests.csproj b/tests/Aspire.Hosting.LocalStack.Integration.Tests/Aspire.Hosting.LocalStack.Integration.Tests.csproj
index b5308d3..152d65c 100644
--- a/tests/Aspire.Hosting.LocalStack.Integration.Tests/Aspire.Hosting.LocalStack.Integration.Tests.csproj
+++ b/tests/Aspire.Hosting.LocalStack.Integration.Tests/Aspire.Hosting.LocalStack.Integration.Tests.csproj
@@ -28,11 +28,5 @@
-
-
-
- PreserveNewest
-
-
diff --git a/tests/Aspire.Hosting.LocalStack.Integration.Tests/EagerLoadedServices/EagerLoadedServicesTests.cs b/tests/Aspire.Hosting.LocalStack.Integration.Tests/EagerLoadedServices/EagerLoadedServicesTests.cs
index 4386323..8ddb741 100644
--- a/tests/Aspire.Hosting.LocalStack.Integration.Tests/EagerLoadedServices/EagerLoadedServicesTests.cs
+++ b/tests/Aspire.Hosting.LocalStack.Integration.Tests/EagerLoadedServices/EagerLoadedServicesTests.cs
@@ -1,19 +1,13 @@
-using System.Net.Http.Json;
-using System.Text.Json.Nodes;
-using Amazon;
-using Amazon.SQS.Model;
-using Aspire.Hosting.LocalStack.Container;
-using LocalStack.Client.Enums;
-
namespace Aspire.Hosting.LocalStack.Integration.Tests.EagerLoadedServices;
+[NotInParallel("IntegrationTests")]
public class EagerLoadedServicesTests
{
- [Fact]
- public async Task LocalStack_Should_Lazy_Load_Services_By_Default_Async()
+ [Test]
+ public async Task LocalStack_Should_Lazy_Load_Services_By_Default_Async(CancellationToken cancellationToken)
{
using var parentCts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
- using var cts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token, TestContext.Current.CancellationToken);
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token, cancellationToken);
#pragma warning disable CA1849
await using var builder = DistributedApplicationTestingBuilder.Create("LocalStack:UseLocalStack=true");
@@ -37,16 +31,16 @@ public async Task LocalStack_Should_Lazy_Load_Services_By_Default_Async()
using var httpClient = app.CreateHttpClient("localstack", "http");
var healthResponse = await httpClient.GetAsync(new Uri("/_localstack/health", UriKind.Relative), cts.Token);
var healthContent = await healthResponse.Content.ReadFromJsonAsync(cts.Token);
- Assert.Equal(HttpStatusCode.OK, healthResponse.StatusCode);
+ await Assert.That(healthResponse.StatusCode).IsEqualTo(HttpStatusCode.OK);
var servicesNode = healthContent?["services"]?.AsObject();
- Assert.NotNull(servicesNode);
- Assert.True(servicesNode.ContainsKey("sqs"));
- Assert.NotEqual("running", servicesNode["sqs"]?.ToString());
+ await Assert.That(servicesNode).IsNotNull();
+ await Assert.That(servicesNode.ContainsKey("sqs")).IsTrue();
+ await Assert.That(servicesNode["sqs"]?.ToString()).IsNotEqualTo("running");
var connectionString = await app.GetConnectionStringAsync("localstack", cancellationToken: cts.Token);
- Assert.NotNull(connectionString);
- Assert.NotEmpty(connectionString);
+ await Assert.That(connectionString).IsNotNull();
+ await Assert.That(connectionString).IsNotEmpty();
var connectionStringUri = new Uri(connectionString);
@@ -59,19 +53,19 @@ public async Task LocalStack_Should_Lazy_Load_Services_By_Default_Async()
var laterHealthResponse = await httpClient.GetAsync(new Uri("/_localstack/health", UriKind.Relative), cts.Token);
var laterHealthContent = await laterHealthResponse.Content.ReadFromJsonAsync(cts.Token);
- Assert.Equal(HttpStatusCode.OK, laterHealthResponse.StatusCode);
+ await Assert.That(laterHealthResponse.StatusCode).IsEqualTo(HttpStatusCode.OK);
var sqsServicesNode = laterHealthContent?["services"]?.AsObject();
- Assert.NotNull(sqsServicesNode);
- Assert.True(sqsServicesNode.ContainsKey("sqs"));
- Assert.Equal("running", sqsServicesNode["sqs"]?.ToString());
+ await Assert.That(sqsServicesNode).IsNotNull();
+ await Assert.That(sqsServicesNode.ContainsKey("sqs")).IsTrue();
+ await Assert.That(sqsServicesNode["sqs"]?.ToString()).IsEqualTo("running");
}
- [Fact]
- public async Task LocalStack_Should_Eagerly_Load_Services_When_Configured_Async()
+ [Test]
+ public async Task LocalStack_Should_Eagerly_Load_Services_When_Configured_Async(CancellationToken cancellationToken)
{
using var parentCts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
- using var cts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token, TestContext.Current.CancellationToken);
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token, cancellationToken);
#pragma warning disable CA1849
await using var builder = DistributedApplicationTestingBuilder.Create("LocalStack:UseLocalStack=true");
@@ -96,19 +90,19 @@ public async Task LocalStack_Should_Eagerly_Load_Services_When_Configured_Async(
using var httpClient = app.CreateHttpClient("localstack", "http");
var healthResponse = await httpClient.GetAsync(new Uri("/_localstack/health", UriKind.Relative), cts.Token);
var healthContent = await healthResponse.Content.ReadFromJsonAsync(cts.Token);
- Assert.Equal(HttpStatusCode.OK, healthResponse.StatusCode);
+ await Assert.That(healthResponse.StatusCode).IsEqualTo(HttpStatusCode.OK);
var servicesNode = healthContent?["services"]?.AsObject();
- Assert.NotNull(servicesNode);
- Assert.True(servicesNode.ContainsKey("sqs"));
- Assert.Equal("running", servicesNode["sqs"]?.ToString());
+ await Assert.That(servicesNode).IsNotNull();
+ await Assert.That(servicesNode.ContainsKey("sqs")).IsTrue();
+ await Assert.That(servicesNode["sqs"]?.ToString()).IsEqualTo("running");
}
- [Fact]
- public async Task LocalStack_Should_Eagerly_Load_Multiple_Services_Async()
+ [Test]
+ public async Task LocalStack_Should_Eagerly_Load_Multiple_Services_Async(CancellationToken cancellationToken)
{
using var parentCts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
- using var cts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token, TestContext.Current.CancellationToken);
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token, cancellationToken);
#pragma warning disable CA1849
await using var builder = DistributedApplicationTestingBuilder.Create("LocalStack:UseLocalStack=true");
@@ -132,25 +126,25 @@ public async Task LocalStack_Should_Eagerly_Load_Multiple_Services_Async()
using var httpClient = app.CreateHttpClient("localstack", "http");
var healthResponse = await httpClient.GetAsync(new Uri("/_localstack/health", UriKind.Relative), cts.Token);
var healthContent = await healthResponse.Content.ReadFromJsonAsync(cts.Token);
- Assert.Equal(HttpStatusCode.OK, healthResponse.StatusCode);
+ await Assert.That(healthResponse.StatusCode).IsEqualTo(HttpStatusCode.OK);
var servicesNode = healthContent?["services"]?.AsObject();
- Assert.NotNull(servicesNode);
+ await Assert.That(servicesNode).IsNotNull();
// All three services should be running
- Assert.True(servicesNode.ContainsKey("sqs"));
- Assert.Equal("running", servicesNode["sqs"]?.ToString());
- Assert.True(servicesNode.ContainsKey("dynamodb"));
- Assert.Equal("running", servicesNode["dynamodb"]?.ToString());
- Assert.True(servicesNode.ContainsKey("s3"));
- Assert.Equal("running", servicesNode["s3"]?.ToString());
+ await Assert.That(servicesNode.ContainsKey("sqs")).IsTrue();
+ await Assert.That(servicesNode["sqs"]?.ToString()).IsEqualTo("running");
+ await Assert.That(servicesNode.ContainsKey("dynamodb")).IsTrue();
+ await Assert.That(servicesNode["dynamodb"]?.ToString()).IsEqualTo("running");
+ await Assert.That(servicesNode.ContainsKey("s3")).IsTrue();
+ await Assert.That(servicesNode["s3"]?.ToString()).IsEqualTo("running");
}
- [Fact]
- public async Task LocalStack_Should_Handle_Empty_EagerLoadedServices_Like_Lazy_Loading_Async()
+ [Test]
+ public async Task LocalStack_Should_Handle_Empty_EagerLoadedServices_Like_Lazy_Loading_Async(CancellationToken cancellationToken)
{
using var parentCts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
- using var cts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token, TestContext.Current.CancellationToken);
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token, cancellationToken);
#pragma warning disable CA1849
await using var builder = DistributedApplicationTestingBuilder.Create("LocalStack:UseLocalStack=true");
@@ -174,15 +168,15 @@ public async Task LocalStack_Should_Handle_Empty_EagerLoadedServices_Like_Lazy_L
using var httpClient = app.CreateHttpClient("localstack", "http");
var healthResponse = await httpClient.GetAsync(new Uri("/_localstack/health", UriKind.Relative), cts.Token);
var healthContent = await healthResponse.Content.ReadFromJsonAsync(cts.Token);
- Assert.Equal(HttpStatusCode.OK, healthResponse.StatusCode);
+ await Assert.That(healthResponse.StatusCode).IsEqualTo(HttpStatusCode.OK);
var servicesNode = healthContent?["services"]?.AsObject();
- Assert.NotNull(servicesNode);
+ await Assert.That(servicesNode).IsNotNull();
// Services should not be running by default (lazy loading)
if (servicesNode.ContainsKey("sqs"))
{
- Assert.NotEqual("running", servicesNode["sqs"]?.ToString());
+ await Assert.That(servicesNode["sqs"]?.ToString()).IsNotEqualTo("running");
}
}
diff --git a/tests/Aspire.Hosting.LocalStack.Integration.Tests/GlobalSuppressions.cs b/tests/Aspire.Hosting.LocalStack.Integration.Tests/GlobalSuppressions.cs
deleted file mode 100644
index fc4e418..0000000
--- a/tests/Aspire.Hosting.LocalStack.Integration.Tests/GlobalSuppressions.cs
+++ /dev/null
@@ -1,2 +0,0 @@
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Test methods use descriptive Should naming convention")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "CA1707:Naming Styles", Justification = "Test methods use descriptive Should naming convention")]
diff --git a/tests/Aspire.Hosting.LocalStack.Integration.Tests/GlobalUsings.cs b/tests/Aspire.Hosting.LocalStack.Integration.Tests/GlobalUsings.cs
index ea4825e..cb63b66 100644
--- a/tests/Aspire.Hosting.LocalStack.Integration.Tests/GlobalUsings.cs
+++ b/tests/Aspire.Hosting.LocalStack.Integration.Tests/GlobalUsings.cs
@@ -1,14 +1,27 @@
-global using System.Net;
+global using System.Globalization;
+global using System.Net;
+global using System.Net.Http.Json;
+global using System.Text.Json;
+global using System.Text.Json.Nodes;
+global using Amazon;
global using Amazon.DynamoDBv2;
global using Amazon.S3;
global using Amazon.S3.Util;
global using Amazon.SimpleNotificationService;
global using Amazon.SQS;
+global using Amazon.SQS.Model;
global using Aspire.Hosting.ApplicationModel;
global using Aspire.Hosting.AWS.CloudFormation;
+global using Aspire.Hosting.LocalStack.Container;
+global using Aspire.Hosting.LocalStack.Integration.Tests.TestInfrastructure;
global using Aspire.Hosting.Testing;
global using LocalStack.Client;
+global using LocalStack.Client.Contracts;
+global using LocalStack.Client.Enums;
global using LocalStack.Client.Options;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Logging;
-global using Xunit;
+global using TUnit.Core;
+global using TUnit.Assertions;
+global using TUnit.Assertions.Extensions;
+global using TUnit.Core.Interfaces;
diff --git a/tests/Aspire.Hosting.LocalStack.Integration.Tests/Playground/Lambda/LocalStackLambdaFunctionalTests.cs b/tests/Aspire.Hosting.LocalStack.Integration.Tests/Playground/Lambda/LocalStackLambdaFunctionalTests.cs
index 55a4c68..137f922 100644
--- a/tests/Aspire.Hosting.LocalStack.Integration.Tests/Playground/Lambda/LocalStackLambdaFunctionalTests.cs
+++ b/tests/Aspire.Hosting.LocalStack.Integration.Tests/Playground/Lambda/LocalStackLambdaFunctionalTests.cs
@@ -1,16 +1,12 @@
-using System.Net.Http.Json;
-using System.Text.Json;
-using Amazon.DynamoDBv2.Model;
-using Aspire.Hosting.LocalStack.Integration.Tests.TestInfrastructure;
-
namespace Aspire.Hosting.LocalStack.Integration.Tests.Playground.Lambda;
///
/// End-to-end functional tests for the Lambda playground.
/// These tests validate the complete URL shortener and analytics flow.
///
-[Collection("LocalStackLambda")]
-public class LocalStackLambdaFunctionalTests(LocalStackLambdaFixture fixture, ITestOutputHelper outputHelper)
+[NotInParallel("IntegrationTests")]
+[ClassDataSource(Shared = SharedType.PerTestSession)]
+public class LocalStackLambdaFunctionalTests(LocalStackLambdaFixture fixture)
{
// Cache JsonSerializerOptions to avoid creating new instances for each test (CA1869)
private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true };
@@ -21,72 +17,72 @@ public class LocalStackLambdaFunctionalTests(LocalStackLambdaFixture fixture, IT
PropertyNamingPolicy = null, // Use default PascalCase, not camelCase
};
- [Fact]
- public async Task UrlShortener_Should_Create_Short_Url()
+ [Test]
+ public async Task UrlShortener_Should_Create_Short_Url(CancellationToken cancellationToken)
{
// Arrange
using var httpClient = fixture.CreateApiGatewayClient();
var request = new { Url = "https://aws.amazon.com", Format = (string?)null };
// Act
- var response = await httpClient.PostAsJsonAsync("/shorten", request, PostJsonOptions, TestContext.Current.CancellationToken);
- var content = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
+ var response = await httpClient.PostAsJsonAsync("/shorten", request, PostJsonOptions, cancellationToken);
+ var content = await response.Content.ReadAsStringAsync(cancellationToken);
// Log response for debugging
- outputHelper.WriteLine($"Response Status: {response.StatusCode}");
- outputHelper.WriteLine($"Response Body: {content}");
+ await TestOutputHelper.WriteLineAsync($"Response Status: {response.StatusCode}");
+ await TestOutputHelper.WriteLineAsync($"Response Body: {content}");
// Assert
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+ await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.Created);
var result = JsonSerializer.Deserialize(content, JsonOptions);
- Assert.NotNull(result);
- Assert.NotNull(result.Id);
- Assert.NotEmpty(result.Id);
- Assert.Null(result.QrUrl); // No QR code requested
+ await Assert.That(result).IsNotNull();
+ await Assert.That(result.Id).IsNotNull();
+ await Assert.That(result.Id).IsNotEmpty();
+ await Assert.That(result.QrUrl).IsNull(); // No QR code requested
- outputHelper.WriteLine($"Created short URL with ID: {result.Id}");
+ await TestOutputHelper.WriteLineAsync($"Created short URL with ID: {result.Id}");
}
- [Fact]
- public async Task UrlShortener_Should_Create_Short_Url_With_QrCode()
+ [Test]
+ public async Task UrlShortener_Should_Create_Short_Url_With_QrCode(CancellationToken cancellationToken)
{
// Arrange
using var httpClient = fixture.CreateApiGatewayClient();
var request = new { Url = "https://localstack.cloud", Format = "qr" };
// Act
- var response = await httpClient.PostAsJsonAsync("/shorten", request, PostJsonOptions, TestContext.Current.CancellationToken);
- var content = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
+ var response = await httpClient.PostAsJsonAsync("/shorten", request, PostJsonOptions, cancellationToken);
+ var content = await response.Content.ReadAsStringAsync(cancellationToken);
// Assert
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+ await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.Created);
var result = JsonSerializer.Deserialize(content, JsonOptions);
- Assert.NotNull(result);
- Assert.NotNull(result.Id);
- Assert.NotEmpty(result.Id);
- Assert.NotNull(result.QrUrl); // QR code was requested
- Assert.NotEmpty(result.QrUrl);
-
- outputHelper.WriteLine($"Created short URL with ID: {result.Id}");
- outputHelper.WriteLine($"QR Code URL: {result.QrUrl}");
+ await Assert.That(result).IsNotNull();
+ await Assert.That(result.Id).IsNotNull();
+ await Assert.That(result.Id).IsNotEmpty();
+ await Assert.That(result.QrUrl).IsNotNull(); // QR code was requested
+ await Assert.That(result.QrUrl).IsNotEmpty();
+
+ await TestOutputHelper.WriteLineAsync($"Created short URL with ID: {result.Id}");
+ await TestOutputHelper.WriteLineAsync($"QR Code URL: {result.QrUrl}");
}
- [Fact]
- public async Task Redirector_Should_Redirect_To_Original_Url()
+ [Test]
+ public async Task Redirector_Should_Redirect_To_Original_Url(CancellationToken cancellationToken)
{
// Arrange: First create a short URL
using var httpClient = fixture.CreateApiGatewayClient();
httpClient.Timeout = TimeSpan.FromSeconds(90);
var createRequest = new { Url = "https://docs.localstack.cloud", Format = (string?)null };
- var createResponse = await httpClient.PostAsJsonAsync("/shorten", createRequest, PostJsonOptions, TestContext.Current.CancellationToken);
- var createContent = await createResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
+ var createResponse = await httpClient.PostAsJsonAsync("/shorten", createRequest, PostJsonOptions, cancellationToken);
+ var createContent = await createResponse.Content.ReadAsStringAsync(cancellationToken);
var createResult = JsonSerializer.Deserialize(createContent, JsonOptions);
- Assert.NotNull(createResult);
- Assert.NotNull(createResult.Id);
+ await Assert.That(createResult).IsNotNull();
+ await Assert.That(createResult.Id).IsNotNull();
// Act: Access the redirect endpoint (don't follow redirects automatically)
using var handler = new HttpClientHandler();
@@ -95,19 +91,20 @@ public async Task Redirector_Should_Redirect_To_Original_Url()
redirectClient.BaseAddress = httpClient.BaseAddress;
redirectClient.Timeout = TimeSpan.FromSeconds(90);
- var redirectResponse = await redirectClient.GetAsync(new Uri($"/{createResult.Id}", UriKind.Relative), TestContext.Current.CancellationToken);
+ var redirectResponse = await redirectClient.GetAsync(new Uri($"/{createResult.Id}", UriKind.Relative), cancellationToken);
+ var headersLocation = redirectResponse.Headers.Location;
// Assert
- Assert.Equal(HttpStatusCode.Found, redirectResponse.StatusCode); // 302 Found
- Assert.True(redirectResponse.Headers.Location is not null);
+ await Assert.That(redirectResponse.StatusCode).IsEqualTo(HttpStatusCode.Found); // 302 Found
+ await Assert.That(headersLocation).IsNotNull();
// Trim trailing slash for comparison as DynamoDB might normalize URLs
- Assert.Equal("https://docs.localstack.cloud", redirectResponse.Headers.Location.ToString().TrimEnd('/'));
+ await Assert.That(headersLocation.ToString().TrimEnd('/')).IsEqualTo("https://docs.localstack.cloud");
- outputHelper.WriteLine($"Redirect from /{createResult.Id} to {redirectResponse.Headers.Location}");
+ await TestOutputHelper.WriteLineAsync($"Redirect from /{createResult.Id} to {headersLocation}");
}
- [Fact]
- public async Task Analyzer_Lambda_Should_Process_Events_From_Queue()
+ [Test]
+ public async Task Analyzer_Lambda_Should_Process_Events_From_Queue(CancellationToken cancellationToken)
{
// Arrange
using var httpClient = fixture.CreateApiGatewayClient();
@@ -122,43 +119,43 @@ public async Task Analyzer_Lambda_Should_Process_Events_From_Queue()
using var stringContent = new StringContent(payload, System.Text.Encoding.UTF8, "application/json");
// Act: Create a short URL (this triggers analytics event → SQS → Analyzer Lambda)
- var createResponse = await httpClient.PostAsync(new Uri("/shorten", UriKind.Relative), stringContent, TestContext.Current.CancellationToken);
- var createContent = await createResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
+ var createResponse = await httpClient.PostAsync(new Uri("/shorten", UriKind.Relative), stringContent, cancellationToken);
+ var createContent = await createResponse.Content.ReadAsStringAsync(cancellationToken);
var createResult = JsonSerializer.Deserialize(createContent, JsonOptions);
- Assert.NotNull(createResult);
- Assert.NotNull(createResult.Id);
+ await Assert.That(createResult).IsNotNull();
+ await Assert.That(createResult.Id).IsNotNull();
// Wait for SQS Event Source to trigger Analyzer Lambda and process the event
- outputHelper.WriteLine("Waiting for Analyzer Lambda to process event...");
- await Task.Delay(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
+ await TestOutputHelper.WriteLineAsync("Waiting for Analyzer Lambda to process event...");
+ await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
// Assert: Verify event was written to AnalyticsTable by Analyzer Lambda
- var scanResponse = await dynamoDbClient.ScanAsync(new ScanRequest
+ var scanResponse = await dynamoDbClient.ScanAsync(new Amazon.DynamoDBv2.Model.ScanRequest
{
TableName = analyticsTableName,
FilterExpression = "Slug = :slug AND EventType = :eventType",
- ExpressionAttributeValues = new Dictionary
+ ExpressionAttributeValues = new Dictionary
(StringComparer.OrdinalIgnoreCase)
{
[":slug"] = new() { S = createResult.Id },
[":eventType"] = new() { S = "url_created" },
},
- }, TestContext.Current.CancellationToken);
+ }, cancellationToken);
- Assert.NotEmpty(scanResponse.Items);
- Assert.Single(scanResponse.Items);
+ await Assert.That(scanResponse.Items).IsNotEmpty();
+ await Assert.That(scanResponse.Items).HasSingleItem();
var analyticsItem = scanResponse.Items[0];
- Assert.Equal(createResult.Id, analyticsItem["Slug"].S);
- Assert.Equal("url_created", analyticsItem["EventType"].S);
- Assert.Equal(testUrl, analyticsItem["OriginalUrl"].S);
+ await Assert.That(analyticsItem["Slug"].S).IsEqualTo(createResult.Id);
+ await Assert.That(analyticsItem["EventType"].S).IsEqualTo("url_created");
+ await Assert.That(analyticsItem["OriginalUrl"].S).IsEqualTo(testUrl);
- outputHelper.WriteLine($"Analyzer Lambda successfully processed event for slug: {createResult.Id}");
+ await TestOutputHelper.WriteLineAsync($"Analyzer Lambda successfully processed event for slug: {createResult.Id}");
}
- [Fact]
- public async Task Redirecting_Url_Should_Send_Analytics_Event_And_Be_Processed()
+ [Test]
+ public async Task Redirecting_Url_Should_Send_Analytics_Event_And_Be_Processed(CancellationToken cancellationToken)
{
// Arrange: First create a short URL
using var httpClient = fixture.CreateApiGatewayClient();
@@ -170,15 +167,15 @@ public async Task Redirecting_Url_Should_Send_Analytics_Event_And_Be_Processed()
var testUrl = $"https://redirect-analytics-test.example.com/{Guid.NewGuid()}";
var createRequest = new { Url = testUrl, Format = (string?)null };
- var createResponse = await httpClient.PostAsJsonAsync("/shorten", createRequest, PostJsonOptions, TestContext.Current.CancellationToken);
- var createContent = await createResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
+ var createResponse = await httpClient.PostAsJsonAsync("/shorten", createRequest, PostJsonOptions, cancellationToken);
+ var createContent = await createResponse.Content.ReadAsStringAsync(cancellationToken);
var createResult = JsonSerializer.Deserialize(createContent, JsonOptions);
- Assert.NotNull(createResult);
- Assert.NotNull(createResult.Id);
+ await Assert.That(createResult).IsNotNull();
+ await Assert.That(createResult.Id).IsNotNull();
// Wait for creation analytics to be processed
- await Task.Delay(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
+ await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
// Act: Access the redirect endpoint
using var handler = new HttpClientHandler();
@@ -187,33 +184,33 @@ public async Task Redirecting_Url_Should_Send_Analytics_Event_And_Be_Processed()
redirectClient.BaseAddress = httpClient.BaseAddress;
redirectClient.Timeout = TimeSpan.FromSeconds(90);
- var redirectResponse = await redirectClient.GetAsync(new Uri($"/{createResult.Id}", UriKind.Relative), TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.Found, redirectResponse.StatusCode);
+ var redirectResponse = await redirectClient.GetAsync(new Uri($"/{createResult.Id}", UriKind.Relative), cancellationToken);
+ await Assert.That(redirectResponse.StatusCode).IsEqualTo(HttpStatusCode.Found);
// Wait for Analyzer Lambda to process the url_accessed event
- outputHelper.WriteLine("Waiting for Analyzer Lambda to process url_accessed event...");
- await Task.Delay(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
+ await TestOutputHelper.WriteLineAsync("Waiting for Analyzer Lambda to process url_accessed event...");
+ await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
// Assert: Verify both url_created and url_accessed events are in AnalyticsTable
- var scanResponse = await dynamoDbClient.ScanAsync(new ScanRequest
+ var scanResponse = await dynamoDbClient.ScanAsync(new Amazon.DynamoDBv2.Model.ScanRequest
{
TableName = analyticsTableName,
FilterExpression = "Slug = :slug",
- ExpressionAttributeValues = new Dictionary
+ ExpressionAttributeValues = new Dictionary
(StringComparer.OrdinalIgnoreCase)
{
[":slug"] = new() { S = createResult.Id },
},
- }, TestContext.Current.CancellationToken);
+ }, cancellationToken);
- Assert.NotEmpty(scanResponse.Items);
- Assert.Equal(2, scanResponse.Items.Count); // Both url_created and url_accessed
+ await Assert.That(scanResponse.Items).IsNotEmpty();
+ await Assert.That(scanResponse.Items.Count).IsEqualTo(2); // Both url_created and url_accessed
var eventTypes = scanResponse.Items.Select(item => item["EventType"].S).ToList();
- Assert.Contains("url_created", eventTypes);
- Assert.Contains("url_accessed", eventTypes);
+ await Assert.That(eventTypes).Contains("url_created");
+ await Assert.That(eventTypes).Contains("url_accessed");
- outputHelper.WriteLine($"Successfully verified both url_created and url_accessed events for slug: {createResult.Id}");
+ await TestOutputHelper.WriteLineAsync($"Successfully verified both url_created and url_accessed events for slug: {createResult.Id}");
}
///
diff --git a/tests/Aspire.Hosting.LocalStack.Integration.Tests/Playground/Lambda/LocalStackLambdaResourceTests.cs b/tests/Aspire.Hosting.LocalStack.Integration.Tests/Playground/Lambda/LocalStackLambdaResourceTests.cs
index 47a4864..5fc4434 100644
--- a/tests/Aspire.Hosting.LocalStack.Integration.Tests/Playground/Lambda/LocalStackLambdaResourceTests.cs
+++ b/tests/Aspire.Hosting.LocalStack.Integration.Tests/Playground/Lambda/LocalStackLambdaResourceTests.cs
@@ -1,53 +1,52 @@
-using Aspire.Hosting.LocalStack.Integration.Tests.TestInfrastructure;
-
namespace Aspire.Hosting.LocalStack.Integration.Tests.Playground.Lambda;
///
/// Tests for LocalStack Lambda playground resources.
/// These tests verify that CloudFormation stack outputs and AWS resources are created correctly.
///
-[Collection("LocalStackLambda")]
-public class LocalStackLambdaResourceTests(LocalStackLambdaFixture fixture, ITestOutputHelper outputHelper)
+[NotInParallel("IntegrationTests")]
+[ClassDataSource(Shared = SharedType.PerTestSession)]
+public class LocalStackLambdaResourceTests(LocalStackLambdaFixture fixture)
{
- [Fact]
- public async Task LocalStack_Should_Be_Healthy()
+ [Test]
+ public async Task LocalStack_Should_Be_Healthy(CancellationToken cancellationToken)
{
using var httpClient = fixture.App.CreateHttpClient("localstack", "http");
- var healthResponse = await httpClient.GetAsync(new Uri("/_localstack/health", UriKind.Relative), TestContext.Current.CancellationToken);
- var healthContent = await healthResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
+ var healthResponse = await httpClient.GetAsync(new Uri("/_localstack/health", UriKind.Relative), cancellationToken);
+ var healthContent = await healthResponse.Content.ReadAsStringAsync(cancellationToken);
- Assert.Equal(HttpStatusCode.OK, healthResponse.StatusCode);
- Assert.NotEmpty(healthContent);
+ await Assert.That(healthResponse.StatusCode).IsEqualTo(HttpStatusCode.OK);
+ await Assert.That(healthContent).IsNotEmpty();
- outputHelper.WriteLine($"LocalStack health check passed: {healthResponse.StatusCode}");
- outputHelper.WriteLine($"Health response: {healthContent}");
+ await TestOutputHelper.WriteLineAsync($"LocalStack health check passed: {healthResponse.StatusCode}");
+ await TestOutputHelper.WriteLineAsync($"Health response: {healthContent}");
}
- [Fact]
- public void CloudFormation_Stack_Should_Have_Required_Outputs()
+ [Test]
+ public async Task CloudFormation_Stack_Should_Have_Required_Outputs()
{
var qrBucketName = fixture.StackOutputs.GetOutput("QrBucketName");
var urlsTableName = fixture.StackOutputs.GetOutput("UrlsTableName");
var analyticsQueueUrl = fixture.StackOutputs.GetOutput("AnalyticsQueueUrl");
var analyticsTableName = fixture.StackOutputs.GetOutput("AnalyticsTableName");
- Assert.NotNull(qrBucketName);
- Assert.NotEmpty(qrBucketName);
- Assert.NotNull(urlsTableName);
- Assert.NotEmpty(urlsTableName);
- Assert.NotNull(analyticsQueueUrl);
- Assert.NotEmpty(analyticsQueueUrl);
- Assert.NotNull(analyticsTableName);
- Assert.NotEmpty(analyticsTableName);
-
- outputHelper.WriteLine($"QrBucketName: {qrBucketName}");
- outputHelper.WriteLine($"UrlsTableName: {urlsTableName}");
- outputHelper.WriteLine($"AnalyticsQueueUrl: {analyticsQueueUrl}");
- outputHelper.WriteLine($"AnalyticsTableName: {analyticsTableName}");
+ await Assert.That(qrBucketName).IsNotNull();
+ await Assert.That(qrBucketName).IsNotEmpty();
+ await Assert.That(urlsTableName).IsNotNull();
+ await Assert.That(urlsTableName).IsNotEmpty();
+ await Assert.That(analyticsQueueUrl).IsNotNull();
+ await Assert.That(analyticsQueueUrl).IsNotEmpty();
+ await Assert.That(analyticsTableName).IsNotNull();
+ await Assert.That(analyticsTableName).IsNotEmpty();
+
+ await TestOutputHelper.WriteLineAsync($"QrBucketName: {qrBucketName}");
+ await TestOutputHelper.WriteLineAsync($"UrlsTableName: {urlsTableName}");
+ await TestOutputHelper.WriteLineAsync($"AnalyticsQueueUrl: {analyticsQueueUrl}");
+ await TestOutputHelper.WriteLineAsync($"AnalyticsTableName: {analyticsTableName}");
}
- [Fact]
- public async Task S3_QrBucket_Should_Exist()
+ [Test]
+ public async Task S3_QrBucket_Should_Exist(CancellationToken cancellationToken)
{
var bucketName = fixture.StackOutputs.GetOutput("QrBucketName")
?? throw new InvalidOperationException("QrBucketName output not found");
@@ -57,12 +56,12 @@ public async Task S3_QrBucket_Should_Exist()
var s3Client = session.CreateClientByImplementation();
var doesS3BucketExist = await AmazonS3Util.DoesS3BucketExistV2Async(s3Client, bucketName);
- Assert.True(doesS3BucketExist, $"S3 bucket '{bucketName}' should exist");
- outputHelper.WriteLine($"S3 bucket '{bucketName}' exists ✓");
+ await Assert.That(doesS3BucketExist).IsTrue().Because($"S3 bucket '{bucketName}' should exist");
+ await TestOutputHelper.WriteLineAsync($"S3 bucket '{bucketName}' exists ✓");
}
- [Fact]
- public async Task DynamoDB_UrlsTable_Should_Exist()
+ [Test]
+ public async Task DynamoDB_UrlsTable_Should_Exist(CancellationToken cancellationToken)
{
var tableName = fixture.StackOutputs.GetOutput("UrlsTableName")
?? throw new InvalidOperationException("UrlsTableName output not found");
@@ -70,18 +69,18 @@ public async Task DynamoDB_UrlsTable_Should_Exist()
var session = LocalStackTestHelpers.CreateLocalStackSession(fixture.LocalStackConnectionString, fixture.RegionName);
var dynamoDbClient = session.CreateClientByImplementation();
- var tableResponse = await dynamoDbClient.DescribeTableAsync(tableName, TestContext.Current.CancellationToken);
+ var tableResponse = await dynamoDbClient.DescribeTableAsync(tableName, cancellationToken);
- Assert.NotNull(tableResponse);
- Assert.Equal(HttpStatusCode.OK, tableResponse.HttpStatusCode);
- Assert.Equal(tableName, tableResponse.Table.TableName);
+ await Assert.That(tableResponse).IsNotNull();
+ await Assert.That(tableResponse.HttpStatusCode).IsEqualTo(HttpStatusCode.OK);
+ await Assert.That(tableResponse.Table.TableName).IsEqualTo(tableName);
- outputHelper.WriteLine($"DynamoDB table '{tableName}' exists ✓");
- outputHelper.WriteLine($" Partition Key: {tableResponse.Table.KeySchema[0].AttributeName}");
+ await TestOutputHelper.WriteLineAsync($"DynamoDB table '{tableName}' exists ✓");
+ await TestOutputHelper.WriteLineAsync($" Partition Key: {tableResponse.Table.KeySchema[0].AttributeName}");
}
- [Fact]
- public async Task SQS_AnalyticsQueue_Should_Exist()
+ [Test]
+ public async Task SQS_AnalyticsQueue_Should_Exist(CancellationToken cancellationToken)
{
var queueUrl = fixture.StackOutputs.GetOutput("AnalyticsQueueUrl")
?? throw new InvalidOperationException("AnalyticsQueueUrl output not found");
@@ -89,19 +88,19 @@ public async Task SQS_AnalyticsQueue_Should_Exist()
var session = LocalStackTestHelpers.CreateLocalStackSession(fixture.LocalStackConnectionString, fixture.RegionName);
var sqsClient = session.CreateClientByImplementation();
- var queueAttributesResponse = await sqsClient.GetQueueAttributesAsync(queueUrl, ["QueueArn"], TestContext.Current.CancellationToken);
+ var queueAttributesResponse = await sqsClient.GetQueueAttributesAsync(queueUrl, ["QueueArn"], cancellationToken);
- Assert.NotNull(queueAttributesResponse);
- Assert.Equal(HttpStatusCode.OK, queueAttributesResponse.HttpStatusCode);
- Assert.NotEmpty(queueAttributesResponse.Attributes);
- Assert.True(queueAttributesResponse.Attributes.ContainsKey("QueueArn"));
+ await Assert.That(queueAttributesResponse).IsNotNull();
+ await Assert.That(queueAttributesResponse.HttpStatusCode).IsEqualTo(HttpStatusCode.OK);
+ await Assert.That(queueAttributesResponse.Attributes).IsNotEmpty();
+ await Assert.That(queueAttributesResponse.Attributes.ContainsKey("QueueArn")).IsTrue();
- outputHelper.WriteLine($"SQS queue '{queueUrl}' exists ✓");
- outputHelper.WriteLine($" Queue ARN: {queueAttributesResponse.Attributes["QueueArn"]}");
+ await TestOutputHelper.WriteLineAsync($"SQS queue '{queueUrl}' exists ✓");
+ await TestOutputHelper.WriteLineAsync($" Queue ARN: {queueAttributesResponse.Attributes["QueueArn"]}");
}
- [Fact]
- public async Task DynamoDB_AnalyticsTable_Should_Exist_With_Correct_Schema()
+ [Test]
+ public async Task DynamoDB_AnalyticsTable_Should_Exist_With_Correct_Schema(CancellationToken cancellationToken)
{
var tableName = fixture.StackOutputs.GetOutput("AnalyticsTableName")
?? throw new InvalidOperationException("AnalyticsTableName output not found");
@@ -109,23 +108,23 @@ public async Task DynamoDB_AnalyticsTable_Should_Exist_With_Correct_Schema()
var session = LocalStackTestHelpers.CreateLocalStackSession(fixture.LocalStackConnectionString, fixture.RegionName);
var dynamoDbClient = session.CreateClientByImplementation();
- var tableResponse = await dynamoDbClient.DescribeTableAsync(tableName, TestContext.Current.CancellationToken);
+ var tableResponse = await dynamoDbClient.DescribeTableAsync(tableName, cancellationToken);
- Assert.NotNull(tableResponse);
- Assert.Equal(HttpStatusCode.OK, tableResponse.HttpStatusCode);
- Assert.Equal(tableName, tableResponse.Table.TableName);
+ await Assert.That(tableResponse).IsNotNull();
+ await Assert.That(tableResponse.HttpStatusCode).IsEqualTo(HttpStatusCode.OK);
+ await Assert.That(tableResponse.Table.TableName).IsEqualTo(tableName);
// Verify key schema
var partitionKey = tableResponse.Table.KeySchema.FirstOrDefault(k => k.KeyType == KeyType.HASH);
- Assert.NotNull(partitionKey);
- Assert.Equal("EventId", partitionKey.AttributeName);
+ await Assert.That(partitionKey).IsNotNull();
+ await Assert.That(partitionKey.AttributeName).IsEqualTo("EventId");
var sortKey = tableResponse.Table.KeySchema.FirstOrDefault(k => k.KeyType == KeyType.RANGE);
- Assert.NotNull(sortKey);
- Assert.Equal("Timestamp", sortKey.AttributeName);
+ await Assert.That(sortKey).IsNotNull();
+ await Assert.That(sortKey.AttributeName).IsEqualTo("Timestamp");
- outputHelper.WriteLine($"DynamoDB analytics table '{tableName}' exists ✓");
- outputHelper.WriteLine($" Partition Key: {partitionKey.AttributeName}");
- outputHelper.WriteLine($" Sort Key: {sortKey.AttributeName}");
+ await TestOutputHelper.WriteLineAsync($"DynamoDB analytics table '{tableName}' exists ✓");
+ await TestOutputHelper.WriteLineAsync($" Partition Key: {partitionKey.AttributeName}");
+ await TestOutputHelper.WriteLineAsync($" Sort Key: {sortKey.AttributeName}");
}
}
diff --git a/tests/Aspire.Hosting.LocalStack.Integration.Tests/Playground/Provisioning/LocalStackCDKResourceTests.cs b/tests/Aspire.Hosting.LocalStack.Integration.Tests/Playground/Provisioning/LocalStackCDKResourceTests.cs
index 3d6a585..db595a1 100644
--- a/tests/Aspire.Hosting.LocalStack.Integration.Tests/Playground/Provisioning/LocalStackCDKResourceTests.cs
+++ b/tests/Aspire.Hosting.LocalStack.Integration.Tests/Playground/Provisioning/LocalStackCDKResourceTests.cs
@@ -1,53 +1,52 @@
-using Aspire.Hosting.LocalStack.Integration.Tests.TestInfrastructure;
-
namespace Aspire.Hosting.LocalStack.Integration.Tests.Playground.Provisioning;
///
/// Tests for LocalStack CDK provisioning playground resources.
/// These tests verify that CloudFormation stack outputs and AWS resources are created correctly.
///
-[Collection("LocalStackCDK")]
-public class LocalStackCdkResourceTests(LocalStackCdkFixture fixture, ITestOutputHelper outputHelper)
+[NotInParallel("IntegrationTests")]
+[ClassDataSource(Shared = SharedType.PerTestSession)]
+public class LocalStackCdkResourceTests(LocalStackCdkFixture fixture)
{
- [Fact]
- public async Task LocalStack_Should_Be_Healthy()
+ [Test]
+ public async Task LocalStack_Should_Be_Healthy(CancellationToken cancellationToken)
{
using var httpClient = fixture.App.CreateHttpClient("localstack", "http");
- var healthResponse = await httpClient.GetAsync(new Uri("/_localstack/health", UriKind.Relative), TestContext.Current.CancellationToken);
- var healthContent = await healthResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
+ var healthResponse = await httpClient.GetAsync(new Uri("/_localstack/health", UriKind.Relative), cancellationToken);
+ var healthContent = await healthResponse.Content.ReadAsStringAsync(cancellationToken);
- Assert.Equal(HttpStatusCode.OK, healthResponse.StatusCode);
- Assert.NotEmpty(healthContent);
+ await Assert.That(healthResponse.StatusCode).IsEqualTo(HttpStatusCode.OK);
+ await Assert.That(healthContent).IsNotEmpty();
- outputHelper.WriteLine($"LocalStack health check passed: {healthResponse.StatusCode}");
- outputHelper.WriteLine($"Health response: {healthContent}");
+ await TestOutputHelper.WriteLineAsync($"LocalStack health check passed: {healthResponse.StatusCode}");
+ await TestOutputHelper.WriteLineAsync($"Health response: {healthContent}");
}
- [Fact]
- public void CloudFormation_Stack_Should_Have_Required_Outputs()
+ [Test]
+ public async Task CloudFormation_Stack_Should_Have_Required_Outputs()
{
var bucketName = fixture.StackOutputs.GetOutput("BucketName");
var chatTopicArn = fixture.StackOutputs.GetOutput("ChatTopicArn");
var chatMessagesQueueUrl = fixture.StackOutputs.GetOutput("ChatMessagesQueueUrl");
var chatMessagesTableName = fixture.StackOutputs.GetOutput("ChatMessagesTableName");
- Assert.NotNull(bucketName);
- Assert.NotEmpty(bucketName);
- Assert.NotNull(chatTopicArn);
- Assert.NotEmpty(chatTopicArn);
- Assert.NotNull(chatMessagesQueueUrl);
- Assert.NotEmpty(chatMessagesQueueUrl);
- Assert.NotNull(chatMessagesTableName);
- Assert.NotEmpty(chatMessagesTableName);
-
- outputHelper.WriteLine($"BucketName: {bucketName}");
- outputHelper.WriteLine($"ChatTopicArn: {chatTopicArn}");
- outputHelper.WriteLine($"ChatMessagesQueueUrl: {chatMessagesQueueUrl}");
- outputHelper.WriteLine($"ChatMessagesTableName: {chatMessagesTableName}");
+ await Assert.That(bucketName).IsNotNull();
+ await Assert.That(bucketName).IsNotEmpty();
+ await Assert.That(chatTopicArn).IsNotNull();
+ await Assert.That(chatTopicArn).IsNotEmpty();
+ await Assert.That(chatMessagesQueueUrl).IsNotNull();
+ await Assert.That(chatMessagesQueueUrl).IsNotEmpty();
+ await Assert.That(chatMessagesTableName).IsNotNull();
+ await Assert.That(chatMessagesTableName).IsNotEmpty();
+
+ await TestOutputHelper.WriteLineAsync($"BucketName: {bucketName}");
+ await TestOutputHelper.WriteLineAsync($"ChatTopicArn: {chatTopicArn}");
+ await TestOutputHelper.WriteLineAsync($"ChatMessagesQueueUrl: {chatMessagesQueueUrl}");
+ await TestOutputHelper.WriteLineAsync($"ChatMessagesTableName: {chatMessagesTableName}");
}
- [Fact]
- public async Task S3_Bucket_Should_Exist()
+ [Test]
+ public async Task S3_Bucket_Should_Exist(CancellationToken cancellationToken)
{
var bucketName = fixture.StackOutputs.GetOutput("BucketName")
?? throw new InvalidOperationException("BucketName output not found");
@@ -57,12 +56,12 @@ public async Task S3_Bucket_Should_Exist()
var s3Client = session.CreateClientByImplementation();
var doesS3BucketExist = await AmazonS3Util.DoesS3BucketExistV2Async(s3Client, bucketName);
- Assert.True(doesS3BucketExist, $"S3 bucket '{bucketName}' should exist");
- outputHelper.WriteLine($"S3 bucket '{bucketName}' exists ✓");
+ await Assert.That(doesS3BucketExist).IsTrue().Because($"S3 bucket '{bucketName}' should exist");
+ await TestOutputHelper.WriteLineAsync($"S3 bucket '{bucketName}' exists ✓");
}
- [Fact]
- public async Task SNS_ChatTopic_Should_Exist()
+ [Test]
+ public async Task SNS_ChatTopic_Should_Exist(CancellationToken cancellationToken)
{
var topicArn = fixture.StackOutputs.GetOutput("ChatTopicArn")
?? throw new InvalidOperationException("ChatTopicArn output not found");
@@ -70,18 +69,18 @@ public async Task SNS_ChatTopic_Should_Exist()
var session = LocalStackTestHelpers.CreateLocalStackSession(fixture.LocalStackConnectionString, fixture.RegionName);
var snsClient = session.CreateClientByImplementation();
- var topicAttributesResponse = await snsClient.GetTopicAttributesAsync(topicArn, TestContext.Current.CancellationToken);
+ var topicAttributesResponse = await snsClient.GetTopicAttributesAsync(topicArn, cancellationToken);
- Assert.NotNull(topicAttributesResponse);
- Assert.Equal(HttpStatusCode.OK, topicAttributesResponse.HttpStatusCode);
- Assert.NotEmpty(topicAttributesResponse.Attributes);
+ await Assert.That(topicAttributesResponse).IsNotNull();
+ await Assert.That(topicAttributesResponse.HttpStatusCode).IsEqualTo(HttpStatusCode.OK);
+ await Assert.That(topicAttributesResponse.Attributes).IsNotEmpty();
- outputHelper.WriteLine($"SNS topic '{topicArn}' exists ✓");
- outputHelper.WriteLine($" Display Name: {topicAttributesResponse.Attributes.GetValueOrDefault("DisplayName", "N/A")}");
+ await TestOutputHelper.WriteLineAsync($"SNS topic '{topicArn}' exists ✓");
+ await TestOutputHelper.WriteLineAsync($" Display Name: {topicAttributesResponse.Attributes.GetValueOrDefault("DisplayName", "N/A")}");
}
- [Fact]
- public async Task SQS_ChatMessagesQueue_Should_Exist()
+ [Test]
+ public async Task SQS_ChatMessagesQueue_Should_Exist(CancellationToken cancellationToken)
{
var queueUrl = fixture.StackOutputs.GetOutput("ChatMessagesQueueUrl")
?? throw new InvalidOperationException("ChatMessagesQueueUrl output not found");
@@ -89,19 +88,19 @@ public async Task SQS_ChatMessagesQueue_Should_Exist()
var session = LocalStackTestHelpers.CreateLocalStackSession(fixture.LocalStackConnectionString, fixture.RegionName);
var sqsClient = session.CreateClientByImplementation();
- var queueAttributesResponse = await sqsClient.GetQueueAttributesAsync(queueUrl, ["QueueArn"], TestContext.Current.CancellationToken);
+ var queueAttributesResponse = await sqsClient.GetQueueAttributesAsync(queueUrl, ["QueueArn"], cancellationToken);
- Assert.NotNull(queueAttributesResponse);
- Assert.Equal(HttpStatusCode.OK, queueAttributesResponse.HttpStatusCode);
- Assert.NotEmpty(queueAttributesResponse.Attributes);
- Assert.True(queueAttributesResponse.Attributes.ContainsKey("QueueArn"));
+ await Assert.That(queueAttributesResponse).IsNotNull();
+ await Assert.That(queueAttributesResponse.HttpStatusCode).IsEqualTo(HttpStatusCode.OK);
+ await Assert.That(queueAttributesResponse.Attributes).IsNotEmpty();
+ await Assert.That(queueAttributesResponse.Attributes.ContainsKey("QueueArn")).IsTrue();
- outputHelper.WriteLine($"SQS queue '{queueUrl}' exists ✓");
- outputHelper.WriteLine($" Queue ARN: {queueAttributesResponse.Attributes["QueueArn"]}");
+ await TestOutputHelper.WriteLineAsync($"SQS queue '{queueUrl}' exists ✓");
+ await TestOutputHelper.WriteLineAsync($" Queue ARN: {queueAttributesResponse.Attributes["QueueArn"]}");
}
- [Fact]
- public async Task DynamoDB_ChatMessagesTable_Should_Exist()
+ [Test]
+ public async Task DynamoDB_ChatMessagesTable_Should_Exist(CancellationToken cancellationToken)
{
var tableName = fixture.StackOutputs.GetOutput("ChatMessagesTableName")
?? throw new InvalidOperationException("ChatMessagesTableName output not found");
@@ -109,13 +108,13 @@ public async Task DynamoDB_ChatMessagesTable_Should_Exist()
var session = LocalStackTestHelpers.CreateLocalStackSession(fixture.LocalStackConnectionString, fixture.RegionName);
var dynamoDbClient = session.CreateClientByImplementation();
- var tableResponse = await dynamoDbClient.DescribeTableAsync(tableName, TestContext.Current.CancellationToken);
+ var tableResponse = await dynamoDbClient.DescribeTableAsync(tableName, cancellationToken);
- Assert.NotNull(tableResponse);
- Assert.Equal(HttpStatusCode.OK, tableResponse.HttpStatusCode);
- Assert.Equal(tableName, tableResponse.Table.TableName);
+ await Assert.That(tableResponse).IsNotNull();
+ await Assert.That(tableResponse.HttpStatusCode).IsEqualTo(HttpStatusCode.OK);
+ await Assert.That(tableResponse.Table.TableName).IsEqualTo(tableName);
- outputHelper.WriteLine($"DynamoDB table '{tableName}' exists ✓");
- outputHelper.WriteLine($" Partition Key: {tableResponse.Table.KeySchema[0].AttributeName}");
+ await TestOutputHelper.WriteLineAsync($"DynamoDB table '{tableName}' exists ✓");
+ await TestOutputHelper.WriteLineAsync($" Partition Key: {tableResponse.Table.KeySchema[0].AttributeName}");
}
}
diff --git a/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/LocalStackCDKFixture.cs b/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/LocalStackCDKFixture.cs
index 71772cb..f3b028e 100644
--- a/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/LocalStackCDKFixture.cs
+++ b/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/LocalStackCDKFixture.cs
@@ -2,9 +2,9 @@ namespace Aspire.Hosting.LocalStack.Integration.Tests.TestInfrastructure;
///
/// Test fixture for LocalStack CDK provisioning integration tests.
-/// Starts the AppHost once and shares it across all tests in the collection.
+/// Starts the AppHost once and shares it across all tests in the class.
///
-public sealed class LocalStackCdkFixture : IAsyncLifetime
+public sealed class LocalStackCdkFixture : IAsyncInitializer, IAsyncDisposable
{
private DistributedApplication? _app;
private CloudFormationStackOutputs? _stackOutputs;
@@ -18,26 +18,20 @@ public sealed class LocalStackCdkFixture : IAsyncLifetime
///
public string RegionName { get; private set; } = string.Empty;
- public CloudFormationStackOutputs StackOutputs =>
- _stackOutputs ?? throw new InvalidOperationException("Stack outputs not initialized");
+ public CloudFormationStackOutputs StackOutputs => _stackOutputs ?? throw new InvalidOperationException("Stack outputs not initialized");
- public async ValueTask InitializeAsync()
+ public async Task InitializeAsync()
{
- using var parentCts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
- using var cts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token, TestContext.Current.CancellationToken);
+ using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync(["LocalStack:UseLocalStack=true"], cts.Token);
- // Configure logging to capture Aspire app logs in xUnit test output
+ // Configure logging to capture Aspire app logs
appHost.Services.AddLogging(logging =>
{
- if (TestContext.Current.TestOutputHelper is not null)
- {
- logging.AddXUnit(TestContext.Current.TestOutputHelper);
- }
logging.SetMinimumLevel(LogLevel.Information)
- .AddFilter("Aspire.Hosting.Dcp", LogLevel.Warning);
+ .AddFilter("Aspire.Hosting.Dcp", LogLevel.Warning);
});
_app = await appHost.BuildAsync(cts.Token);
@@ -73,9 +67,3 @@ public async ValueTask DisposeAsync()
}
}
}
-
-///
-/// xUnit collection definition for CDK provisioning tests to share the fixture.
-///
-[CollectionDefinition("LocalStackCDK", DisableParallelization = true)]
-public sealed class LocalStackCdkCollectionDefinition : ICollectionFixture;
diff --git a/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/LocalStackLambdaFixture.cs b/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/LocalStackLambdaFixture.cs
index 2a8bebc..9c92105 100644
--- a/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/LocalStackLambdaFixture.cs
+++ b/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/LocalStackLambdaFixture.cs
@@ -2,9 +2,9 @@ namespace Aspire.Hosting.LocalStack.Integration.Tests.TestInfrastructure;
///
/// Test fixture for LocalStack Lambda integration tests.
-/// Starts the AppHost once and shares it across all tests in the collection.
+/// Starts the AppHost once and shares it across all tests in the class.
///
-public sealed class LocalStackLambdaFixture : IAsyncLifetime
+public sealed class LocalStackLambdaFixture : IAsyncInitializer, IAsyncDisposable
{
private DistributedApplication? _app;
private CloudFormationStackOutputs? _stackOutputs;
@@ -26,21 +26,16 @@ public sealed class LocalStackLambdaFixture : IAsyncLifetime
///
public HttpClient CreateApiGatewayClient() => App.CreateHttpClient("APIGatewayEmulator");
- public async ValueTask InitializeAsync()
+ public async Task InitializeAsync()
{
- using var parentCts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
- using var cts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token, TestContext.Current.CancellationToken);
+ using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync(["LocalStack:UseLocalStack=true"], cts.Token);
- // Configure logging to capture Aspire app logs in xUnit test output
+ // Configure logging to capture Aspire app logs
appHost.Services.AddLogging(logging =>
{
- if (TestContext.Current.TestOutputHelper is not null)
- {
- logging.AddXUnit(TestContext.Current.TestOutputHelper);
- }
logging.SetMinimumLevel(LogLevel.Information)
.AddFilter("Aspire.Hosting.Dcp", LogLevel.Warning);
});
@@ -78,9 +73,3 @@ public async ValueTask DisposeAsync()
}
}
}
-
-///
-/// xUnit collection definition for Lambda tests to share the fixture.
-///
-[CollectionDefinition("LocalStackLambda", DisableParallelization = true)]
-public class LocalStackLambdaCollectionDefinition : ICollectionFixture;
diff --git a/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/LocalStackTestHelpers.cs b/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/LocalStackTestHelpers.cs
index cd0bf1e..378d70b 100644
--- a/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/LocalStackTestHelpers.cs
+++ b/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/LocalStackTestHelpers.cs
@@ -1,5 +1,3 @@
-using LocalStack.Client.Contracts;
-
namespace Aspire.Hosting.LocalStack.Integration.Tests.TestInfrastructure;
///
diff --git a/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/TestOutputHelper.cs b/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/TestOutputHelper.cs
new file mode 100644
index 0000000..05b1162
--- /dev/null
+++ b/tests/Aspire.Hosting.LocalStack.Integration.Tests/TestInfrastructure/TestOutputHelper.cs
@@ -0,0 +1,33 @@
+namespace Aspire.Hosting.LocalStack.Integration.Tests.TestInfrastructure;
+
+///
+/// Helper for safely writing test output, handling cases where TestContext.Current may be null.
+///
+internal static class TestOutputHelper
+{
+ ///
+ /// Safely writes a line to the test output. Does nothing if TestContext.Current is null.
+ ///
+ /// The message to write.
+ public static async Task WriteLineAsync(string message)
+ {
+ if (TestContext.Current is { } context)
+ {
+ await context.OutputWriter.WriteLineAsync(message);
+ }
+ }
+
+ ///
+ /// Safely writes a line to the test output using an interpolated string.
+ /// Does nothing if TestContext.Current is null.
+ ///
+ /// The interpolated string handler.
+ public static async Task WriteLineAsync(FormattableString handler)
+ {
+ if (TestContext.Current is { } context)
+ {
+ await context.OutputWriter.WriteLineAsync(handler.ToString(CultureInfo.InvariantCulture));
+ }
+ }
+}
+
diff --git a/tests/Aspire.Hosting.LocalStack.Integration.Tests/xunit.runner.json b/tests/Aspire.Hosting.LocalStack.Integration.Tests/xunit.runner.json
deleted file mode 100644
index 6d2c252..0000000
--- a/tests/Aspire.Hosting.LocalStack.Integration.Tests/xunit.runner.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
- "diagnosticMessages": false,
- "methodDisplay": "classAndMethod",
- "parallelizeTestCollections": false,
- "showLiveOutput": true,
- "stopOnFail": false,
- "longRunningTestSeconds": 300
-}
diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Annotations/LocalStackEnabledAnnotationTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Annotations/LocalStackEnabledAnnotationTests.cs
index 7c8cd82..fe375c5 100644
--- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Annotations/LocalStackEnabledAnnotationTests.cs
+++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Annotations/LocalStackEnabledAnnotationTests.cs
@@ -2,46 +2,46 @@ namespace Aspire.Hosting.LocalStack.Unit.Tests.Annotations;
public class LocalStackEnabledAnnotationTests
{
- [Fact]
- public void LocalStackEnabledAnnotation_Should_Implement_IResourceAnnotation()
+ [Test]
+ public async Task LocalStackEnabledAnnotation_Should_Implement_IResourceAnnotation()
{
var localStackResource = CreateTestLocalStackResource();
var annotation = new LocalStackEnabledAnnotation(localStackResource);
- Assert.IsType(annotation, exactMatch: false);
+ await Assert.That(annotation).IsAssignableTo();
}
- [Fact]
- public void LocalStackEnabledAnnotation_Should_Store_LocalStack_Resource_Reference()
+ [Test]
+ public async Task LocalStackEnabledAnnotation_Should_Store_LocalStack_Resource_Reference()
{
var localStackResource = CreateTestLocalStackResource();
var annotation = new LocalStackEnabledAnnotation(localStackResource);
- Assert.Same(localStackResource, annotation.LocalStackResource);
+ await Assert.That(annotation.LocalStackResource).IsSameReferenceAs(localStackResource);
}
- [Fact]
- public void LocalStackEnabledAnnotation_Should_Throw_ArgumentNullException_For_Null_LocalStack_Resource()
+ [Test]
+ public async Task LocalStackEnabledAnnotation_Should_Throw_ArgumentNullException_For_Null_LocalStack_Resource()
{
- Assert.Throws(() => new LocalStackEnabledAnnotation(null!));
+ await Assert.That(() => new LocalStackEnabledAnnotation(null!)).ThrowsExactly();
}
- [Fact]
- public void LocalStackEnabledAnnotation_Should_Maintain_Same_Resource_Reference()
+ [Test]
+ public async Task LocalStackEnabledAnnotation_Should_Maintain_Same_Resource_Reference()
{
var localStackResource = CreateTestLocalStackResource();
var annotation = new LocalStackEnabledAnnotation(localStackResource);
// Test that the resource reference remains consistent
- Assert.NotNull(annotation);
- Assert.NotNull(annotation.LocalStackResource);
- Assert.Same(localStackResource, annotation.LocalStackResource);
+ await Assert.That(annotation).IsNotNull();
+ await Assert.That(annotation.LocalStackResource).IsNotNull();
+ await Assert.That(annotation.LocalStackResource).IsSameReferenceAs(localStackResource);
// Multiple calls should return the same reference
- Assert.Same(annotation.LocalStackResource, annotation.LocalStackResource);
+ await Assert.That(annotation.LocalStackResource).IsSameReferenceAs(annotation.LocalStackResource);
}
private static LocalStackResource CreateTestLocalStackResource()
diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Annotations/LocalStackReferenceAnnotationTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Annotations/LocalStackReferenceAnnotationTests.cs
index 9aca077..a349a39 100644
--- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Annotations/LocalStackReferenceAnnotationTests.cs
+++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Annotations/LocalStackReferenceAnnotationTests.cs
@@ -2,59 +2,59 @@ namespace Aspire.Hosting.LocalStack.Unit.Tests.Annotations;
public class LocalStackReferenceAnnotationTests
{
- [Fact]
- public void LocalStackReferenceAnnotation_Should_Implement_IResourceAnnotation()
+ [Test]
+ public async Task LocalStackReferenceAnnotation_Should_Implement_IResourceAnnotation()
{
var testResource = Substitute.For();
testResource.Name.Returns("test-resource");
var annotation = new LocalStackReferenceAnnotation(testResource);
- Assert.IsType(annotation, exactMatch: false);
+ await Assert.That(annotation).IsAssignableTo();
}
- [Fact]
- public void LocalStackReferenceAnnotation_Should_Store_Target_Resource_Reference()
+ [Test]
+ public async Task LocalStackReferenceAnnotation_Should_Store_Target_Resource_Reference()
{
var testResource = Substitute.For();
testResource.Name.Returns("test-resource");
var annotation = new LocalStackReferenceAnnotation(testResource);
- Assert.Same(testResource, annotation.Resource);
+ await Assert.That(annotation.Resource).IsSameReferenceAs(testResource);
}
- [Fact]
- public void LocalStackReferenceAnnotation_Should_Throw_ArgumentNullException_For_Null_Target_Resource()
+ [Test]
+ public async Task LocalStackReferenceAnnotation_Should_Throw_ArgumentNullException_For_Null_Target_Resource()
{
- Assert.Throws(() => new LocalStackReferenceAnnotation(null!));
+ await Assert.That(() => new LocalStackReferenceAnnotation(null!)).ThrowsExactly();
}
- [Fact]
- public void LocalStackReferenceAnnotation_Should_Maintain_Same_Target_Resource_Reference()
+ [Test]
+ public async Task LocalStackReferenceAnnotation_Should_Maintain_Same_Target_Resource_Reference()
{
var testResource = Substitute.For();
testResource.Name.Returns("valid-resource-name");
var annotation = new LocalStackReferenceAnnotation(testResource);
- Assert.NotNull(annotation);
- Assert.Same(testResource, annotation.Resource);
+ await Assert.That(annotation).IsNotNull();
+ await Assert.That(annotation.Resource).IsSameReferenceAs(testResource);
// Multiple calls should return the same reference
- Assert.Same(annotation.Resource, annotation.Resource);
+ await Assert.That(annotation.Resource).IsSameReferenceAs(annotation.Resource);
}
- [Fact]
- public void LocalStackReferenceAnnotation_Should_Accept_Resource_With_Empty_Name()
+ [Test]
+ public async Task LocalStackReferenceAnnotation_Should_Accept_Resource_With_Empty_Name()
{
var testResource = Substitute.For();
testResource.Name.Returns(string.Empty);
var annotation = new LocalStackReferenceAnnotation(testResource);
- Assert.NotNull(annotation);
- Assert.Same(testResource, annotation.Resource);
- Assert.Equal(string.Empty, annotation.Resource.Name);
+ await Assert.That(annotation).IsNotNull();
+ await Assert.That(annotation.Resource).IsSameReferenceAs(testResource);
+ await Assert.That(annotation.Resource.Name).IsEqualTo(string.Empty);
}
}
diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Aspire.Hosting.LocalStack.Unit.Tests.csproj b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Aspire.Hosting.LocalStack.Unit.Tests.csproj
index 8908e18..5248a06 100644
--- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Aspire.Hosting.LocalStack.Unit.Tests.csproj
+++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Aspire.Hosting.LocalStack.Unit.Tests.csproj
@@ -8,11 +8,6 @@
-
-
-
-
-
diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/CDK/CdkBootstrapManagerTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/CDK/CdkBootstrapManagerTests.cs
index d021aac..be55a5c 100644
--- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/CDK/CdkBootstrapManagerTests.cs
+++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/CDK/CdkBootstrapManagerTests.cs
@@ -1,19 +1,18 @@
namespace Aspire.Hosting.LocalStack.Unit.Tests.CDK;
-[Collection("SequentialCdkTests")]
public sealed class CdkBootstrapManagerTests
{
- [Fact]
- public void GetBootstrapTemplatePath_Should_Return_Valid_File_Path()
+ [Test]
+ public async Task GetBootstrapTemplatePath_Should_Return_Valid_File_Path()
{
var testDir = CreateTestDirectory();
try
{
var templatePath = CdkBootstrapManager.GetBootstrapTemplatePath(testDir);
- Assert.NotNull(templatePath);
- Assert.NotEmpty(templatePath);
- Assert.True(Path.IsPathFullyQualified(templatePath));
+ await Assert.That(templatePath).IsNotNull();
+ await Assert.That(templatePath).IsNotEmpty();
+ await Assert.That(Path.IsPathFullyQualified(templatePath)).IsTrue();
}
finally
{
@@ -21,17 +20,17 @@ public void GetBootstrapTemplatePath_Should_Return_Valid_File_Path()
}
}
- [Fact]
- public void GetBootstrapTemplatePath_Should_Extract_Template_To_Temp_Directory()
+ [Test]
+ public async Task GetBootstrapTemplatePath_Should_Extract_Template_To_Temp_Directory()
{
var testDir = CreateTestDirectory();
try
{
var templatePath = CdkBootstrapManager.GetBootstrapTemplatePath(testDir);
- Assert.Contains(testDir, templatePath, StringComparison.Ordinal);
- Assert.EndsWith(".template", templatePath, StringComparison.Ordinal);
- Assert.Contains("cdk-bootstrap-", templatePath, StringComparison.Ordinal);
+ await Assert.That(templatePath).Contains(testDir);
+ await Assert.That(templatePath).EndsWith(".template");
+ await Assert.That(templatePath).Contains("cdk-bootstrap-");
}
finally
{
@@ -39,15 +38,15 @@ public void GetBootstrapTemplatePath_Should_Extract_Template_To_Temp_Directory()
}
}
- [Fact]
- public void GetBootstrapTemplatePath_Should_Create_File_That_Exists()
+ [Test]
+ public async Task GetBootstrapTemplatePath_Should_Create_File_That_Exists()
{
var testDir = CreateTestDirectory();
try
{
var templatePath = CdkBootstrapManager.GetBootstrapTemplatePath(testDir);
- Assert.True(File.Exists(templatePath));
+ await Assert.That(File.Exists(templatePath)).IsTrue();
}
finally
{
@@ -55,8 +54,8 @@ public void GetBootstrapTemplatePath_Should_Create_File_That_Exists()
}
}
- [Fact]
- public void GetBootstrapTemplatePath_Should_Create_Non_Empty_Template_File()
+ [Test]
+ public async Task GetBootstrapTemplatePath_Should_Create_Non_Empty_Template_File()
{
var testDir = CreateTestDirectory();
try
@@ -64,7 +63,7 @@ public void GetBootstrapTemplatePath_Should_Create_Non_Empty_Template_File()
var templatePath = CdkBootstrapManager.GetBootstrapTemplatePath(testDir);
var fileInfo = new FileInfo(templatePath);
- Assert.True(fileInfo.Length > 0);
+ await Assert.That(fileInfo.Length > 0).IsTrue();
}
finally
{
@@ -72,8 +71,8 @@ public void GetBootstrapTemplatePath_Should_Create_Non_Empty_Template_File()
}
}
- [Fact]
- public void GetBootstrapTemplatePath_Should_Return_Same_Path_On_Multiple_Calls()
+ [Test]
+ public async Task GetBootstrapTemplatePath_Should_Return_Same_Path_On_Multiple_Calls()
{
var testDir = CreateTestDirectory();
try
@@ -81,7 +80,7 @@ public void GetBootstrapTemplatePath_Should_Return_Same_Path_On_Multiple_Calls()
var path1 = CdkBootstrapManager.GetBootstrapTemplatePath(testDir);
var path2 = CdkBootstrapManager.GetBootstrapTemplatePath(testDir);
- Assert.Equal(path1, path2);
+ await Assert.That(path1).IsEqualTo(path2);
}
finally
{
@@ -89,8 +88,8 @@ public void GetBootstrapTemplatePath_Should_Return_Same_Path_On_Multiple_Calls()
}
}
- [Fact]
- public void GetBootstrapTemplatePath_Should_Create_Directory_If_It_Does_Not_Exist()
+ [Test]
+ public async Task GetBootstrapTemplatePath_Should_Create_Directory_If_It_Does_Not_Exist()
{
var testDir = CreateTestDirectory();
// Delete the directory to test creation
@@ -98,12 +97,12 @@ public void GetBootstrapTemplatePath_Should_Create_Directory_If_It_Does_Not_Exis
try
{
- Assert.False(Directory.Exists(testDir));
+ await Assert.That(Directory.Exists(testDir)).IsFalse();
var templatePath = CdkBootstrapManager.GetBootstrapTemplatePath(testDir);
- Assert.True(Directory.Exists(testDir));
- Assert.True(File.Exists(templatePath));
+ await Assert.That(Directory.Exists(testDir)).IsTrue();
+ await Assert.That(File.Exists(templatePath)).IsTrue();
}
finally
{
@@ -111,8 +110,8 @@ public void GetBootstrapTemplatePath_Should_Create_Directory_If_It_Does_Not_Exis
}
}
- [Fact]
- public void GetBootstrapTemplatePath_Should_Handle_Multiple_Sequential_Calls()
+ [Test]
+ public async Task GetBootstrapTemplatePath_Should_Handle_Multiple_Sequential_Calls()
{
var testDir = CreateTestDirectory();
try
@@ -125,9 +124,12 @@ public void GetBootstrapTemplatePath_Should_Handle_Multiple_Sequential_Calls()
results.Add(CdkBootstrapManager.GetBootstrapTemplatePath(testDir));
}
- Assert.All(results, Assert.NotNull);
- Assert.All(results, path => Assert.True(File.Exists(path)));
- Assert.True(results.TrueForAll(path => string.Equals(path, results[0], StringComparison.Ordinal)));
+ foreach (var path in results)
+ {
+ await Assert.That(path).IsNotNull();
+ await Assert.That(File.Exists(path)).IsTrue();
+ }
+ await Assert.That(results.TrueForAll(path => string.Equals(path, results[0], StringComparison.Ordinal))).IsTrue();
}
finally
{
@@ -135,23 +137,23 @@ public void GetBootstrapTemplatePath_Should_Handle_Multiple_Sequential_Calls()
}
}
- [Fact]
- public void GetBootstrapTemplatePath_Should_Overwrite_Existing_File()
+ [Test]
+ public async Task GetBootstrapTemplatePath_Should_Overwrite_Existing_File()
{
var testDir = CreateTestDirectory();
try
{
var firstPath = CdkBootstrapManager.GetBootstrapTemplatePath(testDir);
- var originalContent = File.ReadAllText(firstPath);
+ var originalContent = await File.ReadAllTextAsync(firstPath);
- File.WriteAllText(firstPath, "outdated content");
+ await File.WriteAllTextAsync(firstPath, "outdated content");
var secondPath = CdkBootstrapManager.GetBootstrapTemplatePath(testDir);
- var newContent = File.ReadAllText(secondPath);
+ var newContent = await File.ReadAllTextAsync(secondPath);
- Assert.Equal(firstPath, secondPath);
- Assert.Equal(originalContent, newContent);
- Assert.NotEqual("outdated content", newContent);
+ await Assert.That(firstPath).IsEqualTo(secondPath);
+ await Assert.That(originalContent).IsEqualTo(newContent);
+ await Assert.That(newContent).IsNotEqualTo("outdated content");
}
finally
{
@@ -159,7 +161,7 @@ public void GetBootstrapTemplatePath_Should_Overwrite_Existing_File()
}
}
- [Fact]
+ [Test]
public async Task GetBootstrapTemplatePath_Should_Be_Thread_Safe_And_Handle_Parallel_Calls()
{
var testDir = CreateTestDirectory();
@@ -175,15 +177,22 @@ public async Task GetBootstrapTemplatePath_Should_Be_Thread_Safe_And_Handle_Para
var results = await Task.WhenAll(tasks);
- Assert.Equal(parallelTasks, results.Length);
- Assert.All(results, path => Assert.False(string.IsNullOrEmpty(path)));
+ await Assert.That(results.Length).IsEqualTo(parallelTasks);
+
+ foreach (var path in results)
+ {
+ await Assert.That(string.IsNullOrEmpty(path)).IsFalse();
+ }
var firstPath = results[0];
- Assert.All(results, path => Assert.Equal(firstPath, path));
+ foreach (var path in results)
+ {
+ await Assert.That(path).IsEqualTo(firstPath);
+ }
- Assert.True(File.Exists(firstPath));
+ await Assert.That(File.Exists(firstPath)).IsTrue();
var fileInfo = new FileInfo(firstPath);
- Assert.True(fileInfo.Length > 0);
+ await Assert.That(fileInfo.Length > 0).IsTrue();
}
finally
{
@@ -215,9 +224,3 @@ private static void CleanupTestDirectory(string testDirectory)
}
}
}
-
-[CollectionDefinition("CdkManagerSequential", DisableParallelization = true)]
-[SuppressMessage("Maintainability", "CA1515:Consider making public types internal")]
-[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix")]
-[SuppressMessage("Design", "MA0048:File name must match type name")]
-public class CdkManagerSequentialCollection : ICollectionFixture;
diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Container/LocalStackContainerOptionsTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Container/LocalStackContainerOptionsTests.cs
index 4359d6c..87c61f4 100644
--- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Container/LocalStackContainerOptionsTests.cs
+++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Container/LocalStackContainerOptionsTests.cs
@@ -1,177 +1,179 @@
-using LocalStack.Client.Enums;
-
#pragma warning disable S4143
namespace Aspire.Hosting.LocalStack.Unit.Tests.Container;
public class LocalStackContainerOptionsTests
{
- [Fact]
- public void Constructor_Should_Set_Correct_Defaults()
+ [Test]
+ public async Task Constructor_Should_Set_Correct_Defaults()
{
var options = new LocalStackContainerOptions();
- Assert.Equal(ContainerLifetime.Session, options.Lifetime);
- Assert.Equal(0, options.DebugLevel);
- Assert.Equal(LocalStackLogLevel.Error, options.LogLevel);
- Assert.NotNull(options.AdditionalEnvironmentVariables);
- Assert.Empty(options.AdditionalEnvironmentVariables);
- Assert.False(options.EnableDockerSocket);
+ await Assert.That(options.Lifetime).IsEqualTo(ContainerLifetime.Session);
+ await Assert.That(options.DebugLevel).IsEqualTo(0);
+ await Assert.That(options.LogLevel).IsEqualTo(LocalStackLogLevel.Error);
+ await Assert.That(options.AdditionalEnvironmentVariables).IsNotNull();
+ await Assert.That(options.AdditionalEnvironmentVariables).IsEmpty();
+ await Assert.That(options.EnableDockerSocket).IsFalse();
}
- [Fact]
- public void AdditionalEnvironmentVariables_Should_Be_Mutable_Dictionary()
+ [Test]
+ public async Task AdditionalEnvironmentVariables_Should_Be_Mutable_Dictionary()
{
- var options = new LocalStackContainerOptions();
-
- options.AdditionalEnvironmentVariables["TEST_KEY"] = "test_value";
- options.AdditionalEnvironmentVariables["ANOTHER_KEY"] = "another_value";
+ var options = new LocalStackContainerOptions
+ {
+ AdditionalEnvironmentVariables =
+ {
+ ["TEST_KEY"] = "test_value",
+ ["ANOTHER_KEY"] = "another_value"
+ }
+ };
- Assert.Equal(2, options.AdditionalEnvironmentVariables.Count);
- Assert.Equal("test_value", options.AdditionalEnvironmentVariables["TEST_KEY"]);
- Assert.Equal("another_value", options.AdditionalEnvironmentVariables["ANOTHER_KEY"]);
+ await Assert.That(options.AdditionalEnvironmentVariables.Count).IsEqualTo(2);
+ await Assert.That(options.AdditionalEnvironmentVariables["TEST_KEY"]).IsEqualTo("test_value");
+ await Assert.That(options.AdditionalEnvironmentVariables["ANOTHER_KEY"]).IsEqualTo("another_value");
}
- [Fact]
- public void AdditionalEnvironmentVariables_Should_Use_Ordinal_StringComparer()
+ [Test]
+ public async Task AdditionalEnvironmentVariables_Should_Use_Ordinal_StringComparer()
{
var options = new LocalStackContainerOptions();
options.AdditionalEnvironmentVariables["TestKey"] = "value1";
options.AdditionalEnvironmentVariables["testkey"] = "value2";
- Assert.Equal(2, options.AdditionalEnvironmentVariables.Count);
- Assert.Equal("value1", options.AdditionalEnvironmentVariables["TestKey"]);
- Assert.Equal("value2", options.AdditionalEnvironmentVariables["testkey"]);
+ await Assert.That(options.AdditionalEnvironmentVariables.Count).IsEqualTo(2);
+ await Assert.That(options.AdditionalEnvironmentVariables["TestKey"]).IsEqualTo("value1");
+ await Assert.That(options.AdditionalEnvironmentVariables["testkey"]).IsEqualTo("value2");
}
- [Fact]
- public void EagerLoadedServices_Should_Default_To_Empty_Collection()
+ [Test]
+ public async Task EagerLoadedServices_Should_Default_To_Empty_Collection()
{
var options = new LocalStackContainerOptions();
- Assert.NotNull(options.EagerLoadedServices);
- Assert.Empty(options.EagerLoadedServices);
+ await Assert.That(options.EagerLoadedServices).IsNotNull();
+ await Assert.That(options.EagerLoadedServices).IsEmpty();
}
- [Fact]
- public void EagerLoadedServices_Should_Accept_Single_Service()
+ [Test]
+ public async Task EagerLoadedServices_Should_Accept_Single_Service()
{
var options = new LocalStackContainerOptions
{
EagerLoadedServices = [AwsService.Sqs],
};
- Assert.Single(options.EagerLoadedServices);
- Assert.Contains(AwsService.Sqs, options.EagerLoadedServices);
+ await Assert.That(options.EagerLoadedServices).HasSingleItem();
+ await Assert.That(options.EagerLoadedServices).Contains(AwsService.Sqs);
}
- [Fact]
- public void EagerLoadedServices_Should_Accept_Multiple_Services()
+ [Test]
+ public async Task EagerLoadedServices_Should_Accept_Multiple_Services()
{
var options = new LocalStackContainerOptions
{
EagerLoadedServices = [AwsService.Sqs, AwsService.DynamoDb, AwsService.S3],
};
- Assert.Equal(3, options.EagerLoadedServices.Count);
- Assert.Contains(AwsService.Sqs, options.EagerLoadedServices);
- Assert.Contains(AwsService.DynamoDb, options.EagerLoadedServices);
- Assert.Contains(AwsService.S3, options.EagerLoadedServices);
+ await Assert.That(options.EagerLoadedServices.Count).IsEqualTo(3);
+ await Assert.That(options.EagerLoadedServices).Contains(AwsService.Sqs);
+ await Assert.That(options.EagerLoadedServices).Contains(AwsService.DynamoDb);
+ await Assert.That(options.EagerLoadedServices).Contains(AwsService.S3);
}
- [Fact]
- public void EnableDockerSocket_Should_Default_To_False()
+ [Test]
+ public async Task EnableDockerSocket_Should_Default_To_False()
{
var options = new LocalStackContainerOptions();
- Assert.False(options.EnableDockerSocket);
+ await Assert.That(options.EnableDockerSocket).IsFalse();
}
- [Fact]
- public void EnableDockerSocket_Should_Be_Settable()
+ [Test]
+ public async Task EnableDockerSocket_Should_Be_Settable()
{
var options = new LocalStackContainerOptions
{
EnableDockerSocket = true,
};
- Assert.True(options.EnableDockerSocket);
+ await Assert.That(options.EnableDockerSocket).IsTrue();
}
- [Fact]
- public void Port_Should_Default_To_Null()
+ [Test]
+ public async Task Port_Should_Default_To_Null()
{
var options = new LocalStackContainerOptions();
- Assert.Null(options.Port);
+ await Assert.That(options.Port).IsNull();
}
- [Fact]
- public void Port_Should_Be_Settable()
+ [Test]
+ public async Task Port_Should_Be_Settable()
{
var options = new LocalStackContainerOptions
{
Port = 1234,
};
- Assert.Equal(1234, options.Port);
+ await Assert.That(options.Port).IsEqualTo(1234);
}
- [Fact]
- public void ContainerRegistry_Should_Default_To_Null()
+ [Test]
+ public async Task ContainerRegistry_Should_Default_To_Null()
{
var options = new LocalStackContainerOptions();
- Assert.Null(options.ContainerRegistry);
+ await Assert.That(options.ContainerRegistry).IsNull();
}
- [Fact]
- public void ContainerRegistry_Should_Be_Settable()
+ [Test]
+ public async Task ContainerRegistry_Should_Be_Settable()
{
var options = new LocalStackContainerOptions
{
ContainerRegistry = "artifactory.company.com",
};
- Assert.Equal("artifactory.company.com", options.ContainerRegistry);
+ await Assert.That(options.ContainerRegistry).IsEqualTo("artifactory.company.com");
}
- [Fact]
- public void ContainerImage_Should_Default_To_Null()
+ [Test]
+ public async Task ContainerImage_Should_Default_To_Null()
{
var options = new LocalStackContainerOptions();
- Assert.Null(options.ContainerImage);
+ await Assert.That(options.ContainerImage).IsNull();
}
- [Fact]
- public void ContainerImage_Should_Be_Settable()
+ [Test]
+ public async Task ContainerImage_Should_Be_Settable()
{
var options = new LocalStackContainerOptions
{
ContainerImage = "custom/localstack",
};
- Assert.Equal("custom/localstack", options.ContainerImage);
+ await Assert.That(options.ContainerImage).IsEqualTo("custom/localstack");
}
- [Fact]
- public void ContainerImageTag_Should_Default_To_Null()
+ [Test]
+ public async Task ContainerImageTag_Should_Default_To_Null()
{
var options = new LocalStackContainerOptions();
- Assert.Null(options.ContainerImageTag);
+ await Assert.That(options.ContainerImageTag).IsNull();
}
- [Fact]
- public void ContainerImageTag_Should_Be_Settable()
+ [Test]
+ public async Task ContainerImageTag_Should_Be_Settable()
{
var options = new LocalStackContainerOptions
{
ContainerImageTag = "4.9.2",
};
- Assert.Equal("4.9.2", options.ContainerImageTag);
+ await Assert.That(options.ContainerImageTag).IsEqualTo("4.9.2");
}
}
diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Container/LocalStackLogLevelTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Container/LocalStackLogLevelTests.cs
index 64e0e58..e7be3a2 100644
--- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Container/LocalStackLogLevelTests.cs
+++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Container/LocalStackLogLevelTests.cs
@@ -2,67 +2,67 @@ namespace Aspire.Hosting.LocalStack.Unit.Tests.Container;
public class LocalStackLogLevelTests
{
- [Theory]
- [MemberData(nameof(AllLogLevelMappingsTestData))]
- public void ToEnvironmentValue_Should_Map_All_Enum_Values_Correctly(LocalStackLogLevel logLevel, string expectedValue)
+ [Test]
+ [MethodDataSource(nameof(AllLogLevelMappingsTestData))]
+ public async Task ToEnvironmentValue_Should_Map_All_Enum_Values_Correctly(LocalStackLogLevel logLevel, string expectedValue)
{
var result = logLevel.ToEnvironmentValue();
- Assert.Equal(expectedValue, result);
+ await Assert.That(result).IsEqualTo(expectedValue);
}
- [Fact]
- public void LocalStackLogLevel_Should_Have_All_Expected_Values()
+ [Test]
+ public async Task LocalStackLogLevel_Should_Have_All_Expected_Values()
{
var enumValues = Enum.GetValues();
- Assert.Equal(7, enumValues.Length);
- Assert.Contains(LocalStackLogLevel.Trace, enumValues);
- Assert.Contains(LocalStackLogLevel.TraceInternal, enumValues);
- Assert.Contains(LocalStackLogLevel.Debug, enumValues);
- Assert.Contains(LocalStackLogLevel.Info, enumValues);
- Assert.Contains(LocalStackLogLevel.Warn, enumValues);
- Assert.Contains(LocalStackLogLevel.Error, enumValues);
- Assert.Contains(LocalStackLogLevel.Warning, enumValues);
+ await Assert.That(enumValues.Length).IsEqualTo(7);
+ await Assert.That(enumValues).Contains(LocalStackLogLevel.Trace);
+ await Assert.That(enumValues).Contains(LocalStackLogLevel.TraceInternal);
+ await Assert.That(enumValues).Contains(LocalStackLogLevel.Debug);
+ await Assert.That(enumValues).Contains(LocalStackLogLevel.Info);
+ await Assert.That(enumValues).Contains(LocalStackLogLevel.Warn);
+ await Assert.That(enumValues).Contains(LocalStackLogLevel.Error);
+ await Assert.That(enumValues).Contains(LocalStackLogLevel.Warning);
}
- [Fact]
- public void LocalStackLogLevel_Should_Have_Unique_Integer_Values()
+ [Test]
+ public async Task LocalStackLogLevel_Should_Have_Unique_Integer_Values()
{
var enumValues = Enum.GetValues();
var integerValues = enumValues.Cast().ToArray();
- Assert.Equal(integerValues.Length, integerValues.Distinct().Count());
+ await Assert.That(integerValues.Length).IsEqualTo(integerValues.Distinct().Count());
}
- [Fact]
- public void ToEnvironmentValue_Should_Handle_Invalid_Enum_Values_Gracefully()
+ [Test]
+ public async Task ToEnvironmentValue_Should_Handle_Invalid_Enum_Values_Gracefully()
{
const LocalStackLogLevel invalidLogLevel = (LocalStackLogLevel)999;
// The implementation doesn't throw for invalid values, it handles them gracefully
var result = invalidLogLevel.ToEnvironmentValue();
- Assert.NotNull(result);
+ await Assert.That(result).IsNotNull();
}
- [Fact]
- public void ToEnvironmentValue_Should_Handle_Negative_Enum_Values_Gracefully()
+ [Test]
+ public async Task ToEnvironmentValue_Should_Handle_Negative_Enum_Values_Gracefully()
{
const LocalStackLogLevel invalidLogLevel = (LocalStackLogLevel)(-1);
// The implementation doesn't throw for invalid values, it handles them gracefully
var result = invalidLogLevel.ToEnvironmentValue();
- Assert.NotNull(result);
+ await Assert.That(result).IsNotNull();
}
- public static IEnumerable