Skip to content

Fix: derive the integrations version assertion from package.json and run the dead unit:integrations vitest project - #476

Closed
AmaadMartin wants to merge 2 commits into
mainfrom
fix/integrations-version-test-and-vitest-project
Closed

Fix: derive the integrations version assertion from package.json and run the dead unit:integrations vitest project#476
AmaadMartin wants to merge 2 commits into
mainfrom
fix/integrations-version-test-and-vitest-project

Conversation

@AmaadMartin

@AmaadMartin AmaadMartin commented Aug 1, 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):
    Related: feat(integrations): create new top-level integrations package google/adk-js#449 (the PR that introduced the now-stale literal)
  2. Or, if no issue exists, describe the change:

Problem: Two coupled defects, neither of which is fixable alone.

  1. Stale version literal. integrations/test/version_test.ts asserted
    expect(version).toBe('1.3.0'), but integrations/src/version.ts exports
    '1.5.0'. release-please-config.json lists integrations/src/version.ts as an
    extra-files entry for the integrations component, so release-please rewrites
    it (and integrations/package.json) on every release — but it has no knowledge
    of the test. The test was added at 1.3.0 in feat(integrations): create new top-level integrations package google/adk-js#449 and never touched
    since, while version.ts was bumped twice by release PRs. Any literal in this
    file is guaranteed to go stale at the next release, so simply changing '1.3.0'
    to '1.5.0' would reintroduce the identical bug.
  2. Dead vitest project. vitest.config.ts defines a unit:integrations
    project, but no root npm script named it. --project is an allowlist and vitest
    emits no warning for a project that is never selected, so integrations/test/
    had never executed anywhere — including CI, which runs npm run test:coverage.
    That is why defect 1 went unnoticed.

Reproduced on main before the change:

$ CI=true npx vitest run --project unit:integrations
 FAIL  |unit:integrations| integrations/test/version_test.ts > version > should return the correct version
AssertionError: expected '1.5.0' to be '1.3.0' // Object.is equality

...while npm run test:coverage passed the unit:integrations project by
entirely: grep -c "unit:integrations" over the full pre-change
npm run test:coverage output returns 0.

Solution: two commits, both required.

  1. Derive the expectation instead of hardcoding it. The test reads the sibling
    integrations/package.json at runtime and asserts version equals
    manifest.version, plus a shape check pinning the major.minor.patch format
    that the // version: major.minor.patch comment in version.ts documents.
    Neither assertion subsumes the other: the shape check pins the format, the
    manifest check pins agreement between the two files release-please rewrites
    together — which is the real failure mode (a rewrite of package.json that
    silently misses the x-release-please-version extra-file, or a hand-edit of one
    without the other). The manifest path is resolved from import.meta.url, not the
    process CWD, because vitest projects run from the repository root; that also
    keeps it correct on the Windows and macOS legs of the CI matrix.
  2. Add --project unit:integrations to test, test:unit and test:coverage, so
    the directory actually gates merges. .github/workflows/validation.yaml already
    calls npm run test:coverage, so no workflow change is needed.

Why a runtime readFileSync rather than import pkg from '../package.json':
resolveJsonModule is not enabled anywhere in the tsconfig chain (root
tsconfig.json extends gts/tsconfig-google.json), so a static JSON import does
not type-check, and under "module": "nodenext" it would additionally require an
import attribute.

An existing test was rewritten rather than added to. The repository convention
is normally "add a new test, do not modify an existing one", and that convention
exists to preserve a regression signal. This is the documented exception: the
existing assertion was itself the defect — a stale literal that had been failing
invisibly — so there was no correct signal to preserve. The file's regression signal
is strictly strengthened, from one brittle assertion that must be hand-edited every
release to two derived assertions that never need editing. No other test was
modified, skipped, weakened or deleted.

Not touched (all verified with git diff --quiet):
integrations/src/version.ts (release-please managed — the
// x-release-please-version and // version: major.minor.patch comments are
load-bearing), release-please-config.json, vitest.config.ts (the project
definition was already correct; only the scripts selecting it were wrong), the
coverage thresholds block, .github/workflows/validation.yaml, and
package-lock.json (no dependency added). The diff is 2 files, +27/-5.

Collision check. Per the pre-work check against the open PRs on this fork
(gh pr list --limit 200, then gh pr diff --name-only over every one of them,
filtered for package.json and integrations/):

  • Feat: port FirestoreSessionService from adk-python to @google/adk-integrations #466 (Feat: port FirestoreSessionService...) overlaps. It contains a
    byte-identical version of the package.json script wiring in this PR (its
    post-image blob for package.json is cf793293, the same as this branch's), and
    it also touches integrations/test/version_test.ts — but with
    -expect(version).toBe('1.3.0') / +expect(version).toBe('1.5.0'), i.e. the
    hardcoded-literal fix that this PR exists to eliminate. It therefore does not land
    this change. I did not stack on it: Feat: port FirestoreSessionService from adk-python to @google/adk-integrations #466 is a large feature port that adds a
    new runtime dependency (@google-cloud/firestore), and gating a two-file CI
    hygiene fix behind it — including for the separate upstream-port step — would be a
    worse outcome than a trivially resolvable overlap. Whichever merges second, the
    package.json hunks are identical and the version_test.ts resolution should be
    this PR's derived assertion, not the literal.
  • Test: fail CI when a root test script targets a vitest project no workflow runs #418 (Test: fail CI when a root test script targets a vitest project no workflow runs) is adjacent but complementary and does not conflict — it adds
    tests/integration/repo_config/workflow_scripts_test.ts only. Its assertion
    (every project a root script targets must be run by some workflow) still holds
    after this change, because unit:integrations is added to test:coverage, which
    validation.yaml invokes.
  • Test: pin core and dev version exports to their package.json #417 (Test: pin core and dev version exports to their package.json) covers
    the core and dev packages and touches neither file in this diff. Deliberately
    left out of scope here.
  • No other open PR touches root package.json scripts or integrations/.

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.

The change is the test. Both new assertions execute on every run and the file has
no branches, so new-line coverage is 100% by construction.

$ CI=true npx vitest run --project unit:integrations
 ✓  unit:integrations  integrations/test/version_test.ts (2 tests) 5ms
 Test Files  1 passed (1)
      Tests  2 passed (2)

Mutation testing — proof each assertion can actually fail. Coverage alone is not
proof, so each test was run against mutated source. integrations/src/version.ts
was reverted after every mutation and confirmed byte-identical to HEAD
(git diff --exit-code integrations/src/version.ts).

Mutation 1export const version = '1.5.0''9.9.9' (simulates
release-please rewriting the manifest but missing the extra-file). The
manifest-agreement test fails; the shape test still passes, which is what proves the
manifest check — not the shape check — is the one that catches release drift:

 ✓ version > should be a semantic version string 2ms
 × version > should match the version declared in package.json 11ms
AssertionError: expected '9.9.9' to be '1.5.0' // Object.is equality
 Test Files  1 failed (1)
      Tests  1 failed | 1 passed (2)

Mutation 2'1.5.0''not-a-version'. Both tests fail, proving the shape
check is live:

 × version > should be a semantic version string 16ms
 × version > should match the version declared in package.json 5ms
AssertionError: expected 'not-a-version' to match /^\d+\.\d+\.\d+/
AssertionError: expected 'not-a-version' to be '1.5.0' // Object.is equality
 Test Files  1 failed (1)
      Tests  2 failed (2)

Proof the script wiring is live (defect 2). npm run test:unit now picks the
project up — 182 → 183 test files, 2574 → 2576 tests:

$ CI=true npm run test:unit
> vitest --project unit:core --project unit:dev --project unit:integrations
 ✓  unit:integrations  integrations/test/version_test.ts (2 tests) 5ms

And with mutation 1 re-applied, the failure now reaches the exact command CI runs —
which, as shown above, it provably did not before (0 matches pre-change):

$ npm run test:coverage
> vitest run --project unit:core --project unit:dev --project unit:integrations --project integration --project e2e --coverage
 FAIL  |unit:integrations| integrations/test/version_test.ts > version > should match the version declared in package.json

Coverage before/after. Measured on real CI (run-tests (ubuntu-latest), the same
job on both sides) so these are full npm run test:coverage numbers, not a subset:
fork main at this branch's exact base commit b390217e versus this PR.

% Stmts % Branch % Funcs % Lines
All files — before (main @ b390217e) 90.67 89.58 91.4 90.67
All files — after (this PR) 90.68 89.61 91.55 90.68
BEFORE                                     AFTER
 integrations/src |  0 | 0 | 0 |  0         integrations/src | 66.66 | 0 | 0 | 66.66
  index.ts        |  0 | 0 | 0 |  0          index.ts        |   100 |100|100|   100
  index_web.ts    |  0 | 0 | 0 |  0          index_web.ts    |     0 | 0 | 0 |     0
  version.ts      |  0 | 0 | 0 |  0          version.ts      |   100 |100|100|   100

Test files 224 → 225, tests 2678 → 2680 passed. Every metric is above the thresholds
(statements 86 / branches 87 / functions 88 / lines 86) and every metric moved up,
which is the direction the change predicts: vitest.config.ts already listed
integrations/src/**/*.ts under coverage.include, so those three files were already
in the denominator at 0%; executing the test loads two of them for the first time.
Nothing is added to the denominator. (index_web.ts is the browser entry point, is not
imported by this test, and stays at 0% — pre-existing and out of scope.)
The thresholds block was not modified.

That same before/after CI log pair is also the cleanest proof of defect 2:
grep -c unit:integrations over the pre-change CI log returns 0, while over this
PR's CI log it matches both the command line and
✓ unit:integrations integrations/test/version_test.ts (2 tests) 8ms.

(The numbers come from CI rather than my sandbox because the full npm run test:coverage cannot go green there for reasons that pre-date this change — the e2e
project needs a real GOOGLE_GENAI_API_KEY, the integration fixtures need network
npm install, and one unit:dev gcloud test picks up the machine's real gcloud
config through its mock — and vitest does not emit the coverage table when tests fail.)

CI status on this PR: green on all three OS legsrun-tests (ubuntu-latest),
run-tests (macos-latest) and run-tests (windows-latest) all pass
(run 30715200431),
with the new project executing on each: ✓ unit:integrations integrations/test/version_test.ts (2 tests).

The Windows leg needed two reruns, and it is worth recording why so a reviewer does
not read it as instability introduced here. It failed on a different test each
time, and never on this diff:

attempt failing test budget
base commit b390217e (main, before this PR) adk_web/webui_test.ts > 'Run from ADK CLI'
1 integration/build_setup/build_setup_test.ts > js_esm Test timed out in 20000ms
2 unit:core core/test/code_executors/unsafe_local_code_executor_test.ts > should execute shell code and return stdout Test timed out in 5000ms
3 none — green

All three are process-spawning tests racing a fixed timeout on a slow runner, and the
Windows leg is chronically red on this fork independently of this PR: it failed or was
cancelled in 4 of the last 6 validation.yaml runs on main (macOS failed in 2 of
those 6). This change adds two filesystem-read assertions and alters no timeout, no
testTimeout, and not the integration project's selection.

Other CI steps, run on the exact pushed commit:

$ npm run build          # OK
$ npx tsc --noEmit       # 0 errors
$ npm run lint           # OK (eslint "**/*.ts")
$ npm run format:check   # All matched files use Prettier code style!
$ npx secretlint integrations/test/version_test.ts package.json   # OK

No @ts-expect-error, @ts-ignore, eslint-disable, as any, as never, or
coverage-ignore pragma appears anywhere in this diff, and there is no console.log.
const manifest: {version: string} = JSON.parse(...) needs no suppression:
eslint.config.js uses the untyped tseslint.configs.recommended preset, so
no-unsafe-assignment is not active, and tsc --noEmit is clean.

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

npm install
npm run build

# 1. The previously-dead project is green.
CI=true npx vitest run --project unit:integrations     # 1 file, 2 tests, passing

# 2. It is no longer dead -- the CI command now selects it.
npm run test:coverage 2>&1 | grep unit:integrations    # matches (pre-change: no matches)

# 3. It is release-proof. Edit integrations/package.json's "version" and
#    integrations/src/version.ts to any other matching value (e.g. both to
#    "9.9.9") and re-run step 1: still green, with no test edit. Change only
#    one of the two and it goes red. Revert both afterwards.

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.

Amaad Martin added 2 commits August 1, 2026 12:36
The assertion hardcoded '1.3.0' while integrations/src/version.ts exports
'1.5.0'. release-please rewrites integrations/package.json and the
x-release-please-version line in integrations/src/version.ts together but
has no knowledge of this test, so any literal here is guaranteed to go
stale at the next release.

Read the sibling manifest at test runtime (resolved from import.meta.url,
since vitest runs from the repository root) and assert against it. That
keeps the expectation correct across future bumps and, unlike a literal,
actually detects the failure mode this file exists to guard: the two
release-please targets drifting out of agreement.
vitest.config.ts defines a unit:integrations project, but no npm script
named it. --project is an allowlist and vitest warns about nothing, so
integrations/test/ had never executed anywhere -- including in CI, which
runs npm run test:coverage.

Select the project from test, test:unit and test:coverage so a regression
under integrations/test/ fails the build.
@AmaadMartin

Copy link
Copy Markdown
Owner Author

Closing this as a duplicate of #236, which already lands both halves of this task and is open, green on all three OS legs, and MERGEABLE/CLEAN.

My pre-work collision scan missed it: I ran gh pr list --state open --limit 100 and then --limit 200, but this fork currently has 379 open PRs, so the listing was silently truncated well above #236. That is my error, and this PR should not have been opened. Recording the comparison here so the duplication is not repeated:

#236 this PR
root package.json script wiring adds --project unit:integrations to test, test:unit, test:coverage byte-identical
integrations/test/version_test.ts derives the expectation via import packageJson from '../package.json' with {type: 'json'} derives it via readFileSync resolved from import.meta.url
assertions 1 (manifest agreement) 2 (manifest agreement + major.minor.patch shape)
vitest.config.ts adds comments distinguishing unit:integrations from integration untouched

I checked whether #236's static JSON import has a latent typing problem, because the spec I was working from asserted that resolveJsonModule is not enabled anywhere in the tsconfig chain and that a static JSON import therefore would not type-check. That claim is wrong and I am correcting it rather than letting it stand as a reason to prefer this PR: npx tsc --showConfig reports "resolveJsonModule": true (implied by "module": "nodenext"), and npx tsc --noEmit returns 0 errors with #236's exact import form. I confirmed integrations/test/version_test.ts is genuinely in the compilation program by injecting a deliberate type error and watching tsc report it. So #236's approach is sound and needs no change.

The only substantive delta left is the extra major.minor.patch shape assertion, which is not worth a stacked PR: npm already rejects a non-semver version in package.json, so once the manifest-agreement assertion holds the shape check is very nearly redundant. #236 should be merged as-is.

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