diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e4754d3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +**/bin/ +**/obj/ +**/.vs/ +**/.weave/ +**/node_modules/ +**/*.user +**/*.suo +**/TestResults/ +.git/ +docs/ +tests/ +*.md +*.txt +*.ncrunchsolution +*.ncrunchsolution.user +*.DotSettings.user +assets/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd559ea..06a270e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,14 +33,7 @@ jobs: run: dotnet build MicroStack.slnx -c Release --no-restore - name: Test - run: dotnet test MicroStack.slnx -c Release --no-build --logger "trx;LogFileName=results.trx" - - - name: Upload test results - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-results - path: '**/results.trx' + run: dotnet test --project tests/MicroStack.Tests/MicroStack.Tests.csproj -c Release --no-build --filter-not-class MicroStack.Tests.ContainerSmokeTests - name: Pack NuGet packages run: dotnet pack src/MicroStack.Aspire.Hosting/MicroStack.Aspire.Hosting.csproj -c Release --no-build --output ./packages diff --git a/.weave/learnings/port-ministack.md b/.weave/learnings/port-ministack.md index 7954f77..9621ec4 100644 --- a/.weave/learnings/port-ministack.md +++ b/.weave/learnings/port-ministack.md @@ -96,7 +96,7 @@ - `ApplyResultPath` unwraps both `__scalar__` and `__list__` before applying to paths (so `ResultPath: "$.status"` with scalar `"enriched"` sets `output["status"] = "enriched"` directly) - `ApplyResultPathRaw("$", ...)` re-wraps scalars as `__scalar__` and lists as `__list__` to maintain the `Dictionary` return type - **PostAsJsonAsync camelCase**: `HttpClient.PostAsJsonAsync` uses `JsonSerializerDefaults.Web` in .NET 10 which camelCases property names. Mock config endpoint expects PascalCase keys (StateMachines, TestCases, MockedResponses). Solution: send raw JSON strings with explicit property names instead of using anonymous types. -- **Config endpoint nested properties**: The `/_ministack/config` endpoint receives `{"stepfunctions": {"_sfn_mock_config": {...}}}` as a nested object, NOT a dotted property name. Must navigate `doc.RootElement.TryGetProperty("stepfunctions", out var sfnEl)` then `sfnEl.TryGetProperty("_sfn_mock_config", out var mockEl)`. +- **Config endpoint nested properties**: The `/_microstack/config` endpoint receives `{"stepfunctions": {"_sfn_mock_config": {...}}}` as a nested object, NOT a dotted property name. Must navigate `doc.RootElement.TryGetProperty("stepfunctions", out var sfnEl)` then `sfnEl.TryGetProperty("_sfn_mock_config", out var mockEl)`. - **SNS protocol mismatch**: SFN's `DispatchToService` sends JSON body with `x-amz-target: SNS.Publish`, but the SNS handler expects query/XML (form-encoded) protocol. Integration test skipped. - **AWS SDK v4 type ambiguity**: `Tag`, `TagResourceRequest`, `UntagResourceRequest` are ambiguous between StepFunctions and DynamoDBv2 namespaces — use type aliases (`SfnTag`, `SfnTagResourceRequest`, `SfnUntagResourceRequest`). - **xUnit2012**: Don't use `Assert.True(collection.Any(...))` — use `Assert.Contains(collection, predicate)` instead. diff --git a/.weave/plans/aot-migration.md b/.weave/plans/aot-migration.md index 946b2ac..ebc2922 100644 --- a/.weave/plans/aot-migration.md +++ b/.weave/plans/aot-migration.md @@ -35,7 +35,7 @@ Make MicroStack (.NET 10 ASP.NET Core app emulating 39 AWS services) compatible 7. **Program.cs** — 3 anonymous types in `Results.Ok(new { ... })`. -8. **Program.cs `/_ministack/config`** — `JsonElementToDict()` / `JsonElementToObject()` helper converts `JsonElement` → `Dictionary` → passed to `sfnHandler.SetMockConfig()`. +8. **Program.cs `/_microstack/config`** — `JsonElementToDict()` / `JsonElementToObject()` helper converts `JsonElement` → `Dictionary` → passed to `sfnHandler.SetMockConfig()`. **Critical insight about the `Dictionary` pattern:** @@ -329,10 +329,10 @@ Enable Native AOT publishing for MicroStack with zero trim warnings and all 1,17 - [x] 17. Verify AOT binary runs correctly **What**: Run the AOT-compiled binary and execute a smoke test: 1. Start the binary - 2. Hit `/_ministack/health` endpoint + 2. Hit `/_microstack/health` endpoint 3. Create an SQS queue via AWS SDK 4. Send/receive a message - 5. Hit `/_ministack/reset` + 5. Hit `/_microstack/reset` 6. Verify health returns clean state **Acceptance**: AOT binary starts, serves requests, returns correct responses diff --git a/.weave/plans/port-ministack.md b/.weave/plans/port-ministack.md index 108a7aa..55cb8d1 100644 --- a/.weave/plans/port-ministack.md +++ b/.weave/plans/port-ministack.md @@ -62,7 +62,7 @@ Create a fully functional .NET 10 port of MiniStack that passes equivalent integ ### Definition of Done - [ ] `dotnet build` succeeds with zero warnings - [ ] `dotnet test` passes all integration tests -- [ ] Health endpoint returns service list at `http://localhost:4566/_ministack/health` +- [ ] Health endpoint returns service list at `http://localhost:4566/_microstack/health` - [ ] AWS SDK for .NET clients can connect and perform CRUD operations on all ported services ### Guardrails (Must NOT) @@ -123,7 +123,7 @@ Create a fully functional .NET 10 port of MiniStack that passes equivalent integ Source reference: `ministack/app.py` lines 193-552 (ASGI app function) - [x] 6 Special Route Handling (Pre-Router) - **What**: Implement the special routing paths that are handled before general service dispatch in `app.py`: (a) S3 virtual-hosted style (`{bucket}.localhost` or `{bucket}.s3.localhost`), (b) API Gateway execute-api data plane (`{apiId}.execute-api.localhost`), (c) ALB data plane (`{lb}.alb.localhost` or `/_alb/{lb}/...`), (d) S3 Control API (`/v20180820/...`), (e) RDS Data API REST paths (`/Execute`, `/BeginTransaction`, etc.), (f) SES v2 REST paths (`/v2/email/...`), (g) Cognito well-known endpoints (`/.well-known/jwks.json`, `/.well-known/openid-configuration`), (h) Lambda layer content download (`/_ministack/lambda-layers/...`). For Phase 1, stub these routes to return 501 — they'll be implemented when their respective services are ported. + **What**: Implement the special routing paths that are handled before general service dispatch in `app.py`: (a) S3 virtual-hosted style (`{bucket}.localhost` or `{bucket}.s3.localhost`), (b) API Gateway execute-api data plane (`{apiId}.execute-api.localhost`), (c) ALB data plane (`{lb}.alb.localhost` or `/_alb/{lb}/...`), (d) S3 Control API (`/v20180820/...`), (e) RDS Data API REST paths (`/Execute`, `/BeginTransaction`, etc.), (f) SES v2 REST paths (`/v2/email/...`), (g) Cognito well-known endpoints (`/.well-known/jwks.json`, `/.well-known/openid-configuration`), (h) Lambda layer content download (`/_microstack/lambda-layers/...`). For Phase 1, stub these routes to return 501 — they'll be implemented when their respective services are ported. **Files**: - `src/MicroStack/Internal/AwsRequestMiddleware.cs` (extend) **Acceptance**: Virtual-hosted S3 request to `mybucket.localhost:4566/key` is recognized and routed (to stub). Execute-api host header is recognized. @@ -131,11 +131,11 @@ Create a fully functional .NET 10 port of MiniStack that passes equivalent integ Source reference: `ministack/app.py` lines 263-513 (pre-router special cases) - [x] 7 Admin Endpoints (Health, Reset, Config) - **What**: Implement the three admin endpoints: (a) `GET /_ministack/health` (and aliases `/health`, `/_localstack/health`) returns JSON `{"services": {...}, "edition": "light", "version": "0.1.0"}` listing all registered service handlers, (b) `POST /_ministack/reset` calls `Reset()` on all registered service handlers and wipes persistence files, (c) `POST /_ministack/config` accepts JSON body with allowed config keys (stub for now). Register these as minimal API endpoints or handle in middleware before service dispatch. + **What**: Implement the three admin endpoints: (a) `GET /_microstack/health` (and aliases `/health`, `/_localstack/health`) returns JSON `{"services": {...}, "edition": "light", "version": "0.1.0"}` listing all registered service handlers, (b) `POST /_microstack/reset` calls `Reset()` on all registered service handlers and wipes persistence files, (c) `POST /_microstack/config` accepts JSON body with allowed config keys (stub for now). Register these as minimal API endpoints or handle in middleware before service dispatch. **Files**: - `src/MicroStack/Internal/AdminEndpoints.cs` - `src/MicroStack/Program.cs` (wire up) - **Acceptance**: `curl http://localhost:4566/_ministack/health` returns 200 with services JSON. `curl -X POST http://localhost:4566/_ministack/reset` returns 200 with `{"reset":"ok"}`. + **Acceptance**: `curl http://localhost:4566/_microstack/health` returns 200 with services JSON. `curl -X POST http://localhost:4566/_microstack/reset` returns 200 with `{"reset":"ok"}`. Source reference: `ministack/app.py` lines 289-321, 399-408, 750-819 @@ -630,12 +630,12 @@ Create a fully functional .NET 10 port of MiniStack that passes equivalent integ Source reference: `ministack/tests/test_multitenancy.py` - [x] 62 Persistence Integration Test - **What**: Port `test_ministack_persist.py` (272 lines). Verify that with `PERSIST_STATE=1`, service state survives server restart. + **What**: Port `test_microstack_persist.py` (272 lines). Verify that with `PERSIST_STATE=1`, service state survives server restart. **Files**: - `tests/MicroStack.Tests/PersistenceTests.cs` **Acceptance**: Create resources, restart server, resources still exist. - Source reference: `ministack/tests/test_ministack_persist.py` + Source reference: `ministack/tests/test_microstack_persist.py` - [ ] 63 Unicode Handling Test **What**: Port `test_unicode.py` (64 lines). Verify Unicode characters in S3 keys, SQS messages, DynamoDB items, SNS messages are handled correctly. @@ -650,8 +650,8 @@ Create a fully functional .NET 10 port of MiniStack that passes equivalent integ - [ ] `dotnet build src/MicroStack/MicroStack.csproj` succeeds with zero warnings - [ ] `dotnet build tests/MicroStack.Tests/MicroStack.Tests.csproj` succeeds with zero warnings - [ ] `dotnet test tests/MicroStack.Tests/` — all integration tests pass -- [ ] Health endpoint at `http://localhost:4566/_ministack/health` returns all services -- [ ] Reset endpoint at `http://localhost:4566/_ministack/reset` clears all state +- [ ] Health endpoint at `http://localhost:4566/_microstack/health` returns all services +- [ ] Reset endpoint at `http://localhost:4566/_microstack/reset` clears all state - [ ] AWS SDK for .NET clients with `ServiceURL=http://localhost:4566` can perform operations on all services - [ ] Docker build succeeds and container runs correctly - [ ] No public types in `*.Internal.*` namespaces diff --git a/.weave/plans/task-27-step-functions.md b/.weave/plans/task-27-step-functions.md index 90f08cf..da28b98 100644 --- a/.weave/plans/task-27-step-functions.md +++ b/.weave/plans/task-27-step-functions.md @@ -39,9 +39,9 @@ Test file: `test_sfn.py` (2,213 lines, 68 tests). We port ~55 tests initially (s - For direct service integrations (SQS, SNS, DynamoDB), call handlers directly ### Mock Config -- Python uses `_ministack_config` endpoint to set `stepfunctions._sfn_mock_config` at runtime +- Python uses `_microstack_config` endpoint to set `stepfunctions._sfn_mock_config` at runtime - C# approach: Expose internal `SetMockConfig(Dictionary config)` method -- Wire up `/_ministack/config` endpoint to route SFN mock config to the handler +- Wire up `/_microstack/config` endpoint to route SFN mock config to the handler - Tests that use mock config: `test_sfn_mock_config_return`, `test_sfn_mock_config_throw` — these need the config endpoint enhanced ### Tests to Skip (dependencies not yet ported) @@ -55,7 +55,7 @@ These tests depend on ECS or RDS handlers which are Phase 5/6: - `test_sfn_aws_sdk_rds_not_found_error` (rds) - `test_sfn_aws_sdk_query_acronym_param_mapping` (rds) -Also these use `_ministack_config` which needs special handling: +Also these use `_microstack_config` which needs special handling: - `test_sfn_mock_config_return` - `test_sfn_mock_config_throw` @@ -227,7 +227,7 @@ These are pure unit tests of helper functions — port as C# tests: - Build and verify 0 warnings ### Step 10: Mock Config Support -**What**: Wire up the `/_ministack/config` endpoint to set SFN mock config at runtime. +**What**: Wire up the `/_microstack/config` endpoint to set SFN mock config at runtime. **Files**: - `src/MicroStack/Services/StepFunctions/StepFunctionsServiceHandler.cs` — add `SetMockConfig` method @@ -254,7 +254,7 @@ These are pure unit tests of helper functions — port as C# tests: - For sync client, the SDK adds `sync-` prefix to hostname — need to disable. Check if `AmazonStepFunctionsConfig.DisableHostPrefixInjection` is available, or use custom `HttpClientFactory` - `_sqs`, `_ddb`, `_sns`, `_lambda`, `_sm` (SecretsManager), `_ssm` clients for integration tests - `WaitForExecution` polling helper -- `InitializeAsync` calls `/_ministack/reset` +- `InitializeAsync` calls `/_microstack/reset` - Build and verify 0 warnings ### Step 12: Core Tests (State Machine CRUD + Execution) diff --git a/@progress.txt b/@progress.txt index a78f80a..48021e5 100644 --- a/@progress.txt +++ b/@progress.txt @@ -342,3 +342,20 @@ Actions: CreateFileSystem, GetFileSystem, ListFileSystems, DeleteFileSystem, Cre **Build result:** 0 warnings, 0 errors **Test result:** 1160 passed, 1 skipped (pre-existing), 0 failed (16 new tests: 9 multi-tenancy + 7 unicode) + +### 2026-04-22 - Fix xunit v2→v3 breaking changes (44 files) + +**Problem:** Package references were updated from xunit 2.9.3 to xunit.v3.core.mtp-v2 3.2.2, causing ~80+ compilation errors. + +**Changes made:** +1. **Both test csproj files** — Added `Exe` (required by xunit v3) +2. **42 test .cs files** — Changed `IAsyncLifetime` methods from `Task` to `ValueTask`: + - `public async Task InitializeAsync()` → `public async ValueTask InitializeAsync()` + - `public async Task DisposeAsync()` → `public async ValueTask DisposeAsync()` + - `public Task DisposeAsync() => Task.CompletedTask;` → `public ValueTask DisposeAsync() => ValueTask.CompletedTask;` + - `return Task.CompletedTask;` → `return ValueTask.CompletedTask;` in DisposeAsync block bodies +3. **LambdaTests.cs** — Replaced incorrect `Assert.SkipUnless(...)` calls with conditional `TestContext.Current.CancelCurrentTest()` (Assert class not available in xunit.v3.core.mtp-v2 without xunit.v3.assert package) + +**Files changed:** 44 (2 csproj + 42 .cs files including MicroStackSmokeTests.cs in Aspire project) + +**Build result:** 0 warnings, 0 errors diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bff0af2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build +RUN apk add --no-cache clang binutils musl-dev build-base zlib-dev zlib-static +WORKDIR /src + +# Copy props/solution files first for layer caching +COPY Directory.Build.props ./ +COPY src/MicroStack/MicroStack.csproj src/MicroStack/ + +RUN dotnet restore src/MicroStack/MicroStack.csproj -r linux-musl-x64 + +# Copy source and publish +COPY src/MicroStack/ src/MicroStack/ +RUN dotnet publish src/MicroStack/MicroStack.csproj -c Release -r linux-musl-x64 -o /app --no-restore + +FROM alpine:3.21 +COPY --from=build /app /app +ENV ASPNETCORE_HTTP_PORTS=4566 +EXPOSE 4566 +ENTRYPOINT ["/app/MicroStack"] diff --git a/MicroStack.slnx b/MicroStack.slnx index b6c2b49..4a010c8 100644 --- a/MicroStack.slnx +++ b/MicroStack.slnx @@ -6,6 +6,8 @@ + + diff --git a/README.md b/README.md index cfc6f2e..5c6cc3f 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ dotnet run --project src/MicroStack/MicroStack.csproj **Verify:** ```bash -curl http://localhost:4566/_ministack/health +curl http://localhost:4566/_microstack/health ``` ## Usage @@ -107,13 +107,13 @@ See [Services Overview](https://damianh.github.io/microstack/services/overview) ```bash # Health check — service status -curl http://localhost:4566/_ministack/health +curl http://localhost:4566/_microstack/health # Reset all state — useful between test runs -curl -X POST http://localhost:4566/_ministack/reset +curl -X POST http://localhost:4566/_microstack/reset # Runtime config — change settings without restart -curl -X POST http://localhost:4566/_ministack/config \ +curl -X POST http://localhost:4566/_microstack/config \ -H "Content-Type: application/json" \ -d '{"stepfunctions._sfn_mock_config": "{...}"}' ``` diff --git a/docs/Docs/Content/docs/architecture/design.md b/docs/Docs/Content/docs/architecture/design.md index a231549..1a0b40d 100644 --- a/docs/Docs/Content/docs/architecture/design.md +++ b/docs/Docs/Content/docs/architecture/design.md @@ -82,7 +82,7 @@ The registry (`src/MicroStack/Internal/ServiceRegistry.cs`) maps service names t - Handlers register at startup in `Program.cs`. - The `SERVICES` environment variable filters which handlers are active. - Service name aliases are supported (e.g., `cloudwatch` -> `monitoring`, `eventbridge` -> `events`). -- Provides `ResetAll()` for the `/_ministack/reset` endpoint and `GetServiceStatus()` for health checks. +- Provides `ResetAll()` for the `/_microstack/reset` endpoint and `GetServiceStatus()` for health checks. ### IServiceHandler @@ -126,9 +126,9 @@ MicroStack handles four AWS API protocols: Three admin endpoints are registered before the AWS middleware: -- `GET /_ministack/health` — Returns service status and version. Also available at `/health` and `/_localstack/health`. -- `POST /_ministack/reset` — Clears all in-memory state and deletes persisted state files. -- `POST /_ministack/config` — Applies runtime configuration (currently supports Step Functions mock config). +- `GET /_microstack/health` — Returns service status and version. Also available at `/health` and `/_localstack/health`. +- `POST /_microstack/reset` — Clears all in-memory state and deletes persisted state files. +- `POST /_microstack/config` — Applies runtime configuration (currently supports Step Functions mock config). ## Startup Flow diff --git a/docs/Docs/Content/docs/architecture/multi-tenancy.md b/docs/Docs/Content/docs/architecture/multi-tenancy.md index 7778212..91542aa 100644 --- a/docs/Docs/Content/docs/architecture/multi-tenancy.md +++ b/docs/Docs/Content/docs/architecture/multi-tenancy.md @@ -109,4 +109,4 @@ When persistence is enabled, `AccountScopedDictionary.ToRaw()` serializes **all* ## Reset Behavior -The `POST /_ministack/reset` endpoint calls `Clear()` on all `AccountScopedDictionary` instances, which removes data for **all** accounts. There is no per-account reset. +The `POST /_microstack/reset` endpoint calls `Clear()` on all `AccountScopedDictionary` instances, which removes data for **all** accounts. There is no per-account reset. diff --git a/docs/Docs/Content/docs/architecture/persistence.md b/docs/Docs/Content/docs/architecture/persistence.md index 6937042..b50a2ec 100644 --- a/docs/Docs/Content/docs/architecture/persistence.md +++ b/docs/Docs/Content/docs/architecture/persistence.md @@ -41,7 +41,7 @@ The `StatePersistence` class (`src/MicroStack/Internal/StatePersistence.cs`) man ### Reset -The `POST /_ministack/reset` endpoint calls: +The `POST /_microstack/reset` endpoint calls: - `registry.ResetAll()` — clears all in-memory state. - `persistence.DeleteAll()` — deletes all `.json` files in `STATE_DIR`. diff --git a/docs/Docs/Content/docs/docker.md b/docs/Docs/Content/docs/docker.md index 0152412..086dc26 100644 --- a/docs/Docs/Content/docs/docker.md +++ b/docs/Docs/Content/docs/docker.md @@ -44,7 +44,7 @@ volumes: ## Health Check ```bash -curl http://localhost:4566/_ministack/health +curl http://localhost:4566/_microstack/health ``` Returns JSON with all available services: @@ -65,7 +65,7 @@ Returns JSON with all available services: ## Resetting State ```bash -curl -X POST http://localhost:4566/_ministack/reset +curl -X POST http://localhost:4566/_microstack/reset ``` Clears all in-memory state across all services. diff --git a/docs/Docs/Content/docs/getting-started.md b/docs/Docs/Content/docs/getting-started.md index 6029f1e..701dcbc 100644 --- a/docs/Docs/Content/docs/getting-started.md +++ b/docs/Docs/Content/docs/getting-started.md @@ -56,7 +56,7 @@ docker run -p 4566:4566 ghcr.io/damianh/microstack:latest Verify it's running: ```bash -curl http://localhost:4566/_ministack/health +curl http://localhost:4566/_microstack/health ``` ## Quick Start with .NET diff --git a/docs/Docs/Content/docs/internal-api.md b/docs/Docs/Content/docs/internal-api.md index 097333e..0a356bb 100644 --- a/docs/Docs/Content/docs/internal-api.md +++ b/docs/Docs/Content/docs/internal-api.md @@ -12,7 +12,7 @@ MicroStack exposes internal endpoints for health checking, state management, and ## Health Check ```bash -curl http://localhost:4566/_ministack/health +curl http://localhost:4566/_microstack/health ``` Returns JSON with service availability, edition, and version: @@ -39,7 +39,7 @@ curl http://localhost:4566/health ## Reset State ```bash -curl -X POST http://localhost:4566/_ministack/reset +curl -X POST http://localhost:4566/_microstack/reset ``` Wipes all in-memory state across every service. Returns `200 OK` on success. @@ -50,7 +50,7 @@ This is the recommended way to get a clean environment between test runs without public async Task InitializeAsync() { using var http = new HttpClient { BaseAddress = new Uri(connectionString) }; - await http.PostAsync("/_ministack/reset", null); + await http.PostAsync("/_microstack/reset", null); } ``` @@ -59,7 +59,7 @@ If state persistence is enabled (`PERSIST_STATE=1`), reset also deletes the pers ## Runtime Config ```bash -curl -X POST http://localhost:4566/_ministack/config \ +curl -X POST http://localhost:4566/_microstack/config \ -H "Content-Type: application/json" \ -d '{"stepfunctions._sfn_mock_config": "{...}"}' ``` diff --git a/docs/Docs/Content/docs/services/step-functions.md b/docs/Docs/Content/docs/services/step-functions.md index 9d5e0ef..b3e399c 100644 --- a/docs/Docs/Content/docs/services/step-functions.md +++ b/docs/Docs/Content/docs/services/step-functions.md @@ -25,7 +25,7 @@ MicroStack includes a full AWS States Language (ASL) execution engine supporting Configure mock responses for Task states via the config endpoint: ```bash -curl -X POST http://localhost:4566/_ministack/config \ +curl -X POST http://localhost:4566/_microstack/config \ -H 'Content-Type: application/json' \ -d '{ "stepfunctions": { diff --git a/docs/Docs/Content/docs/testing.md b/docs/Docs/Content/docs/testing.md index ef8d5a6..3c2c865 100644 --- a/docs/Docs/Content/docs/testing.md +++ b/docs/Docs/Content/docs/testing.md @@ -121,7 +121,7 @@ public sealed class SqsTests : IClassFixture, IAsyncLifetime { // Reset state between tests using var http = new HttpClient { BaseAddress = new Uri(_fixture.ConnectionString) }; - await http.PostAsync("/_ministack/reset", null); + await http.PostAsync("/_microstack/reset", null); } public Task DisposeAsync() diff --git a/global.json b/global.json new file mode 100644 index 0000000..3140116 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "test": { + "runner": "Microsoft.Testing.Platform" + } +} diff --git a/progress.txt b/progress.txt index 93ed57d..032b26e 100644 --- a/progress.txt +++ b/progress.txt @@ -449,7 +449,7 @@ **Key implementation details:** - Internal `__scalar__` and `__list__` wrapper conventions for representing non-dict results in the `Dictionary` internal model - `SerializeOutput` + `UnwrapForSerialization` recursively unwrap these wrappers at serialization boundaries -- Mock config endpoint at `/_ministack/config` accepts `{"stepfunctions": {"_sfn_mock_config": {...}}}` with PascalCase keys (StateMachines, TestCases, MockedResponses) +- Mock config endpoint at `/_microstack/config` accepts `{"stepfunctions": {"_sfn_mock_config": {...}}}` with PascalCase keys (StateMachines, TestCases, MockedResponses) - Background execution thread with `Thread.IsBackground = true` for async StartExecution - Service dispatch via `DispatchToService` calling handler's `HandleAsync` directly (in-process) - SNS integration skipped in tests (SNS uses query/XML protocol, SFN sends JSON — protocol mismatch) diff --git a/src/MicroStack.Aspire.Hosting/MicroStackBuilderExtensions.cs b/src/MicroStack.Aspire.Hosting/MicroStackBuilderExtensions.cs index 84c87ba..0789c5b 100644 --- a/src/MicroStack.Aspire.Hosting/MicroStackBuilderExtensions.cs +++ b/src/MicroStack.Aspire.Hosting/MicroStackBuilderExtensions.cs @@ -32,7 +32,7 @@ public static IResourceBuilder AddMicroStack( .WithImage(MicroStackContainerImageTags.Image, MicroStackContainerImageTags.Tag) .WithImageRegistry(MicroStackContainerImageTags.Registry) .WithHttpEndpoint(targetPort: DefaultContainerPort, name: MicroStackResource.HttpEndpointName) - .WithHttpHealthCheck("/_ministack/health"); + .WithHttpHealthCheck("/_microstack/health"); } /// @@ -56,7 +56,7 @@ public static IResourceBuilder AddMicroStack( .WithImage(MicroStackContainerImageTags.Image, MicroStackContainerImageTags.Tag) .WithImageRegistry(MicroStackContainerImageTags.Registry) .WithHttpEndpoint(port: port, targetPort: DefaultContainerPort, name: MicroStackResource.HttpEndpointName) - .WithHttpHealthCheck("/_ministack/health"); + .WithHttpHealthCheck("/_microstack/health"); } /// diff --git a/src/MicroStack.LambdaBootstrap/Program.cs b/src/MicroStack.LambdaBootstrap/Program.cs index bb63345..e04d717 100644 --- a/src/MicroStack.LambdaBootstrap/Program.cs +++ b/src/MicroStack.LambdaBootstrap/Program.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.Loader; using System.Text; using System.Text.Json; diff --git a/src/MicroStack/IServiceHandler.cs b/src/MicroStack/IServiceHandler.cs index 43f974e..897bf33 100644 --- a/src/MicroStack/IServiceHandler.cs +++ b/src/MicroStack/IServiceHandler.cs @@ -13,7 +13,7 @@ public interface IServiceHandler /// Handle an incoming AWS request and return the response. Task HandleAsync(ServiceRequest request); - /// Reset all in-memory state (called by /_ministack/reset). + /// Reset all in-memory state (called by /_microstack/reset). void Reset(); /// Serialize current state for persistence. Returns null if this handler has no persistent state. diff --git a/src/MicroStack/Internal/AwsRequestMiddleware.cs b/src/MicroStack/Internal/AwsRequestMiddleware.cs index e6d7362..4b535d7 100644 --- a/src/MicroStack/Internal/AwsRequestMiddleware.cs +++ b/src/MicroStack/Internal/AwsRequestMiddleware.cs @@ -160,7 +160,7 @@ public async Task InvokeAsync(HttpContext context) } // Lambda layer content download - if (path.StartsWith("/_ministack/lambda-layers/", StringComparison.OrdinalIgnoreCase) && method == "GET") + if (path.StartsWith("/_microstack/lambda-layers/", StringComparison.OrdinalIgnoreCase) && method == "GET") { await DispatchToService(context, serviceRequest, "lambda"); return; diff --git a/src/MicroStack/Internal/AwsResponseHelpers.cs b/src/MicroStack/Internal/AwsResponseHelpers.cs index 851a79b..8aac259 100644 --- a/src/MicroStack/Internal/AwsResponseHelpers.cs +++ b/src/MicroStack/Internal/AwsResponseHelpers.cs @@ -1,4 +1,3 @@ -using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/src/MicroStack/MicroStack.csproj b/src/MicroStack/MicroStack.csproj index c4a459c..8bc10e1 100644 --- a/src/MicroStack/MicroStack.csproj +++ b/src/MicroStack/MicroStack.csproj @@ -18,7 +18,7 @@ - alpine:3.21 + docker.io/alpine:3.21 microstack $(MinVerVersion);latest diff --git a/src/MicroStack/Program.cs b/src/MicroStack/Program.cs index 04c932d..ef2453f 100644 --- a/src/MicroStack/Program.cs +++ b/src/MicroStack/Program.cs @@ -40,6 +40,12 @@ var builder = WebApplication.CreateBuilder(args); +// Configure JSON serialization for minimal API endpoints (required for native AOT) +builder.Services.ConfigureHttpJsonOptions(jsonOptions => +{ + jsonOptions.SerializerOptions.TypeInfoResolverChain.Insert(0, MicroStackJsonContext.Default); +}); + // Bind all environment variables into strongly-typed options (single source of truth) var options = MicroStackOptions.BindFromEnvironment(); builder.Services.AddSingleton(options); @@ -117,7 +123,7 @@ registry.Register(new S3FilesServiceHandler()); // Health endpoint (multiple aliases for LocalStack compatibility) -foreach (var healthPath in new[] { "/_ministack/health", "/health", "/_localstack/health" }) +foreach (var healthPath in new[] { "/_microstack/health", "/health", "/_localstack/health" }) { app.MapGet(healthPath, () => { @@ -127,7 +133,7 @@ } // Reset endpoint -app.MapPost("/_ministack/reset", () => +app.MapPost("/_microstack/reset", () => { registry.ResetAll(); persistence.DeleteAll(); @@ -135,7 +141,7 @@ }); // Config endpoint (stub — populated when services implement it) -app.MapPost("/_ministack/config", async (HttpContext ctx) => +app.MapPost("/_microstack/config", async (HttpContext ctx) => { using var reader = new StreamReader(ctx.Request.Body); var bodyText = await reader.ReadToEndAsync(); diff --git a/src/MicroStack/Properties/launchSettings.json b/src/MicroStack/Properties/launchSettings.json new file mode 100644 index 0000000..040e12d --- /dev/null +++ b/src/MicroStack/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "MicroStack": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:53486;http://localhost:53487" + } + } +} \ No newline at end of file diff --git a/src/MicroStack/Services/Ec2/Ec2ServiceHandler.cs b/src/MicroStack/Services/Ec2/Ec2ServiceHandler.cs index db2c297..3280ff2 100644 --- a/src/MicroStack/Services/Ec2/Ec2ServiceHandler.cs +++ b/src/MicroStack/Services/Ec2/Ec2ServiceHandler.cs @@ -1,7 +1,6 @@ using System.Text.Json; using System.Text; using System.Web; -using System.Xml.Linq; using MicroStack.Internal; namespace MicroStack.Services.Ec2; diff --git a/src/MicroStack/Services/Lambda/EventSourceMappingPoller.cs b/src/MicroStack/Services/Lambda/EventSourceMappingPoller.cs index cd98f5f..a58aa5a 100644 --- a/src/MicroStack/Services/Lambda/EventSourceMappingPoller.cs +++ b/src/MicroStack/Services/Lambda/EventSourceMappingPoller.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using MicroStack.Internal; using MicroStack.Services.DynamoDb; using MicroStack.Services.Sqs; diff --git a/src/MicroStack/Services/Lambda/LambdaServiceHandler.cs b/src/MicroStack/Services/Lambda/LambdaServiceHandler.cs index 4723b11..6b2ed4b 100644 --- a/src/MicroStack/Services/Lambda/LambdaServiceHandler.cs +++ b/src/MicroStack/Services/Lambda/LambdaServiceHandler.cs @@ -48,7 +48,7 @@ public Task HandleAsync(ServiceRequest request) var method = request.Method.ToUpperInvariant(); // Lambda layer content download - if (path.StartsWith("/_ministack/lambda-layers/", StringComparison.OrdinalIgnoreCase) && method == "GET") + if (path.StartsWith("/_microstack/lambda-layers/", StringComparison.OrdinalIgnoreCase) && method == "GET") { return Task.FromResult(HandleLayerContentDownload(path)); } @@ -1371,7 +1371,7 @@ private ServiceResponse HandlePublishLayerVersion(string layerName, ServiceReque { ["CodeSha256"] = codeSha256, ["CodeSize"] = codeSize, - ["Location"] = $"/_ministack/lambda-layers/{accountId}/{layerName}/{versionNum}", + ["Location"] = $"/_microstack/lambda-layers/{accountId}/{layerName}/{versionNum}", }, }, 201); } @@ -1407,7 +1407,7 @@ private ServiceResponse HandleGetLayerVersion(string layerName, int versionNum) { ["CodeSha256"] = version.CodeSha256, ["CodeSize"] = version.CodeSize, - ["Location"] = $"/_ministack/lambda-layers/{accountId}/{layerName}/{versionNum}", + ["Location"] = $"/_microstack/lambda-layers/{accountId}/{layerName}/{versionNum}", }, }); } @@ -1491,7 +1491,7 @@ private ServiceResponse HandleDeleteLayerVersion(string layerName, int versionNu private ServiceResponse HandleLayerContentDownload(string path) { - // /_ministack/lambda-layers/{accountId}/{layerName}/{version} + // /_microstack/lambda-layers/{accountId}/{layerName}/{version} var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries); if (segments.Length < 5) { diff --git a/src/MicroStack/Services/RdsData/RdsDataServiceHandler.cs b/src/MicroStack/Services/RdsData/RdsDataServiceHandler.cs index 7b56348..45971c8 100644 --- a/src/MicroStack/Services/RdsData/RdsDataServiceHandler.cs +++ b/src/MicroStack/Services/RdsData/RdsDataServiceHandler.cs @@ -1,4 +1,3 @@ -using System.Text; using System.Text.Json; using MicroStack.Internal; diff --git a/src/MicroStack/Services/Sns/SnsServiceHandler.cs b/src/MicroStack/Services/Sns/SnsServiceHandler.cs index a587ceb..0bcbb64 100644 --- a/src/MicroStack/Services/Sns/SnsServiceHandler.cs +++ b/src/MicroStack/Services/Sns/SnsServiceHandler.cs @@ -1,4 +1,3 @@ -using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Web; diff --git a/tests/MicroStack.Aspire.Tests.AppHost/MicroStack.Aspire.Tests.AppHost.csproj b/tests/MicroStack.Aspire.Tests.AppHost/MicroStack.Aspire.Tests.AppHost.csproj new file mode 100644 index 0000000..4259b4d --- /dev/null +++ b/tests/MicroStack.Aspire.Tests.AppHost/MicroStack.Aspire.Tests.AppHost.csproj @@ -0,0 +1,13 @@ + + + + Exe + false + + + + + + + diff --git a/tests/MicroStack.Aspire.Tests.AppHost/Program.cs b/tests/MicroStack.Aspire.Tests.AppHost/Program.cs new file mode 100644 index 0000000..24c77f0 --- /dev/null +++ b/tests/MicroStack.Aspire.Tests.AppHost/Program.cs @@ -0,0 +1,9 @@ +// Copyright (c) MicroStack contributors. All rights reserved. +// Licensed under the MIT License. + +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddMicroStack("microstack") + .WithServices("s3,sqs,sns"); + +builder.Build().Run(); diff --git a/tests/MicroStack.Aspire.Tests/MicroStack.Aspire.Tests.csproj b/tests/MicroStack.Aspire.Tests/MicroStack.Aspire.Tests.csproj new file mode 100644 index 0000000..7566e8e --- /dev/null +++ b/tests/MicroStack.Aspire.Tests/MicroStack.Aspire.Tests.csproj @@ -0,0 +1,31 @@ + + + + Exe + false + true + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/MicroStack.Aspire.Tests/MicroStackSmokeTests.cs b/tests/MicroStack.Aspire.Tests/MicroStackSmokeTests.cs new file mode 100644 index 0000000..0d1e3f5 --- /dev/null +++ b/tests/MicroStack.Aspire.Tests/MicroStackSmokeTests.cs @@ -0,0 +1,124 @@ +// Copyright (c) MicroStack contributors. All rights reserved. +// Licensed under the MIT License. + +using Amazon.Runtime; +using Amazon.S3; +using Amazon.S3.Model; +using Amazon.SimpleNotificationService; +using Amazon.SQS; +using Microsoft.Extensions.DependencyInjection; + +namespace MicroStack.Aspire.Tests; + +public sealed class MicroStackSmokeTests : IAsyncLifetime +{ + private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(120); + + private DistributedApplication? _app; + private string? _connectionString; + + public async ValueTask InitializeAsync() + { + var builder = await DistributedApplicationTestingBuilder + .CreateAsync(); + + builder.Services.ConfigureHttpClientDefaults(clientBuilder => + { + clientBuilder.AddStandardResilienceHandler(); + }); + + _app = await builder.BuildAsync(); + await _app.StartAsync(); + + using var cts = new CancellationTokenSource(DefaultTimeout); + await _app.ResourceNotifications.WaitForResourceHealthyAsync( + "microstack", cts.Token); + + _connectionString = await _app.GetConnectionStringAsync("microstack"); + } + + public async ValueTask DisposeAsync() + { + if (_app is not null) + { + await _app.DisposeAsync(); + } + } + + [Fact] + public void ConnectionStringShouldBeAvailable() + { + _connectionString.ShouldNotBeNullOrWhiteSpace(); + _connectionString.ShouldStartWith("http://"); + } + + [Fact] + public async Task HealthEndpointShouldReturnOk() + { + var httpClient = _app!.CreateHttpClient("microstack"); + + using var response = await httpClient.GetAsync("/_microstack/health"); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + [Fact] + public async Task S3CreateBucketAndPutObject() + { + using var s3 = CreateClient(); + + await s3.PutBucketAsync("test-bucket"); + + await s3.PutObjectAsync(new PutObjectRequest + { + BucketName = "test-bucket", + Key = "hello.txt", + ContentBody = "Hello from Aspire!", + }); + + var obj = await s3.GetObjectAsync("test-bucket", "hello.txt"); + + using var reader = new StreamReader(obj.ResponseStream); + var content = await reader.ReadToEndAsync(); + content.ShouldBe("Hello from Aspire!"); + } + + [Fact] + public async Task SqsCreateQueueAndSendMessage() + { + using var sqs = CreateClient(); + + var createResult = await sqs.CreateQueueAsync("test-queue"); + createResult.HttpStatusCode.ShouldBe(HttpStatusCode.OK); + + await sqs.SendMessageAsync(createResult.QueueUrl, "hello from aspire"); + + var receiveResult = await sqs.ReceiveMessageAsync(createResult.QueueUrl); + receiveResult.Messages.ShouldHaveSingleItem(); + receiveResult.Messages[0].Body.ShouldBe("hello from aspire"); + } + + [Fact] + public async Task SnsCreateTopic() + { + using var sns = CreateClient(); + + var createResult = await sns.CreateTopicAsync("test-topic"); + createResult.HttpStatusCode.ShouldBe(HttpStatusCode.OK); + createResult.TopicArn.ShouldNotBeNullOrWhiteSpace(); + + var listResult = await sns.ListTopicsAsync(); + listResult.Topics.ShouldContain(t => t.TopicArn == createResult.TopicArn); + } + + private T CreateClient() where T : AmazonServiceClient + { + var credentials = new BasicAWSCredentials("test", "test"); + var config = (ClientConfig)Activator.CreateInstance(typeof(T).Assembly + .GetTypes() + .Single(t => t.IsSubclassOf(typeof(ClientConfig)) && !t.IsAbstract))!; + config.ServiceURL = _connectionString; + config.AuthenticationRegion = "us-east-1"; + return (T)Activator.CreateInstance(typeof(T), credentials, config)!; + } +} diff --git a/tests/MicroStack.Tests/AcmTests.cs b/tests/MicroStack.Tests/AcmTests.cs index 9ff9234..0fe7736 100644 --- a/tests/MicroStack.Tests/AcmTests.cs +++ b/tests/MicroStack.Tests/AcmTests.cs @@ -41,15 +41,15 @@ private static AmazonCertificateManagerClient CreateAcmClient(MicroStackFixture new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await _fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _acm.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // -- RequestCertificate --------------------------------------------------- diff --git a/tests/MicroStack.Tests/AlbTests.cs b/tests/MicroStack.Tests/AlbTests.cs index fd90950..f73c3eb 100644 --- a/tests/MicroStack.Tests/AlbTests.cs +++ b/tests/MicroStack.Tests/AlbTests.cs @@ -5,16 +5,9 @@ namespace MicroStack.Tests; -public sealed class AlbTests : IClassFixture, IAsyncLifetime +public sealed class AlbTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonElasticLoadBalancingV2Client _elb; - - public AlbTests(MicroStackFixture fixture) - { - _fixture = fixture; - _elb = CreateElbClient(fixture); - } + private readonly AmazonElasticLoadBalancingV2Client _elb = CreateElbClient(fixture); private static AmazonElasticLoadBalancingV2Client CreateElbClient(MicroStackFixture fixture) { @@ -33,15 +26,15 @@ private static AmazonElasticLoadBalancingV2Client CreateElbClient(MicroStackFixt new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _elb.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Load Balancer CRUD ────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/ApiGatewayV1Tests.cs b/tests/MicroStack.Tests/ApiGatewayV1Tests.cs index c966759..7961c0f 100644 --- a/tests/MicroStack.Tests/ApiGatewayV1Tests.cs +++ b/tests/MicroStack.Tests/ApiGatewayV1Tests.cs @@ -1,13 +1,10 @@ using System.IO.Compression; -using System.Net; -using System.Text; using Amazon; using Amazon.APIGateway; using Amazon.APIGateway.Model; using Amazon.Lambda; using Amazon.Lambda.Model; using Amazon.Runtime; -using Microsoft.AspNetCore.Mvc.Testing; namespace MicroStack.Tests; @@ -75,17 +72,17 @@ private static AmazonLambdaClient CreateLambdaClient(MicroStackFixture fixture) new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await _fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _apigw.Dispose(); _lambda.Dispose(); _rawHttp.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // -- Helper: Create a Lambda zip with simple Python handler ---------------- diff --git a/tests/MicroStack.Tests/ApiGatewayV2Tests.cs b/tests/MicroStack.Tests/ApiGatewayV2Tests.cs index 7acf6d7..1b335cb 100644 --- a/tests/MicroStack.Tests/ApiGatewayV2Tests.cs +++ b/tests/MicroStack.Tests/ApiGatewayV2Tests.cs @@ -1,12 +1,10 @@ using System.IO.Compression; -using System.Text; using Amazon; using Amazon.ApiGatewayV2; using Amazon.ApiGatewayV2.Model; using Amazon.Lambda; using Amazon.Lambda.Model; using Amazon.Runtime; -using Microsoft.AspNetCore.Mvc.Testing; namespace MicroStack.Tests; @@ -16,21 +14,13 @@ namespace MicroStack.Tests; /// /// Ports coverage from ministack/tests/test_apigw.py (~45 tests). /// -public sealed class ApiGatewayV2Tests : IClassFixture, IAsyncLifetime +public sealed class ApiGatewayV2Tests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonApiGatewayV2Client _apigw; - private readonly AmazonLambdaClient _lambda; + private readonly AmazonApiGatewayV2Client _apigw = CreateApigwClient(fixture); + private readonly AmazonLambdaClient _lambda = CreateLambdaClient(fixture); private const string LambdaRole = "arn:aws:iam::000000000000:role/lambda-role"; - public ApiGatewayV2Tests(MicroStackFixture fixture) - { - _fixture = fixture; - _apigw = CreateApigwClient(fixture); - _lambda = CreateLambdaClient(fixture); - } - private static AmazonApiGatewayV2Client CreateApigwClient(MicroStackFixture fixture) { var innerHandler = fixture.Factory.Server.CreateHandler(); @@ -69,16 +59,16 @@ private static AmazonLambdaClient CreateLambdaClient(MicroStackFixture fixture) new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _apigw.Dispose(); _lambda.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } private static MemoryStream MakeZip(string filename, string content) @@ -154,7 +144,7 @@ private async Task ExecuteApiRequest( { var request = new HttpRequestMessage(method, path); request.Headers.Host = $"{apiId}.execute-api.localhost"; - return await _fixture.HttpClient.SendAsync(request); + return await fixture.HttpClient.SendAsync(request); } private async Task ExecuteApiRequest( @@ -167,7 +157,7 @@ private async Task ExecuteApiRequest( { request.Headers.TryAddWithoutValidation(key, value); } - return await _fixture.HttpClient.SendAsync(request); + return await fixture.HttpClient.SendAsync(request); } // ── Control Plane: API CRUD ───────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/AppSyncTests.cs b/tests/MicroStack.Tests/AppSyncTests.cs index d5612ac..ba08d33 100644 --- a/tests/MicroStack.Tests/AppSyncTests.cs +++ b/tests/MicroStack.Tests/AppSyncTests.cs @@ -12,16 +12,9 @@ namespace MicroStack.Tests; /// Mirrors management-plane coverage from ministack/tests/test_appsync.py. /// GraphQL data-plane tests are excluded as they use raw HTTP against localhost:4566. /// -public sealed class AppSyncTests : IClassFixture, IAsyncLifetime +public sealed class AppSyncTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonAppSyncClient _appsync; - - public AppSyncTests(MicroStackFixture fixture) - { - _fixture = fixture; - _appsync = CreateAppSyncClient(fixture); - } + private readonly AmazonAppSyncClient _appsync = CreateAppSyncClient(fixture); private static AmazonAppSyncClient CreateAppSyncClient(MicroStackFixture fixture) { @@ -42,15 +35,15 @@ private static AmazonAppSyncClient CreateAppSyncClient(MicroStackFixture fixture new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _appsync.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/tests/MicroStack.Tests/AthenaTests.cs b/tests/MicroStack.Tests/AthenaTests.cs index 718d366..88774c6 100644 --- a/tests/MicroStack.Tests/AthenaTests.cs +++ b/tests/MicroStack.Tests/AthenaTests.cs @@ -11,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_athena.py. /// -public sealed class AthenaTests : IClassFixture, IAsyncLifetime +public sealed class AthenaTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonAthenaClient _athena; - - public AthenaTests(MicroStackFixture fixture) - { - _fixture = fixture; - _athena = CreateClient(fixture); - } + private readonly AmazonAthenaClient _athena = CreateClient(fixture); private static AmazonAthenaClient CreateClient(MicroStackFixture fixture) { @@ -40,15 +33,15 @@ private static AmazonAthenaClient CreateClient(MicroStackFixture fixture) return new AmazonAthenaClient(new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _athena.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // -- Query Execution ------------------------------------------------------- diff --git a/tests/MicroStack.Tests/CloudFormationTests.cs b/tests/MicroStack.Tests/CloudFormationTests.cs index 7e2228e..912d762 100644 --- a/tests/MicroStack.Tests/CloudFormationTests.cs +++ b/tests/MicroStack.Tests/CloudFormationTests.cs @@ -20,36 +20,19 @@ namespace MicroStack.Tests; /// Uses the AWS SDK for .NET v4 pointed at the in-process MicroStack server. /// Mirrors coverage from ministack/tests/test_cfn.py. /// -public sealed class CloudFormationTests : IClassFixture, IAsyncLifetime +public sealed class CloudFormationTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonCloudFormationClient _cfn; - private readonly AmazonS3Client _s3; - private readonly AmazonSQSClient _sqs; - private readonly AmazonSimpleNotificationServiceClient _sns; - private readonly AmazonIdentityManagementServiceClient _iam; - private readonly AmazonLambdaClient _lambda; - private readonly AmazonSimpleSystemsManagementClient _ssm; - private readonly AmazonDynamoDBClient _ddb; - private readonly AmazonKinesisClient _kinesis; - private readonly AmazonECRClient _ecr; - private readonly AmazonElasticLoadBalancingV2Client _elbv2; - - public CloudFormationTests(MicroStackFixture fixture) - { - _fixture = fixture; - _cfn = CreateClient(fixture); - _s3 = CreateClient(fixture); - _sqs = CreateClient(fixture); - _sns = CreateClient(fixture); - _iam = CreateClient(fixture); - _lambda = CreateClient(fixture); - _ssm = CreateClient(fixture); - _ddb = CreateClient(fixture); - _kinesis = CreateClient(fixture); - _ecr = CreateClient(fixture); - _elbv2 = CreateClient(fixture); - } + private readonly AmazonCloudFormationClient _cfn = CreateClient(fixture); + private readonly AmazonS3Client _s3 = CreateClient(fixture); + private readonly AmazonSQSClient _sqs = CreateClient(fixture); + private readonly AmazonSimpleNotificationServiceClient _sns = CreateClient(fixture); + private readonly AmazonIdentityManagementServiceClient _iam = CreateClient(fixture); + private readonly AmazonLambdaClient _lambda = CreateClient(fixture); + private readonly AmazonSimpleSystemsManagementClient _ssm = CreateClient(fixture); + private readonly AmazonDynamoDBClient _ddb = CreateClient(fixture); + private readonly AmazonKinesisClient _kinesis = CreateClient(fixture); + private readonly AmazonECRClient _ecr = CreateClient(fixture); + private readonly AmazonElasticLoadBalancingV2Client _elbv2 = CreateClient(fixture); private static TClient CreateClient(MicroStackFixture fixture) where TClient : AmazonServiceClient @@ -74,12 +57,12 @@ private static TClient CreateClient(MicroStackFixture fixture) config)!; } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _cfn.Dispose(); _s3.Dispose(); @@ -92,7 +75,7 @@ public Task DisposeAsync() _kinesis.Dispose(); _ecr.Dispose(); _elbv2.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } private static string ToJson(object obj) diff --git a/tests/MicroStack.Tests/CloudFrontTests.cs b/tests/MicroStack.Tests/CloudFrontTests.cs index f417421..1bc2236 100644 --- a/tests/MicroStack.Tests/CloudFrontTests.cs +++ b/tests/MicroStack.Tests/CloudFrontTests.cs @@ -11,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_cloudfront.py. /// -public sealed class CloudFrontTests : IClassFixture, IAsyncLifetime +public sealed class CloudFrontTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonCloudFrontClient _cf; - - public CloudFrontTests(MicroStackFixture fixture) - { - _fixture = fixture; - _cf = CreateCloudFrontClient(fixture); - } + private readonly AmazonCloudFrontClient _cf = CreateCloudFrontClient(fixture); private static AmazonCloudFrontClient CreateCloudFrontClient(MicroStackFixture fixture) { @@ -41,15 +34,15 @@ private static AmazonCloudFrontClient CreateCloudFrontClient(MicroStackFixture f new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _cf.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } private static DistributionConfig MakeDistConfig(string callerRef, string comment, bool enabled = true) diff --git a/tests/MicroStack.Tests/CloudWatchLogsTests.cs b/tests/MicroStack.Tests/CloudWatchLogsTests.cs index 1ea1328..d21f5de 100644 --- a/tests/MicroStack.Tests/CloudWatchLogsTests.cs +++ b/tests/MicroStack.Tests/CloudWatchLogsTests.cs @@ -11,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_logs.py. /// -public sealed class CloudWatchLogsTests : IClassFixture, IAsyncLifetime +public sealed class CloudWatchLogsTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonCloudWatchLogsClient _logs; - - public CloudWatchLogsTests(MicroStackFixture fixture) - { - _fixture = fixture; - _logs = CreateLogsClient(fixture); - } + private readonly AmazonCloudWatchLogsClient _logs = CreateLogsClient(fixture); private static AmazonCloudWatchLogsClient CreateLogsClient(MicroStackFixture fixture) { @@ -41,15 +34,15 @@ private static AmazonCloudWatchLogsClient CreateLogsClient(MicroStackFixture fix new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _logs.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // -- PutLogEvents / GetLogEvents ------------------------------------------ diff --git a/tests/MicroStack.Tests/CloudWatchTests.cs b/tests/MicroStack.Tests/CloudWatchTests.cs index f733e66..e187a76 100644 --- a/tests/MicroStack.Tests/CloudWatchTests.cs +++ b/tests/MicroStack.Tests/CloudWatchTests.cs @@ -11,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_cloudwatch.py. /// -public sealed class CloudWatchTests : IClassFixture, IAsyncLifetime +public sealed class CloudWatchTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonCloudWatchClient _cw; - - public CloudWatchTests(MicroStackFixture fixture) - { - _fixture = fixture; - _cw = CreateCloudWatchClient(fixture); - } + private readonly AmazonCloudWatchClient _cw = CreateCloudWatchClient(fixture); private static AmazonCloudWatchClient CreateCloudWatchClient(MicroStackFixture fixture) { @@ -41,15 +34,15 @@ private static AmazonCloudWatchClient CreateCloudWatchClient(MicroStackFixture f new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _cw.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── PutMetricData + ListMetrics ────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/CognitoTests.cs b/tests/MicroStack.Tests/CognitoTests.cs index 1f69eb9..b62f4bb 100644 --- a/tests/MicroStack.Tests/CognitoTests.cs +++ b/tests/MicroStack.Tests/CognitoTests.cs @@ -18,18 +18,10 @@ namespace MicroStack.Tests; /// /// Port of ministack tests/test_cognito.py (1102 lines, ~50 test cases). /// -public sealed class CognitoTests : IClassFixture, IAsyncLifetime +public sealed class CognitoTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonCognitoIdentityProviderClient _idp; - private readonly AmazonCognitoIdentityClient _identity; - - public CognitoTests(MicroStackFixture fixture) - { - _fixture = fixture; - _idp = CreateIdpClient(fixture); - _identity = CreateIdentityClient(fixture); - } + private readonly AmazonCognitoIdentityProviderClient _idp = CreateIdpClient(fixture); + private readonly AmazonCognitoIdentityClient _identity = CreateIdentityClient(fixture); private static AmazonCognitoIdentityProviderClient CreateIdpClient(MicroStackFixture fixture) { @@ -65,16 +57,16 @@ private static AmazonCognitoIdentityClient CreateIdentityClient(MicroStackFixtur new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _idp.Dispose(); _identity.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ═══════════════════════════════════════════════════════════════════════ @@ -1713,7 +1705,7 @@ public async Task JwksEndpoint() var pool = (await _idp.CreateUserPoolAsync(new CreateUserPoolRequest { PoolName = "JwksPool" })).UserPool; var poolId = pool.Id; - var resp = await _fixture.HttpClient.GetAsync($"/{poolId}/.well-known/jwks.json"); + var resp = await fixture.HttpClient.GetAsync($"/{poolId}/.well-known/jwks.json"); resp.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK); var json = await resp.Content.ReadAsStringAsync(); @@ -1731,7 +1723,7 @@ public async Task OpenIdConfigurationEndpoint() var pool = (await _idp.CreateUserPoolAsync(new CreateUserPoolRequest { PoolName = "OidcConfigPool" })).UserPool; var poolId = pool.Id; - var resp = await _fixture.HttpClient.GetAsync($"/{poolId}/.well-known/openid-configuration"); + var resp = await fixture.HttpClient.GetAsync($"/{poolId}/.well-known/openid-configuration"); resp.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK); var json = await resp.Content.ReadAsStringAsync(); diff --git a/tests/MicroStack.Tests/ContainerSmokeTests.cs b/tests/MicroStack.Tests/ContainerSmokeTests.cs new file mode 100644 index 0000000..ae87794 --- /dev/null +++ b/tests/MicroStack.Tests/ContainerSmokeTests.cs @@ -0,0 +1,250 @@ +using System.Diagnostics; +using System.Text; + +namespace MicroStack.Tests; + +/// +/// Publishes the MicroStack container image via WSL (native AOT requires Linux), +/// starts the container, verifies startup via console log output, and hits the health endpoint. +/// +public sealed class ContainerSmokeTests : IAsyncLifetime +{ + private const string ImageTag = "microstack-smoketest:test"; + + private string? _containerId; + private readonly List _logs = []; + + public async ValueTask InitializeAsync() + { + var repoRoot = FindRepoRoot(); + var wslPath = ToWslPath(repoRoot); + + // Step 1: Publish native AOT binary in WSL (cross-compile requires Linux toolchain). + var publish = await RunProcessAsync( + "wsl", + $"bash -c 'export DOTNET_ROOT=$HOME/.dotnet && export PATH=$DOTNET_ROOT:$PATH && cd \"{wslPath}\" && dotnet publish src/MicroStack/MicroStack.csproj -r linux-x64 -c Release --nologo -v:q'", + timeoutSeconds: 600); + + publish.ExitCode.ShouldBe(0, $"dotnet publish via WSL failed:\n{publish.Output}"); + + // Step 2: Build Docker image from Windows using the published binary. + var publishDir = Path.Combine(repoRoot, "src", "MicroStack", "bin", "Release", "net10.0", "linux-x64", "publish"); + Directory.Exists(publishDir).ShouldBeTrue($"Publish directory not found: {publishDir}"); + + var dockerfilePath = Path.Combine(publishDir, "Dockerfile"); + File.WriteAllText(dockerfilePath, + """ + FROM docker.io/debian:bookworm-slim + RUN apt-get update -qq && apt-get install -y --no-install-recommends libssl3 ca-certificates && rm -rf /var/lib/apt/lists/* + COPY MicroStack /app/MicroStack + RUN chmod +x /app/MicroStack + ENV ASPNETCORE_HTTP_PORTS=4566 + EXPOSE 4566 + ENTRYPOINT ["/app/MicroStack"] + """); + + var build = await RunProcessAsync( + "docker", + $"build -t {ImageTag} \"{publishDir}\"", + timeoutSeconds: 120); + + build.ExitCode.ShouldBe(0, $"docker build failed:\n{build.Output}"); + } + + public async ValueTask DisposeAsync() + { + if (_containerId is not null) + { + await RunProcessAsync("docker", $"rm -f {_containerId}", timeoutSeconds: 10); + } + + await RunProcessAsync("docker", $"rmi -f {ImageTag}", timeoutSeconds: 10); + } + + [Fact] + public async Task ContainerStartsSuccessfully() + { + // Start container in detached mode with an ephemeral host port + var run = await RunProcessAsync( + "docker", + $"run -d -p 0:4566 {ImageTag}", + timeoutSeconds: 30); + + run.ExitCode.ShouldBe(0, $"docker run failed:\n{run.Output}"); + _containerId = run.Output.Trim(); + _containerId.ShouldNotBeNullOrWhiteSpace(); + + // Resolve the mapped host port + var portResult = await RunProcessAsync( + "docker", + $"port {_containerId} 4566", + timeoutSeconds: 10); + + portResult.ExitCode.ShouldBe(0, $"docker port failed:\n{portResult.Output}"); + + // Parse "0.0.0.0:XXXXX" or ":::XXXXX" → extract port number + var portLine = portResult.Output + .Split('\n', StringSplitOptions.RemoveEmptyEntries) + .First(l => l.Contains("0.0.0.0") || l.Contains(":::")); + var hostPort = int.Parse(portLine[(portLine.LastIndexOf(':') + 1)..].Trim()); + + // Poll container logs for the ASP.NET Core startup message + var started = await WaitForLogAsync("Now listening on:", timeoutSeconds: 60); + + started.ShouldBeTrue( + $"Container did not emit 'Now listening on:' within timeout.\nCaptured logs:\n{string.Join('\n', _logs)}"); + + // Hit the health endpoint to confirm the service is responsive + using var httpClient = new HttpClient + { + BaseAddress = new Uri($"http://localhost:{hostPort}"), + }; + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + + HttpResponseMessage? healthResponse = null; + while (!cts.Token.IsCancellationRequested) + { + try + { + healthResponse = await httpClient.GetAsync("/_microstack/health", cts.Token); + if (healthResponse.IsSuccessStatusCode) + { + break; + } + } + catch (HttpRequestException) + { + // Container not ready yet — retry + } + catch (TaskCanceledException) + { + break; + } + + await Task.Delay(500, cts.Token); + } + + healthResponse.ShouldNotBeNull("Health endpoint never responded"); + healthResponse.StatusCode.ShouldBe(HttpStatusCode.OK); + + var body = await healthResponse.Content.ReadFromJsonAsync(); + body.GetProperty("edition").GetString().ShouldBe("light"); + } + + private async Task WaitForLogAsync(string marker, int timeoutSeconds) + { + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)); + + while (!cts.Token.IsCancellationRequested) + { + var logResult = await RunProcessAsync( + "docker", + $"logs {_containerId}", + timeoutSeconds: 10); + + if (logResult.ExitCode == 0) + { + var lines = logResult.Output.Split('\n'); + _logs.Clear(); + _logs.AddRange(lines); + + if (lines.Any(l => l.Contains(marker, StringComparison.OrdinalIgnoreCase))) + { + return true; + } + } + + try + { + await Task.Delay(1000, cts.Token); + } + catch (OperationCanceledException) + { + break; + } + } + + return false; + } + + private static string ToWslPath(string windowsPath) + { + // D:\repos\foo → /mnt/d/repos/foo + var drive = char.ToLowerInvariant(windowsPath[0]); + var rest = windowsPath[2..].Replace('\\', '/'); + return $"/mnt/{drive}{rest}"; + } + + private static string FindRepoRoot() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + + while (dir is not null) + { + if (File.Exists(Path.Combine(dir.FullName, "MicroStack.slnx"))) + { + return dir.FullName; + } + + dir = dir.Parent; + } + + throw new InvalidOperationException( + "Could not find repo root (looked for MicroStack.slnx). Ensure the test runs from within the repository."); + } + + private static async Task RunProcessAsync( + string fileName, + string arguments, + int timeoutSeconds) + { + using var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + }, + }; + + var stdout = new StringBuilder(); + var stderr = new StringBuilder(); + + process.OutputDataReceived += (_, e) => + { + if (e.Data is not null) + { + stdout.AppendLine(e.Data); + } + }; + process.ErrorDataReceived += (_, e) => + { + if (e.Data is not null) + { + stderr.AppendLine(e.Data); + } + }; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)); + try + { + await process.WaitForExitAsync(cts.Token); + } + catch (OperationCanceledException) + { + process.Kill(entireProcessTree: true); + } + + return new ProcessResult(process.ExitCode, $"{stdout}{stderr}"); + } + + private sealed record ProcessResult(int ExitCode, string Output); +} diff --git a/tests/MicroStack.Tests/DynamoDbTests.cs b/tests/MicroStack.Tests/DynamoDbTests.cs index 58c8e06..5a40baf 100644 --- a/tests/MicroStack.Tests/DynamoDbTests.cs +++ b/tests/MicroStack.Tests/DynamoDbTests.cs @@ -2,7 +2,6 @@ using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.Model; using Amazon.Runtime; -using Microsoft.AspNetCore.Mvc.Testing; namespace MicroStack.Tests; @@ -12,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_dynamodb.py. /// -public sealed class DynamoDbTests : IClassFixture, IAsyncLifetime +public sealed class DynamoDbTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonDynamoDBClient _ddb; - - public DynamoDbTests(MicroStackFixture fixture) - { - _fixture = fixture; - _ddb = CreateDdbClient(fixture); - } + private readonly AmazonDynamoDBClient _ddb = CreateDdbClient(fixture); private static AmazonDynamoDBClient CreateDdbClient(MicroStackFixture fixture) { @@ -41,15 +33,15 @@ private static AmazonDynamoDBClient CreateDdbClient(MicroStackFixture fixture) return new AmazonDynamoDBClient(new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _ddb.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Helpers ───────────────────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/Ec2Tests.cs b/tests/MicroStack.Tests/Ec2Tests.cs index 668fac6..a6ab665 100644 --- a/tests/MicroStack.Tests/Ec2Tests.cs +++ b/tests/MicroStack.Tests/Ec2Tests.cs @@ -5,16 +5,9 @@ namespace MicroStack.Tests; -public sealed class Ec2Tests : IClassFixture, IAsyncLifetime +public sealed class Ec2Tests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonEC2Client _ec2; - - public Ec2Tests(MicroStackFixture fixture) - { - _fixture = fixture; - _ec2 = CreateEc2Client(fixture); - } + private readonly AmazonEC2Client _ec2 = CreateEc2Client(fixture); private static AmazonEC2Client CreateEc2Client(MicroStackFixture fixture) { @@ -32,15 +25,15 @@ private static AmazonEC2Client CreateEc2Client(MicroStackFixture fixture) return new AmazonEC2Client(new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _ec2.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── VPC Tests ─────────────────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/EcrTests.cs b/tests/MicroStack.Tests/EcrTests.cs index 5cc5cd0..b9b1800 100644 --- a/tests/MicroStack.Tests/EcrTests.cs +++ b/tests/MicroStack.Tests/EcrTests.cs @@ -10,16 +10,9 @@ namespace MicroStack.Tests; /// Integration tests for the ECR service handler. /// Mirrors coverage from ministack/tests/test_ecr.py. /// -public sealed class EcrTests : IClassFixture, IAsyncLifetime +public sealed class EcrTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonECRClient _ecr; - - public EcrTests(MicroStackFixture fixture) - { - _fixture = fixture; - _ecr = CreateClient(fixture); - } + private readonly AmazonECRClient _ecr = CreateClient(fixture); private static AmazonECRClient CreateClient(MicroStackFixture fixture) { @@ -38,15 +31,15 @@ private static AmazonECRClient CreateClient(MicroStackFixture fixture) new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _ecr.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Repository CRUD ─────────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/EcsTests.cs b/tests/MicroStack.Tests/EcsTests.cs index acb23db..18b8cb3 100644 --- a/tests/MicroStack.Tests/EcsTests.cs +++ b/tests/MicroStack.Tests/EcsTests.cs @@ -12,16 +12,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/services/ecs.py. /// -public sealed class EcsTests : IClassFixture, IAsyncLifetime +public sealed class EcsTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonECSClient _ecs; - - public EcsTests(MicroStackFixture fixture) - { - _fixture = fixture; - _ecs = CreateClient(fixture); - } + private readonly AmazonECSClient _ecs = CreateClient(fixture); private static AmazonECSClient CreateClient(MicroStackFixture fixture) { @@ -42,15 +35,15 @@ private static AmazonECSClient CreateClient(MicroStackFixture fixture) new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _ecs.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // -- Cluster tests --------------------------------------------------------- diff --git a/tests/MicroStack.Tests/EfsTests.cs b/tests/MicroStack.Tests/EfsTests.cs index 7311a59..143d074 100644 --- a/tests/MicroStack.Tests/EfsTests.cs +++ b/tests/MicroStack.Tests/EfsTests.cs @@ -11,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_efs.py. /// -public sealed class EfsTests : IClassFixture, IAsyncLifetime +public sealed class EfsTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonElasticFileSystemClient _efs; - - public EfsTests(MicroStackFixture fixture) - { - _fixture = fixture; - _efs = CreateEfsClient(fixture); - } + private readonly AmazonElasticFileSystemClient _efs = CreateEfsClient(fixture); private static AmazonElasticFileSystemClient CreateEfsClient(MicroStackFixture fixture) { @@ -41,15 +34,15 @@ private static AmazonElasticFileSystemClient CreateEfsClient(MicroStackFixture f new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _efs.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/tests/MicroStack.Tests/ElastiCacheTests.cs b/tests/MicroStack.Tests/ElastiCacheTests.cs index 7ff6e1b..da3b57a 100644 --- a/tests/MicroStack.Tests/ElastiCacheTests.cs +++ b/tests/MicroStack.Tests/ElastiCacheTests.cs @@ -10,16 +10,9 @@ namespace MicroStack.Tests; /// Integration tests for the ElastiCache service handler. /// Mirrors coverage from ministack/tests/test_elasticache.py. /// -public sealed class ElastiCacheTests : IClassFixture, IAsyncLifetime +public sealed class ElastiCacheTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonElastiCacheClient _ec; - - public ElastiCacheTests(MicroStackFixture fixture) - { - _fixture = fixture; - _ec = CreateClient(fixture); - } + private readonly AmazonElastiCacheClient _ec = CreateClient(fixture); private static AmazonElastiCacheClient CreateClient(MicroStackFixture fixture) { @@ -38,15 +31,15 @@ private static AmazonElastiCacheClient CreateClient(MicroStackFixture fixture) new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _ec.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Cache Cluster CRUD ──────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/EmrTests.cs b/tests/MicroStack.Tests/EmrTests.cs index 2ee3df2..c39793d 100644 --- a/tests/MicroStack.Tests/EmrTests.cs +++ b/tests/MicroStack.Tests/EmrTests.cs @@ -11,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_emr.py. /// -public sealed class EmrTests : IClassFixture, IAsyncLifetime +public sealed class EmrTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonElasticMapReduceClient _emr; - - public EmrTests(MicroStackFixture fixture) - { - _fixture = fixture; - _emr = CreateEmrClient(fixture); - } + private readonly AmazonElasticMapReduceClient _emr = CreateEmrClient(fixture); private static AmazonElasticMapReduceClient CreateEmrClient(MicroStackFixture fixture) { @@ -41,15 +34,15 @@ private static AmazonElasticMapReduceClient CreateEmrClient(MicroStackFixture fi new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _emr.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/tests/MicroStack.Tests/EventBridgeTests.cs b/tests/MicroStack.Tests/EventBridgeTests.cs index d10e0bb..73a3bb6 100644 --- a/tests/MicroStack.Tests/EventBridgeTests.cs +++ b/tests/MicroStack.Tests/EventBridgeTests.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using Amazon; using Amazon.EventBridge; using Amazon.EventBridge.Model; @@ -12,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_eventbridge.py. /// -public sealed class EventBridgeTests : IClassFixture, IAsyncLifetime +public sealed class EventBridgeTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonEventBridgeClient _eb; - - public EventBridgeTests(MicroStackFixture fixture) - { - _fixture = fixture; - _eb = CreateEventBridgeClient(fixture); - } + private readonly AmazonEventBridgeClient _eb = CreateEventBridgeClient(fixture); private static AmazonEventBridgeClient CreateEventBridgeClient(MicroStackFixture fixture) { @@ -41,15 +33,15 @@ private static AmazonEventBridgeClient CreateEventBridgeClient(MicroStackFixture return new AmazonEventBridgeClient(new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _eb.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Event bus lifecycle ───────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/FirehoseTests.cs b/tests/MicroStack.Tests/FirehoseTests.cs index 07bfec3..827d715 100644 --- a/tests/MicroStack.Tests/FirehoseTests.cs +++ b/tests/MicroStack.Tests/FirehoseTests.cs @@ -12,16 +12,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_firehose.py. /// -public sealed class FirehoseTests : IClassFixture, IAsyncLifetime +public sealed class FirehoseTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonKinesisFirehoseClient _fh; - - public FirehoseTests(MicroStackFixture fixture) - { - _fixture = fixture; - _fh = CreateClient(fixture); - } + private readonly AmazonKinesisFirehoseClient _fh = CreateClient(fixture); private static AmazonKinesisFirehoseClient CreateClient(MicroStackFixture fixture) { @@ -42,15 +35,15 @@ private static AmazonKinesisFirehoseClient CreateClient(MicroStackFixture fixtur new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _fh.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // -- CreateDeliveryStream / DescribeDeliveryStream ------------------------- diff --git a/tests/MicroStack.Tests/GlueTests.cs b/tests/MicroStack.Tests/GlueTests.cs index e69fa29..ef41ac7 100644 --- a/tests/MicroStack.Tests/GlueTests.cs +++ b/tests/MicroStack.Tests/GlueTests.cs @@ -11,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_glue.py. /// -public sealed class GlueTests : IClassFixture, IAsyncLifetime +public sealed class GlueTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonGlueClient _glue; - - public GlueTests(MicroStackFixture fixture) - { - _fixture = fixture; - _glue = CreateClient(fixture); - } + private readonly AmazonGlueClient _glue = CreateClient(fixture); private static AmazonGlueClient CreateClient(MicroStackFixture fixture) { @@ -40,15 +33,15 @@ private static AmazonGlueClient CreateClient(MicroStackFixture fixture) return new AmazonGlueClient(new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _glue.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // -- Database CRUD --------------------------------------------------------- diff --git a/tests/MicroStack.Tests/HealthTests.cs b/tests/MicroStack.Tests/HealthTests.cs index 2d54c3e..8a23e19 100644 --- a/tests/MicroStack.Tests/HealthTests.cs +++ b/tests/MicroStack.Tests/HealthTests.cs @@ -1,27 +1,20 @@ -using Microsoft.AspNetCore.Mvc.Testing; - namespace MicroStack.Tests; -public sealed class HealthTests : IClassFixture +public sealed class HealthTests(MicroStackFixture fixture) : IClassFixture { - private readonly HttpClient _client; - - public HealthTests(MicroStackFixture fixture) - { - _client = fixture.HttpClient; - } + private readonly HttpClient _client = fixture.HttpClient; [Fact] public async Task HealthEndpointReturns200() { - var response = await _client.GetAsync("/_ministack/health"); + var response = await _client.GetAsync("/_microstack/health"); response.StatusCode.ShouldBe(HttpStatusCode.OK); } [Fact] public async Task HealthEndpointReturnsEditionAndVersion() { - var response = await _client.GetAsync("/_ministack/health"); + var response = await _client.GetAsync("/_microstack/health"); var body = await response.Content.ReadFromJsonAsync(); body.GetProperty("edition").GetString().ShouldBe("light"); @@ -31,7 +24,7 @@ public async Task HealthEndpointReturnsEditionAndVersion() [Fact] public async Task HealthEndpointReturnsServicesDict() { - var response = await _client.GetAsync("/_ministack/health"); + var response = await _client.GetAsync("/_microstack/health"); var body = await response.Content.ReadFromJsonAsync(); body.GetProperty("services").ValueKind.ShouldBe(JsonValueKind.Object); @@ -54,7 +47,7 @@ public async Task ShortHealthAliasReturns200() [Fact] public async Task ResetEndpointReturnsOk() { - var response = await _client.PostAsync("/_ministack/reset", null); + var response = await _client.PostAsync("/_microstack/reset", null); response.StatusCode.ShouldBe(HttpStatusCode.OK); var body = await response.Content.ReadFromJsonAsync(); @@ -64,7 +57,7 @@ public async Task ResetEndpointReturnsOk() [Fact] public async Task OptionsRequestReturnsCorsHeaders() { - var request = new HttpRequestMessage(HttpMethod.Options, "/_ministack/health"); + var request = new HttpRequestMessage(HttpMethod.Options, "/_microstack/health"); var response = await _client.SendAsync(request); response.Headers.Contains("Access-Control-Allow-Origin").ShouldBe(true, "Expected Access-Control-Allow-Origin CORS header on OPTIONS response"); diff --git a/tests/MicroStack.Tests/IamTests.cs b/tests/MicroStack.Tests/IamTests.cs index 8d81f60..97305c4 100644 --- a/tests/MicroStack.Tests/IamTests.cs +++ b/tests/MicroStack.Tests/IamTests.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using Amazon; using Amazon.IdentityManagement; using Amazon.IdentityManagement.Model; @@ -12,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_iam.py. /// -public sealed class IamTests : IClassFixture, IAsyncLifetime +public sealed class IamTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonIdentityManagementServiceClient _iam; - - public IamTests(MicroStackFixture fixture) - { - _fixture = fixture; - _iam = CreateIamClient(fixture); - } + private readonly AmazonIdentityManagementServiceClient _iam = CreateIamClient(fixture); private static AmazonIdentityManagementServiceClient CreateIamClient(MicroStackFixture fixture) { @@ -42,15 +34,15 @@ private static AmazonIdentityManagementServiceClient CreateIamClient(MicroStackF new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _iam.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Helpers ───────────────────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/KinesisTests.cs b/tests/MicroStack.Tests/KinesisTests.cs index 1f4eaa2..e08b71f 100644 --- a/tests/MicroStack.Tests/KinesisTests.cs +++ b/tests/MicroStack.Tests/KinesisTests.cs @@ -12,16 +12,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_kinesis.py. /// -public sealed class KinesisTests : IClassFixture, IAsyncLifetime +public sealed class KinesisTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonKinesisClient _kin; - - public KinesisTests(MicroStackFixture fixture) - { - _fixture = fixture; - _kin = CreateKinesisClient(fixture); - } + private readonly AmazonKinesisClient _kin = CreateKinesisClient(fixture); private static AmazonKinesisClient CreateKinesisClient(MicroStackFixture fixture) { @@ -41,15 +34,15 @@ private static AmazonKinesisClient CreateKinesisClient(MicroStackFixture fixture return new AmazonKinesisClient(new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _kin.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Stream lifecycle ──────────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/KmsTests.cs b/tests/MicroStack.Tests/KmsTests.cs index d940a93..eb340fb 100644 --- a/tests/MicroStack.Tests/KmsTests.cs +++ b/tests/MicroStack.Tests/KmsTests.cs @@ -11,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack tests/test_kms.py (33 test cases). /// -public sealed class KmsTests : IClassFixture, IAsyncLifetime +public sealed class KmsTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonKeyManagementServiceClient _kms; - - public KmsTests(MicroStackFixture fixture) - { - _fixture = fixture; - _kms = CreateClient(fixture); - } + private readonly AmazonKeyManagementServiceClient _kms = CreateClient(fixture); private static AmazonKeyManagementServiceClient CreateClient(MicroStackFixture fixture) { @@ -41,15 +34,15 @@ private static AmazonKeyManagementServiceClient CreateClient(MicroStackFixture f new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _kms.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // -- CreateKey tests ------------------------------------------------------- diff --git a/tests/MicroStack.Tests/LambdaTests.cs b/tests/MicroStack.Tests/LambdaTests.cs index 946322b..8ffff49 100644 --- a/tests/MicroStack.Tests/LambdaTests.cs +++ b/tests/MicroStack.Tests/LambdaTests.cs @@ -2,7 +2,6 @@ using System.IO.Compression; using System.Runtime.InteropServices; using System.Text; -using System.Text.Json; using Amazon; using Amazon.Lambda; using Amazon.Lambda.Model; @@ -52,15 +51,15 @@ private static AmazonLambdaClient CreateClient(MicroStackFixture fixture) new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await _fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _lambda.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } private static MemoryStream MakeZip(string filename, string content) @@ -1315,7 +1314,7 @@ def handler(event, context): var bootTime1 = doc1.RootElement.GetProperty("boot").GetDouble(); // Reset kills all workers - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await _fixture.HttpClient.PostAsync("/_microstack/reset", null); // Re-create the function (reset cleared the store) await CreatePythonFunction("invoke-py-reset", code); @@ -1646,11 +1645,13 @@ await sqsClient.SendMessageAsync(new Amazon.SQS.Model.SendMessageRequest // -- .NET Lambda Invocation Tests ----------------------------------------- - [SkippableFact] + [Fact] public async Task InvokeDotnetRequestResponse() { - Skip.If(!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null, - "MicroStack.LambdaBootstrap or SimpleHandler build output not found."); +if (!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null) + { + return; + } await CreateDotnetFunction("invoke-dotnet-func", "GreetHandler"); @@ -1668,11 +1669,13 @@ public async Task InvokeDotnetRequestResponse() doc.RootElement.GetProperty("greeting").GetString().ShouldBe("hello DotNet"); } - [SkippableFact] + [Fact] public async Task InvokeDotnetReturnsPayload() { - Skip.If(!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null, - "MicroStack.LambdaBootstrap or SimpleHandler build output not found."); +if (!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null) + { + return; + } await CreateDotnetFunction("invoke-dotnet-payload", "SumHandler"); @@ -1691,11 +1694,13 @@ public async Task InvokeDotnetReturnsPayload() doc.RootElement.GetProperty("sum").GetInt32().ShouldBe(15); } - [SkippableFact] + [Fact] public async Task InvokeDotnetWithEnvironmentVariables() { - Skip.If(!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null, - "MicroStack.LambdaBootstrap or SimpleHandler build output not found."); +if (!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null) + { + return; + } var envVars = new Dictionary { @@ -1720,11 +1725,13 @@ public async Task InvokeDotnetWithEnvironmentVariables() doc.RootElement.GetProperty("region").GetString().ShouldBe("eu-west-1"); } - [SkippableFact] + [Fact] public async Task InvokeDotnetWarmStart() { - Skip.If(!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null, - "MicroStack.LambdaBootstrap or SimpleHandler build output not found."); +if (!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null) + { + return; + } await CreateDotnetFunction("invoke-dotnet-warm", "EchoHandler"); @@ -1753,11 +1760,13 @@ public async Task InvokeDotnetWarmStart() doc2.RootElement.GetProperty("seq").GetInt32().ShouldBe(2); } - [SkippableFact] + [Fact] public async Task InvokeDotnetErrorReturnsUnhandled() { - Skip.If(!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null, - "MicroStack.LambdaBootstrap or SimpleHandler build output not found."); +if (!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null) + { + return; + } await CreateDotnetFunction("invoke-dotnet-error", "ErrorHandler"); @@ -1775,11 +1784,13 @@ public async Task InvokeDotnetErrorReturnsUnhandled() doc.RootElement.GetProperty("errorMessage").GetString()!.ShouldContain("something went wrong"); } - [SkippableFact] + [Fact] public async Task InvokeDotnetWithContext() { - Skip.If(!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null, - "MicroStack.LambdaBootstrap or SimpleHandler build output not found."); +if (!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null) + { + return; + } await CreateDotnetFunction("invoke-dotnet-context", "ContextHandler"); @@ -1799,11 +1810,13 @@ public async Task InvokeDotnetWithContext() doc.RootElement.GetProperty("has_request_id").GetBoolean().ShouldBe(true); } - [SkippableFact] + [Fact] public async Task UpdateCodeInvalidatesDotnetWorker() { - Skip.If(!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null, - "MicroStack.LambdaBootstrap or SimpleHandler build output not found."); +if (!IsDotnetBootstrapAvailable() || FindSimpleHandlerDir() is null) + { + return; + } // Create function with EchoHandler await CreateDotnetFunction("invoke-dotnet-update", "EchoHandler"); diff --git a/tests/MicroStack.Tests/MicroStack.Tests.csproj b/tests/MicroStack.Tests/MicroStack.Tests.csproj index 7603741..e7ce42f 100644 --- a/tests/MicroStack.Tests/MicroStack.Tests.csproj +++ b/tests/MicroStack.Tests/MicroStack.Tests.csproj @@ -1,7 +1,9 @@ + Exe false + true @@ -45,15 +47,9 @@ - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + - diff --git a/tests/MicroStack.Tests/MicroStackFixture.cs b/tests/MicroStack.Tests/MicroStackFixture.cs index 9ba8a8b..f8361fb 100644 --- a/tests/MicroStack.Tests/MicroStackFixture.cs +++ b/tests/MicroStack.Tests/MicroStackFixture.cs @@ -13,7 +13,7 @@ public sealed class MicroStackFixture : IDisposable public MicroStackFixture() { - Factory = new WebApplicationFactory(); + Factory = new WebApplicationFactory(); HttpClient = Factory.CreateClient(); } diff --git a/tests/MicroStack.Tests/MultiTenancyTests.cs b/tests/MicroStack.Tests/MultiTenancyTests.cs index ccbcefa..a56f4cf 100644 --- a/tests/MicroStack.Tests/MultiTenancyTests.cs +++ b/tests/MicroStack.Tests/MultiTenancyTests.cs @@ -3,7 +3,6 @@ using Amazon.DynamoDBv2.Model; using Amazon.Runtime; using Amazon.S3; -using Amazon.S3.Model; using Amazon.SecurityToken; using Amazon.SecurityToken.Model; using Amazon.SQS; @@ -20,27 +19,20 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_multitenancy.py. /// -public sealed class MultiTenancyTests : IClassFixture, IAsyncLifetime +public sealed class MultiTenancyTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - - public MultiTenancyTests(MicroStackFixture fixture) - { - _fixture = fixture; - } - - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() => Task.CompletedTask; + public ValueTask DisposeAsync() => ValueTask.CompletedTask; // ── Client factory helpers ──────────────────────────────────────────────── private AmazonSecurityTokenServiceClient CreateStsClient(string accessKey) { - var innerHandler = _fixture.Factory.Server.CreateHandler(); + var innerHandler = fixture.Factory.Server.CreateHandler(); var httpClient = new HttpClient(new CanonicalizeUriHandler(innerHandler)) { BaseAddress = new Uri("http://localhost/"), @@ -59,7 +51,7 @@ private AmazonSecurityTokenServiceClient CreateStsClient(string accessKey) private AmazonSQSClient CreateSqsClient(string accessKey) { - var innerHandler = _fixture.Factory.Server.CreateHandler(); + var innerHandler = fixture.Factory.Server.CreateHandler(); var httpClient = new HttpClient(new CanonicalizeUriHandler(innerHandler)) { BaseAddress = new Uri("http://localhost/"), @@ -77,7 +69,7 @@ private AmazonSQSClient CreateSqsClient(string accessKey) private AmazonS3Client CreateS3Client(string accessKey) { - var innerHandler = _fixture.Factory.Server.CreateHandler(); + var innerHandler = fixture.Factory.Server.CreateHandler(); var httpClient = new HttpClient(new CanonicalizeUriHandler(innerHandler)) { BaseAddress = new Uri("http://localhost/"), @@ -96,7 +88,7 @@ private AmazonS3Client CreateS3Client(string accessKey) private AmazonDynamoDBClient CreateDdbClient(string accessKey) { - var innerHandler = _fixture.Factory.Server.CreateHandler(); + var innerHandler = fixture.Factory.Server.CreateHandler(); var httpClient = new HttpClient(new CanonicalizeUriHandler(innerHandler)) { BaseAddress = new Uri("http://localhost/"), @@ -287,7 +279,7 @@ public async Task ResetClearsAllAccounts() listB.QueueUrls.ShouldContain(u => u.Contains("reset-test-b")); // Reset - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); // Both should be empty after reset listA = await sqsA.ListQueuesAsync(new ListQueuesRequest()); diff --git a/tests/MicroStack.Tests/PersistenceTests.cs b/tests/MicroStack.Tests/PersistenceTests.cs index 0c7c2ca..db89b5a 100644 --- a/tests/MicroStack.Tests/PersistenceTests.cs +++ b/tests/MicroStack.Tests/PersistenceTests.cs @@ -3,7 +3,6 @@ using Amazon.SecretsManager; using Amazon.SecretsManager.Model; using Amazon.SimpleNotificationService; -using Amazon.SimpleNotificationService.Model; using Amazon.SimpleSystemsManagement; using Amazon.SimpleSystemsManagement.Model; using Amazon.SQS; @@ -18,39 +17,28 @@ namespace MicroStack.Tests; /// /// Pattern: create resource via SDK → GetState() → Reset() → verify gone → RestoreState() → verify back. /// -/// Port of ministack/tests/test_ministack_persist.py. +/// Port of ministack/tests/test_microstack_persist.py. /// -public sealed class PersistenceTests : IClassFixture, IAsyncLifetime +public sealed class PersistenceTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly ServiceRegistry _registry; - private readonly AmazonSQSClient _sqs; - private readonly AmazonSimpleNotificationServiceClient _sns; - private readonly AmazonSimpleSystemsManagementClient _ssm; - private readonly AmazonSecretsManagerClient _sm; - - public PersistenceTests(MicroStackFixture fixture) - { - _fixture = fixture; - _registry = (ServiceRegistry)fixture.Factory.Services.GetService(typeof(ServiceRegistry))!; - _sqs = CreateSqsClient(fixture); - _sns = CreateSnsClient(fixture); - _ssm = CreateSsmClient(fixture); - _sm = CreateSmClient(fixture); - } + private readonly ServiceRegistry _registry = (ServiceRegistry)fixture.Factory.Services.GetService(typeof(ServiceRegistry))!; + private readonly AmazonSQSClient _sqs = CreateSqsClient(fixture); + private readonly AmazonSimpleNotificationServiceClient _sns = CreateSnsClient(fixture); + private readonly AmazonSimpleSystemsManagementClient _ssm = CreateSsmClient(fixture); + private readonly AmazonSecretsManagerClient _sm = CreateSmClient(fixture); - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _sqs.Dispose(); _sns.Dispose(); _ssm.Dispose(); _sm.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── SDK client helpers ────────────────────────────────────────────────────── @@ -296,7 +284,7 @@ await _ssm.PutParameterAsync(new PutParameterRequest }); // Hit the reset endpoint - var response = await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + var response = await fixture.HttpClient.PostAsync("/_microstack/reset", null); response.StatusCode.ShouldBe(HttpStatusCode.OK); // Verify all resources are gone diff --git a/tests/MicroStack.Tests/RdsDataTests.cs b/tests/MicroStack.Tests/RdsDataTests.cs index 69f5413..87a95ad 100644 --- a/tests/MicroStack.Tests/RdsDataTests.cs +++ b/tests/MicroStack.Tests/RdsDataTests.cs @@ -1,7 +1,4 @@ -using System.Net; -using System.Net.Http.Json; using System.Text; -using System.Text.Json; using Task = System.Threading.Tasks.Task; namespace MicroStack.Tests; @@ -13,32 +10,25 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_rds_data.py. /// -public sealed class RdsDataTests : IClassFixture, IAsyncLifetime +public sealed class RdsDataTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { private const string Region = "us-east-1"; private const string AccountId = "000000000000"; - private static readonly string FakeClusterArn = $"arn:aws:rds:{Region}:{AccountId}:cluster:nonexistent-cluster"; - private static readonly string FakeSecretArn = $"arn:aws:secretsmanager:{Region}:{AccountId}:secret:nonexistent-secret"; + private const string FakeClusterArn = $"arn:aws:rds:{Region}:{AccountId}:cluster:nonexistent-cluster"; + private const string FakeSecretArn = $"arn:aws:secretsmanager:{Region}:{AccountId}:secret:nonexistent-secret"; - private readonly MicroStackFixture _fixture; - - public RdsDataTests(MicroStackFixture fixture) - { - _fixture = fixture; - } - - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() => Task.CompletedTask; + public ValueTask DisposeAsync() => ValueTask.CompletedTask; private async Task<(HttpStatusCode StatusCode, JsonElement Body)> RawPost(string path, object body) { var json = JsonSerializer.Serialize(body); using var content = new StringContent(json, Encoding.UTF8, "application/json"); - var response = await _fixture.HttpClient.PostAsync(path, content); + var response = await fixture.HttpClient.PostAsync(path, content); var responseBody = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(responseBody); return (response.StatusCode, doc.RootElement.Clone()); @@ -47,7 +37,7 @@ public async Task InitializeAsync() private async Task<(HttpStatusCode StatusCode, JsonElement Body)> RawPostRaw(string path, string rawBody) { using var content = new StringContent(rawBody, Encoding.UTF8, "application/json"); - var response = await _fixture.HttpClient.PostAsync(path, content); + var response = await fixture.HttpClient.PostAsync(path, content); var responseBody = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(responseBody); return (response.StatusCode, doc.RootElement.Clone()); diff --git a/tests/MicroStack.Tests/RdsTests.cs b/tests/MicroStack.Tests/RdsTests.cs index 820b0ae..1eff924 100644 --- a/tests/MicroStack.Tests/RdsTests.cs +++ b/tests/MicroStack.Tests/RdsTests.cs @@ -5,16 +5,9 @@ namespace MicroStack.Tests; -public sealed class RdsTests : IClassFixture, IAsyncLifetime +public sealed class RdsTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonRDSClient _rds; - - public RdsTests(MicroStackFixture fixture) - { - _fixture = fixture; - _rds = CreateRdsClient(fixture); - } + private readonly AmazonRDSClient _rds = CreateRdsClient(fixture); private static AmazonRDSClient CreateRdsClient(MicroStackFixture fixture) { @@ -32,15 +25,15 @@ private static AmazonRDSClient CreateRdsClient(MicroStackFixture fixture) return new AmazonRDSClient(new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _rds.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── DB Instance CRUD ──────────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/Route53Tests.cs b/tests/MicroStack.Tests/Route53Tests.cs index b4ceb81..096c984 100644 --- a/tests/MicroStack.Tests/Route53Tests.cs +++ b/tests/MicroStack.Tests/Route53Tests.cs @@ -11,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_route53.py. /// -public sealed class Route53Tests : IClassFixture, IAsyncLifetime +public sealed class Route53Tests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonRoute53Client _r53; - - public Route53Tests(MicroStackFixture fixture) - { - _fixture = fixture; - _r53 = CreateClient(fixture); - } + private readonly AmazonRoute53Client _r53 = CreateClient(fixture); private static AmazonRoute53Client CreateClient(MicroStackFixture fixture) { @@ -41,15 +34,15 @@ private static AmazonRoute53Client CreateClient(MicroStackFixture fixture) new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _r53.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/S3Tests.cs b/tests/MicroStack.Tests/S3Tests.cs index ef2c5fd..28962b7 100644 --- a/tests/MicroStack.Tests/S3Tests.cs +++ b/tests/MicroStack.Tests/S3Tests.cs @@ -11,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_s3.py. /// -public sealed class S3Tests : IClassFixture, IAsyncLifetime +public sealed class S3Tests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonS3Client _s3; - - public S3Tests(MicroStackFixture fixture) - { - _fixture = fixture; - _s3 = CreateS3Client(fixture); - } + private readonly AmazonS3Client _s3 = CreateS3Client(fixture); private static AmazonS3Client CreateS3Client(MicroStackFixture fixture) { @@ -41,15 +34,15 @@ private static AmazonS3Client CreateS3Client(MicroStackFixture fixture) return new AmazonS3Client(new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _s3.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/SecretsManagerTests.cs b/tests/MicroStack.Tests/SecretsManagerTests.cs index 23417a0..b8b44ef 100644 --- a/tests/MicroStack.Tests/SecretsManagerTests.cs +++ b/tests/MicroStack.Tests/SecretsManagerTests.cs @@ -11,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/services/secretsmanager.py. /// -public sealed class SecretsManagerTests : IClassFixture, IAsyncLifetime +public sealed class SecretsManagerTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonSecretsManagerClient _sm; - - public SecretsManagerTests(MicroStackFixture fixture) - { - _fixture = fixture; - _sm = CreateClient(fixture); - } + private readonly AmazonSecretsManagerClient _sm = CreateClient(fixture); private static AmazonSecretsManagerClient CreateClient(MicroStackFixture fixture) { @@ -41,15 +34,15 @@ private static AmazonSecretsManagerClient CreateClient(MicroStackFixture fixture new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _sm.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // -- CreateSecret / GetSecretValue ----------------------------------------- diff --git a/tests/MicroStack.Tests/ServiceDiscoveryTests.cs b/tests/MicroStack.Tests/ServiceDiscoveryTests.cs index 101c570..c8081a1 100644 --- a/tests/MicroStack.Tests/ServiceDiscoveryTests.cs +++ b/tests/MicroStack.Tests/ServiceDiscoveryTests.cs @@ -13,18 +13,10 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_servicediscovery.py. /// -public sealed class ServiceDiscoveryTests : IClassFixture, IAsyncLifetime +public sealed class ServiceDiscoveryTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonServiceDiscoveryClient _sd; - private readonly AmazonRoute53Client _r53; - - public ServiceDiscoveryTests(MicroStackFixture fixture) - { - _fixture = fixture; - _sd = CreateServiceDiscoveryClient(fixture); - _r53 = CreateRoute53Client(fixture); - } + private readonly AmazonServiceDiscoveryClient _sd = CreateServiceDiscoveryClient(fixture); + private readonly AmazonRoute53Client _r53 = CreateRoute53Client(fixture); private static AmazonServiceDiscoveryClient CreateServiceDiscoveryClient(MicroStackFixture fixture) { @@ -64,16 +56,16 @@ private static AmazonRoute53Client CreateRoute53Client(MicroStackFixture fixture new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _sd.Dispose(); _r53.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/SesTests.cs b/tests/MicroStack.Tests/SesTests.cs index 3b9ecac..b0ff19f 100644 --- a/tests/MicroStack.Tests/SesTests.cs +++ b/tests/MicroStack.Tests/SesTests.cs @@ -1,6 +1,5 @@ using Amazon; using Amazon.SimpleEmail; -using Amazon.SimpleEmail.Model; using Amazon.SimpleEmailV2; using Amazon.SimpleEmailV2.Model; using Amazon.Runtime; @@ -15,18 +14,10 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_ses.py. /// -public sealed class SesTests : IClassFixture, IAsyncLifetime +public sealed class SesTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonSimpleEmailServiceClient _ses; - private readonly AmazonSimpleEmailServiceV2Client _sesV2; - - public SesTests(MicroStackFixture fixture) - { - _fixture = fixture; - _ses = CreateSesClient(fixture); - _sesV2 = CreateSesV2Client(fixture); - } + private readonly AmazonSimpleEmailServiceClient _ses = CreateSesClient(fixture); + private readonly AmazonSimpleEmailServiceV2Client _sesV2 = CreateSesV2Client(fixture); private static AmazonSimpleEmailServiceClient CreateSesClient(MicroStackFixture fixture) { @@ -66,16 +57,16 @@ private static AmazonSimpleEmailServiceV2Client CreateSesV2Client(MicroStackFixt new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _ses.Dispose(); _sesV2.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/tests/MicroStack.Tests/SnsTests.cs b/tests/MicroStack.Tests/SnsTests.cs index 82396e6..8c2cd35 100644 --- a/tests/MicroStack.Tests/SnsTests.cs +++ b/tests/MicroStack.Tests/SnsTests.cs @@ -1,11 +1,9 @@ -using System.Text.Json; using Amazon; using Amazon.Runtime; using Amazon.SimpleNotificationService; using Amazon.SimpleNotificationService.Model; using Amazon.SQS; using Amazon.SQS.Model; -using Microsoft.AspNetCore.Mvc.Testing; namespace MicroStack.Tests; @@ -16,18 +14,10 @@ namespace MicroStack.Tests; /// Mirrors coverage from ministack/tests/test_sns.py. /// Lambda-dependent tests are skipped until the Lambda handler is ported. /// -public sealed class SnsTests : IClassFixture, IAsyncLifetime +public sealed class SnsTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonSimpleNotificationServiceClient _sns; - private readonly AmazonSQSClient _sqs; - - public SnsTests(MicroStackFixture fixture) - { - _fixture = fixture; - _sns = CreateSnsClient(fixture); - _sqs = CreateSqsClient(fixture); - } + private readonly AmazonSimpleNotificationServiceClient _sns = CreateSnsClient(fixture); + private readonly AmazonSQSClient _sqs = CreateSqsClient(fixture); private static AmazonSimpleNotificationServiceClient CreateSnsClient(MicroStackFixture fixture) { @@ -66,16 +56,16 @@ private static AmazonSQSClient CreateSqsClient(MicroStackFixture fixture) return new AmazonSQSClient(new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _sns.Dispose(); _sqs.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Topic CRUD ────────────────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/SqsTests.cs b/tests/MicroStack.Tests/SqsTests.cs index f124a22..74b7441 100644 --- a/tests/MicroStack.Tests/SqsTests.cs +++ b/tests/MicroStack.Tests/SqsTests.cs @@ -2,7 +2,6 @@ using Amazon.Runtime; using Amazon.SQS; using Amazon.SQS.Model; -using Microsoft.AspNetCore.Mvc.Testing; namespace MicroStack.Tests; @@ -12,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_sqs.py. /// -public sealed class SqsTests : IClassFixture, IAsyncLifetime +public sealed class SqsTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonSQSClient _sqs; - - public SqsTests(MicroStackFixture fixture) - { - _fixture = fixture; - _sqs = CreateSqsClient(fixture); - } + private readonly AmazonSQSClient _sqs = CreateSqsClient(fixture); private static AmazonSQSClient CreateSqsClient(MicroStackFixture fixture) { @@ -46,16 +38,16 @@ private static AmazonSQSClient CreateSqsClient(MicroStackFixture fixture) return new AmazonSQSClient(new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { // Reset state before each test class run - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _sqs.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Basic queue operations ────────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/SsmTests.cs b/tests/MicroStack.Tests/SsmTests.cs index 75b36e9..7e360c3 100644 --- a/tests/MicroStack.Tests/SsmTests.cs +++ b/tests/MicroStack.Tests/SsmTests.cs @@ -11,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_ssm.py. /// -public sealed class SsmTests : IClassFixture, IAsyncLifetime +public sealed class SsmTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonSimpleSystemsManagementClient _ssm; - - public SsmTests(MicroStackFixture fixture) - { - _fixture = fixture; - _ssm = CreateSsmClient(fixture); - } + private readonly AmazonSimpleSystemsManagementClient _ssm = CreateSsmClient(fixture); private static AmazonSimpleSystemsManagementClient CreateSsmClient(MicroStackFixture fixture) { @@ -41,15 +34,15 @@ private static AmazonSimpleSystemsManagementClient CreateSsmClient(MicroStackFix new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _ssm.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // -- PutParameter / GetParameter ------------------------------------------ diff --git a/tests/MicroStack.Tests/StepFunctionsTests.cs b/tests/MicroStack.Tests/StepFunctionsTests.cs index 7357e78..4ab8a62 100644 --- a/tests/MicroStack.Tests/StepFunctionsTests.cs +++ b/tests/MicroStack.Tests/StepFunctionsTests.cs @@ -1,6 +1,4 @@ -using System.Net.Http.Json; using System.Text; -using System.Text.Json; using Amazon; using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.Model; @@ -116,18 +114,18 @@ private static AmazonSimpleNotificationServiceClient CreateSnsClient(MicroStackF new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await _fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _sfn.Dispose(); _sqs.Dispose(); _ddb.Dispose(); _sns.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Helpers ──────────────────────────────────────────────────────────────── @@ -1744,7 +1742,7 @@ public async Task MockConfigReturn() } } """; - await _fixture.HttpClient.PostAsync("/_ministack/config", + await _fixture.HttpClient.PostAsync("/_microstack/config", new StringContent(mockCfgJson, Encoding.UTF8, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"))); var definition = """ @@ -1779,7 +1777,7 @@ await _fixture.HttpClient.PostAsync("/_ministack/config", output.GetProperty("value").GetInt32().ShouldBe(42); // Clean up - await _fixture.HttpClient.PostAsJsonAsync("/_ministack/config", + await _fixture.HttpClient.PostAsJsonAsync("/_microstack/config", new { stepfunctions = new { _sfn_mock_config = new { } } }); } @@ -1806,7 +1804,7 @@ public async Task MockConfigThrow() } } """; - await _fixture.HttpClient.PostAsync("/_ministack/config", + await _fixture.HttpClient.PostAsync("/_microstack/config", new StringContent(mockCfgJson, Encoding.UTF8, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"))); var definition = """ @@ -1838,7 +1836,7 @@ await _fixture.HttpClient.PostAsync("/_ministack/config", desc.Status.ShouldBe(ExecutionStatus.FAILED); // Clean up - await _fixture.HttpClient.PostAsJsonAsync("/_ministack/config", + await _fixture.HttpClient.PostAsJsonAsync("/_microstack/config", new { stepfunctions = new { _sfn_mock_config = new { } } }); } diff --git a/tests/MicroStack.Tests/StsTests.cs b/tests/MicroStack.Tests/StsTests.cs index e0ca5db..248b998 100644 --- a/tests/MicroStack.Tests/StsTests.cs +++ b/tests/MicroStack.Tests/StsTests.cs @@ -13,18 +13,10 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_sts.py. /// -public sealed class StsTests : IClassFixture, IAsyncLifetime +public sealed class StsTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonSecurityTokenServiceClient _sts; - private readonly AmazonIdentityManagementServiceClient _iam; - - public StsTests(MicroStackFixture fixture) - { - _fixture = fixture; - _sts = CreateStsClient(fixture); - _iam = CreateIamClient(fixture); - } + private readonly AmazonSecurityTokenServiceClient _sts = CreateStsClient(fixture); + private readonly AmazonIdentityManagementServiceClient _iam = CreateIamClient(fixture); private static AmazonSecurityTokenServiceClient CreateStsClient(MicroStackFixture fixture) { @@ -64,16 +56,16 @@ private static AmazonIdentityManagementServiceClient CreateIamClient(MicroStackF new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _sts.Dispose(); _iam.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } [Fact] diff --git a/tests/MicroStack.Tests/UnicodeTests.cs b/tests/MicroStack.Tests/UnicodeTests.cs index f9e07e0..85db34b 100644 --- a/tests/MicroStack.Tests/UnicodeTests.cs +++ b/tests/MicroStack.Tests/UnicodeTests.cs @@ -5,7 +5,6 @@ using Amazon.S3; using Amazon.S3.Model; using Amazon.SecretsManager; -using Amazon.SecretsManager.Model; using Amazon.SimpleSystemsManagement; using Amazon.SimpleSystemsManagement.Model; using Amazon.SQS; @@ -22,33 +21,21 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_unicode.py. /// -public sealed class UnicodeTests : IClassFixture, IAsyncLifetime +public sealed class UnicodeTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonS3Client _s3; - private readonly AmazonSQSClient _sqs; - private readonly AmazonDynamoDBClient _ddb; - private readonly AmazonSecretsManagerClient _sm; - private readonly AmazonSimpleSystemsManagementClient _ssm; - private readonly AmazonRoute53Client _r53; - - public UnicodeTests(MicroStackFixture fixture) + private readonly AmazonS3Client _s3 = CreateS3Client(fixture); + private readonly AmazonSQSClient _sqs = CreateSqsClient(fixture); + private readonly AmazonDynamoDBClient _ddb = CreateDdbClient(fixture); + private readonly AmazonSecretsManagerClient _sm = CreateSmClient(fixture); + private readonly AmazonSimpleSystemsManagementClient _ssm = CreateSsmClient(fixture); + private readonly AmazonRoute53Client _r53 = CreateR53Client(fixture); + + public async ValueTask InitializeAsync() { - _fixture = fixture; - _s3 = CreateS3Client(fixture); - _sqs = CreateSqsClient(fixture); - _ddb = CreateDdbClient(fixture); - _sm = CreateSmClient(fixture); - _ssm = CreateSsmClient(fixture); - _r53 = CreateR53Client(fixture); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public async Task InitializeAsync() - { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); - } - - public Task DisposeAsync() + public ValueTask DisposeAsync() { _s3.Dispose(); _sqs.Dispose(); @@ -56,7 +43,7 @@ public Task DisposeAsync() _sm.Dispose(); _ssm.Dispose(); _r53.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ── Client factory helpers ──────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/WafTests.cs b/tests/MicroStack.Tests/WafTests.cs index a2e3752..d2ba3b4 100644 --- a/tests/MicroStack.Tests/WafTests.cs +++ b/tests/MicroStack.Tests/WafTests.cs @@ -11,16 +11,9 @@ namespace MicroStack.Tests; /// /// Mirrors coverage from ministack/tests/test_waf.py. /// -public sealed class WafTests : IClassFixture, IAsyncLifetime +public sealed class WafTests(MicroStackFixture fixture) : IClassFixture, IAsyncLifetime { - private readonly MicroStackFixture _fixture; - private readonly AmazonWAFV2Client _waf; - - public WafTests(MicroStackFixture fixture) - { - _fixture = fixture; - _waf = CreateWafClient(fixture); - } + private readonly AmazonWAFV2Client _waf = CreateWafClient(fixture); private static AmazonWAFV2Client CreateWafClient(MicroStackFixture fixture) { @@ -41,15 +34,15 @@ private static AmazonWAFV2Client CreateWafClient(MicroStackFixture fixture) new BasicAWSCredentials("test", "test"), config); } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { - await _fixture.HttpClient.PostAsync("/_ministack/reset", null); + await fixture.HttpClient.PostAsync("/_microstack/reset", null); } - public Task DisposeAsync() + public ValueTask DisposeAsync() { _waf.Dispose(); - return Task.CompletedTask; + return ValueTask.CompletedTask; } // ═══════════════════════════════════════════════════════════════════════════