Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions core/test/utils/hermetic_env_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {describe, expect, it} from 'vitest';
import {
AMBIENT_CLOUD_ENV_VARS,
scrubAmbientCloudEnv,
} from '../../../tests/hermetic_env.js';

describe('scrubAmbientCloudEnv', () => {
it('removes every ambient cloud variable and keeps the rest', () => {
const env: Record<string, string | undefined> = {PATH: '/usr/bin'};
for (const name of AMBIENT_CLOUD_ENV_VARS) {
env[name] = `ambient-${name}`;
}

scrubAmbientCloudEnv(env);

expect(env).toEqual({PATH: '/usr/bin'});
});
});

/**
* Pins the `setupFiles` wiring in `vitest.config.ts`. Importing
* `hermetic_env.js` above has no side effects, so this only passes when the
* setup file really ran in this worker.
*
* Vacuous on a CI runner by construction -- GitHub Actions exports none of
* these -- and the assertion that fires on a developer machine.
*/
describe('unit test worker environment', () => {
it('has no ambient cloud variables', () => {
for (const name of AMBIENT_CLOUD_ENV_VARS) {
expect(process.env[name]).toBeUndefined();
}
});
});
58 changes: 58 additions & 0 deletions core/test/utils/log_level_pin_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {getLogger} from '@google/adk';
import {Console} from 'node:console';
import {Writable} from 'node:stream';
import {describe, expect, it} from 'vitest';

/**
* Returns everything `fn` writes through the global console.
*
* `SimpleLogger`'s winston Console transport writes to the stream held by
* whatever `globalThis.console` is at log time, so swapping in a `Console`
* bound to an in-memory stream captures it. Vitest replaces the global console
* with its own instance backed by a private stream, which is why spying on
* `process.stdout.write` or `console.log` captures nothing here.
*/
function captureConsoleOutput(fn: () => void): string {
const chunks: string[] = [];
const stream = new Writable({
write(chunk: unknown, _encoding: string, callback: () => void) {
chunks.push(String(chunk));
callback();
},
});
const original = globalThis.console;
globalThis.console = new Console({stdout: stream, stderr: stream});
try {
fn();
} finally {
globalThis.console = original;
}
return chunks.join('');
}

/**
* Pins the `setupFiles` wiring in `vitest.config.ts`. The log level is
* module-level state, so a `globalSetup` file running in the Vitest main
* process cannot reach the forked worker this test runs in; only a setup file
* can. There is no public read accessor for the effective level, so these
* assert on what the logger writes.
*/
describe('test worker log level', () => {
it('suppresses info logs', () => {
expect(captureConsoleOutput(() => getLogger().info('info-pin-probe'))).toBe(
'',
);
});

it('still emits error logs', () => {
expect(
captureConsoleOutput(() => getLogger().error('error-pin-probe')),
).toContain('error-pin-probe');
});
});
15 changes: 0 additions & 15 deletions tests/global_setup.ts

This file was deleted.

45 changes: 45 additions & 0 deletions tests/hermetic_env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

/**
* Cloud configuration and credential variables that a developer machine is
* likely to export (via `gcloud`, a shell profile, or a sourced `.env`).
*
* All but one are read straight from `process.env` by ADK source.
* `GOOGLE_APPLICATION_CREDENTIALS` is the exception: it is consumed by
* `google-auth-library` beneath the genai SDK, so grepping ADK for it finds
* nothing. Keep it in the list -- it is the entry that points at real
* credentials.
*/
export const AMBIENT_CLOUD_ENV_VARS = [
'GEMINI_API_KEY',
'GOOGLE_API_KEY',
'GOOGLE_APPLICATION_CREDENTIALS',
'GOOGLE_CLOUD_AGENT_ENGINE_ID',
'GOOGLE_CLOUD_LOCATION',
'GOOGLE_CLOUD_PROJECT',
'GOOGLE_GENAI_API_KEY',
'GOOGLE_GENAI_USE_VERTEXAI',
];

/**
* Removes the ambient cloud configuration from `env`.
*
* GitHub Actions runners never export these variables, so a unit test that
* reads one of them passes in CI and fails only on a developer machine.
* Unit tests that need a value set one explicitly with `vi.stubEnv`.
*
* This module is deliberately free of side effects: `hermetic_env_setup.ts` is
* what applies the scrub. That split is what lets a test import the list and
* still observe whether the setup file actually ran.
*/
export function scrubAmbientCloudEnv(
env: Record<string, string | undefined> = process.env,
) {
for (const name of AMBIENT_CLOUD_ENV_VARS) {
delete env[name];
}
}
12 changes: 12 additions & 0 deletions tests/hermetic_env_setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {scrubAmbientCloudEnv} from './hermetic_env.js';

// Runs in every unit-test worker before the test module is imported, so no
// unit test can read the developer machine's real gcloud / Vertex AI settings.
// A plain delete (rather than `vi.stubEnv`) survives `vi.unstubAllEnvs()`.
scrubAmbientCloudEnv();
17 changes: 17 additions & 0 deletions tests/setup_log_level.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {LogLevel, setLogLevel} from '../core/src/utils/logger.js';

// Must run inside the test worker: vitest `globalSetup` executes in the main
// process, and the log level is module-level state a forked worker never
// inherits.
//
// Import the logger module, not the `@google/adk` barrel. A setup file is
// evaluated before the test module, so importing the barrel here caches the
// whole real core graph before a test file's `vi.mock` can replace any of it,
// which breaks mocking in over a hundred suites.
setLogLevel(LogLevel.ERROR);
16 changes: 15 additions & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export default defineConfig({
test: {
name: 'unit:core',
environment: 'node',
setupFiles: [
'./tests/hermetic_env_setup.ts',
'./tests/setup_log_level.ts',
],
alias: {
'@google/adk': path.resolve(__dirname, './core/src'),
'@google/adk-integrations': path.resolve(
Expand All @@ -36,6 +40,10 @@ export default defineConfig({
test: {
name: 'unit:dev',
environment: 'node',
setupFiles: [
'./tests/hermetic_env_setup.ts',
'./tests/setup_log_level.ts',
],
alias: {
'@google/adk': path.resolve(__dirname, './core/src'),
'@google/adk-integrations': path.resolve(
Expand All @@ -50,6 +58,10 @@ export default defineConfig({
test: {
name: 'unit:integrations',
environment: 'node',
setupFiles: [
'./tests/hermetic_env_setup.ts',
'./tests/setup_log_level.ts',
],
alias: {
'@google/adk': path.resolve(__dirname, './core/src'),
'@google/adk-integrations': path.resolve(
Expand All @@ -64,6 +76,7 @@ export default defineConfig({
test: {
name: 'integration',
environment: 'node',
setupFiles: ['./tests/setup_log_level.ts'],
alias: {
'@google/adk': path.resolve(__dirname, './core/src'),
'@google/adk-integrations': path.resolve(
Expand All @@ -78,6 +91,7 @@ export default defineConfig({
test: {
name: 'e2e',
environment: 'node',
setupFiles: ['./tests/setup_log_level.ts'],
alias: {
'@google/adk': path.resolve(__dirname, './core/src'),
'@google/adk-integrations': path.resolve(
Expand All @@ -92,6 +106,7 @@ export default defineConfig({
test: {
name: 'cross-language',
environment: 'node',
setupFiles: ['./tests/setup_log_level.ts'],
alias: {
'@google/adk': path.resolve(__dirname, './core/src'),
'@google/adk-integrations': path.resolve(
Expand Down Expand Up @@ -121,6 +136,5 @@ export default defineConfig({
lines: 86,
},
},
globalSetup: ['./tests/global_setup.ts'],
},
});
Loading