From b010250372bcfa4f5c8f8260d7254d0ce596131c Mon Sep 17 00:00:00 2001 From: zy6p Date: Wed, 4 Mar 2026 16:13:24 +0800 Subject: [PATCH] fix(cli): preserve selected auth type on startup auth failure --- packages/cli/src/core/initializer.test.ts | 109 ++++++++++++++++++++++ packages/cli/src/core/initializer.ts | 10 +- 2 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 packages/cli/src/core/initializer.test.ts diff --git a/packages/cli/src/core/initializer.test.ts b/packages/cli/src/core/initializer.test.ts new file mode 100644 index 000000000..7b1b92696 --- /dev/null +++ b/packages/cli/src/core/initializer.test.ts @@ -0,0 +1,109 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { AuthType } from '@qwen-code/qwen-code-core'; +import type { LoadedSettings } from '../config/settings.js'; +import type { Config } from '@qwen-code/qwen-code-core'; +import { initializeApp } from './initializer.js'; +import { performInitialAuth } from './auth.js'; +import { validateTheme } from './theme.js'; +import { initializeI18n } from '../i18n/index.js'; + +vi.mock('./auth.js', () => ({ + performInitialAuth: vi.fn(), +})); + +vi.mock('./theme.js', () => ({ + validateTheme: vi.fn(), +})); + +vi.mock('../i18n/index.js', () => ({ + initializeI18n: vi.fn(), +})); + +describe('initializeApp', () => { + beforeEach(() => { + vi.clearAllMocks(); + delete process.env['QWEN_CODE_LANG']; + + vi.mocked(initializeI18n).mockResolvedValue(undefined); + vi.mocked(validateTheme).mockReturnValue(null); + }); + + function createMockConfig( + options: { + authType?: AuthType; + wasAuthTypeExplicitlyProvided?: boolean; + geminiMdFileCount?: number; + ideMode?: boolean; + } = {}, + ): Config { + const { + authType = AuthType.USE_OPENAI, + wasAuthTypeExplicitlyProvided = true, + geminiMdFileCount = 0, + ideMode = false, + } = options; + + return { + getModelsConfig: vi.fn().mockReturnValue({ + getCurrentAuthType: vi.fn().mockReturnValue(authType), + wasAuthTypeExplicitlyProvided: vi + .fn() + .mockReturnValue(wasAuthTypeExplicitlyProvided), + }), + getIdeMode: vi.fn().mockReturnValue(ideMode), + getGeminiMdFileCount: vi.fn().mockReturnValue(geminiMdFileCount), + } as unknown as Config; + } + + function createMockSettings(): LoadedSettings { + return { + merged: { + general: { + language: 'en', + }, + }, + setValue: vi.fn(), + } as unknown as LoadedSettings; + } + + it('should not clear selected auth type when initial auth fails', async () => { + vi.mocked(performInitialAuth).mockResolvedValue( + 'Failed to login. Message: missing OLLAMA_API_KEY', + ); + + const config = createMockConfig({ + authType: AuthType.USE_OPENAI, + wasAuthTypeExplicitlyProvided: true, + }); + const settings = createMockSettings(); + + const result = await initializeApp(config, settings); + + expect(result.authError).toBe( + 'Failed to login. Message: missing OLLAMA_API_KEY', + ); + expect(result.shouldOpenAuthDialog).toBe(true); + expect(settings.setValue).not.toHaveBeenCalled(); + }); + + it('should not open auth dialog when auth is explicit and succeeds', async () => { + vi.mocked(performInitialAuth).mockResolvedValue(null); + + const config = createMockConfig({ + authType: AuthType.USE_OPENAI, + wasAuthTypeExplicitlyProvided: true, + }); + const settings = createMockSettings(); + + const result = await initializeApp(config, settings); + + expect(result.authError).toBeNull(); + expect(result.shouldOpenAuthDialog).toBe(false); + }); +}); diff --git a/packages/cli/src/core/initializer.ts b/packages/cli/src/core/initializer.ts index 25825ce6d..ce16d1941 100644 --- a/packages/cli/src/core/initializer.ts +++ b/packages/cli/src/core/initializer.ts @@ -11,7 +11,7 @@ import { logIdeConnection, type Config, } from '@qwen-code/qwen-code-core'; -import { type LoadedSettings, SettingScope } from '../config/settings.js'; +import { type LoadedSettings } from '../config/settings.js'; import { performInitialAuth } from './auth.js'; import { validateTheme } from './theme.js'; import { initializeI18n, type SupportedLanguage } from '../i18n/index.js'; @@ -46,14 +46,6 @@ export async function initializeApp( const authType = config.getModelsConfig().getCurrentAuthType(); const authError = await performInitialAuth(config, authType); - // Fallback to user select when initial authentication fails - if (authError) { - settings.setValue( - SettingScope.User, - 'security.auth.selectedType', - undefined, - ); - } const themeError = validateTheme(settings); const shouldOpenAuthDialog =