Fix: scrub ambient DATABASE_URL across the whole dev CLI unit suite - #348
Open
AmaadMartin wants to merge 1 commit into
Open
Conversation
Every web/api_server/run case in dev/test/cli/cli_test.ts resolves a session service through cli.ts's `options.session_service_uri || process.env.DATABASE_URL || 'memory://'` fallback, but only the session-service-resolution cases pinned DATABASE_URL. On a machine exporting it the remaining cases either failed outright (an unrecognised scheme makes getSessionServiceFromUri throw and the CLI exit 1 - 9 of 26 tests) or silently exercised a DatabaseSessionService instead of the InMemorySessionService they assume. Stub DATABASE_URL to undefined in the shared beforeEach so every case owns it, and add one case that leaves it unstubbed to pin that scrub. The ambient value is assigned at module scope rather than in a hook: the automatic unstub between tests restores each variable to the value it held at the first vi.stubEnv, so an injection made after that point is erased instead of inherited. Also type the @google/adk importOriginal call, which drops an `as object` cast and lets the factory wrap the real getSessionServiceFromUri in a spy.
This was referenced Jul 31, 2026
Fix: gitignore the .env that adk create writes so scaffolded agents cannot commit their API key
#364
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
Closes: #issue_number
Related: #issue_number
Problem:
dev/test/cli/cli_test.tsis not hermetic against an ambientDATABASE_URL.Every
web/api_server/runcase routes throughgetSessionServiceFromOptions, which resolvesoptions['session_service_uri'] || process.env.DATABASE_URL || 'memory://'(dev/src/cli/cli.ts:62). On the base branch only the five cases insidedescribe('session service resolution')pinDATABASE_URL; the other 21 inherit whatever the developer's shell exports. That produces two failure modes, both measured on the base commit (ae2dcbde):DATABASE_URL='bogus://nope'(unrecognised scheme)getSessionServiceFromUrithrowsUnsupported session service URI, the CLI error handler callsprocess.exit(1)DATABASE_URL='postgres://u:p@localhost:5432/db'(recognised scheme)AdkApiServeraDatabaseSessionServicewhere the tests assume anInMemorySessionService. Nothing asserts on it, so the suite stays green while exercising the wrong object.The second is the worse of the two: it is invisible.
Solution: Stub
DATABASE_URLtoundefinedin the suite's sharedbeforeEach, so every case owns the variable instead of inheriting it, and add one case that deliberately leaves it unstubbed to pin that scrub.Two details that are load-bearing rather than incidental:
unstubEnvs: trueforunit:dev, and Vitest snapshots each variable at its firstvi.stubEnvcall and restores to that snapshot between tests. An ambient value injected after that point is therefore erased rather than inherited, which makes the test vacuous — see mutation M2 below, which is exactly the trap this hit during development. A plain assignment made before any stub is the only way to simulate a shell-exported variable faithfully.importOriginalis now typed (importOriginal<typeof import('@google/adk')>()), which drops anas objectcast and lets the mock factory wrap the realgetSessionServiceFromUriin a spy. AssertingtoHaveBeenCalledWith('memory://')states precisely what the CLI decided, avoidsinstanceof(unreliable when two copies of@google/adkshare a runtime), and names the leaked value in the failure message. The five sibling cases keep theirtoBeInstanceOfassertions — this PR does not rewrite existing tests.No production source file is modified.
dev/src/cli/cli.tsis behaving as designed; theDATABASE_URLfallback is a documented CLI feature and is left intact.Collision check.
gh pr list --repo AmaadMartin/adk-js --state open --limit 100(100 open PRs), thengh pr diff --name-onlyon every plausibly adjacent one. This found #281fix/vitest-env-stub-hermeticity, which touches both files this task originally targeted. I checked out its head and re-ran the repro rather than reading the diff:core/test/telemetry/setup_test.tswithOTEL_EXPORTER_OTLP_ENDPOINTexported → 6/6 pass. Fix: make env-var stubbing hermetic across the test suite (vitest unstubEnvs + ambient-env coverage) #281 adds the same four-variableOTEL_ENDPOINT_ENV_VARSscrub this task called for. The OTLP half is fully landed; reimplementing it would have been a byte-for-byte duplicate, so this PR does not touch that file.dev/test/cli/cli_test.tswithDATABASE_URL='bogus://nope'→ 9 of 26 still fail. Fix: make env-var stubbing hermetic across the test suite (vitest unstubEnvs + ambient-env coverage) #281 stubsDATABASE_URLonly inside its five new cases, never in the shared hook, so the pre-existing cases stay exposed.Because the overlap is partial, this branches from
fix/vitest-env-stub-hermeticityinstead ofmainand targets it as the base. Other PRs scanned and ruled out: #302 (tests/hermetic_env_setup.ts; its scrub list is credentials-only —GOOGLE_*— and deliberately excludes OTLP andDATABASE_URL), #308 (tracing_test.ts,ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS), #259 (cli_create_test.ts).Out of scope.
dev/test/cli/cli_create_test.ts > should handle Vertex AI selection with gcloud defaultsfails on this machine both before and after this change. It is caused by an ambientGOOGLE_CLOUD_PROJECT(dev/src/cli/cli_create.ts:104prefers the env var over the mockedexecSynclookup) and belongs to the separate credential-scrub work. Untouched here.Testing Plan
Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Unit Tests:
[x] I have added or updated unit tests for my change.
[x] All unit tests pass locally.
dev/test/cli/cli_test.ts— 27/27 pass in all three environments, where the base branch passes only two of the three:Whole unit suite, clean vs. fully polluted — identical, which is the acceptance criterion:
The single failure in both runs is the pre-existing
cli_create_test.tscase described above.Proof the new test can fail. Three mutations, each applied, run, and reverted. The working tree is clean and this PR contains no
srcchanges.vi.stubEnv('DATABASE_URL', undefined)from the sharedbeforeEachshould ignore an ambient DATABASE_URLFAILS on a clean environment —AssertionError: expected "getSessionServiceFromUri" to be called with arguments: [ 'memory://' ], received"postgresql://ambient:pass@localhost:5432/ambient". 1 failed | 26 passed.beforeAllnested insidedescribe('session service resolution')— i.e. after the sibling cases have already stubbedDATABASE_URLDATABASE_URLto the value recorded at the firstvi.stubEnv(undefined), wiping the injection before the test body runs. This is why the assignment sits at module scope. An outerbeforeAllalso works, since it too runs before the first stub; only a nested one placed after the sibling stubs is silently ineffective.dev/src/cli/cli.ts:62— delete theprocess.env.DATABASE_URL ||termweb,api_server,run). The new case correctly does not fail: it assertsmemory://, which is what this mutation produces. That confirms it pins the hermeticity scrub and not the fallback, so the two sets of tests are orthogonal. Source restored.M2 is the one worth flagging to reviewers: it is a genuine trap, and the first version of this test fell into it and passed for the wrong reason.
Repo gates, run on the exact pushed commit:
CI is expected to be
absenton this PR, not green. Every test workflow is gated onpull_request: branches: [main](cross-language-integration.yml,license-check.yml), and this PR's base isfix/vitest-env-stub-hermeticity, so those jobs will not trigger. The local runs above are the validation.Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.
From the repository root, after
npm install && npm run build:Checklist
[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.