test: add tests for confirmation-bus, prompt-registry, and cli/core modules (#2272)

* test: add tests for confirmation-bus, prompt-registry, and cli/core modules

Add 42 new tests covering previously untested core modules:
- MessageBus: publish, subscribe/unsubscribe, request-response pattern (13 tests)
- PromptRegistry: register, dedup, query by server, clear, remove (11 tests)
- performInitialAuth: success, failure, no authType cases (3 tests)
- validateTheme: found, not found, no config cases (4 tests)
- initializeApp: i18n, auth, theme, IDE mode, auth dialog logic (11 tests)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: improve test quality - copyright headers, env safety, cleanup

- Fix copyright headers from Google LLC to Qwen Code in all 5 test files
- Use vi.stubEnv() instead of manual process.env mutation in initializer test
- Add removeAllListeners() cleanup in message-bus debug test
- Add void prefix to un-awaited publish() calls in message-bus test
- Verify invoke reference preserved after prompt rename in prompt-registry test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(message-bus): add AbortSignal coverage for request()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Shaojin Wen 2026-04-09 13:54:59 +08:00 committed by GitHub
parent e0aeee5414
commit d22b7e61ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 740 additions and 75 deletions

View file

@ -0,0 +1,66 @@
/**
* @license
* Copyright 2025 Qwen Code
* SPDX-License-Identifier: Apache-2.0
*/
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { performInitialAuth } from './auth.js';
const mockLogAuth = vi.fn();
vi.mock('@qwen-code/qwen-code-core', () => ({
getErrorMessage: (e: unknown) => (e instanceof Error ? e.message : String(e)),
logAuth: (...args: unknown[]) => mockLogAuth(...args),
AuthEvent: vi.fn().mockImplementation((type, method, status, message?) => ({
type,
method,
status,
message,
})),
}));
describe('performInitialAuth', () => {
let mockConfig: {
refreshAuth: ReturnType<typeof vi.fn>;
};
beforeEach(() => {
vi.clearAllMocks();
mockConfig = {
refreshAuth: vi.fn(),
};
});
it('should return null when authType is undefined', async () => {
const result = await performInitialAuth(mockConfig as never, undefined);
expect(result).toBeNull();
expect(mockConfig.refreshAuth).not.toHaveBeenCalled();
expect(mockLogAuth).not.toHaveBeenCalled();
});
it('should return null on successful authentication', async () => {
mockConfig.refreshAuth.mockResolvedValue(undefined);
const result = await performInitialAuth(
mockConfig as never,
'api_key' as never,
);
expect(result).toBeNull();
expect(mockConfig.refreshAuth).toHaveBeenCalledWith('api_key', true);
expect(mockLogAuth).toHaveBeenCalledTimes(1);
});
it('should return error message on authentication failure', async () => {
mockConfig.refreshAuth.mockRejectedValue(new Error('Invalid API key'));
const result = await performInitialAuth(
mockConfig as never,
'api_key' as never,
);
expect(result).toBe('Failed to login. Message: Invalid API key');
expect(mockLogAuth).toHaveBeenCalledTimes(1);
});
});

View file

@ -1,109 +1,190 @@
/**
* @license
* Copyright 2025 Google LLC
* Copyright 2025 Qwen Code
* 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 { beforeEach, describe, expect, it, vi } from 'vitest';
import { initializeApp } from './initializer.js';
import { performInitialAuth } from './auth.js';
import { validateTheme } from './theme.js';
import { initializeI18n } from '../i18n/index.js';
const mockPerformInitialAuth = vi.fn();
const mockValidateTheme = vi.fn();
const mockInitializeI18n = vi.fn();
vi.mock('./auth.js', () => ({
performInitialAuth: vi.fn(),
performInitialAuth: (...args: unknown[]) => mockPerformInitialAuth(...args),
}));
vi.mock('./theme.js', () => ({
validateTheme: vi.fn(),
validateTheme: (...args: unknown[]) => mockValidateTheme(...args),
}));
vi.mock('../i18n/index.js', () => ({
initializeI18n: vi.fn(),
initializeI18n: (...args: unknown[]) => mockInitializeI18n(...args),
}));
const mockConnect = vi.fn();
const mockGetInstance = vi.fn().mockResolvedValue({ connect: mockConnect });
const mockLogIdeConnection = vi.fn();
vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {
const actual = await importOriginal<Record<string, unknown>>();
return {
...actual,
IdeClient: { getInstance: () => mockGetInstance() },
IdeConnectionEvent: vi.fn().mockImplementation((type) => ({ type })),
IdeConnectionType: { START: 'start' },
logIdeConnection: (...args: unknown[]) => mockLogIdeConnection(...args),
};
});
describe('initializeApp', () => {
let mockConfig: {
getModelsConfig: ReturnType<typeof vi.fn>;
getIdeMode: ReturnType<typeof vi.fn>;
getGeminiMdFileCount: ReturnType<typeof vi.fn>;
};
let mockSettings: {
merged: Record<string, unknown>;
setValue: ReturnType<typeof vi.fn>;
};
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 {
mockConfig = {
getModelsConfig: vi.fn().mockReturnValue({
getCurrentAuthType: vi.fn().mockReturnValue(authType),
wasAuthTypeExplicitlyProvided: vi
.fn()
.mockReturnValue(wasAuthTypeExplicitlyProvided),
getCurrentAuthType: vi.fn().mockReturnValue('api_key'),
wasAuthTypeExplicitlyProvided: vi.fn().mockReturnValue(false),
}),
getIdeMode: vi.fn().mockReturnValue(ideMode),
getGeminiMdFileCount: vi.fn().mockReturnValue(geminiMdFileCount),
} as unknown as Config;
}
getIdeMode: vi.fn().mockReturnValue(false),
getGeminiMdFileCount: vi.fn().mockReturnValue(0),
};
function createMockSettings(): LoadedSettings {
return {
merged: {
general: {
language: 'en',
},
},
mockSettings = {
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();
mockPerformInitialAuth.mockResolvedValue(null);
mockValidateTheme.mockReturnValue(null);
mockInitializeI18n.mockResolvedValue(undefined);
});
it('should not open auth dialog when auth is explicit and succeeds', async () => {
vi.mocked(performInitialAuth).mockResolvedValue(null);
it('should initialize i18n with language from settings', async () => {
await initializeApp(mockConfig as never, mockSettings as never);
const config = createMockConfig({
authType: AuthType.USE_OPENAI,
wasAuthTypeExplicitlyProvided: true,
});
const settings = createMockSettings();
expect(mockInitializeI18n).toHaveBeenCalledWith('en');
});
const result = await initializeApp(config, settings);
it('should initialize i18n with QWEN_CODE_LANG env var if set', async () => {
vi.stubEnv('QWEN_CODE_LANG', 'zh');
await initializeApp(mockConfig as never, mockSettings as never);
expect(mockInitializeI18n).toHaveBeenCalledWith('zh');
vi.unstubAllEnvs();
});
it('should return no errors on successful initialization', async () => {
const result = await initializeApp(
mockConfig as never,
mockSettings as never,
);
expect(result.authError).toBeNull();
expect(result.themeError).toBeNull();
expect(result.geminiMdFileCount).toBe(0);
});
it('should return authError when auth fails', async () => {
mockPerformInitialAuth.mockResolvedValue('Auth failed');
const result = await initializeApp(
mockConfig as never,
mockSettings as never,
);
expect(result.authError).toBe('Auth failed');
expect(result.shouldOpenAuthDialog).toBe(true);
// initializeApp does not clear the selected auth type on failure
expect(mockSettings.setValue).not.toHaveBeenCalled();
});
it('should return themeError when theme validation fails', async () => {
mockValidateTheme.mockReturnValue('Theme not found');
const result = await initializeApp(
mockConfig as never,
mockSettings as never,
);
expect(result.themeError).toBe('Theme not found');
});
it('should set shouldOpenAuthDialog when auth was not explicitly provided', async () => {
mockConfig
.getModelsConfig()
.wasAuthTypeExplicitlyProvided.mockReturnValue(false);
const result = await initializeApp(
mockConfig as never,
mockSettings as never,
);
expect(result.shouldOpenAuthDialog).toBe(true);
});
it('should set shouldOpenAuthDialog when auth error occurs', async () => {
mockConfig
.getModelsConfig()
.wasAuthTypeExplicitlyProvided.mockReturnValue(true);
mockPerformInitialAuth.mockResolvedValue('Auth failed');
const result = await initializeApp(
mockConfig as never,
mockSettings as never,
);
expect(result.shouldOpenAuthDialog).toBe(true);
});
it('should not open auth dialog when auth was explicitly provided and succeeds', async () => {
mockConfig
.getModelsConfig()
.wasAuthTypeExplicitlyProvided.mockReturnValue(true);
const result = await initializeApp(
mockConfig as never,
mockSettings as never,
);
expect(result.shouldOpenAuthDialog).toBe(false);
});
it('should connect to IDE when in IDE mode', async () => {
mockConfig.getIdeMode.mockReturnValue(true);
await initializeApp(mockConfig as never, mockSettings as never);
expect(mockGetInstance).toHaveBeenCalled();
expect(mockConnect).toHaveBeenCalled();
expect(mockLogIdeConnection).toHaveBeenCalled();
});
it('should not connect to IDE when not in IDE mode', async () => {
mockConfig.getIdeMode.mockReturnValue(false);
await initializeApp(mockConfig as never, mockSettings as never);
expect(mockGetInstance).not.toHaveBeenCalled();
});
it('should default language to auto when no setting is provided', async () => {
mockSettings.merged = {};
await initializeApp(mockConfig as never, mockSettings as never);
expect(mockInitializeI18n).toHaveBeenCalledWith('auto');
});
});

View file

@ -0,0 +1,64 @@
/**
* @license
* Copyright 2025 Qwen Code
* SPDX-License-Identifier: Apache-2.0
*/
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { validateTheme } from './theme.js';
const mockFindThemeByName = vi.fn();
vi.mock('../ui/themes/theme-manager.js', () => ({
themeManager: {
findThemeByName: (...args: unknown[]) => mockFindThemeByName(...args),
},
}));
vi.mock('../i18n/index.js', () => ({
t: (msg: string, params?: Record<string, string>) => {
if (params) {
return msg.replace(
/\{\{(\w+)\}\}/g,
(_, key) => params[key] ?? `{{${key}}}`,
);
}
return msg;
},
}));
describe('validateTheme', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should return null when no theme is configured', () => {
const settings = { merged: { ui: {} } };
const result = validateTheme(settings as never);
expect(result).toBeNull();
});
it('should return null when theme is found', () => {
mockFindThemeByName.mockReturnValue({ name: 'dark' });
const settings = { merged: { ui: { theme: 'dark' } } };
const result = validateTheme(settings as never);
expect(result).toBeNull();
expect(mockFindThemeByName).toHaveBeenCalledWith('dark');
});
it('should return error message when theme is not found', () => {
mockFindThemeByName.mockReturnValue(undefined);
const settings = { merged: { ui: { theme: 'nonexistent-theme' } } };
const result = validateTheme(settings as never);
expect(result).toBe('Theme "nonexistent-theme" not found.');
});
it('should return null when ui section is undefined', () => {
const settings = { merged: {} };
const result = validateTheme(settings as never);
expect(result).toBeNull();
});
});