chore: replace LocalStack with Moto for AWS test infrastructure#4069
chore: replace LocalStack with Moto for AWS test infrastructure#4069holytshirt merged 4 commits intomasterfrom
Conversation
Code Review: Replace LocalStack with Moto for AWS test infrastructureOverviewClean, well-scoped migration driven by LocalStack Community Edition dropping its free tier in February 2026. The change is mechanically straightforward — env var rename, docker image swap, S3 URL workaround removal — and the PR description is admirably thorough about what was re-verified, un-skipped, and left out of scope. Positive observations
Issues and suggestions1. Pin the Moto image tag (medium priority)Both # docker-compose-aws.yaml and ci.yml
image: motoserver/moto:5.1.4 # pin to a known-good versionAdd a comment referencing the release date so it's easy to update intentionally. 2. No health check for the Moto service in CI (medium priority)The PR description says health checks were added for other services, but the new services:
moto:
image: motoserver/moto:latest
ports:
- 4566:4566
env:
MOTO_PORT: "4566"
# Missing:
options: >-
--health-cmd "curl -sf http://localhost:4566/moto-api/reset || exit 1"
--health-interval 5s
--health-timeout 3s
--health-retries 10Without this, a test step could start before Moto is accepting connections, causing flaky failures. 3.
|
|
Not sure if this merged in the global inbox fixes, but failing tests seem to relate to those. |
|
I'll pull in the latest changes |
4575811 to
62687e3
Compare
Code Review — chore: replace LocalStack with Moto for AWS test infrastructureOverall this is a well-executed migration. The motivation is clear (LocalStack CE dropped its free tier), the rename from A few issues worth addressing before merging: Medium — Moto image not pinnedBoth Suggestion: Pin to a specific digest or semver tag, e.g. Medium — No health check on the Moto service in CIThe Suggestion: Add a health check to the service block: services:
moto:
image: motoserver/moto:5.1.0 # pin the tag too
ports:
- 4566:4566
env:
MOTO_PORT: "4566"
options: >-
--health-cmd "curl -sf http://localhost:4566/moto-api/state || exit 1"
--health-interval 5s
--health-timeout 3s
--health-retries 10
--health-start-period 10s
Low —
|
| Issue | Severity | Status |
|---|---|---|
Moto image not pinned (latest) |
Medium | Open |
| No Moto health check in CI | Medium | Open |
GreetingsSender hardcoded URL |
Low | Open |
docker-compose-aws.yaml no health check |
Low | Open |
| Skip logic comment | Nit | Open |
None of these are blockers, but the two Medium items (pinning + health check) are worth fixing before the draft is marked ready — they're exactly the class of issue that causes intermittent CI failures and is hard to diagnose later.
Code Review: Replace LocalStack with Moto for AWS test infrastructureOverall assessment: This is a well-motivated and cleanly executed infrastructure migration. The rationale (LocalStack Community Edition dropping its free tier in February 2026) is sound, and Moto is a solid open-source alternative for the AWS services Brighter uses. The diff is large in file count but mechanically consistent. Items below range from genuine bugs/gaps to minor polish suggestions. 1. Potential Bug — SNS Tagged Resources Test May Also Need
|
Code Review — PR #4069: Replace LocalStack with MotoOverall: This is a well-motivated and well-executed infrastructure migration. The switch from LocalStack (dropped free tier) to Moto is clearly necessary, and the approach is sound. A few issues worth addressing before merge. What this does well
Issues1. Moto pinned to In multiple V4 test files (e.g. If 5.1.23 is available and doesn't break other tests, bumping the pinned image from 2. Missing The diff adds the package reference only to Please confirm 3. diff --git a/PR-DESCRIPTION.md b/PR-DESCRIPTION.mdThis file is being maintained as a committed file. The PR description on GitHub is the right home for this content. Committed Minor observations
SummaryThe migration itself is clean and the testing strategy (SkippableFact for Moto gaps, full run otherwise) is solid. The two actionable items before merging:
The bug fixes to Proactor/Reactor handler wiring and the 🤖 Reviewed with Claude Code |
73eab0b to
c714499
Compare
Code Review: Replace LocalStack with Moto for AWS test infrastructureOverall this is a well-executed migration with a clear motivation (LocalStack dropped its free tier). The PR description is thorough and the approach is sound. A few items worth discussing before merge. ✅ What's done wellInfrastructure migration
Latent bug fixes
Moto-skip strategy
|
LocalStack Community dropped its free tier and archived its image in
Feb 2026. Moto (motoserver/moto) is a drop-in open-source mock covering
the SQS, SNS, S3, and STS surface used by the AWS test suites.
- Replace docker-compose-localstack.yaml with docker-compose-aws.yaml
- Rename CI job localstack-ci -> aws-mock-ci
- Rename env var LOCALSTACK_SERVICE_URL -> AWS_SERVICE_URL across tests
and samples
- Remove [Trait("Fragile", "CI")] from AWS.Tests / AWS.V4.Tests. These
tests were marked fragile under LocalStack; they now pass reliably
against Moto (v3: 131/131, v4: 133/133)
- Add [Trait("Category", "AWS")] to the remaining AWS test classes that
were missing it for consistency
- Add Xunit.SkippableFact; runtime-skip the SQS 'resources are tagged'
test when AWS_SERVICE_URL is set. The test fails against Moto only;
it runs normally against real AWS (SNS equivalent passes on Moto)
- Update CONTRIBUTING.md and the Quartz scheduler sample README
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Review fixes: - Pin Moto image to 5.1.22 in docker-compose-aws.yaml and ci.yml so unannounced upstream releases can't break CI. - Add health checks for the Moto service in both compose and CI so a test step never starts before Moto accepts connections. - Drop the hardcoded http://localhost:4566 in the GreetingsSender sample in favour of reading AWS_SERVICE_URL. Latent test bugs uncovered while re-investigating skipped tests: - 4 SNS Proactor redrive tests (v3+v4) and 2 SQS Fifo Proactor redrive tests (v3+v4) registered a synchronous message mapper on an async pump. The mapper never dispatched, so the deferred message never redrove, masking as flake. Switched to MyDeferredCommandMessageMapperAsync via SimpleMessageMapperFactoryAsync + RegisterAsync. - 2 SQS Fifo Reactor redrive tests (v3+v4) registered async handlers on a sync pump (the inverse mismatch). Switched to MyDeferredCommandHandler via QuickHandlerFactory + Register. - 2 SNS Fifo Proactor redrive tests (v3+v4) were missing type: SqsType.Fifo on queueAttributes; recently-added validation in master rejects mixed Standard/Fifo queue+topic. All 8 previously-skipped tests now run green against Moto. V4 only: SQS fair-queue support landed in Moto >= 5.1.23 (currently only available on the dev0 latest tag, not on the 5.1.22 stable we pin). The 8 fairQueue: True theory cases are now SkippableTheory and skip when AWS_SERVICE_URL is set, so they still run against real AWS. Test results against pinned Moto 5.1.22: - v3: 136 passed, 1 skipped (SQS tag test, .NET SDK/Moto interop). - v4: 130 passed, 8 skipped (fairQueue: True theories). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Switch Moto health check from python -c '...' to curl -sf in both
ci.yml and docker-compose-aws.yaml. Verified: image ships curl, and
curl is universally available so the check stays portable if the
image's python ever drops.
- Drop the now-vestigial --filter "Fragile!=CI" from the aws-mock-ci
AWS test step. After dropping the trait from every AWS test class,
it matched everything; remove for clarity.
- Remove the two stragglers with the key/value flipped form of the
trait, [Trait("CI", "Fragile")], which my earlier scrub missed
because it searched for ("Fragile", "CI").
- Rename GetDLQCountAsync -> GetDLQCount in the un-skipped SQS Fifo
Reactor test (v3 + v4). The method is synchronous internally
(.GetAwaiter().GetResult()), so the Async suffix was misleading.
- Replace the stale LOCALSTACK_SERVICE_URL string in the commented-out
override blocks of the two AWSScheduler GatewayFactory helpers with
AWS_SERVICE_URL, matching the rest of the migration.
Follow-up issue #4096 tracks bumping the Moto pin to 5.1.23+ when it
ships, so the eight V4 fairQueue: True theory cases can come off the
SkippableTheory guard.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Delete PR-DESCRIPTION.md from the repo. The file was a tooling artifact already on master from #3994; this PR's diff only touched it for naming consistency. The PR body covers what it documented. - Rename CredentialsChain.GetBucketAddressTemple -> GetBucketAddressTemplate in both v3 and v4 test helpers (and update all 5 callers in each). Pre-existing typo, surfaced because this PR touches the helpers. - Rename SQS Fifo Reactor test method When_throwing_defer_action_respect_redrive_async -> When_throwing_defer_action_respect_redrive in v3 + v4. The method is sync (returns void) after switching the Reactor to a sync handler; the _async suffix was a leftover from before the fix. - Drop the dead, double-commented localhost:4566 / AWS_SERVICE_URL blocks from the two AWS scheduler samples and replace with a one-line comment explaining that EventBridge Scheduler is not implemented by Moto, so the samples always target real AWS. - Add `// TODO: remove once Moto pin in #4096 is bumped to 5.1.23+` next to the eight `Skip.If(fairQueue && AWS_SERVICE_URL ...)` guards in the V4 fair-queue tests so the skip is traceable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
c714499 to
5b8ebb1
Compare
Code Review: chore: replace LocalStack with Moto for AWS test infrastructureOverviewThis PR migrates AWS test infrastructure from LocalStack (which dropped its free tier in February 2026) to Moto, an open-source AWS mock. The changes span CI configuration, Docker Compose, test helpers, samples, and documentation — and along the way surface and fix several latent bugs in the test suite that were previously hidden under blanket StrengthsInfrastructure migration is clean and well-considered:
Genuine bug fixes found during the migration:
Typo fix ( Issues and Suggestions1. Two tests may not need
Similarly, Suggest verifying whether these three test classes make any network calls. If not, the 2. The returned template 3. CI: The PR description notes the real-AWS job is wedged on 4. The test: ["CMD", "curl", "-sf", "http://localhost:4566/moto-api/"]This is correct because Docker executes health check commands inside the container, so SummaryThe migration is well-executed, the latent test fixes are genuine improvements, and the scoped skips are better than the previous blanket 🤖 Generated with Claude Code |
Code Review: Replace LocalStack with Moto for AWS test infrastructureThis is a well-executed infrastructure migration. The PR description is thorough and honest about the scope, known skips, and follow-up work. Below is my assessment. OverviewReplaces LocalStack with Moto as the AWS mock for local and CI runs, driven by LocalStack dropping its free tier. Also fixes eight previously-skipped tests whose root causes were mapper/handler mismatches — not LocalStack flake. What's GoodClean, complete migration
Latent bug fixes are properly scoped and explained
Typo fix
Moto-incompatible test handling
Observations / Minor Issues1. return $"http://{{BucketName}}.s3.{{BucketRegion}}.{uri.Authority}";This constructs a virtual-hosted-style URL ( 2. var serviceURL = Environment.GetEnvironmentVariable("AWS_SERVICE_URL");
var region = string.IsNullOrWhiteSpace(serviceURL) ? RegionEndpoint.EUWest1 : RegionEndpoint.USEast1;This is pre-existing behaviour, but now that the hardcoded 3. The old 4. This is pre-existing; the PR preserves it correctly for the DLQ settling wait. Not a regression, but worth flagging as tech debt — a poll-with-timeout would be more reliable and faster on fast machines. 5. Env var name: The AWS SDK for .NET (v3.7.300+) has added support for 6. CI timeout still 8 minutes The Verdict✅ Approve. The migration is thorough, the latent bug root causes are correctly diagnosed and fixed, the Moto-incompatible skips are well-guarded and documented, and the PR description gives future maintainers everything they need. The observations above are minor and none are blockers. The one thing to validate before merging: confirm 🤖 Generated with Claude Code |
f2e81d1 to
5b8ebb1
Compare
There was a problem hiding this comment.
No application code in the PR — skipped Code Health checks.
See analysis details in CodeScene
Quality Gate Profile: Clean Code Collective
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.
Code Review: Replace LocalStack with Moto (#4069)Summary: Well-executed infrastructure migration with a clean scope, good engineering practices, and a bonus set of real bug fixes. The PR description is thorough and honest about what's known to still be broken (real-AWS CI account state, fairQueue Moto version gap). Overall this is ready to merge with a few minor observations. ✅ Strengths
🔍 Observations / Suggestions1. Unit tests gaining Three test classes that appear to be pure unit tests (no real AWS infrastructure) have been tagged:
Adding 2. return $"http://{{BucketName}}.s3.{{BucketRegion}}.{uri.Authority}";
// → http://{BucketName}.s3.{BucketRegion}.localhost:4566Moto supports virtual-hosted-style access, so this works in practice, but it's different from the simpler path-style that 3. // When_throwing_defer_action_respect_redrive.cs, ~line 1088
Task.Delay(5000).GetAwaiter().GetResult();This is a pre-existing hardcoded 5-second wait before checking the DLQ count. Now that this test runs in CI, it will contribute to the 8-minute job budget on every run. Not a blocker, but consider a polling helper with a shorter poll interval + timeout (e.g., poll every 200ms up to 10s) if the test proves flaky under load. 4. The PR correctly notes the 5. Minor: The v3 Reactor file renames 🔒 Security / Configuration
📋 Test Coverage
136 v3 / 130 v4 passed locally against Moto — solid result. VerdictThe core migration is clean and correct. The bug fixes are a genuine improvement. The outstanding items (real-AWS CI cleanup, fairQueue Moto bump) have appropriate tracking in #4096. The unit-test categorization point is the only thing worth a deliberate decision before merge. 🤖 Generated with Claude Code |
Closes #4068
Summary
Replace LocalStack with Moto as the default AWS mock for local and CI runs, after LocalStack Community Edition dropped its free tier in February 2026. Moto is open-source, covers the AWS services Brighter exercises (SQS, SNS, S3, STS), and runs as a single Docker image.
Migration
docker-compose-localstack.yaml→docker-compose-aws.yaml(singlemotoserver/motoservice on:4566).LOCALSTACK_SERVICE_URL→AWS_SERVICE_URLacross test helpers, CI, samples, docs, and commented-out override blocks.localstack-ci→aws-mock-ci.motoserver/moto:5.1.22in both compose and CI so an unannounced upstream release can't silently break CI.curl -sf http://localhost:4566/moto-api/health check to the Moto service in both compose and CI so test steps wait for Moto to accept connections.http://s3.{authority}) fromGatewayFactory.[Trait("Fragile", "CI")](and the inverted[Trait("CI", "Fragile")]) from AWS test classes — these were workarounds for LocalStack flake. After this PR no AWS test carries that trait, so the redundant--filter "Fragile!=CI"is removed from theaws-mock-cistep.[Trait("Category", "AWS")].http://localhost:4566/URL insamples/TaskQueue/AWSTaskQueue/GreetingsSender/Program.cswithEnvironment.GetEnvironmentVariable("AWS_SERVICE_URL"), matching every other AWS sample.localhost:4566 / AWS_SERVICE_URLblocks in the AWS scheduler samples (the live code intentionally hardcodes real AWS because EventBridge Scheduler isn't implemented by Moto — a one-line comment explains the pin).Latent test bugs found and fixed
While re-investigating the previously-skipped redrive tests, three pump/mapper/handler mismatches surfaced — these were the real cause of the "task scheduler issues" / "DLQ flaky" skip reasons, not LocalStack:
SimpleMessageMapperFactory+ syncMyDeferredCommandMessageMapperon a Proactor. The mapper never dispatched, so the deferred message was never redriven. Switched toSimpleMessageMapperFactoryAsync+MyDeferredCommandMessageMapperAsync+RegisterAsync.QuickHandlerFactory+MyDeferredCommandHandler+Register.topicAttributessetType = SqsType.FifobutqueueAttributesdid not. Recently-addedSqsSubscriptionvalidation on master correctly rejects this. Addedtype: SqsType.Fifoto the queue attributes.All eight previously-skipped tests now run green against Moto.
Approach to running against real AWS
No trait filter required — if
AWS_SERVICE_URLis unset and a local AWS profile is configured, the helpers use real AWS credentials.Two scoped runtime skips when running against Moto (skipped via
Skip.If(AWS_SERVICE_URL != null, …), so they still run against real AWS):SkippableFact— one SQS-only producer-tag verification test that fails against Moto due to a.NET SDK/ MotoListQueueTagsinterop issue.SkippableTheory— eight V4fairQueue: Truetheory cases. SQS fair-queue support landed in Moto >= 5.1.23; the latest stable is 5.1.22. Each guard carries a// TODO: remove once Moto pin in #4096 is bumped to 5.1.23+comment.Out of scope: AWSScheduler tests still target real AWS — EventBridge Scheduler is not implemented by Moto.
Drive-by polish
These are all pre-existing issues that surfaced because this PR touches the same files; cheap to fix in passing.
CredentialsChain.GetBucketAddressTemple→GetBucketAddressTemplatein v3 + v4 (typo, 5 callers each).When_throwing_defer_action_respect_redrive_async→When_throwing_defer_action_respect_redrivein the v3 + v4 SQS Fifo Reactor tests — the methods are now sync (void), so the_asyncsuffix was misleading.PR-DESCRIPTION.mdfrom the repo root (a tooling artifact that was already on master from Fix CI acceptance test reliability: Add monitoring, increase timeouts and health check retries, add Kafka readiness verification #3994; this PR's body covers what it documented).Test plan
Against pinned
motoserver/moto:5.1.22, full suites with no filters:aws-mock-cijob passes against Moto in GitHub Actionsaws-cijob (real AWS) — currently wedged onAmazon.SimpleNotificationService.Model.TopicLimitExceededException. This is a CI account state issue, not a regression from this PR — master is failing the same way (e.g. run24963243390). The CI account has accumulated SNS FIFO topics from prior runs and needs a one-off cleanup before the real-AWS smoke run can be confirmed.Follow-up
SkippableTheoryguard.aws-cican run again (separate to this PR).🤖 Generated with Claude Code