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 AllLogLevelMappingsTestData => - [ - [LocalStackLogLevel.Trace, "trace"], - [LocalStackLogLevel.TraceInternal, "trace-internal"], - [LocalStackLogLevel.Debug, "debug"], - [LocalStackLogLevel.Info, "info"], - [LocalStackLogLevel.Warn, "warn"], - [LocalStackLogLevel.Error, "error"], - [LocalStackLogLevel.Warning, "warning"], - ]; + public static IEnumerable<(LocalStackLogLevel, string)> AllLogLevelMappingsTestData() + { + yield return (LocalStackLogLevel.Trace, "trace"); + yield return (LocalStackLogLevel.TraceInternal, "trace-internal"); + yield return (LocalStackLogLevel.Debug, "debug"); + yield return (LocalStackLogLevel.Info, "info"); + yield return (LocalStackLogLevel.Warn, "warn"); + yield return (LocalStackLogLevel.Error, "error"); + yield return (LocalStackLogLevel.Warning, "warning"); + } } diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Core/LocalStackResourceTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Core/LocalStackResourceTests.cs index 1ead3d0..a6d8748 100644 --- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Core/LocalStackResourceTests.cs +++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Core/LocalStackResourceTests.cs @@ -2,65 +2,65 @@ namespace Aspire.Hosting.LocalStack.Unit.Tests.Core; public class LocalStackResourceTests { - [Fact] - public void LocalStackResource_Should_Implement_ILocalStackResource() + [Test] + public async Task LocalStackResource_Should_Implement_ILocalStackResource() { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); var resource = new LocalStackResource("test-localstack", options); - Assert.IsType(resource, exactMatch: false); - Assert.IsType(resource, exactMatch: false); - Assert.IsType(resource, exactMatch: false); + await Assert.That(resource).IsAssignableTo(); + await Assert.That(resource).IsAssignableTo(); + await Assert.That(resource).IsAssignableTo(); } - [Fact] - public void LocalStackResource_Should_Store_Name_And_Options() + [Test] + public async Task LocalStackResource_Should_Store_Name_And_Options() { const string resourceName = "my-localstack"; var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); var resource = new LocalStackResource(resourceName, options); - Assert.Equal(resourceName, resource.Name); - Assert.Same(options, resource.Options); + await Assert.That(resource.Name).IsEqualTo(resourceName); + await Assert.That(resource.Options).IsSameReferenceAs(options); } - [Fact] - public void LocalStackResource_Should_Generate_HTTP_Connection_String_When_SSL_Disabled() + [Test] + public async Task LocalStackResource_Should_Generate_HTTP_Connection_String_When_SSL_Disabled() { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(edgePort: 4566, useSsl: false); var resource = new LocalStackResource("test-localstack", options); var connectionString = resource.ConnectionStringExpression.ValueExpression; - Assert.StartsWith("http://", connectionString, StringComparison.Ordinal); + await Assert.That(connectionString).StartsWith("http://"); } - [Fact] - public void LocalStackResource_Should_Generate_HTTPS_Connection_String_When_SSL_Enabled() + [Test] + public async Task LocalStackResource_Should_Generate_HTTPS_Connection_String_When_SSL_Enabled() { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(edgePort: 4566, useSsl: true); var resource = new LocalStackResource("test-localstack", options); var connectionString = resource.ConnectionStringExpression.ValueExpression; - Assert.StartsWith("https://", connectionString, StringComparison.Ordinal); + await Assert.That(connectionString).StartsWith("https://"); } - [Fact] - public void LocalStackResource_Should_Have_Primary_Endpoint() + [Test] + public async Task LocalStackResource_Should_Have_Primary_Endpoint() { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); var resource = new LocalStackResource("test-localstack", options); - Assert.NotNull(resource.PrimaryEndpoint); - Assert.Equal("http", resource.PrimaryEndpoint.EndpointName); + await Assert.That(resource.PrimaryEndpoint).IsNotNull(); + await Assert.That(resource.PrimaryEndpoint.EndpointName).IsEqualTo("http"); } - [Fact] - public void LocalStackResource_Should_Return_Same_Primary_Endpoint_Instance() + [Test] + public async Task LocalStackResource_Should_Return_Same_Primary_Endpoint_Instance() { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); var resource = new LocalStackResource("test-localstack", options); @@ -68,51 +68,51 @@ public void LocalStackResource_Should_Return_Same_Primary_Endpoint_Instance() var endpoint1 = resource.PrimaryEndpoint; var endpoint2 = resource.PrimaryEndpoint; - Assert.Same(endpoint1, endpoint2); + await Assert.That(endpoint1).IsSameReferenceAs(endpoint2); } - [Fact] - public void LocalStackResource_Should_Have_Correct_Primary_Endpoint_Name() + [Test] + public async Task LocalStackResource_Should_Have_Correct_Primary_Endpoint_Name() { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); var resource = new LocalStackResource("test-localstack", options); - Assert.Equal(LocalStackResource.PrimaryEndpointName, resource.PrimaryEndpoint.EndpointName); - Assert.Equal("http", LocalStackResource.PrimaryEndpointName); + await Assert.That(resource.PrimaryEndpoint.EndpointName).IsEqualTo(LocalStackResource.PrimaryEndpointName); + await Assert.That(resource.PrimaryEndpoint.EndpointName).IsEqualTo("http"); } - [Theory] - [InlineData("")] - [InlineData(" ")] - public void LocalStackResource_Should_Throw_ArgumentException_For_Invalid_Name(string invalidName) + [Test] + [Arguments("")] + [Arguments(" ")] + public async Task LocalStackResource_Should_Throw_ArgumentException_For_Invalid_Name(string invalidName) { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); - Assert.Throws(() => new LocalStackResource(invalidName, options)); + await Assert.That(() => new LocalStackResource(invalidName, options)).ThrowsExactly(); } - [Fact] - public void LocalStackResource_Should_Throw_ArgumentNullException_For_Null_Name() + [Test] + public async Task LocalStackResource_Should_Throw_ArgumentNullException_For_Null_Name() { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); - Assert.Throws(() => new LocalStackResource(null!, options)); + await Assert.That(() => new LocalStackResource(null!, options)).ThrowsExactly(); } - [Fact] - public void LocalStackResource_Should_Throw_ArgumentNullException_For_Null_Options() + [Test] + public async Task LocalStackResource_Should_Throw_ArgumentNullException_For_Null_Options() { - Assert.Throws(() => new LocalStackResource("test", null!)); + await Assert.That(() => new LocalStackResource("test", null!)).ThrowsExactly(); } - [Fact] - public void LocalStackResource_Should_Be_Container_Resource() + [Test] + public async Task LocalStackResource_Should_Be_Container_Resource() { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); var resource = new LocalStackResource("test-localstack", options); - Assert.IsType(resource, exactMatch: false); + await Assert.That(resource).IsAssignableTo(); } } diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/LocalStackCloudFormationResourceExtensionsTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/LocalStackCloudFormationResourceExtensionsTests.cs index cd731c0..bd76cef 100644 --- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/LocalStackCloudFormationResourceExtensionsTests.cs +++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/LocalStackCloudFormationResourceExtensionsTests.cs @@ -2,8 +2,8 @@ namespace Aspire.Hosting.LocalStack.Unit.Tests.Extensions; public class LocalStackCloudFormationResourceExtensionsTests { - [Fact] - public void WithReference_Should_Configure_CloudFormation_Client_For_LocalStack() + [Test] + public async Task WithReference_Should_Configure_CloudFormation_Client_For_LocalStack() { const string cfResourceName = "test-cf"; @@ -19,16 +19,16 @@ public void WithReference_Should_Configure_CloudFormation_Client_For_LocalStack( }); var localStackResource = app.GetResource("localstack"); - cfResource.ShouldHaveLocalStackEnabledAnnotation(); - cfResource.ShouldWaitFor(localStackResource); + await cfResource.ShouldHaveLocalStackEnabledAnnotation(); + await cfResource.ShouldWaitFor(localStackResource); } - [Fact] - public void WithReference_Should_Add_LocalStack_Enabled_Annotation() + [Test] + public async Task WithReference_Should_Add_LocalStack_Enabled_Annotation() { const string cfResourceName = "test-cf"; - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var awsConfig = builder.AddAWSSDKConfig().WithRegion(Amazon.RegionEndpoint.USEast1); var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); @@ -42,16 +42,16 @@ public void WithReference_Should_Add_LocalStack_Enabled_Annotation() var cfResource = app.GetResource(cfResourceName); var localStackResource = app.GetResource("localstack"); - cfResource.ShouldHaveLocalStackEnabledAnnotation(localStackResource); - cfResource.ShouldWaitFor(localStackResource); + await cfResource.ShouldHaveLocalStackEnabledAnnotation(localStackResource); + await cfResource.ShouldWaitFor(localStackResource); } - [Fact] - public void WithReference_Should_Establish_Bidirectional_Reference() + [Test] + public async Task WithReference_Should_Establish_Bidirectional_Reference() { const string cfResourceName = "test-cf"; - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var awsConfig = builder.AddAWSSDKConfig().WithRegion(Amazon.RegionEndpoint.USEast1); var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); @@ -66,18 +66,18 @@ public void WithReference_Should_Establish_Bidirectional_Reference() var localStackResource = app.GetResource("localstack"); // CloudFormation should have LocalStack annotation - cfResource.ShouldHaveLocalStackEnabledAnnotation(localStackResource); + await cfResource.ShouldHaveLocalStackEnabledAnnotation(localStackResource); // LocalStack should have reference to CloudFormation resource - localStackResource.ShouldHaveReferenceToResource(cfResource); + await localStackResource.ShouldHaveReferenceToResource(cfResource); } - [Fact] - public void WithReference_Should_Add_Wait_Dependency_On_LocalStack() + [Test] + public async Task WithReference_Should_Add_Wait_Dependency_On_LocalStack() { const string cfResourceName = "test-cf"; - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var awsConfig = builder.AddAWSSDKConfig().WithRegion(Amazon.RegionEndpoint.USEast1); var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); @@ -91,11 +91,11 @@ public void WithReference_Should_Add_Wait_Dependency_On_LocalStack() var cfResource = app.GetResource(cfResourceName); var localStackResource = app.GetResource("localstack"); - cfResource.ShouldWaitFor(localStackResource); + await cfResource.ShouldWaitFor(localStackResource); } - [Fact] - public void WithReference_Should_Return_Builder_When_LocalStack_Is_Null() + [Test] + public async Task WithReference_Should_Return_Builder_When_LocalStack_Is_Null() { const string cfResourceName = "test-cf"; @@ -107,12 +107,12 @@ public void WithReference_Should_Return_Builder_When_LocalStack_Is_Null() cfBuilder.WithReference(localStackBuilder: null); }); - Assert.False(app.HasResource("localstack")); - cfResource.ShouldNotHaveLocalStackEnabledAnnotation(); + await Assert.That(app.HasResource("localstack")).IsFalse(); + await cfResource.ShouldNotHaveLocalStackEnabledAnnotation(); } - [Fact] - public void WithReference_Should_Return_Builder_When_LocalStack_Is_Disabled() + [Test] + public async Task WithReference_Should_Return_Builder_When_LocalStack_Is_Disabled() { const string cfResourceName = "test-cf"; @@ -127,25 +127,25 @@ public void WithReference_Should_Return_Builder_When_LocalStack_Is_Disabled() .WithReference(localStack); }); - Assert.False(app.HasResource("localstack")); - cfResource.ShouldNotHaveLocalStackEnabledAnnotation(); + await Assert.That(app.HasResource("localstack")).IsFalse(); + await cfResource.ShouldNotHaveLocalStackEnabledAnnotation(); } - [Fact] - public void WithReference_Should_Throw_ArgumentNullException_When_Builder_Is_Null() + [Test] + public async Task WithReference_Should_Throw_ArgumentNullException_When_Builder_Is_Null() { IResourceBuilder builder = null!; var localStack = Substitute.For>(); - Assert.Throws(() => builder.WithReference(localStack)); + await Assert.That(() => builder.WithReference(localStack)).ThrowsExactly(); } - [Fact] - public void WithReference_Should_Not_Duplicate_References_When_Called_Multiple_Times() + [Test] + public async Task WithReference_Should_Not_Duplicate_References_When_Called_Multiple_Times() { const string cfResourceName = "test-cf"; - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var awsConfig = builder.AddAWSSDKConfig().WithRegion(Amazon.RegionEndpoint.USEast1); var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); @@ -165,13 +165,13 @@ public void WithReference_Should_Not_Duplicate_References_When_Called_Multiple_T .Where(a => string.Equals(a.Resource.Name, cfResourceName, StringComparison.Ordinal)) .ToList(); - Assert.Single(referenceAnnotations); + await Assert.That(referenceAnnotations).HasSingleItem(); } - [Fact] - public void WithReference_Should_Work_With_Multiple_CloudFormation_Resources() + [Test] + public async Task WithReference_Should_Work_With_Multiple_CloudFormation_Resources() { - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var awsConfig = builder.AddAWSSDKConfig().WithRegion(Amazon.RegionEndpoint.USEast1); var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); @@ -190,13 +190,13 @@ public void WithReference_Should_Work_With_Multiple_CloudFormation_Resources() var cf2Resource = app.GetResource("cf-2"); var localStackResource = app.GetResource("localstack"); - cf1Resource.ShouldHaveLocalStackEnabledAnnotation(); - cf2Resource.ShouldHaveLocalStackEnabledAnnotation(); + await cf1Resource.ShouldHaveLocalStackEnabledAnnotation(); + await cf2Resource.ShouldHaveLocalStackEnabledAnnotation(); - cf1Resource.ShouldWaitFor(localStackResource); - cf1Resource.ShouldWaitFor(localStackResource); + await cf1Resource.ShouldWaitFor(localStackResource); + await cf1Resource.ShouldWaitFor(localStackResource); - localStackResource.ShouldHaveReferenceToResource(cf1Resource); - localStackResource.ShouldHaveReferenceToResource(cf2Resource); + await localStackResource.ShouldHaveReferenceToResource(cf1Resource); + await localStackResource.ShouldHaveReferenceToResource(cf2Resource); } } diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/LocalStackProjectExtensionsTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/LocalStackProjectExtensionsTests.cs index e683415..3487e41 100644 --- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/LocalStackProjectExtensionsTests.cs +++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/LocalStackProjectExtensionsTests.cs @@ -2,8 +2,8 @@ namespace Aspire.Hosting.LocalStack.Unit.Tests.Extensions; public class LocalStackProjectExtensionsTests { - [Fact] - public void WithReference_Should_Add_LocalStack_Reference_To_Project() + [Test] + public async Task WithReference_Should_Add_LocalStack_Reference_To_Project() { const string testProjectResourceName = "test-project"; @@ -16,29 +16,35 @@ public void WithReference_Should_Add_LocalStack_Reference_To_Project() }); var localStackResource = app.GetResource("localstack"); - projectResource.ShouldHaveLocalStackEnabledAnnotation(localStackResource); + await projectResource.ShouldHaveLocalStackEnabledAnnotation(localStackResource); } - [Fact] - public void WithReference_Should_Return_Builder_When_LocalStack_Is_Null() + [Test] + public async Task WithReference_Should_Return_Builder_When_LocalStack_Is_Null() { const string testProjectResourceName = "test-project"; + IResourceBuilder? capturedBuilder = null; + IResourceBuilder? capturedResult = null; + var (app, projectResource) = TestApplicationBuilder.CreateWithResource(testProjectResourceName, builder => { var projectBuilder = builder.AddProject(testProjectResourceName, TestDataBuilders.GetTestProjectPath()); var result = projectBuilder.WithReference(localStackBuilder: null); - // Should return the same builder - Assert.Same(projectBuilder, result); + capturedBuilder = projectBuilder; + capturedResult = result; }); - Assert.False(app.HasResource("localstack")); - projectResource.ShouldNotHaveLocalStackEnabledAnnotation(); + // Should return the same builder + await Assert.That(capturedResult).IsSameReferenceAs(capturedBuilder); + + await Assert.That(app.HasResource("localstack")).IsFalse(); + await projectResource.ShouldNotHaveLocalStackEnabledAnnotation(); } - [Fact] - public void WithReference_Should_Return_Builder_When_LocalStack_Is_Disabled() + [Test] + public async Task WithReference_Should_Return_Builder_When_LocalStack_Is_Disabled() { const string testProjectResourceName = "test-project"; @@ -51,25 +57,25 @@ public void WithReference_Should_Return_Builder_When_LocalStack_Is_Disabled() projectBuilder.WithReference(localStackBuilder: localStack); }); - Assert.False(app.HasResource("localstack")); - cfResource.ShouldNotHaveLocalStackEnabledAnnotation(); + await Assert.That(app.HasResource("localstack")).IsFalse(); + await cfResource.ShouldNotHaveLocalStackEnabledAnnotation(); } - [Fact] - public void WithReference_Should_Throw_ArgumentNullException_When_Builder_Is_Null() + [Test] + public async Task WithReference_Should_Throw_ArgumentNullException_When_Builder_Is_Null() { IResourceBuilder builder = null!; var localStack = Substitute.For>(); - Assert.Throws(() => builder.WithReference(localStack)); + await Assert.That(() => builder.WithReference(localStack)).ThrowsExactly(); } - [Fact] - public void WithReference_Should_Establish_Bidirectional_Reference() + [Test] + public async Task WithReference_Should_Establish_Bidirectional_Reference() { const string testProjectResourceName = "test-project"; - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); var localStack = builder.AddLocalStack(localStackOptions: options); @@ -80,16 +86,16 @@ public void WithReference_Should_Establish_Bidirectional_Reference() var localStackResource = app.GetResource("localstack"); var projectResource = app.GetResource(testProjectResourceName); - projectResource.ShouldHaveLocalStackEnabledAnnotation(localStackResource); - localStackResource.ShouldHaveReferenceToResource(projectResource); + await projectResource.ShouldHaveLocalStackEnabledAnnotation(localStackResource); + await localStackResource.ShouldHaveReferenceToResource(projectResource); } - [Fact] - public void WithReference_Should_Add_Wait_Dependency_On_LocalStack() + [Test] + public async Task WithReference_Should_Add_Wait_Dependency_On_LocalStack() { const string testProjectResourceName = "test-project"; - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); var localStack = builder.AddLocalStack(localStackOptions: options); @@ -100,15 +106,15 @@ public void WithReference_Should_Add_Wait_Dependency_On_LocalStack() var localStackResource = app.GetResource("localstack"); var projectResource = app.GetResource(testProjectResourceName); - projectResource.ShouldWaitFor(localStackResource); + await projectResource.ShouldWaitFor(localStackResource); } - [Fact] - public void WithReference_Should_Configure_LocalStack_Environment_Variables() + [Test] + public async Task WithReference_Should_Configure_LocalStack_Environment_Variables() { const string testProjectResourceName = "test-project"; - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions( useLocalStack: true, @@ -123,15 +129,15 @@ public void WithReference_Should_Configure_LocalStack_Environment_Variables() var projectResource = app.GetResource(testProjectResourceName); var localStackResource = app.GetResource("localstack"); - projectResource.ShouldHaveLocalStackEnvironmentConfiguration(localStackResource); + await projectResource.ShouldHaveLocalStackEnvironmentConfiguration(localStackResource); } - [Fact] - public void WithReference_Should_Not_Duplicate_References_When_Called_Multiple_Times() + [Test] + public async Task WithReference_Should_Not_Duplicate_References_When_Called_Multiple_Times() { const string testProjectResourceName = "test-project"; - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); var localStack = builder.AddLocalStack(localStackOptions: options); @@ -149,6 +155,6 @@ public void WithReference_Should_Not_Duplicate_References_When_Called_Multiple_T .Where(a => string.Equals(a.Resource.Name, testProjectResourceName, StringComparison.Ordinal)) .ToList(); - Assert.Single(referenceAnnotations); + await Assert.That(referenceAnnotations).HasSingleItem(); } } diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/ResourceBuilderExtensionsTests/AddAWSCDKBootstrapCfTemplateForLocalStackTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/ResourceBuilderExtensionsTests/AddAWSCDKBootstrapCfTemplateForLocalStackTests.cs index 244323a..0be5b65 100644 --- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/ResourceBuilderExtensionsTests/AddAWSCDKBootstrapCfTemplateForLocalStackTests.cs +++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/ResourceBuilderExtensionsTests/AddAWSCDKBootstrapCfTemplateForLocalStackTests.cs @@ -2,18 +2,18 @@ namespace Aspire.Hosting.LocalStack.Unit.Tests.Extensions.ResourceBuilderExtensi public class AddAWSCDKBootstrapCfTemplateForLocalStackTests { - [Fact] - public void AddAWSCDKBootstrapCloudFormationTemplateForLocalStack_Should_Return_Null_When_LocalStack_Is_Null() + [Test] + public async Task AddAWSCDKBootstrapCloudFormationTemplateForLocalStack_Should_Return_Null_When_LocalStack_Is_Null() { var builder = DistributedApplication.CreateBuilder([]); var result = builder.AddAWSCDKBootstrapCloudFormationTemplateForLocalStack(localStackBuilder: null); - Assert.Null(result); + await Assert.That(result).IsNull(); } - [Fact] - public void AddAWSCDKBootstrapCloudFormationTemplateForLocalStack_Should_Return_Null_When_UseLocalStack_Is_False() + [Test] + public async Task AddAWSCDKBootstrapCloudFormationTemplateForLocalStack_Should_Return_Null_When_UseLocalStack_Is_False() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: false); @@ -21,11 +21,11 @@ public void AddAWSCDKBootstrapCloudFormationTemplateForLocalStack_Should_Return_ var result = builder.AddAWSCDKBootstrapCloudFormationTemplateForLocalStack(localStackBuilder); - Assert.Null(result); + await Assert.That(result).IsNull(); } - [Fact] - public void AddAWSCDKBootstrapCloudFormationTemplateForLocalStack_Should_Create_Template_When_LocalStack_Enabled() + [Test] + public async Task AddAWSCDKBootstrapCloudFormationTemplateForLocalStack_Should_Create_Template_When_LocalStack_Enabled() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -33,8 +33,8 @@ public void AddAWSCDKBootstrapCloudFormationTemplateForLocalStack_Should_Create_ var result = builder.AddAWSCDKBootstrapCloudFormationTemplateForLocalStack(localStackBuilder); - Assert.NotNull(result); - Assert.NotNull(result.Resource); - Assert.Equal("CDKBootstrap", result.Resource.Name); + await Assert.That(result).IsNotNull(); + await Assert.That(result!.Resource).IsNotNull(); + await Assert.That(result.Resource.Name).IsEqualTo("CDKBootstrap"); } } diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/ResourceBuilderExtensionsTests/AddLocalStackTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/ResourceBuilderExtensionsTests/AddLocalStackTests.cs index de73787..fba1896 100644 --- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/ResourceBuilderExtensionsTests/AddLocalStackTests.cs +++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/ResourceBuilderExtensionsTests/AddLocalStackTests.cs @@ -1,47 +1,45 @@ -using LocalStack.Client.Enums; - -namespace Aspire.Hosting.LocalStack.Unit.Tests.Extensions.ResourceBuilderExtensionsTests; +namespace Aspire.Hosting.LocalStack.Unit.Tests.Extensions.ResourceBuilderExtensionsTests; public class AddLocalStackTests { - [Fact] - public void AddLocalStack_Should_Return_Null_When_UseLocalStack_Is_False() + [Test] + public async Task AddLocalStack_Should_Return_Null_When_UseLocalStack_Is_False() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: false); var result = builder.AddLocalStack(localStackOptions: localStackOptions); - Assert.Null(result); + await Assert.That(result).IsNull(); } - [Fact] - public void AddLocalStack_Should_Create_LocalStack_Resource_When_UseLocalStack_Is_True() + [Test] + public async Task AddLocalStack_Should_Create_LocalStack_Resource_When_UseLocalStack_Is_True() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); var result = builder.AddLocalStack(localStackOptions: localStackOptions); - Assert.NotNull(result); - Assert.NotNull(result.Resource); - Assert.IsType(result.Resource); + await Assert.That(result).IsNotNull(); + await Assert.That(result!.Resource).IsNotNull(); + await Assert.That(result.Resource).IsTypeOf(); } - [Fact] - public void AddLocalStack_Should_Use_Default_Name_When_Not_Specified() + [Test] + public async Task AddLocalStack_Should_Use_Default_Name_When_Not_Specified() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); var result = builder.AddLocalStack(localStackOptions: localStackOptions); - Assert.NotNull(result); - Assert.Equal("localstack", result.Resource.Name); + await Assert.That(result).IsNotNull(); + await Assert.That(result!.Resource.Name).IsEqualTo("localstack"); } - [Fact] - public void AddLocalStack_Should_Use_Custom_Name_When_Specified() + [Test] + public async Task AddLocalStack_Should_Use_Custom_Name_When_Specified() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -49,12 +47,12 @@ public void AddLocalStack_Should_Use_Custom_Name_When_Specified() var result = builder.AddLocalStack(name: customName, localStackOptions: localStackOptions); - Assert.NotNull(result); - Assert.Equal(customName, result.Resource.Name); + await Assert.That(result).IsNotNull(); + await Assert.That(result!.Resource.Name).IsEqualTo(customName); } - [Fact] - public void AddLocalStack_Should_Configure_Container_Options_When_Action_Provided() + [Test] + public async Task AddLocalStack_Should_Configure_Container_Options_When_Action_Provided() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -62,8 +60,8 @@ public void AddLocalStack_Should_Configure_Container_Options_When_Action_Provide var result = builder.AddLocalStack(localStackOptions: localStackOptions, configureContainer: ConfigureContainer); - Assert.NotNull(result); - Assert.True(configureContainerCalled); + await Assert.That(result).IsNotNull(); + await Assert.That(configureContainerCalled).IsTrue(); return; void ConfigureContainer(LocalStackContainerOptions options) @@ -74,8 +72,8 @@ void ConfigureContainer(LocalStackContainerOptions options) } } - [Fact] - public void AddLocalStack_Should_Inherit_Region_From_AWS_Config() + [Test] + public async Task AddLocalStack_Should_Inherit_Region_From_AWS_Config() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -83,32 +81,32 @@ public void AddLocalStack_Should_Inherit_Region_From_AWS_Config() var result = builder.AddLocalStack(localStackOptions: localStackOptions, awsConfig: awsConfig); - Assert.NotNull(result); - Assert.Equal("us-west-2", result.Resource.Options.Session.RegionName); + await Assert.That(result).IsNotNull(); + await Assert.That(result!.Resource.Options.Session.RegionName).IsEqualTo("us-west-2"); } - [Fact] - public void AddLocalStack_Should_Throw_ArgumentNullException_When_Builder_Is_Null() + [Test] + public async Task AddLocalStack_Should_Throw_ArgumentNullException_When_Builder_Is_Null() { IDistributedApplicationBuilder builder = null!; var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); - Assert.Throws(() => builder.AddLocalStack(localStackOptions: localStackOptions)); + await Assert.That(() => builder.AddLocalStack(localStackOptions: localStackOptions)).ThrowsExactly(); } - [Theory] - [InlineData("")] - [InlineData(" ")] - public void AddLocalStack_Should_Throw_ArgumentException_When_Name_Is_Invalid(string invalidName) + [Test] + [Arguments("")] + [Arguments(" ")] + public async Task AddLocalStack_Should_Throw_ArgumentException_When_Name_Is_Invalid(string invalidName) { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); - Assert.Throws(() => builder.AddLocalStack(name: invalidName, localStackOptions: localStackOptions)); + await Assert.That(() => builder.AddLocalStack(name: invalidName, localStackOptions: localStackOptions)).ThrowsExactly(); } - [Fact] - public void AddLocalStack_Should_Set_EAGER_SERVICE_LOADING_When_EagerLoadedServices_Configured() + [Test] + public async Task AddLocalStack_Should_Set_EAGER_SERVICE_LOADING_When_EagerLoadedServices_Configured() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -119,19 +117,19 @@ public void AddLocalStack_Should_Set_EAGER_SERVICE_LOADING_When_EagerLoadedServi configureContainer: container => container.EagerLoadedServices = [AwsService.Sqs] ); - Assert.NotNull(result); - var resource = result.Resource; + await Assert.That(result).IsNotNull(); + var resource = result!.Resource; // Verify the resource was created - Assert.NotNull(resource); + await Assert.That(resource).IsNotNull(); // Verify eager loading environment variable would be set var envAnnotations = resource.Annotations.OfType(); - Assert.NotEmpty(envAnnotations); + await Assert.That(envAnnotations).IsNotEmpty(); } - [Fact] - public void AddLocalStack_Should_Set_SERVICES_Environment_Variable_With_Comma_Separated_Services() + [Test] + public async Task AddLocalStack_Should_Set_SERVICES_Environment_Variable_With_Comma_Separated_Services() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -142,17 +140,17 @@ public void AddLocalStack_Should_Set_SERVICES_Environment_Variable_With_Comma_Se configureContainer: container => container.EagerLoadedServices = [AwsService.Sqs, AwsService.DynamoDb, AwsService.S3] ); - Assert.NotNull(result); - var resource = result.Resource; - Assert.NotNull(resource); + await Assert.That(result).IsNotNull(); + var resource = result!.Resource; + await Assert.That(resource).IsNotNull(); // Environment variables are set through annotations var envAnnotations = resource.Annotations.OfType(); - Assert.NotEmpty(envAnnotations); + await Assert.That(envAnnotations).IsNotEmpty(); } - [Fact] - public void AddLocalStack_Should_Not_Set_EAGER_SERVICE_LOADING_When_EagerLoadedServices_Empty() + [Test] + public async Task AddLocalStack_Should_Not_Set_EAGER_SERVICE_LOADING_When_EagerLoadedServices_Empty() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -163,47 +161,47 @@ public void AddLocalStack_Should_Not_Set_EAGER_SERVICE_LOADING_When_EagerLoadedS configureContainer: container => container.EagerLoadedServices = [] ); - Assert.NotNull(result); - var resource = result.Resource; - Assert.NotNull(resource); + await Assert.That(result).IsNotNull(); + var resource = result!.Resource; + await Assert.That(resource).IsNotNull(); } - [Fact] - public void AddLocalStack_Should_Throw_When_EagerLoadedServices_Conflicts_With_AdditionalEnvVars_SERVICES() + [Test] + public async Task AddLocalStack_Should_Throw_When_EagerLoadedServices_Conflicts_With_AdditionalEnvVars_SERVICES() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); - var exception = Assert.Throws(() => + var exception = await Assert.That(() => builder.AddLocalStack(localStackOptions: localStackOptions, configureContainer: container => { container.AdditionalEnvironmentVariables["SERVICES"] = "lambda"; container.EagerLoadedServices = [AwsService.Sqs]; - })); + })).ThrowsExactly(); - Assert.Contains("Cannot set 'SERVICES'", exception.Message, StringComparison.OrdinalIgnoreCase); - Assert.Contains("AdditionalEnvironmentVariables", exception.Message, StringComparison.OrdinalIgnoreCase); + await Assert.That(exception!.Message).Contains("Cannot set 'SERVICES'", StringComparison.OrdinalIgnoreCase); + await Assert.That(exception.Message).Contains("AdditionalEnvironmentVariables", StringComparison.OrdinalIgnoreCase); } - [Fact] - public void AddLocalStack_Should_Throw_When_EagerLoadedServices_Conflicts_With_AdditionalEnvVars_EAGER_SERVICE_LOADING() + [Test] + public async Task AddLocalStack_Should_Throw_When_EagerLoadedServices_Conflicts_With_AdditionalEnvVars_EAGER_SERVICE_LOADING() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); - var exception = Assert.Throws(() => + var exception = await Assert.That(() => builder.AddLocalStack(localStackOptions: localStackOptions, configureContainer: container => { container.AdditionalEnvironmentVariables["EAGER_SERVICE_LOADING"] = "1"; container.EagerLoadedServices = [AwsService.Sqs]; - })); + })).ThrowsExactly(); - Assert.Contains("Cannot set", exception.Message, StringComparison.OrdinalIgnoreCase); - Assert.Contains("EAGER_SERVICE_LOADING", exception.Message, StringComparison.OrdinalIgnoreCase); + await Assert.That(exception!.Message).Contains("Cannot set", StringComparison.OrdinalIgnoreCase); + await Assert.That(exception.Message).Contains("EAGER_SERVICE_LOADING", StringComparison.OrdinalIgnoreCase); } - [Fact] - public void AddLocalStack_Should_Throw_When_Unsupported_Service_In_EagerLoadedServices() + [Test] + public async Task AddLocalStack_Should_Throw_When_Unsupported_Service_In_EagerLoadedServices() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -211,18 +209,18 @@ public void AddLocalStack_Should_Throw_When_Unsupported_Service_In_EagerLoadedSe // Note: This test assumes there might be an AwsService enum value with no CliName // If all current services are supported, this validates the error handling mechanism // The actual exception will be thrown during the Select operation when CliName is null - var exception = Assert.ThrowsAny(() => + var exception = await Assert.That(() => builder.AddLocalStack(localStackOptions: localStackOptions, configureContainer: container => { // Using a very high enum value that likely doesn't have metadata container.EagerLoadedServices = [(AwsService)99999]; - })); + })).Throws(); - Assert.Contains("not supported by LocalStack", exception.Message, StringComparison.OrdinalIgnoreCase); + await Assert.That(exception!.Message).Contains("not supported by LocalStack", StringComparison.OrdinalIgnoreCase); } - [Fact] - public void AddLocalStack_Should_Mount_Docker_Socket_When_EnableDockerSocket_Is_True() + [Test] + public async Task AddLocalStack_Should_Mount_Docker_Socket_When_EnableDockerSocket_Is_True() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -233,20 +231,20 @@ public void AddLocalStack_Should_Mount_Docker_Socket_When_EnableDockerSocket_Is_ configureContainer: container => container.EnableDockerSocket = true ); - Assert.NotNull(result); - var resource = result.Resource; - Assert.NotNull(resource); + await Assert.That(result).IsNotNull(); + var resource = result!.Resource; + await Assert.That(resource).IsNotNull(); // Verify the Docker socket bind mount annotation exists var mountAnnotations = resource.Annotations.OfType(); var dockerSocketMount = mountAnnotations.FirstOrDefault (m => m is { Source: "/var/run/docker.sock", Target: "/var/run/docker.sock", Type: ContainerMountType.BindMount }); - Assert.NotNull(dockerSocketMount); + await Assert.That(dockerSocketMount).IsNotNull(); } - [Fact] - public void AddLocalStack_Should_Not_Mount_Docker_Socket_When_EnableDockerSocket_Is_False() + [Test] + public async Task AddLocalStack_Should_Not_Mount_Docker_Socket_When_EnableDockerSocket_Is_False() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -257,44 +255,44 @@ public void AddLocalStack_Should_Not_Mount_Docker_Socket_When_EnableDockerSocket configureContainer: container => container.EnableDockerSocket = false ); - Assert.NotNull(result); - var resource = result.Resource; - Assert.NotNull(resource); + await Assert.That(result).IsNotNull(); + var resource = result!.Resource; + await Assert.That(resource).IsNotNull(); // Verify no Docker socket bind mount annotation exists var mountAnnotations = resource.Annotations.OfType(); var dockerSocketMount = mountAnnotations.FirstOrDefault (m => m is { Source: "/var/run/docker.sock", Target: "/var/run/docker.sock" }); - Assert.Null(dockerSocketMount); + await Assert.That(dockerSocketMount).IsNull(); } - [Fact] - public void AddLocalStack_Should_Not_Mount_Docker_Socket_By_Default() + [Test] + public async Task AddLocalStack_Should_Not_Mount_Docker_Socket_By_Default() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); var result = builder.AddLocalStack(localStackOptions: localStackOptions); - Assert.NotNull(result); - var resource = result.Resource; - Assert.NotNull(resource); + await Assert.That(result).IsNotNull(); + var resource = result!.Resource; + await Assert.That(resource).IsNotNull(); // Verify no Docker socket bind mount annotation exists when not configured var mountAnnotations = resource.Annotations.OfType(); var dockerSocketMount = mountAnnotations.FirstOrDefault (m => m is { Source: "/var/run/docker.sock", Target: "/var/run/docker.sock" }); - Assert.Null(dockerSocketMount); + await Assert.That(dockerSocketMount).IsNull(); } - [Theory] - [InlineData(ContainerLifetime.Session, null, null)] - [InlineData(ContainerLifetime.Session, 1234, 1234)] - [InlineData(ContainerLifetime.Persistent, null, Constants.DefaultContainerPort)] - [InlineData(ContainerLifetime.Persistent, 1234, 1234)] - public void AddLocalStack_Should_Set_Endpoint_Port(ContainerLifetime lifetime, int? port, int? expectedPort) + [Test] + [Arguments(ContainerLifetime.Session, null, null)] + [Arguments(ContainerLifetime.Session, 1234, 1234)] + [Arguments(ContainerLifetime.Persistent, null, Constants.DefaultContainerPort)] + [Arguments(ContainerLifetime.Persistent, 1234, 1234)] + public async Task AddLocalStack_Should_Set_Endpoint_Port(ContainerLifetime lifetime, int? port, int? expectedPort) { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -308,39 +306,39 @@ public void AddLocalStack_Should_Set_Endpoint_Port(ContainerLifetime lifetime, i container.Port = port; }); - Assert.NotNull(result); - var resource = result.Resource; - Assert.NotNull(resource); + await Assert.That(result).IsNotNull(); + var resource = result!.Resource; + await Assert.That(resource).IsNotNull(); // Verify endpoint port configuration var endpointAnnotations = resource.Annotations.OfType(); var httpEndpoint = endpointAnnotations.FirstOrDefault(e => e is { Name: "http" }); - Assert.NotNull(httpEndpoint); - Assert.Equal(expectedPort, httpEndpoint.Port); + await Assert.That(httpEndpoint).IsNotNull(); + await Assert.That(httpEndpoint!.Port).IsEqualTo(expectedPort); } - [Fact] - public void AddLocalStack_Should_Use_Default_Container_Image_Values_When_Not_Specified() + [Test] + public async Task AddLocalStack_Should_Use_Default_Container_Image_Values_When_Not_Specified() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); var result = builder.AddLocalStack(localStackOptions: localStackOptions); - Assert.NotNull(result); - var resource = result.Resource; - Assert.NotNull(resource); + await Assert.That(result).IsNotNull(); + var resource = result!.Resource; + await Assert.That(resource).IsNotNull(); // Verify default image annotations var imageAnnotation = resource.Annotations.OfType().Single(); - Assert.Equal("docker.io", imageAnnotation.Registry); - Assert.Equal("localstack/localstack", imageAnnotation.Image); - Assert.Equal("4.10.0", imageAnnotation.Tag); + await Assert.That(imageAnnotation.Registry).IsEqualTo("docker.io"); + await Assert.That(imageAnnotation.Image).IsEqualTo("localstack/localstack"); + await Assert.That(imageAnnotation.Tag).IsEqualTo("4.12.0"); } - [Fact] - public void AddLocalStack_Should_Use_Custom_Container_Registry_When_Specified() + [Test] + public async Task AddLocalStack_Should_Use_Custom_Container_Registry_When_Specified() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -350,18 +348,18 @@ public void AddLocalStack_Should_Use_Custom_Container_Registry_When_Specified() localStackOptions: localStackOptions, configureContainer: container => container.ContainerRegistry = customRegistry); - Assert.NotNull(result); - var resource = result.Resource; - Assert.NotNull(resource); + await Assert.That(result).IsNotNull(); + var resource = result!.Resource; + await Assert.That(resource).IsNotNull(); var imageAnnotation = resource.Annotations.OfType().Single(); - Assert.Equal(customRegistry, imageAnnotation.Registry); - Assert.Equal("localstack/localstack", imageAnnotation.Image); // Default image - Assert.Equal("4.10.0", imageAnnotation.Tag); // Default tag + await Assert.That(imageAnnotation.Registry).IsEqualTo(customRegistry); + await Assert.That(imageAnnotation.Image).IsEqualTo("localstack/localstack"); // Default image + await Assert.That(imageAnnotation.Tag).IsEqualTo("4.12.0"); // Default tag } - [Fact] - public void AddLocalStack_Should_Use_Custom_Container_Image_When_Specified() + [Test] + public async Task AddLocalStack_Should_Use_Custom_Container_Image_When_Specified() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -371,18 +369,18 @@ public void AddLocalStack_Should_Use_Custom_Container_Image_When_Specified() localStackOptions: localStackOptions, configureContainer: container => container.ContainerImage = customImage); - Assert.NotNull(result); - var resource = result.Resource; - Assert.NotNull(resource); + await Assert.That(result).IsNotNull(); + var resource = result!.Resource; + await Assert.That(resource).IsNotNull(); var imageAnnotation = resource.Annotations.OfType().Single(); - Assert.Equal("docker.io", imageAnnotation.Registry); // Default registry - Assert.Equal(customImage, imageAnnotation.Image); - Assert.Equal("4.10.0", imageAnnotation.Tag); // Default tag + await Assert.That(imageAnnotation.Registry).IsEqualTo("docker.io"); // Default registry + await Assert.That(imageAnnotation.Image).IsEqualTo(customImage); + await Assert.That(imageAnnotation.Tag).IsEqualTo("4.12.0"); // Default tag } - [Fact] - public void AddLocalStack_Should_Use_Custom_Container_ImageTag_When_Specified() + [Test] + public async Task AddLocalStack_Should_Use_Custom_Container_ImageTag_When_Specified() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -392,18 +390,18 @@ public void AddLocalStack_Should_Use_Custom_Container_ImageTag_When_Specified() localStackOptions: localStackOptions, configureContainer: container => container.ContainerImageTag = customTag); - Assert.NotNull(result); - var resource = result.Resource; - Assert.NotNull(resource); + await Assert.That(result).IsNotNull(); + var resource = result!.Resource; + await Assert.That(resource).IsNotNull(); var imageAnnotation = resource.Annotations.OfType().Single(); - Assert.Equal("docker.io", imageAnnotation.Registry); // Default registry - Assert.Equal("localstack/localstack", imageAnnotation.Image); // Default image - Assert.Equal(customTag, imageAnnotation.Tag); + await Assert.That(imageAnnotation.Registry).IsEqualTo("docker.io"); // Default registry + await Assert.That(imageAnnotation.Image).IsEqualTo("localstack/localstack"); // Default image + await Assert.That(imageAnnotation.Tag).IsEqualTo(customTag); } - [Fact] - public void AddLocalStack_Should_Use_All_Custom_Container_Image_Values_When_Specified() + [Test] + public async Task AddLocalStack_Should_Use_All_Custom_Container_Image_Values_When_Specified() { var builder = DistributedApplication.CreateBuilder([]); var (localStackOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: true); @@ -420,13 +418,13 @@ public void AddLocalStack_Should_Use_All_Custom_Container_Image_Values_When_Spec container.ContainerImageTag = customTag; }); - Assert.NotNull(result); - var resource = result.Resource; - Assert.NotNull(resource); + await Assert.That(result).IsNotNull(); + var resource = result!.Resource; + await Assert.That(resource).IsNotNull(); var imageAnnotation = resource.Annotations.OfType().Single(); - Assert.Equal(customRegistry, imageAnnotation.Registry); - Assert.Equal(customImage, imageAnnotation.Image); - Assert.Equal(customTag, imageAnnotation.Tag); + await Assert.That(imageAnnotation.Registry).IsEqualTo(customRegistry); + await Assert.That(imageAnnotation.Image).IsEqualTo(customImage); + await Assert.That(imageAnnotation.Tag).IsEqualTo(customTag); } } diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/ResourceBuilderExtensionsTests/UseLocalStackTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/ResourceBuilderExtensionsTests/UseLocalStackTests.cs index c5cb07d..b7458e0 100644 --- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/ResourceBuilderExtensionsTests/UseLocalStackTests.cs +++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Extensions/ResourceBuilderExtensionsTests/UseLocalStackTests.cs @@ -2,29 +2,29 @@ namespace Aspire.Hosting.LocalStack.Unit.Tests.Extensions.ResourceBuilderExtensi public class UseLocalStackTests { - [Fact] - public void UseLocalStack_Should_Return_Builder_When_LocalStack_Is_Null() + [Test] + public async Task UseLocalStack_Should_Return_Builder_When_LocalStack_Is_Null() { - using var app = TestApplicationBuilder.Create(builder => builder.UseLocalStack(localStack: null)); + await using var app = TestApplicationBuilder.Create(builder => builder.UseLocalStack(localStack: null)); - app.ShouldHaveResourceCount(0); + await app.ShouldHaveResourceCount(0); } - [Fact] - public void UseLocalStack_Should_Return_Builder_When_LocalStack_Is_Disabled() + [Test] + public async Task UseLocalStack_Should_Return_Builder_When_LocalStack_Is_Disabled() { - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var (disabledOptions, _, _) = TestDataBuilders.CreateMockLocalStackOptions(useLocalStack: false); var localStack = builder.AddLocalStack(localStackOptions: disabledOptions); builder.UseLocalStack(localStack); }); - app.ShouldHaveResourceCount(0); + await app.ShouldHaveResourceCount(0); } - [Fact] - public void UseLocalStack_Should_Configure_CloudFormation_Resources_With_LocalStack_Reference() + [Test] + public async Task UseLocalStack_Should_Configure_CloudFormation_Resources_With_LocalStack_Reference() { var (app, cfResource) = TestApplicationBuilder.CreateWithResource("test-cf", builder => { @@ -40,14 +40,14 @@ public void UseLocalStack_Should_Configure_CloudFormation_Resources_With_LocalSt builder.UseLocalStack(localStack); }); - cfResource.ShouldHaveLocalStackEnabledAnnotation(); - app.ShouldHaveResourceCount(1); + await cfResource.ShouldHaveLocalStackEnabledAnnotation(); + await app.ShouldHaveResourceCount(1); } - [Fact] - public void UseLocalStack_Should_Configure_Multiple_CloudFormation_Resources() + [Test] + public async Task UseLocalStack_Should_Configure_Multiple_CloudFormation_Resources() { - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var awsConfig = builder.AddAWSSDKConfig().WithRegion(Amazon.RegionEndpoint.USEast1); var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); @@ -62,16 +62,16 @@ public void UseLocalStack_Should_Configure_Multiple_CloudFormation_Resources() }); var cfResources = app.GetResources().ToList(); - Assert.Equal(2, cfResources.Count); + await Assert.That(cfResources.Count).IsEqualTo(2); foreach (var cfResource in cfResources) { - cfResource.ShouldHaveLocalStackEnabledAnnotation(); + await cfResource.ShouldHaveLocalStackEnabledAnnotation(); } } - [Fact] - public void UseLocalStack_Should_Configure_Projects_That_Reference_AWS_Resources() + [Test] + public async Task UseLocalStack_Should_Configure_Projects_That_Reference_AWS_Resources() { var (_, projectResource) = TestApplicationBuilder.CreateWithResource("test-project", builder => { @@ -90,13 +90,13 @@ public void UseLocalStack_Should_Configure_Projects_That_Reference_AWS_Resources builder.UseLocalStack(localStack); }); - projectResource.ShouldHaveLocalStackEnabledAnnotation(); + await projectResource.ShouldHaveLocalStackEnabledAnnotation(); } - [Fact] - public void UseLocalStack_Should_Create_CDK_Bootstrap_When_Explicitly_Called() + [Test] + public async Task UseLocalStack_Should_Create_CDK_Bootstrap_When_Explicitly_Called() { - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); var localStack = builder.AddLocalStack(localStackOptions: options); @@ -111,14 +111,14 @@ public void UseLocalStack_Should_Create_CDK_Bootstrap_When_Explicitly_Called() .Where(r => string.Equals(r.Name, "CDKBootstrap", StringComparison.Ordinal)) .ToList(); - Assert.Single(bootstrapResources); - bootstrapResources[0].ShouldHaveLocalStackEnabledAnnotation(); + await Assert.That(bootstrapResources).HasSingleItem(); + await bootstrapResources[0].ShouldHaveLocalStackEnabledAnnotation(); } - [Fact] - public void UseLocalStack_Should_Handle_Empty_Application_Gracefully() + [Test] + public async Task UseLocalStack_Should_Handle_Empty_Application_Gracefully() { - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); var localStack = builder.AddLocalStack(localStackOptions: options); @@ -127,13 +127,13 @@ public void UseLocalStack_Should_Handle_Empty_Application_Gracefully() builder.UseLocalStack(localStack); }); - app.ShouldHaveResourceCount(1); + await app.ShouldHaveResourceCount(1); } - [Fact] - public void UseLocalStack_Should_Not_Configure_Resources_Already_Marked_With_LocalStack() + [Test] + public async Task UseLocalStack_Should_Not_Configure_Resources_Already_Marked_With_LocalStack() { - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var awsConfig = builder.AddAWSSDKConfig().WithRegion(Amazon.RegionEndpoint.USEast1); var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); @@ -148,7 +148,7 @@ public void UseLocalStack_Should_Not_Configure_Resources_Already_Marked_With_Loc }); var cfResource = app.GetResource("manually-configured"); - cfResource.ShouldHaveLocalStackEnabledAnnotation(); + await cfResource.ShouldHaveLocalStackEnabledAnnotation(); var localStackResource = app.GetResource("localstack"); var referenceAnnotations = localStackResource.Annotations @@ -156,13 +156,13 @@ public void UseLocalStack_Should_Not_Configure_Resources_Already_Marked_With_Loc .Where(a => string.Equals(a.Resource.Name, "manually-configured", StringComparison.Ordinal)) .ToList(); - Assert.Single(referenceAnnotations); + await Assert.That(referenceAnnotations).HasSingleItem(); } - [Fact] - public void UseLocalStack_Should_Establish_Bidirectional_References() + [Test] + public async Task UseLocalStack_Should_Establish_Bidirectional_References() { - using var app = TestApplicationBuilder.Create(builder => + await using var app = TestApplicationBuilder.Create(builder => { var awsConfig = builder.AddAWSSDKConfig().WithRegion(Amazon.RegionEndpoint.USEast1); var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions(); @@ -178,9 +178,9 @@ public void UseLocalStack_Should_Establish_Bidirectional_References() var cfResource = app.GetResource("test-resource"); // LocalStack should reference the CloudFormation resource - localStackResource.ShouldHaveReferenceToResource(cfResource); + await localStackResource.ShouldHaveReferenceToResource(cfResource); // CloudFormation resource should be enabled for LocalStack - cfResource.ShouldHaveLocalStackEnabledAnnotation(localStackResource); + await cfResource.ShouldHaveLocalStackEnabledAnnotation(localStackResource); } } diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/GlobalSuppressions.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/GlobalSuppressions.cs deleted file mode 100644 index fc4e418..0000000 --- a/tests/Aspire.Hosting.LocalStack.Unit.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.Unit.Tests/GlobalUsings.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/GlobalUsings.cs index 971373f..9d396da 100644 --- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/GlobalUsings.cs +++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/GlobalUsings.cs @@ -1,6 +1,12 @@ // Global using statements for unit tests +global using System.Collections.Immutable; global using System.Diagnostics.CodeAnalysis; +global using System.Net; +global using System.Reflection; +global using System.Runtime.CompilerServices; +global using System.Text; +global using System.Text.Json; global using Amazon.CloudFormation; global using Aspire.Hosting; global using Aspire.Hosting.ApplicationModel; @@ -13,7 +19,11 @@ global using Aspire.Hosting.LocalStack.Internal; global using Aspire.Hosting.LocalStack.Unit.Tests.TestUtilities; global using LocalStack.Client.Contracts; +global using LocalStack.Client.Enums; global using LocalStack.Client.Options; global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Diagnostics.HealthChecks; global using NSubstitute; -global using Xunit; +global using TUnit.Core; +global using TUnit.Assertions; +global using TUnit.Assertions.Extensions; diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/ConstantsTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/ConstantsTests.cs index a1e0d5d..6794513 100644 --- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/ConstantsTests.cs +++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/ConstantsTests.cs @@ -8,79 +8,86 @@ public ConstantsTests() { // Force load AWS assembly for type validation tests // This ensures the assembly is in the AppDomain before we try to find types - var awsAssembly = Assembly.Load("Aspire.Hosting.AWS"); + var awsAssembly = System.Reflection.Assembly.Load("Aspire.Hosting.AWS"); _ = awsAssembly; // Suppress unused warning } - [Fact] - public void DefaultContainerPort_Should_Be_4566() + [Test] + public async Task DefaultContainerPort_Should_Be_4566() { - Assert.Equal(4566, Constants.DefaultContainerPort); +#pragma warning disable TUnitAssertions0005 // These tests intentionally verify constant values + await Assert.That(Constants.DefaultContainerPort).IsEqualTo(4566); +#pragma warning restore TUnitAssertions0005 } - [Fact] - public void CloudFormationReferenceAnnotation_Should_Have_Correct_Type_Name() + [Test] + public async Task CloudFormationReferenceAnnotation_Should_Have_Correct_Type_Name() { - Assert.Equal("Aspire.Hosting.AWS.CloudFormation.CloudFormationReferenceAnnotation", Constants.CloudFormationReferenceAnnotation); +#pragma warning disable TUnitAssertions0005 // These tests intentionally verify constant values + await Assert.That(Constants.CloudFormationReferenceAnnotation).IsEqualTo("Aspire.Hosting.AWS.CloudFormation.CloudFormationReferenceAnnotation"); +#pragma warning restore TUnitAssertions0005 } - [Fact] - public void SQSEventSourceResource_Should_Have_Correct_Type_Name() + [Test] + public async Task SQSEventSourceResource_Should_Have_Correct_Type_Name() { - Assert.Equal("Aspire.Hosting.AWS.Lambda.SQSEventSourceResource", Constants.SQSEventSourceResource); +#pragma warning disable TUnitAssertions0005 // These tests intentionally verify constant values + await Assert.That(Constants.SQSEventSourceResource).IsEqualTo("Aspire.Hosting.AWS.Lambda.SQSEventSourceResource"); +#pragma warning restore TUnitAssertions0005 } - [Fact] - public void CloudFormationReferenceAnnotation_Type_Should_Exist_In_AWS_Assembly() + [Test] + public async Task CloudFormationReferenceAnnotation_Type_Should_Exist_In_AWS_Assembly() { // Act & Assert var type = GetTypeByName(Constants.CloudFormationReferenceAnnotation); - Assert.NotNull(type); - Assert.Equal(Constants.CloudFormationReferenceAnnotation, type.FullName); + await Assert.That(type).IsNotNull(); + await Assert.That(type!.FullName).IsEqualTo(Constants.CloudFormationReferenceAnnotation); // Verify it's an annotation type - Assert.True(typeof(IResourceAnnotation).IsAssignableFrom(type), - $"Type {Constants.CloudFormationReferenceAnnotation} should implement IResourceAnnotation"); + await Assert.That(typeof(IResourceAnnotation).IsAssignableFrom(type)).IsTrue() + .Because($"Type {Constants.CloudFormationReferenceAnnotation} should implement IResourceAnnotation"); } - [Fact] - public void SQSEventSourceResource_Type_Should_Exist_In_AWS_Assembly() + [Test] + public async Task SQSEventSourceResource_Type_Should_Exist_In_AWS_Assembly() { // Act & Assert var type = GetTypeByName(Constants.SQSEventSourceResource); - Assert.NotNull(type); - Assert.Equal(Constants.SQSEventSourceResource, type.FullName); + await Assert.That(type).IsNotNull(); + await Assert.That(type!.FullName).IsEqualTo(Constants.SQSEventSourceResource); // Verify it's an executable resource type - Assert.True(typeof(ExecutableResource).IsAssignableFrom(type), - $"Type {Constants.SQSEventSourceResource} should inherit from ExecutableResource"); + await Assert.That(typeof(ExecutableResource).IsAssignableFrom(type)).IsTrue() + .Because($"Type {Constants.SQSEventSourceResource} should inherit from ExecutableResource"); } - [Theory] - [InlineData("Aspire.Hosting.AWS.CloudFormation.CloudFormationReferenceAnnotation")] - [InlineData("Aspire.Hosting.AWS.Lambda.SQSEventSourceResource")] - public void AWS_Types_Should_Be_Accessible_From_Current_Assembly_Context(string typeName) + [Test] + [Arguments("Aspire.Hosting.AWS.CloudFormation.CloudFormationReferenceAnnotation")] + [Arguments("Aspire.Hosting.AWS.Lambda.SQSEventSourceResource")] + public async Task AWS_Types_Should_Be_Accessible_From_Current_Assembly_Context(string typeName) { // This test ensures we can find AWS types at runtime // Important for catching assembly loading or reference issues var type = GetTypeByName(typeName); - Assert.NotNull(type); - Assert.Equal(typeName, type.FullName); + await Assert.That(type).IsNotNull(); + await Assert.That(type!.FullName).IsEqualTo(typeName); } - [Fact] - public void AWS_Assembly_Dependencies_Should_Be_Available() + [Test] + public async Task AWS_Assembly_Dependencies_Should_Be_Available() { // Verify we can access the AWS assemblies our constants reference var awsAssemblies = AppDomain.CurrentDomain.GetAssemblies() .Where(a => a.FullName?.Contains("Aspire.Hosting.AWS", StringComparison.OrdinalIgnoreCase) == true) .ToList(); - Assert.NotEmpty(awsAssemblies); + await Assert.That(awsAssemblies).IsNotEmpty(); // Log available AWS assemblies for debugging var assemblyNames = string.Join(", ", awsAssemblies.Select(a => a.GetName().Name)); - Assert.True(awsAssemblies.Count > 0, $"Available AWS assemblies: {assemblyNames}"); + await Assert.That(awsAssemblies.Count > 0).IsTrue() + .Because($"Available AWS assemblies: {assemblyNames}"); } private static Type? GetTypeByName(string typeName) @@ -96,7 +103,7 @@ public void AWS_Assembly_Dependencies_Should_Be_Available() return type; } } - catch (Exception ex) when (ex is System.Reflection.ReflectionTypeLoadException or FileNotFoundException) + catch (Exception ex) when (ex is ReflectionTypeLoadException or FileNotFoundException) { // Assembly might not be loaded or accessible, continue searching continue; diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/LocalStackConnectionStringAvailableCallbackTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/LocalStackConnectionStringAvailableCallbackTests.cs index 00b77d0..7a77a94 100644 --- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/LocalStackConnectionStringAvailableCallbackTests.cs +++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/LocalStackConnectionStringAvailableCallbackTests.cs @@ -2,33 +2,33 @@ namespace Aspire.Hosting.LocalStack.Unit.Tests.Internal; public class LocalStackConnectionStringAvailableCallbackTests { - [Fact] - public void CreateCallback_Should_Return_Valid_Callback() + [Test] + public async Task CreateCallback_Should_Return_Valid_Callback() { var builder = Substitute.For(); var callback = LocalStackConnectionStringAvailableCallback.CreateCallback(builder); - Assert.NotNull(callback); + await Assert.That(callback).IsNotNull(); } - [Fact] - public void CreateCallback_Should_Throw_ArgumentNullException_For_Null_Builder() + [Test] + public async Task CreateCallback_Should_Throw_ArgumentNullException_For_Null_Builder() { - Assert.Throws(() => LocalStackConnectionStringAvailableCallback.CreateCallback(null!)); + await Assert.That(() => LocalStackConnectionStringAvailableCallback.CreateCallback(null!)).ThrowsExactly(); } - [Fact] - public void CreateCallback_Should_Return_Function_With_Correct_Signature() + [Test] + public async Task CreateCallback_Should_Return_Function_With_Correct_Signature() { var builder = Substitute.For(); var callback = LocalStackConnectionStringAvailableCallback.CreateCallback(builder); - Assert.IsType>(callback); + await Assert.That(callback).IsTypeOf>(); } - [Fact] + [Test] public async Task Callback_Should_Skip_When_UseLocalStack_Is_False() { var builder = Substitute.For(); diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/LocalStackHealthCheckTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/LocalStackHealthCheckTests.cs index c0cb9d2..4a6c05f 100644 --- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/LocalStackHealthCheckTests.cs +++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/LocalStackHealthCheckTests.cs @@ -1,9 +1,3 @@ -using System.Collections.Immutable; -using System.Net; -using System.Text; -using System.Text.Json; -using Microsoft.Extensions.Diagnostics.HealthChecks; - namespace Aspire.Hosting.LocalStack.Unit.Tests.Internal; public sealed class LocalStackHealthCheckTests : IDisposable @@ -26,7 +20,7 @@ public void Dispose() _messageHandler.Dispose(); } - [Fact] + [Test] public async Task CheckHealthAsync_Returns_Healthy_When_No_Services_Specified_And_Endpoint_Responds() { var healthCheckUri = new Uri("http://localhost:4566/_localstack/health"); @@ -35,13 +29,13 @@ public async Task CheckHealthAsync_Returns_Healthy_When_No_Services_Specified_An _messageHandler.SetupResponse(HttpStatusCode.OK, new { services = new { } }); - var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), TestContext.Current.CancellationToken); + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); - Assert.Equal(HealthStatus.Healthy, result.Status); - Assert.Equal("LocalStack is healthy", result.Description); + await Assert.That(result.Status).IsEqualTo(HealthStatus.Healthy); + await Assert.That(result.Description).IsEqualTo("LocalStack is healthy"); } - [Fact] + [Test] public async Task CheckHealthAsync_Returns_Healthy_When_All_Services_Running() { var healthCheckUri = new Uri("http://localhost:4566/_localstack/health"); @@ -57,13 +51,13 @@ public async Task CheckHealthAsync_Returns_Healthy_When_All_Services_Running() }, }); - var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), TestContext.Current.CancellationToken); + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); - Assert.Equal(HealthStatus.Healthy, result.Status); - Assert.Equal("LocalStack is healthy.", result.Description); + await Assert.That(result.Status).IsEqualTo(HealthStatus.Healthy); + await Assert.That(result.Description).IsEqualTo("LocalStack is healthy."); } - [Fact] + [Test] public async Task CheckHealthAsync_Returns_Unhealthy_When_Service_Not_Running() { var healthCheckUri = new Uri("http://localhost:4566/_localstack/health"); @@ -79,14 +73,14 @@ public async Task CheckHealthAsync_Returns_Unhealthy_When_Service_Not_Running() }, }); - var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), TestContext.Current.CancellationToken); + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); - Assert.Equal(HealthStatus.Unhealthy, result.Status); - Assert.Contains("dynamodb", result.Description, StringComparison.Ordinal); - Assert.Contains("not running", result.Description, StringComparison.Ordinal); + await Assert.That(result.Status).IsEqualTo(HealthStatus.Unhealthy); + await Assert.That(result.Description).Contains("dynamodb"); + await Assert.That(result.Description).Contains("not running"); } - [Fact] + [Test] public async Task CheckHealthAsync_Returns_Unhealthy_When_Service_Missing() { var healthCheckUri = new Uri("http://localhost:4566/_localstack/health"); @@ -102,13 +96,13 @@ public async Task CheckHealthAsync_Returns_Unhealthy_When_Service_Missing() }, }); - var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), TestContext.Current.CancellationToken); + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); - Assert.Equal(HealthStatus.Unhealthy, result.Status); - Assert.Contains("s3", result.Description, StringComparison.Ordinal); + await Assert.That(result.Status).IsEqualTo(HealthStatus.Unhealthy); + await Assert.That(result.Description).Contains("s3"); } - [Fact] + [Test] public async Task CheckHealthAsync_Handles_Case_Insensitive_Service_Names() { var healthCheckUri = new Uri("http://localhost:4566/_localstack/health"); @@ -125,12 +119,12 @@ public async Task CheckHealthAsync_Handles_Case_Insensitive_Service_Names() }, }); - var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), TestContext.Current.CancellationToken); + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); - Assert.Equal(HealthStatus.Healthy, result.Status); + await Assert.That(result.Status).IsEqualTo(HealthStatus.Healthy); } - [Fact] + [Test] public async Task CheckHealthAsync_Returns_Unhealthy_When_Endpoint_Returns_Non_Success_Status() { var healthCheckUri = new Uri("http://localhost:4566/_localstack/health"); @@ -139,13 +133,13 @@ public async Task CheckHealthAsync_Returns_Unhealthy_When_Endpoint_Returns_Non_S _messageHandler.SetupResponse(HttpStatusCode.ServiceUnavailable, new { }); - var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), TestContext.Current.CancellationToken); + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); - Assert.Equal(HealthStatus.Unhealthy, result.Status); - Assert.Equal("LocalStack is unhealthy.", result.Description); + await Assert.That(result.Status).IsEqualTo(HealthStatus.Unhealthy); + await Assert.That(result.Description).IsEqualTo("LocalStack is unhealthy."); } - [Fact] + [Test] public async Task CheckHealthAsync_Returns_Unhealthy_When_Services_Object_Missing() { var healthCheckUri = new Uri("http://localhost:4566/_localstack/health"); @@ -154,13 +148,13 @@ public async Task CheckHealthAsync_Returns_Unhealthy_When_Services_Object_Missin _messageHandler.SetupResponse(HttpStatusCode.OK, new { version = "1.0" }); // No services object - var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), TestContext.Current.CancellationToken); + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); - Assert.Equal(HealthStatus.Unhealthy, result.Status); - Assert.Contains("did not contain a 'services' object", result.Description, StringComparison.Ordinal); + await Assert.That(result.Status).IsEqualTo(HealthStatus.Unhealthy); + await Assert.That(result.Description).Contains("did not contain a 'services' object"); } - [Fact] + [Test] public async Task CheckHealthAsync_Handles_HttpRequestException() { var healthCheckUri = new Uri("http://localhost:4566/_localstack/health"); @@ -169,14 +163,14 @@ public async Task CheckHealthAsync_Handles_HttpRequestException() _messageHandler.SetupException(new HttpRequestException("Network error")); - var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), TestContext.Current.CancellationToken); + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); - Assert.Equal(HealthStatus.Unhealthy, result.Status); - Assert.Contains("network error", result.Description, StringComparison.Ordinal); - Assert.NotNull(result.Exception); + await Assert.That(result.Status).IsEqualTo(HealthStatus.Unhealthy); + await Assert.That(result.Description).Contains("network error"); + await Assert.That(result.Exception).IsNotNull(); } - [Fact] + [Test] public async Task CheckHealthAsync_Handles_Timeout() { var healthCheckUri = new Uri("http://localhost:4566/_localstack/health"); @@ -185,13 +179,43 @@ public async Task CheckHealthAsync_Handles_Timeout() _messageHandler.SetupException(new TaskCanceledException("Timeout", new TimeoutException("Operation timed out"))); - var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), TestContext.Current.CancellationToken); + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); + + await Assert.That(result.Status).IsEqualTo(HealthStatus.Unhealthy); + await Assert.That(result.Description).Contains("timed out"); + await Assert.That(result.Exception).IsNotNull(); + } + [Test] + public async Task CheckHealthAsync_Returns_Unhealthy_When_IOException_Occurs() + { + var healthCheckUri = new Uri("http://localhost:4566/_localstack/health"); + var services = ImmutableArray.Create("sqs"); + var healthCheck = new LocalStackHealthCheck(_httpClientFactory, healthCheckUri, services); + + _messageHandler.SetupException(new IOException("Connection closed prematurely")); + + var result = await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None); - Assert.Equal(HealthStatus.Unhealthy, result.Status); - Assert.Contains("timed out", result.Description, StringComparison.Ordinal); - Assert.NotNull(result.Exception); + await Assert.That(result.Status).IsEqualTo(HealthStatus.Unhealthy); + await Assert.That(result.Description).Contains("starting up"); + await Assert.That(result.Exception).IsTypeOf(); } + [Test] + public async Task CheckHealthAsync_Propagates_OperationCanceledException() + { + var healthCheckUri = new Uri("http://localhost:4566/_localstack/health"); + var services = ImmutableArray.Create("sqs"); + var healthCheck = new LocalStackHealthCheck(_httpClientFactory, healthCheckUri, services); + + _messageHandler.SetupException(new OperationCanceledException("Cancellation requested")); + + // This should NOT be caught by the generic handler anymore + await Assert.That(async () => await healthCheck.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None)) + .ThrowsExactly(); + } + + // Custom HttpMessageHandler for testing private sealed class TestHttpMessageHandler : HttpMessageHandler { diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/LocalStackResourceConfiguratorTests.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/LocalStackResourceConfiguratorTests.cs index faba73f..a0d6851 100644 --- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/LocalStackResourceConfiguratorTests.cs +++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/Internal/LocalStackResourceConfiguratorTests.cs @@ -2,8 +2,8 @@ namespace Aspire.Hosting.LocalStack.Unit.Tests.Internal; public class LocalStackResourceConfiguratorTests { - [Fact] - public void ConfigureCloudFormationResource_Should_Set_CloudFormation_Client() + [Test] + public async Task ConfigureCloudFormationResource_Should_Set_CloudFormation_Client() { var cfResource = Substitute.For(); var localStackUrl = new Uri("http://localhost:4566"); @@ -16,12 +16,12 @@ public void ConfigureCloudFormationResource_Should_Set_CloudFormation_Client() LocalStackResourceConfigurator.ConfigureCloudFormationResource(cfResource, localStackUrl, options); - Assert.NotNull(capturedClient); - Assert.IsType(capturedClient); + await Assert.That(capturedClient).IsNotNull(); + await Assert.That(capturedClient).IsTypeOf(); } - [Fact] - public void ConfigureCloudFormationResource_Should_Configure_Client_With_LocalStack_Endpoint() + [Test] + public async Task ConfigureCloudFormationResource_Should_Configure_Client_With_LocalStack_Endpoint() { var cfResource = Substitute.For(); var localStackUrl = new Uri("http://test-host:9999"); @@ -37,8 +37,8 @@ public void ConfigureCloudFormationResource_Should_Configure_Client_With_LocalSt LocalStackResourceConfigurator.ConfigureCloudFormationResource(cfResource, localStackUrl, options); - Assert.NotNull(capturedClient); - Assert.NotNull(capturedClient.Config); + await Assert.That(capturedClient).IsNotNull(); + await Assert.That(capturedClient.Config).IsNotNull(); // Log some debug info about what's actually configured var config = capturedClient.Config; @@ -48,13 +48,13 @@ public void ConfigureCloudFormationResource_Should_Configure_Client_With_LocalSt $"ProxyHost: '{config.ProxyHost}', " + $"ProxyPort: '{config.ProxyPort}'"; - // For now, just verify the client was created successfully - // Note: LocalStack client configuration may use internal mechanisms to set endpoint URLs - Assert.True(true, $"Client config: {debugInfo}"); + // Verify client was created with valid configuration + await Assert.That(config.ServiceURL ?? config.RegionEndpoint?.SystemName).IsNotNull() + .Because($"Client config: {debugInfo}"); } - [Fact] - public void ConfigureCloudFormationResource_Should_Handle_SSL_Configuration() + [Test] + public async Task ConfigureCloudFormationResource_Should_Handle_SSL_Configuration() { var cfResource = Substitute.For(); var localStackUrl = new Uri("https://localhost:4566"); @@ -67,17 +67,18 @@ public void ConfigureCloudFormationResource_Should_Handle_SSL_Configuration() LocalStackResourceConfigurator.ConfigureCloudFormationResource(cfResource, localStackUrl, options); - Assert.NotNull(capturedClient); - Assert.NotNull(capturedClient.Config); + await Assert.That(capturedClient).IsNotNull(); + await Assert.That(capturedClient.Config).IsNotNull(); var config = capturedClient.Config; - // We can at least verify that a valid client was created - Assert.True(true, $"SSL client created with UseHttp: {config.UseHttp}"); + // Verify SSL client has valid configuration + await Assert.That(capturedClient).IsNotNull() + .Because($"SSL client created with UseHttp: {config.UseHttp}"); } - [Fact] - public void ConfigureProjectResource_Should_Call_WithEnvironment() + [Test] + public async Task ConfigureProjectResource_Should_Call_WithEnvironment() { var mockResource = Substitute.For(); var mockBuilder = Substitute.For>(); @@ -93,12 +94,12 @@ public void ConfigureProjectResource_Should_Call_WithEnvironment() LocalStackResourceConfigurator.ConfigureProjectResource(mockBuilder, localStackUrl, options); - Assert.NotNull(mockBuilder); - Assert.NotNull(options); + await Assert.That(mockBuilder).IsNotNull(); + await Assert.That(options).IsNotNull(); } - [Fact] - public void ConfigureProjectResource_Should_Handle_Custom_Port_And_Host() + [Test] + public async Task ConfigureProjectResource_Should_Handle_Custom_Port_And_Host() { var mockResource = Substitute.For(); var mockBuilder = Substitute.For>(); @@ -111,11 +112,11 @@ public void ConfigureProjectResource_Should_Handle_Custom_Port_And_Host() useSsl: true); LocalStackResourceConfigurator.ConfigureProjectResource(mockBuilder, localStackUrl, options); - Assert.NotNull(mockBuilder); + await Assert.That(mockBuilder).IsNotNull(); } - [Fact] - public void ConfigureProjectResource_Should_Handle_All_Configuration_Options() + [Test] + public async Task ConfigureProjectResource_Should_Handle_All_Configuration_Options() { var mockResource = Substitute.For(); var mockBuilder = Substitute.For>(); @@ -126,13 +127,13 @@ public void ConfigureProjectResource_Should_Handle_All_Configuration_Options() LocalStackResourceConfigurator.ConfigureProjectResource(mockBuilder, localStackUrl, options); - Assert.True(options.UseLocalStack || !options.UseLocalStack); // Verifies bool is accessible - Assert.NotNull(options.Session); - Assert.NotNull(options.Config); + await Assert.That(options.UseLocalStack || !options.UseLocalStack).IsTrue(); // Verifies bool is accessible + await Assert.That(options.Session).IsNotNull(); + await Assert.That(options.Config).IsNotNull(); } - [Fact] - public void ConfigureSqsEventSourceResource_Should_Add_Environment_Annotation_With_AWS_Endpoint_URL() + [Test] + public async Task ConfigureSqsEventSourceResource_Should_Add_Environment_Annotation_With_AWS_Endpoint_URL() { var mockExecutableResource = new ExecutableResource("test-sqs-resource", "test-command", "test-workdir"); var (options, _, _) = TestDataBuilders.CreateMockLocalStackOptions( @@ -154,12 +155,12 @@ public void ConfigureSqsEventSourceResource_Should_Add_Environment_Annotation_Wi .OfType() .ToList(); - Assert.NotEmpty(envAnnotations); - Assert.Single(envAnnotations); + await Assert.That(envAnnotations).IsNotEmpty(); + await Assert.That(envAnnotations).HasSingleItem(); } - [Fact] - public void ConfigureSqsEventSourceResource_Should_Add_Annotation_Via_WithEnvironment() + [Test] + public async Task ConfigureSqsEventSourceResource_Should_Add_Annotation_Via_WithEnvironment() { var mockExecutableResource = new ExecutableResource("test-sqs-resource", "test-command", "test-workdir"); var mockBuilder = Substitute.For>(); @@ -183,16 +184,16 @@ public void ConfigureSqsEventSourceResource_Should_Add_Annotation_Via_WithEnviro LocalStackResourceConfigurator.ConfigureSqsEventSourceResource(mockBuilder, localStackUrl, options); - Assert.Equal(initialAnnotationCount + 1, mockExecutableResource.Annotations.Count); + await Assert.That(mockExecutableResource.Annotations.Count).IsEqualTo(initialAnnotationCount + 1); var envAnnotations = mockExecutableResource.Annotations .OfType() .ToList(); - Assert.Single(envAnnotations); + await Assert.That(envAnnotations).HasSingleItem(); } - [Fact] - public void ConfigureSqsEventSourceResource_Should_Handle_Empty_Annotations_Collection() + [Test] + public async Task ConfigureSqsEventSourceResource_Should_Handle_Empty_Annotations_Collection() { var mockExecutableResource = new ExecutableResource("test-sqs-resource", "test-command", "test-workdir"); var mockBuilder = Substitute.For>(); @@ -212,12 +213,12 @@ public void ConfigureSqsEventSourceResource_Should_Handle_Empty_Annotations_Coll LocalStackResourceConfigurator.ConfigureSqsEventSourceResource(mockBuilder, localStackUrl, options); // Should have added exactly one EnvironmentCallbackAnnotation - Assert.Equal(initialAnnotationCount + 1, mockExecutableResource.Annotations.Count); - Assert.Single(mockExecutableResource.Annotations.OfType()); + await Assert.That(mockExecutableResource.Annotations.Count).IsEqualTo(initialAnnotationCount + 1); + await Assert.That(mockExecutableResource.Annotations.OfType()).HasSingleItem(); } - [Fact] - public void ConfigureSqsEventSourceResource_Should_Handle_Different_LocalStack_URLs() + [Test] + public async Task ConfigureSqsEventSourceResource_Should_Handle_Different_LocalStack_URLs() { var mockExecutableResource = new ExecutableResource("test-sqs-resource", "test-command", "test-workdir"); var mockBuilder = Substitute.For>(); @@ -236,6 +237,6 @@ public void ConfigureSqsEventSourceResource_Should_Handle_Different_LocalStack_U LocalStackResourceConfigurator.ConfigureSqsEventSourceResource(mockBuilder, customLocalStackUrl, options); var envAnnotation = mockExecutableResource.Annotations.OfType().FirstOrDefault(); - Assert.NotNull(envAnnotation); + await Assert.That(envAnnotation).IsNotNull(); } } diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/TestUtilities/LocalStackAssertions.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/TestUtilities/LocalStackAssertions.cs index 9304e26..7fa10cb 100644 --- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/TestUtilities/LocalStackAssertions.cs +++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/TestUtilities/LocalStackAssertions.cs @@ -2,7 +2,7 @@ namespace Aspire.Hosting.LocalStack.Unit.Tests.TestUtilities; internal static class LocalStackAssertions { - public static void ShouldHaveLocalStackEnabledAnnotation(this IResource resource, ILocalStackResource expectedLocalStack) + public static async Task ShouldHaveLocalStackEnabledAnnotation(this IResource resource, ILocalStackResource expectedLocalStack) { ArgumentNullException.ThrowIfNull(resource); ArgumentNullException.ThrowIfNull(expectedLocalStack); @@ -11,11 +11,11 @@ public static void ShouldHaveLocalStackEnabledAnnotation(this IResource resource .OfType() .SingleOrDefault(); - Assert.NotNull(annotation); - Assert.Same(expectedLocalStack, annotation.LocalStackResource); + await Assert.That(annotation).IsNotNull(); + await Assert.That(annotation!.LocalStackResource).IsSameReferenceAs(expectedLocalStack); } - public static void ShouldHaveLocalStackEnabledAnnotation(this IResource resource) + public static async Task ShouldHaveLocalStackEnabledAnnotation(this IResource resource) { ArgumentNullException.ThrowIfNull(resource); @@ -23,10 +23,10 @@ public static void ShouldHaveLocalStackEnabledAnnotation(this IResource resource .OfType() .SingleOrDefault(); - Assert.NotNull(annotation); + await Assert.That(annotation).IsNotNull(); } - public static void ShouldHaveReferenceToResource(this ILocalStackResource localStackResource, IResource expectedTargetResource) + public static async Task ShouldHaveReferenceToResource(this ILocalStackResource localStackResource, IResource expectedTargetResource) { ArgumentNullException.ThrowIfNull(localStackResource); ArgumentNullException.ThrowIfNull(expectedTargetResource); @@ -35,11 +35,11 @@ public static void ShouldHaveReferenceToResource(this ILocalStackResource localS .OfType() .SingleOrDefault(a => ReferenceEquals(a.Resource, expectedTargetResource)); - Assert.NotNull(annotation); - Assert.Same(expectedTargetResource, annotation.Resource); + await Assert.That(annotation).IsNotNull(); + await Assert.That(annotation!.Resource).IsSameReferenceAs(expectedTargetResource); } - public static void ShouldNotHaveLocalStackEnabledAnnotation(this IResource resource) + public static async Task ShouldNotHaveLocalStackEnabledAnnotation(this IResource resource) { ArgumentNullException.ThrowIfNull(resource); @@ -47,44 +47,48 @@ public static void ShouldNotHaveLocalStackEnabledAnnotation(this IResource resou .OfType() .SingleOrDefault(); - Assert.Null(annotation); + await Assert.That(annotation).IsNull(); } - public static void ShouldHaveLocalStackEnvironmentConfiguration(this ProjectResource projectResource, ILocalStackResource expectedLocalStack) + public static async Task ShouldHaveLocalStackEnvironmentConfiguration(this ProjectResource projectResource, ILocalStackResource expectedLocalStack) { ArgumentNullException.ThrowIfNull(projectResource); ArgumentNullException.ThrowIfNull(expectedLocalStack); +#pragma warning disable MA0015 ArgumentNullException.ThrowIfNull(expectedLocalStack.Options); +#pragma warning restore MA0015 - projectResource.ShouldHaveLocalStackEnabledAnnotation(expectedLocalStack); + await projectResource.ShouldHaveLocalStackEnabledAnnotation(expectedLocalStack); var envAnnotations = projectResource.Annotations .OfType() .ToList(); - Assert.NotEmpty(envAnnotations); - Assert.True(envAnnotations.Count > 0, "Project should have environment callback annotations for LocalStack configuration"); + await Assert.That(envAnnotations).IsNotEmpty(); + await Assert.That(envAnnotations.Count > 0).IsTrue() + .Because("Project should have environment callback annotations for LocalStack configuration"); } - public static void ShouldHaveResourceCount(this DistributedApplication app, int expectedCount) + public static async Task ShouldHaveResourceCount(this DistributedApplication app, int expectedCount) where T : IResource { ArgumentNullException.ThrowIfNull(app); var actualCount = app.GetResources().Count(); - Assert.Equal(expectedCount, actualCount); + await Assert.That(actualCount).IsEqualTo(expectedCount); } - public static void ShouldWaitFor(this IResource resource, IResource dependencyResource) + public static async Task ShouldWaitFor(this IResource resource, IResource dependencyResource) { ArgumentNullException.ThrowIfNull(resource); ArgumentNullException.ThrowIfNull(dependencyResource); var tryGetAnnotationsOfType = resource.TryGetAnnotationsOfType(out var annotations); - Assert.True(tryGetAnnotationsOfType); - Assert.NotNull(annotations); + await Assert.That(tryGetAnnotationsOfType).IsTrue(); + await Assert.That(annotations).IsNotNull(); - var hasWaitForDependencyResource = annotations.Any(annotation => annotation.Resource == dependencyResource); - Assert.True(hasWaitForDependencyResource, $"Resource '{resource.Name}' should wait for resource '{dependencyResource.Name}'."); + var hasWaitForDependencyResource = annotations!.Any(annotation => annotation.Resource == dependencyResource); + await Assert.That(hasWaitForDependencyResource).IsTrue() + .Because($"Resource '{resource.Name}' should wait for resource '{dependencyResource.Name}'."); } } diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/TestUtilities/TestDataBuilders.cs b/tests/Aspire.Hosting.LocalStack.Unit.Tests/TestUtilities/TestDataBuilders.cs index 493025a..b7c5770 100644 --- a/tests/Aspire.Hosting.LocalStack.Unit.Tests/TestUtilities/TestDataBuilders.cs +++ b/tests/Aspire.Hosting.LocalStack.Unit.Tests/TestUtilities/TestDataBuilders.cs @@ -1,5 +1,3 @@ -using System.Runtime.CompilerServices; - namespace Aspire.Hosting.LocalStack.Unit.Tests.TestUtilities; internal static class TestDataBuilders diff --git a/tests/Aspire.Hosting.LocalStack.Unit.Tests/xunit.runner.json b/tests/Aspire.Hosting.LocalStack.Unit.Tests/xunit.runner.json deleted file mode 100644 index 894645b..0000000 --- a/tests/Aspire.Hosting.LocalStack.Unit.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": true, - "showLiveOutput": true, - "stopOnFail": false, - "longRunningTestSeconds": 30 -} diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 7ba81bc..77ac833 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -3,27 +3,16 @@ false + + $(NoWarn);CA1515;CA2007;IDE1006;CA1707 + $(NoError);CA1515;CA2007;IDE1006;CA1707 - - - - - all - runtime; build; native; contentfiles; analyzers - - - all - runtime; build; native; contentfiles; analyzers - - - all - runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers