2025-07-22 10:52:40 -04:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright 2025 Google LLC
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
2025-10-18 20:43:19 -07:00
|
|
|
import {
|
|
|
|
|
describe,
|
|
|
|
|
it,
|
|
|
|
|
expect,
|
|
|
|
|
vi,
|
|
|
|
|
beforeEach,
|
|
|
|
|
afterEach,
|
|
|
|
|
type MockInstance,
|
|
|
|
|
type Mock,
|
|
|
|
|
} from 'vitest';
|
2025-08-26 00:04:53 +02:00
|
|
|
import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
|
2025-10-18 20:43:19 -07:00
|
|
|
import {
|
|
|
|
|
AuthType,
|
|
|
|
|
OutputFormat,
|
|
|
|
|
makeFakeConfig,
|
|
|
|
|
} from '@google/gemini-cli-core';
|
2025-09-13 08:18:40 +09:00
|
|
|
import type { Config } from '@google/gemini-cli-core';
|
2025-08-01 11:49:03 -07:00
|
|
|
import * as auth from './config/auth.js';
|
2025-09-03 15:33:37 -07:00
|
|
|
import { type LoadedSettings } from './config/settings.js';
|
2025-07-22 10:52:40 -04:00
|
|
|
|
2025-10-18 20:43:19 -07:00
|
|
|
function createLocalMockConfig(overrides: Partial<Config> = {}): Config {
|
|
|
|
|
const config = makeFakeConfig();
|
|
|
|
|
Object.assign(config, overrides);
|
|
|
|
|
return config;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-22 10:52:40 -04:00
|
|
|
describe('validateNonInterActiveAuth', () => {
|
|
|
|
|
let originalEnvGeminiApiKey: string | undefined;
|
|
|
|
|
let originalEnvVertexAi: string | undefined;
|
2025-07-25 10:19:38 -07:00
|
|
|
let originalEnvGcp: string | undefined;
|
2025-07-22 10:52:40 -04:00
|
|
|
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
2025-10-18 20:43:19 -07:00
|
|
|
let processExitSpy: MockInstance;
|
|
|
|
|
let refreshAuthMock: Mock;
|
2025-09-03 15:33:37 -07:00
|
|
|
let mockSettings: LoadedSettings;
|
2025-07-22 10:52:40 -04:00
|
|
|
|
|
|
|
|
beforeEach(() => {
|
2025-08-17 12:43:21 -04:00
|
|
|
originalEnvGeminiApiKey = process.env['GEMINI_API_KEY'];
|
|
|
|
|
originalEnvVertexAi = process.env['GOOGLE_GENAI_USE_VERTEXAI'];
|
|
|
|
|
originalEnvGcp = process.env['GOOGLE_GENAI_USE_GCA'];
|
|
|
|
|
delete process.env['GEMINI_API_KEY'];
|
|
|
|
|
delete process.env['GOOGLE_GENAI_USE_VERTEXAI'];
|
|
|
|
|
delete process.env['GOOGLE_GENAI_USE_GCA'];
|
2025-07-22 10:52:40 -04:00
|
|
|
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
2025-10-18 20:43:19 -07:00
|
|
|
processExitSpy = vi
|
|
|
|
|
.spyOn(process, 'exit')
|
|
|
|
|
.mockImplementation((code?: string | number | null | undefined) => {
|
|
|
|
|
throw new Error(`process.exit(${code}) called`);
|
|
|
|
|
});
|
2025-10-08 14:21:23 -07:00
|
|
|
vi.spyOn(auth, 'validateAuthMethod').mockReturnValue(null);
|
2025-10-21 16:35:22 -04:00
|
|
|
refreshAuthMock = vi.fn().mockImplementation(async () => 'refreshed');
|
2025-09-03 15:33:37 -07:00
|
|
|
mockSettings = {
|
|
|
|
|
system: { path: '', settings: {} },
|
|
|
|
|
systemDefaults: { path: '', settings: {} },
|
|
|
|
|
user: { path: '', settings: {} },
|
|
|
|
|
workspace: { path: '', settings: {} },
|
|
|
|
|
errors: [],
|
|
|
|
|
setValue: vi.fn(),
|
|
|
|
|
merged: {
|
|
|
|
|
security: {
|
|
|
|
|
auth: {
|
|
|
|
|
enforcedType: undefined,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
isTrusted: true,
|
|
|
|
|
migratedInMemorScopes: new Set(),
|
|
|
|
|
forScope: vi.fn(),
|
|
|
|
|
computeMergedSettings: vi.fn(),
|
|
|
|
|
} as unknown as LoadedSettings;
|
2025-07-22 10:52:40 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
if (originalEnvGeminiApiKey !== undefined) {
|
2025-08-17 12:43:21 -04:00
|
|
|
process.env['GEMINI_API_KEY'] = originalEnvGeminiApiKey;
|
2025-07-22 10:52:40 -04:00
|
|
|
} else {
|
2025-08-17 12:43:21 -04:00
|
|
|
delete process.env['GEMINI_API_KEY'];
|
2025-07-22 10:52:40 -04:00
|
|
|
}
|
|
|
|
|
if (originalEnvVertexAi !== undefined) {
|
2025-08-17 12:43:21 -04:00
|
|
|
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = originalEnvVertexAi;
|
2025-07-22 10:52:40 -04:00
|
|
|
} else {
|
2025-08-17 12:43:21 -04:00
|
|
|
delete process.env['GOOGLE_GENAI_USE_VERTEXAI'];
|
2025-07-22 10:52:40 -04:00
|
|
|
}
|
2025-07-25 10:19:38 -07:00
|
|
|
if (originalEnvGcp !== undefined) {
|
2025-08-17 12:43:21 -04:00
|
|
|
process.env['GOOGLE_GENAI_USE_GCA'] = originalEnvGcp;
|
2025-07-25 10:19:38 -07:00
|
|
|
} else {
|
2025-08-17 12:43:21 -04:00
|
|
|
delete process.env['GOOGLE_GENAI_USE_GCA'];
|
2025-07-25 10:19:38 -07:00
|
|
|
}
|
2025-07-22 10:52:40 -04:00
|
|
|
vi.restoreAllMocks();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('exits if no auth type is configured or env vars set', async () => {
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-07-22 10:52:40 -04:00
|
|
|
refreshAuth: refreshAuthMock,
|
2025-09-13 08:18:40 +09:00
|
|
|
getOutputFormat: vi.fn().mockReturnValue(OutputFormat.TEXT),
|
|
|
|
|
getContentGeneratorConfig: vi
|
|
|
|
|
.fn()
|
|
|
|
|
.mockReturnValue({ authType: undefined }),
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-07-22 10:52:40 -04:00
|
|
|
try {
|
2025-08-01 11:49:03 -07:00
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
undefined,
|
|
|
|
|
undefined,
|
|
|
|
|
nonInteractiveConfig,
|
2025-09-03 15:33:37 -07:00
|
|
|
mockSettings,
|
2025-08-01 11:49:03 -07:00
|
|
|
);
|
2025-07-22 10:52:40 -04:00
|
|
|
expect.fail('Should have exited');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
expect((e as Error).message).toContain('process.exit(1) called');
|
|
|
|
|
}
|
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
|
|
|
expect.stringContaining('Please set an Auth method'),
|
|
|
|
|
);
|
|
|
|
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
|
|
|
});
|
|
|
|
|
|
2025-07-25 10:19:38 -07:00
|
|
|
it('uses LOGIN_WITH_GOOGLE if GOOGLE_GENAI_USE_GCA is set', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
process.env['GOOGLE_GENAI_USE_GCA'] = 'true';
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-07-25 10:19:38 -07:00
|
|
|
refreshAuth: refreshAuthMock,
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-08-01 11:49:03 -07:00
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
undefined,
|
|
|
|
|
undefined,
|
|
|
|
|
nonInteractiveConfig,
|
2025-09-03 15:33:37 -07:00
|
|
|
mockSettings,
|
2025-08-01 11:49:03 -07:00
|
|
|
);
|
2025-07-25 10:19:38 -07:00
|
|
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.LOGIN_WITH_GOOGLE);
|
|
|
|
|
});
|
|
|
|
|
|
2025-07-22 10:52:40 -04:00
|
|
|
it('uses USE_GEMINI if GEMINI_API_KEY is set', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
process.env['GEMINI_API_KEY'] = 'fake-key';
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-07-22 10:52:40 -04:00
|
|
|
refreshAuth: refreshAuthMock,
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-08-01 11:49:03 -07:00
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
undefined,
|
|
|
|
|
undefined,
|
|
|
|
|
nonInteractiveConfig,
|
2025-09-03 15:33:37 -07:00
|
|
|
mockSettings,
|
2025-08-01 11:49:03 -07:00
|
|
|
);
|
2025-07-22 10:52:40 -04:00
|
|
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_GEMINI);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('uses USE_VERTEX_AI if GOOGLE_GENAI_USE_VERTEXAI is true (with GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION)', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
|
|
|
|
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project';
|
|
|
|
|
process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1';
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-07-22 10:52:40 -04:00
|
|
|
refreshAuth: refreshAuthMock,
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-08-01 11:49:03 -07:00
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
undefined,
|
|
|
|
|
undefined,
|
|
|
|
|
nonInteractiveConfig,
|
2025-09-03 15:33:37 -07:00
|
|
|
mockSettings,
|
2025-08-01 11:49:03 -07:00
|
|
|
);
|
2025-07-22 10:52:40 -04:00
|
|
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_VERTEX_AI);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('uses USE_VERTEX_AI if GOOGLE_GENAI_USE_VERTEXAI is true and GOOGLE_API_KEY is set', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
|
|
|
|
process.env['GOOGLE_API_KEY'] = 'vertex-api-key';
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-07-22 10:52:40 -04:00
|
|
|
refreshAuth: refreshAuthMock,
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-08-01 11:49:03 -07:00
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
undefined,
|
|
|
|
|
undefined,
|
|
|
|
|
nonInteractiveConfig,
|
2025-09-03 15:33:37 -07:00
|
|
|
mockSettings,
|
2025-08-01 11:49:03 -07:00
|
|
|
);
|
2025-07-22 10:52:40 -04:00
|
|
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_VERTEX_AI);
|
|
|
|
|
});
|
|
|
|
|
|
2025-07-25 10:19:38 -07:00
|
|
|
it('uses LOGIN_WITH_GOOGLE if GOOGLE_GENAI_USE_GCA is set, even with other env vars', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
process.env['GOOGLE_GENAI_USE_GCA'] = 'true';
|
|
|
|
|
process.env['GEMINI_API_KEY'] = 'fake-key';
|
|
|
|
|
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
|
|
|
|
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project';
|
|
|
|
|
process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1';
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-07-25 10:19:38 -07:00
|
|
|
refreshAuth: refreshAuthMock,
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-08-01 11:49:03 -07:00
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
undefined,
|
|
|
|
|
undefined,
|
|
|
|
|
nonInteractiveConfig,
|
2025-09-03 15:33:37 -07:00
|
|
|
mockSettings,
|
2025-08-01 11:49:03 -07:00
|
|
|
);
|
2025-07-25 10:19:38 -07:00
|
|
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.LOGIN_WITH_GOOGLE);
|
|
|
|
|
});
|
|
|
|
|
|
2025-07-22 10:52:40 -04:00
|
|
|
it('uses USE_VERTEX_AI if both GEMINI_API_KEY and GOOGLE_GENAI_USE_VERTEXAI are set', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
process.env['GEMINI_API_KEY'] = 'fake-key';
|
|
|
|
|
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
|
|
|
|
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project';
|
|
|
|
|
process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1';
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-07-22 10:52:40 -04:00
|
|
|
refreshAuth: refreshAuthMock,
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-08-01 11:49:03 -07:00
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
undefined,
|
|
|
|
|
undefined,
|
|
|
|
|
nonInteractiveConfig,
|
2025-09-03 15:33:37 -07:00
|
|
|
mockSettings,
|
2025-08-01 11:49:03 -07:00
|
|
|
);
|
2025-07-22 10:52:40 -04:00
|
|
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_VERTEX_AI);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('uses USE_GEMINI if GOOGLE_GENAI_USE_VERTEXAI is false, GEMINI_API_KEY is set, and project/location are available', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'false';
|
|
|
|
|
process.env['GEMINI_API_KEY'] = 'fake-key';
|
|
|
|
|
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project';
|
|
|
|
|
process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1';
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-07-22 10:52:40 -04:00
|
|
|
refreshAuth: refreshAuthMock,
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-08-01 11:49:03 -07:00
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
undefined,
|
|
|
|
|
undefined,
|
|
|
|
|
nonInteractiveConfig,
|
2025-09-03 15:33:37 -07:00
|
|
|
mockSettings,
|
2025-08-01 11:49:03 -07:00
|
|
|
);
|
2025-07-22 10:52:40 -04:00
|
|
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_GEMINI);
|
|
|
|
|
});
|
|
|
|
|
|
2025-10-10 16:50:54 -07:00
|
|
|
it('uses configuredAuthType over environment variables', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
process.env['GEMINI_API_KEY'] = 'fake-key';
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-07-22 10:52:40 -04:00
|
|
|
refreshAuth: refreshAuthMock,
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-08-01 11:49:03 -07:00
|
|
|
await validateNonInteractiveAuth(
|
2025-10-10 16:50:54 -07:00
|
|
|
AuthType.LOGIN_WITH_GOOGLE,
|
2025-08-01 11:49:03 -07:00
|
|
|
undefined,
|
|
|
|
|
nonInteractiveConfig,
|
2025-09-03 15:33:37 -07:00
|
|
|
mockSettings,
|
2025-08-01 11:49:03 -07:00
|
|
|
);
|
2025-10-10 16:50:54 -07:00
|
|
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.LOGIN_WITH_GOOGLE);
|
2025-07-22 10:52:40 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('exits if validateAuthMethod returns error', async () => {
|
|
|
|
|
// Mock validateAuthMethod to return error
|
2025-08-01 11:49:03 -07:00
|
|
|
vi.spyOn(auth, 'validateAuthMethod').mockReturnValue('Auth error!');
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-07-22 10:52:40 -04:00
|
|
|
refreshAuth: refreshAuthMock,
|
2025-09-13 08:18:40 +09:00
|
|
|
getOutputFormat: vi.fn().mockReturnValue(OutputFormat.TEXT),
|
|
|
|
|
getContentGeneratorConfig: vi
|
|
|
|
|
.fn()
|
|
|
|
|
.mockReturnValue({ authType: undefined }),
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-07-22 10:52:40 -04:00
|
|
|
try {
|
|
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
AuthType.USE_GEMINI,
|
2025-08-01 11:49:03 -07:00
|
|
|
undefined,
|
2025-07-22 10:52:40 -04:00
|
|
|
nonInteractiveConfig,
|
2025-09-03 15:33:37 -07:00
|
|
|
mockSettings,
|
2025-07-22 10:52:40 -04:00
|
|
|
);
|
|
|
|
|
expect.fail('Should have exited');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
expect((e as Error).message).toContain('process.exit(1) called');
|
|
|
|
|
}
|
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Auth error!');
|
|
|
|
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
|
|
|
});
|
2025-08-01 11:49:03 -07:00
|
|
|
|
|
|
|
|
it('skips validation if useExternalAuth is true', async () => {
|
|
|
|
|
// Mock validateAuthMethod to return error to ensure it's not being called
|
|
|
|
|
const validateAuthMethodSpy = vi
|
|
|
|
|
.spyOn(auth, 'validateAuthMethod')
|
|
|
|
|
.mockReturnValue('Auth error!');
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-08-01 11:49:03 -07:00
|
|
|
refreshAuth: refreshAuthMock,
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-08-01 11:49:03 -07:00
|
|
|
// Even with an invalid auth type, it should not exit
|
|
|
|
|
// because validation is skipped.
|
|
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
'invalid-auth-type' as AuthType,
|
|
|
|
|
true, // useExternalAuth = true
|
|
|
|
|
nonInteractiveConfig,
|
2025-09-03 15:33:37 -07:00
|
|
|
mockSettings,
|
2025-08-01 11:49:03 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(validateAuthMethodSpy).not.toHaveBeenCalled();
|
|
|
|
|
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
|
|
|
expect(processExitSpy).not.toHaveBeenCalled();
|
|
|
|
|
// We still expect refreshAuth to be called with the (invalid) type
|
|
|
|
|
expect(refreshAuthMock).toHaveBeenCalledWith('invalid-auth-type');
|
|
|
|
|
});
|
2025-09-03 15:33:37 -07:00
|
|
|
|
2025-10-10 16:50:54 -07:00
|
|
|
it('succeeds if effectiveAuthType matches enforcedAuthType', async () => {
|
2025-10-18 20:43:19 -07:00
|
|
|
mockSettings.merged.security!.auth!.enforcedType = AuthType.USE_GEMINI;
|
2025-09-03 15:33:37 -07:00
|
|
|
process.env['GEMINI_API_KEY'] = 'fake-key';
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-09-03 15:33:37 -07:00
|
|
|
refreshAuth: refreshAuthMock,
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-09-03 15:33:37 -07:00
|
|
|
await validateNonInteractiveAuth(
|
2025-10-10 16:50:54 -07:00
|
|
|
undefined,
|
2025-09-03 15:33:37 -07:00
|
|
|
undefined,
|
|
|
|
|
nonInteractiveConfig,
|
|
|
|
|
mockSettings,
|
|
|
|
|
);
|
|
|
|
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_GEMINI);
|
|
|
|
|
});
|
|
|
|
|
|
2025-10-10 16:50:54 -07:00
|
|
|
it('exits if configuredAuthType does not match enforcedAuthType', async () => {
|
2025-10-18 20:43:19 -07:00
|
|
|
mockSettings.merged.security!.auth!.enforcedType =
|
|
|
|
|
AuthType.LOGIN_WITH_GOOGLE;
|
|
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-09-03 15:33:37 -07:00
|
|
|
refreshAuth: refreshAuthMock,
|
2025-09-13 08:18:40 +09:00
|
|
|
getOutputFormat: vi.fn().mockReturnValue(OutputFormat.TEXT),
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-09-03 15:33:37 -07:00
|
|
|
try {
|
|
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
AuthType.USE_GEMINI,
|
|
|
|
|
undefined,
|
|
|
|
|
nonInteractiveConfig,
|
|
|
|
|
mockSettings,
|
|
|
|
|
);
|
|
|
|
|
expect.fail('Should have exited');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
expect((e as Error).message).toContain('process.exit(1) called');
|
|
|
|
|
}
|
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
2025-10-10 16:50:54 -07:00
|
|
|
"The enforced authentication type is 'oauth-personal', but the current type is 'gemini-api-key'. Please re-authenticate with the correct type.",
|
|
|
|
|
);
|
|
|
|
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('exits if auth from env var does not match enforcedAuthType', async () => {
|
2025-10-18 20:43:19 -07:00
|
|
|
mockSettings.merged.security!.auth!.enforcedType =
|
|
|
|
|
AuthType.LOGIN_WITH_GOOGLE;
|
2025-10-10 16:50:54 -07:00
|
|
|
process.env['GEMINI_API_KEY'] = 'fake-key';
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-10-10 16:50:54 -07:00
|
|
|
refreshAuth: refreshAuthMock,
|
|
|
|
|
getOutputFormat: vi.fn().mockReturnValue(OutputFormat.TEXT),
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-10-10 16:50:54 -07:00
|
|
|
try {
|
|
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
undefined,
|
|
|
|
|
undefined,
|
|
|
|
|
nonInteractiveConfig,
|
|
|
|
|
mockSettings,
|
|
|
|
|
);
|
|
|
|
|
expect.fail('Should have exited');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
expect((e as Error).message).toContain('process.exit(1) called');
|
|
|
|
|
}
|
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
|
|
|
"The enforced authentication type is 'oauth-personal', but the current type is 'gemini-api-key'. Please re-authenticate with the correct type.",
|
2025-09-03 15:33:37 -07:00
|
|
|
);
|
|
|
|
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
|
|
|
});
|
2025-09-13 08:18:40 +09:00
|
|
|
|
|
|
|
|
describe('JSON output mode', () => {
|
|
|
|
|
it('prints JSON error when no auth is configured and exits with code 1', async () => {
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-09-13 08:18:40 +09:00
|
|
|
refreshAuth: refreshAuthMock,
|
|
|
|
|
getOutputFormat: vi.fn().mockReturnValue(OutputFormat.JSON),
|
|
|
|
|
getContentGeneratorConfig: vi
|
|
|
|
|
.fn()
|
|
|
|
|
.mockReturnValue({ authType: undefined }),
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-09-13 08:18:40 +09:00
|
|
|
|
|
|
|
|
let thrown: Error | undefined;
|
|
|
|
|
try {
|
|
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
undefined,
|
|
|
|
|
undefined,
|
2025-10-18 20:43:19 -07:00
|
|
|
nonInteractiveConfig,
|
2025-09-13 08:18:40 +09:00
|
|
|
mockSettings,
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
thrown = e as Error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(thrown?.message).toBe('process.exit(1) called');
|
|
|
|
|
const errorArg = consoleErrorSpy.mock.calls[0]?.[0] as string;
|
|
|
|
|
const payload = JSON.parse(errorArg);
|
|
|
|
|
expect(payload.error.type).toBe('Error');
|
|
|
|
|
expect(payload.error.code).toBe(1);
|
|
|
|
|
expect(payload.error.message).toContain(
|
|
|
|
|
'Please set an Auth method in your',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('prints JSON error when enforced auth mismatches current auth and exits with code 1', async () => {
|
2025-10-18 20:43:19 -07:00
|
|
|
mockSettings.merged.security!.auth!.enforcedType = AuthType.USE_GEMINI;
|
|
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-09-13 08:18:40 +09:00
|
|
|
refreshAuth: refreshAuthMock,
|
|
|
|
|
getOutputFormat: vi.fn().mockReturnValue(OutputFormat.JSON),
|
|
|
|
|
getContentGeneratorConfig: vi
|
|
|
|
|
.fn()
|
|
|
|
|
.mockReturnValue({ authType: undefined }),
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-09-13 08:18:40 +09:00
|
|
|
|
|
|
|
|
let thrown: Error | undefined;
|
|
|
|
|
try {
|
|
|
|
|
await validateNonInteractiveAuth(
|
2025-10-10 16:50:54 -07:00
|
|
|
AuthType.LOGIN_WITH_GOOGLE,
|
2025-09-13 08:18:40 +09:00
|
|
|
undefined,
|
2025-10-18 20:43:19 -07:00
|
|
|
nonInteractiveConfig,
|
2025-09-13 08:18:40 +09:00
|
|
|
mockSettings,
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
thrown = e as Error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(thrown?.message).toBe('process.exit(1) called');
|
|
|
|
|
{
|
|
|
|
|
const errorArg = consoleErrorSpy.mock.calls[0]?.[0] as string;
|
|
|
|
|
const payload = JSON.parse(errorArg);
|
|
|
|
|
expect(payload.error.type).toBe('Error');
|
|
|
|
|
expect(payload.error.code).toBe(1);
|
|
|
|
|
expect(payload.error.message).toContain(
|
2025-10-10 16:50:54 -07:00
|
|
|
"The enforced authentication type is 'gemini-api-key', but the current type is 'oauth-personal'. Please re-authenticate with the correct type.",
|
2025-09-13 08:18:40 +09:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('prints JSON error when validateAuthMethod fails and exits with code 1', async () => {
|
|
|
|
|
vi.spyOn(auth, 'validateAuthMethod').mockReturnValue('Auth error!');
|
|
|
|
|
process.env['GEMINI_API_KEY'] = 'fake-key';
|
|
|
|
|
|
2025-10-18 20:43:19 -07:00
|
|
|
const nonInteractiveConfig = createLocalMockConfig({
|
2025-09-13 08:18:40 +09:00
|
|
|
refreshAuth: refreshAuthMock,
|
|
|
|
|
getOutputFormat: vi.fn().mockReturnValue(OutputFormat.JSON),
|
|
|
|
|
getContentGeneratorConfig: vi
|
|
|
|
|
.fn()
|
|
|
|
|
.mockReturnValue({ authType: undefined }),
|
2025-10-18 20:43:19 -07:00
|
|
|
});
|
2025-09-13 08:18:40 +09:00
|
|
|
|
|
|
|
|
let thrown: Error | undefined;
|
|
|
|
|
try {
|
|
|
|
|
await validateNonInteractiveAuth(
|
|
|
|
|
AuthType.USE_GEMINI,
|
|
|
|
|
undefined,
|
2025-10-18 20:43:19 -07:00
|
|
|
nonInteractiveConfig,
|
2025-09-13 08:18:40 +09:00
|
|
|
mockSettings,
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
thrown = e as Error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(thrown?.message).toBe('process.exit(1) called');
|
|
|
|
|
{
|
|
|
|
|
const errorArg = consoleErrorSpy.mock.calls[0]?.[0] as string;
|
|
|
|
|
const payload = JSON.parse(errorArg);
|
|
|
|
|
expect(payload.error.type).toBe('Error');
|
|
|
|
|
expect(payload.error.code).toBe(1);
|
|
|
|
|
expect(payload.error.message).toBe('Auth error!');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-07-22 10:52:40 -04:00
|
|
|
});
|