Skip to content

Fix: anchor the vitest workspace aliases so deep @google/adk/* specifiers fail at test time - #380

Open
AmaadMartin wants to merge 3 commits into
mainfrom
fix/vitest-alias-exact-match
Open

Fix: anchor the vitest workspace aliases so deep @google/adk/* specifiers fail at test time#380
AmaadMartin wants to merge 3 commits into
mainfrom
fix/vitest-alias-exact-match

Conversation

@AmaadMartin

@AmaadMartin AmaadMartin commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

  1. Link to an existing issue (if applicable):
    N/A

  2. Or, if no issue exists, describe the change:

Problem: vitest.config.ts declared, identically in all six test projects, a string-keyed alias map:

alias: {
  '@google/adk': path.resolve(__dirname, './core/src'),
  '@google/adk-integrations': path.resolve(__dirname, './integrations/src'),
},

A string find in @rollup/plugin-alias matches the package root and every subpath under it — the matcher is importee === pattern || importee.startsWith(pattern + '/') (vite's bundled copy, node_modules/vite/dist/node/chunks/config.js:8109). So @google/adk was rewritten to core/src (intended), but @google/adk/sessions/session.js was also rewritten to core/src/sessions/session.js (not intended). Note this is subpath matching, not raw prefix matching: the sibling package @google/adk-devtools was never hijacked, since it does not start with @google/adk/. core/package.json and integrations/package.json each declare exactly one export — "." — with no ./* subpath. Those deep specifiers therefore resolve for nobody outside the test runner: tsc reports TS2307 and a consumer of the published package gets a Node exports-map error. The harness was more permissive than the package manifest, so an unresolvable import could land on main with a green CI. Four such imports were already in the tree.

Solution: replace the six duplicated object literals with one shared, exported array of anchored RegExp alias entries. Rollup matches a RegExp find with find.test(importee), so ^…$ gives exact matching and the resolver becomes no more permissive than the published exports map. The four in-tree deep imports are migrated in the same commit so main stays green — there is no flag day.

specifier before after
@google/adk core/src core/src (unchanged)
@google/adk-integrations integrations/src integrations/src (unchanged)
@google/adk/<subpath> core/src/<subpath> not aliased → resolution error
@google/adk-integrations/<subpath> integrations/src/<subpath> not aliased → resolution error
@google/adk-devtools never aliased never aliased (unchanged)

Why the array form and not a stricter string key: @rollup/plugin-alias has no "exact" mode for string keys, and both patterns are anchored with no capture groups, so they cannot overlap and ordering is irrelevant. path.resolve(__dirname, …) is kept verbatim so windows-latest in the CI matrix behaves as it does today.

Migrated imports. Public symbol → package name; non-exported symbol → relative path into the source tree, which is the existing core/test convention (../../src/utils/logger.js is already used by 11 other files there).

  • core/test/sessions/vertex_ai_session_service_test.ts
    • Session is public (core/src/common.ts:226, re-exported by core/src/index.ts) and used only in type position, so it becomes import type {Session} from '@google/adk';.
    • isVertexAiConnectionString / quoteFilterLiteral / logger are not in the exports surface (core/src/common.ts:283-284 exports only LogLevel, getLogger, setLogLevel, setLogger and the Logger type), so they move to ../../src/....
  • tests/integration/agents/agent_with_sandbox_executor_test.ts
    • responseProcessor (core/src/agents/processors/code_execution_request_processor.ts:165) is internal, so it moves to ../../../core/src/.... It was deliberately not added to core/src/index.ts / core/src/common.ts: widening the public API to make a test import prettier would be an API change smuggled into a test-config fix. The relative path is the intended outcome — it makes the test's reach into package internals visible.

Module identity is preserved. vi.spyOn(logger, 'error') (lines 380/474/505/590) still observes the same object the code under test uses: core/test/sessions/../../src/utils/logger.js and core/src/sessions/../utils/logger.js are the same absolute file, and the alias collapses @google/adk onto core/src as well. No assertion in either migrated file was touched — only specifier strings.

Collision check (gh pr list --repo AmaadMartin/adk-js --state open --limit 100, then gh pr diff on every plausibly adjacent PR):

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.

New file tests/integration/vitest_alias_test.ts (2 tests) asserts on workspaceAliases directly: each bare specifier matches exactly one entry; @google/adk/sessions/session.js, @google/adk/agents/processors/code_execution_request_processor.js and @google/adk-integrations/foo.js match none; and each entry resolves to the right source root (derived from import.meta.url, not hardcoded). Matching goes through entry.find instanceof RegExp && entry.find.test(specifier), mirroring @rollup/plugin-alias, so a silent revert to the string form matches nothing and fails.

The test also asserts @google/adk-devtools matches no entry. That is not evidence of the original bug — the old string alias never captured it — but a guard against the obvious wrong fix: an unanchored /^@google\/adk/ would swallow both sibling packages.

Commands run on the pushed commit:

command result
npx vitest run --project unit:core --project unit:dev 2570 passed, 1 pre-existing failure (see below)
npx vitest run --project unit:core core/test/sessions/vertex_ai_session_service_test.ts 54 passed
npx vitest run --project integration tests/integration/vitest_alias_test.ts tests/integration/agents/agent_with_sandbox_executor_test.ts 3 passed
npx vitest run --project unit:integrations 1 pre-existing failure (see below)
npm run build pass
npm run lint pass (0 findings)
npm run format:check pass

Proof the new tests can fail. Three separate mutations of workspaceAliases, each reverted afterwards:

  1. Revert to the old string-keyed object form →
    TypeError: workspaceAliases.filter is not a function — 2 failed.
  2. Keep the array but make find a string (i.e. restore prefix matching) →
    AssertionError: expected [] to have a length of 1 but got +0 and
    AssertionError: expected [] to deeply equal [ { find: Any<RegExp>, …(1) } ] — 2 failed.
  3. Drop the trailing $ from the @google/adk pattern →
    AssertionError: expected [ …(2) ] to have a length of 1 but got 2 — 2 failed (the unanchored pattern swallows @google/adk-integrations and the deep forms).

ts:check delta (reported as a delta only — npm run ts:check is not a CI gate and its overall baseline is a separate task). The four TS2307 errors are gone: 280 total errors on main277 on this branch.

# before, on main
core/test/sessions/vertex_ai_session_service_test.ts:9:23  - error TS2307: Cannot find module '@google/adk/sessions/session.js' ...
core/test/sessions/vertex_ai_session_service_test.ts:27:8  - error TS2307: Cannot find module '@google/adk/sessions/vertex_ai_session_service.js' ...
core/test/sessions/vertex_ai_session_service_test.ts:28:22 - error TS2307: Cannot find module '@google/adk/utils/logger.js' ...
tests/integration/agents/agent_with_sandbox_executor_test.ts:9:33 - error TS2307: Cannot find module '@google/adk/agents/processors/code_execution_request_processor.js' ...
# after: 0 hits for TS2307 on @google/adk deep specifiers

Disclosed honestly: removing the TS2307 at agent_with_sandbox_executor_test.ts:9 surfaces one previously-masked error at line 95, TS2322: Type 'CodeExecutionResponseProcessor' is not assignable to type 'BaseLlmResponseProcessor'. The unresolvable import used to type responseProcessor as any. The real cause is that tsc resolves @google/adk through the exports map to core/dist/types while the relative import points at core/src, so it sees two declarations of InvocationContext (Types have separate declarations of a private property 'updateSessionState'). That split predates this change and applies to any test file mixing the two, and it does not affect what runs: under vitest the alias collapses both onto core/src, which is why the test passes. Net for the file is unchanged (1 error before, 1 after) and net for the repo is −3. I did not "fix" it by importing LlmAgent from a relative path too — that would trade a tsc-only artifact for a real regression in import hygiene.

Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.

From the repo root, after npm install && npm run build:

  1. npm run test:unit — passes (covers the rewritten vertex_ai_session_service_test.ts).
  2. npm run test:integration — passes (covers the rewritten agent_with_sandbox_executor_test.ts and the new vitest_alias_test.ts).
  3. npx vitest run --project unit:integrations — the only project that exercises the @google/adk-integrations alias (integrations/test/version_test.ts imports the bare specifier). The module resolves; see the pre-existing failure note below.
  4. Negative check — add a value import of a deep specifier to any file under core/test/ and run unit:core. It now fails to resolve, with Vite falling through to Node's exports-map check:
 FAIL  |unit:core| core/test/tmp_deep_import_negative_check_test.ts
Error: Missing "./utils/logger.js" specifier in "@google/adk" package
  Plugin: vite:import-analysis
  File: .../core/test/tmp_deep_import_negative_check_test.ts:8:21
  6  |  import { expect, it } from "vitest";
  7  |  import { logger } from "@google/adk/utils/logger.js";
     |                          ^
 ❯ resolveExportsOrImports node_modules/vite/dist/node/chunks/config.js:32907:45
 ❯ resolveDeepImport node_modules/vite/dist/node/chunks/config.js:32921:22

Reverting only vitest.config.ts to the string alias form makes that exact file pass (Test Files 1 passed) — that asymmetry is the bug this PR removes. The temporary file was deleted; it is not part of the diff.

Note the import must be a value import: an import type specifier is erased before resolution and never reaches the resolver, so it neither fails today nor failed before.

Two pre-existing failures found while testing, both reproduced on unmodified main and both unrelated to this change:

  • integrations/test/version_test.ts asserts expect(version).toBe('1.3.0') while integrations/src/version.ts is '1.5.0'. unit:integrations is not part of npm run test:coverage, which is why CI does not see it. Queued as separate work rather than fixed here.
  • dev/test/cli/cli_create_test.ts:214 reads an ambient gcloud project from the developer machine (initialValue: 'gcloud-project' vs 'global').

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.

CI

All checks green on 9ac5a3f8: run-tests on ubuntu-latest, macos-latest and windows-latest, plus check-license and auto-assign.

The first macos-latest attempt failed on tests/integration/app_loader/app_loader_test.ts with Error: Test timed out in 40000ms at the npm install the fixture shells out to (TEST_EXECUTION_TIMEOUT = 40000); windows-latest was then cancelled by fail-fast. That file is not in this diff and imports nothing this PR touches, ubuntu-latest passed the same commit, and re-running the two legs unchanged turned both green — a slow-runner flake, already the subject of #276 (drop the npm install from that fixture).

Amaad Martin added 3 commits July 31, 2026 03:26
A string alias in Vite/Rollup matches by prefix, so '@google/adk' also
rewrote '@google/adk/sessions/session.js' to core/src/sessions/session.js.
core/package.json and integrations/package.json each export only ".", so
those deep specifiers resolve for nobody outside the test runner: tsc
reports TS2307 and a published-package consumer gets a resolution error,
while vitest quietly passed.

Switch the six project blocks to one shared array of anchored RegExp
aliases so the resolver is no more permissive than the exports map, and
migrate the four in-tree deep imports that relied on the old behaviour.
Session is public, so it comes from '@google/adk'; logger,
isVertexAiConnectionString, quoteFilterLiteral and responseProcessor are
not exported, so they move to relative paths into the source tree. Both
forms land on the same absolute file, so vi.spyOn(logger, ...) keeps
observing the same module instance as the code under test.
Asserts the bare specifiers match exactly one entry each and that the
deep forms, plus @google/adk-devtools, match none. Matching goes through
`find instanceof RegExp`, so reverting to the prefix-matching string form
matches nothing and fails the suite.
@rollup/plugin-alias matches a string `find` as `importee === pattern ||
importee.startsWith(pattern + '/')`, not as a raw prefix, so the old
comment claimed a broader bug than existed: `@google/adk-devtools` was
never captured by the `'@google/adk'` key. State the actual mechanism,
name the tsc setting that rejects the subpath, and drop the sentence
restating the preceding one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant