Repository navigation
ci: run tests against the Cosmos vNext emulator instead of a deployed account - #147
Conversation
… account The legacy Cosmos emulator image is amd64-only, needs 3 GB of memory and is unreliable enough that the test workflow ran against a deployed Cosmos account via COSMOS_CONNECTION_STRING. That forced the workflow onto pull_request_target so fork PRs could reach the secret, which means untrusted PR code executed with access to it. The Linux-based vNext emulator (GA 2026-06-02) is multi-arch, boots in ~5s and covers everything the test suite needs, so the tests no longer need any credentials: - env/cosmos: switch to the vnext-latest image and configure it via PROTOCOL, ENABLE_INIT_DATA and a /ready healthcheck. The .NET SDK does not support the emulator's default HTTP mode, hence PROTOCOL=https. - env/cosmos: replace the amd64-only robinmanuelthiel/cosmos-db-setup container with a Cosmos Shell init script, which the emulator runs before it accepts requests. It provisions the same database and containers as before. - env/mongo: add a healthcheck so `up --wait` waits for a usable server. - test.yaml: start both compose stacks with --wait, drop the COSMOS_CONNECTION_STRING and COSMOS_DATABASE_NAME env vars (the test projects read their appsettings.json, which already points at the containers) and move back from pull_request_target to pull_request. - test.yaml: skip the coverage comment for fork PRs, which only get a read-only GITHUB_TOKEN on pull_request. - release.yaml: drop the cosmos-connection-string input, which the dotnet action never declared or used. The COSMOS_CONNECTION_STRING repository secret can now be deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPLcLmnYSJwFVB2b7bVbCK
- env/mongo: the healthcheck probed 127.0.0.1, but the mongo entrypoint runs a temporary mongod bound to loopback while it creates the root user and runs the init scripts. The probe went green against that one, so `up --wait` returned ~1.9s before the real server accepted connections and before the admin user and collections existed. Probing the routable address instead cannot reach the temporary server. Verified: an authenticated connection made immediately after `up --wait` failed before the fix and succeeds on 3/3 cold starts after it. - test.yaml: Dependabot PRs come from branches in this repo, not forks, so the fork guard did not cover them, yet GitHub still forces a read-only GITHUB_TOKEN for them on pull_request. The coverage comment would have 403'd and failed every Dependabot PR, which this repo gets regularly. - test.yaml: split the teardown into two steps. Both `down` calls shared one `run` block, so a non-zero exit from the Cosmos teardown would have skipped the Mongo one despite `if: always()`. - test.yaml: drop the reference to the non-existent `release-version` step, which actionlint flagged. It resolved to an empty string and the composite action never used the input. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPLcLmnYSJwFVB2b7bVbCK
Self-review (
|
|
marocchino/sticky-pull-request-comment ran with recreate: true, which deletes the previous comment and posts a new one, so every run moved the coverage report to the bottom of the PR and generated a fresh notification. mshick/add-pr-comment defaults to allow-repeats: false and refresh-message-position: false, so a stable message-id makes it edit the existing comment in place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPLcLmnYSJwFVB2b7bVbCK
|
Why
The test workflow ran the Cosmos integration tests against a deployed Cosmos account via
secrets.COSMOS_CONNECTION_STRING, because the legacy emulator image (mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator) is amd64-only, needs 3 GB of memory and is unreliable.To make that secret reachable from fork PRs the workflow used
pull_request_target— which runs untrusted PR code with access to the secret. The in-file comment acknowledged this:The Linux-based vNext emulator went GA on 2026-06-02. It is multi-arch (x64 + ARM64), boots in ~5s and covers everything this test suite needs, so the tests need no credentials at all.
Changes
env/cosmos/docker-compose.yamlimage: …:vnext-latestPROTOCOL=https— the vNext emulator defaults to HTTP and the .NET SDK does not support HTTP mode8081(gateway),8080(health probe),1234(Data Explorer); the10251-10254direct-mode ports are gone since vNext is gateway-onlymem_limit: 3g,cpus: 2.0and theAZURE_COSMOS_EMULATOR_*vars — none apply to vNext/readysoup --waitblocks until the emulator is usableenv/cosmos/init-scripts/01-create-containers.csh(new)robinmanuelthiel/cosmos-db-setup:2.0.0service, which was an amd64-only third-party image. vNext runs.cshscripts from/initthrough its built-in Cosmos Shell before accepting requests. Same topology as before:infrastructuredbtestswithuserson/tenantIdanddatacenterson/partitionKey.env/mongo/docker-compose.yamlmongoshping healthcheck soup --waitwaits for a usable server rather than just a started container..github/workflows/test.yaml--waitCOSMOS_CONNECTION_STRING/COSMOS_DATABASE_NAME— both test projects read their committedappsettings.json, which already points at the containerspull_request_target→pull_request, and dropped theref: …head.shacheckout override that went with itGITHUB_TOKENonpull_requestactions/checkout@v2→@v5, consistent with the other workflows.github/workflows/release.yamlcosmos-connection-stringinput —.github/actions/dotnetnever declared or used it.Verification
Local run on arm64, no
COSMOS_*/MONGO_*env vars set, using the exact CI command (dotnet test -c Release /p:CollectCoverage=true -m:1):The skip is pre-existing (
GetLambdaExpression_ShouldBuildAComplexAnyFiltercarries aSkipattribute).Cold start of both stacks to healthy: 5.4s. Cosmos suite runs in 15s, including the three ETag tests — so real
_etagand HTTP 412 optimistic-concurrency semantics work against the emulator.Follow-up
The
COSMOS_CONNECTION_STRINGrepository secret is now unused and can be deleted.Note that
env/mongostill uses the unpinnedimage: mongotag — worth pinning separately for reproducible CI.🤖 Generated with Claude Code
https://claude.ai/code/session_01DPLcLmnYSJwFVB2b7bVbCK