Merge remote-tracking branch 'origin/main' into feat/debug-logging-refactor

This commit is contained in:
tanzhenxin 2026-02-05 20:23:48 +08:00
commit 4abec5c055
331 changed files with 19546 additions and 7771 deletions

View file

@ -10,7 +10,8 @@ import * as os from 'node:os';
import * as path from 'node:path';
import { Session } from './Session.js';
import type { Config, GeminiChat } from '@qwen-code/qwen-code-core';
import { ApprovalMode } from '@qwen-code/qwen-code-core';
import { ApprovalMode, AuthType } from '@qwen-code/qwen-code-core';
import * as core from '@qwen-code/qwen-code-core';
import type * as acp from '../acp.js';
import type { LoadedSettings } from '../../config/settings.js';
import * as nonInteractiveCliCommands from '../../nonInteractiveCliCommands.js';
@ -27,14 +28,19 @@ describe('Session', () => {
let mockSettings: LoadedSettings;
let session: Session;
let currentModel: string;
let setModelSpy: ReturnType<typeof vi.fn>;
let currentAuthType: AuthType;
let switchModelSpy: ReturnType<typeof vi.fn>;
let getAvailableCommandsSpy: ReturnType<typeof vi.fn>;
beforeEach(() => {
currentModel = 'qwen3-code-plus';
setModelSpy = vi.fn().mockImplementation(async (modelId: string) => {
currentModel = modelId;
});
currentAuthType = AuthType.USE_OPENAI;
switchModelSpy = vi
.fn()
.mockImplementation(async (authType: AuthType, modelId: string) => {
currentAuthType = authType;
currentModel = modelId;
});
mockChat = {
sendMessageStream: vi.fn(),
@ -46,7 +52,7 @@ describe('Session', () => {
mockConfig = {
setApprovalMode: vi.fn(),
setModel: setModelSpy,
switchModel: switchModelSpy,
getModel: vi.fn().mockImplementation(() => currentModel),
getSessionId: vi.fn().mockReturnValue('test-session-id'),
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(false),
@ -62,6 +68,7 @@ describe('Session', () => {
getEnableRecursiveFileSearch: vi.fn().mockReturnValue(false),
getTargetDir: vi.fn().mockReturnValue(process.cwd()),
getDebugMode: vi.fn().mockReturnValue(false),
getAuthType: vi.fn().mockImplementation(() => currentAuthType),
} as unknown as Config;
mockClient = {
@ -108,17 +115,21 @@ describe('Session', () => {
describe('setModel', () => {
it('sets model via config and returns current model', async () => {
const requested = `qwen3-coder-plus(${AuthType.USE_OPENAI})`;
const result = await session.setModel({
sessionId: 'test-session-id',
modelId: ' qwen3-coder-plus ',
modelId: ` ${requested} `,
});
expect(mockConfig.setModel).toHaveBeenCalledWith('qwen3-coder-plus', {
reason: 'user_request_acp',
context: 'session/set_model',
});
expect(mockConfig.switchModel).toHaveBeenCalledWith(
AuthType.USE_OPENAI,
'qwen3-coder-plus',
undefined,
);
expect(mockConfig.getModel).toHaveBeenCalled();
expect(result).toEqual({ modelId: 'qwen3-coder-plus' });
expect(result).toEqual({
modelId: `qwen3-coder-plus(${AuthType.USE_OPENAI})`,
});
});
it('rejects empty/whitespace model IDs', async () => {
@ -129,17 +140,17 @@ describe('Session', () => {
}),
).rejects.toThrow('Invalid params');
expect(mockConfig.setModel).not.toHaveBeenCalled();
expect(mockConfig.switchModel).not.toHaveBeenCalled();
});
it('propagates errors from config.setModel', async () => {
it('propagates errors from config.switchModel', async () => {
const configError = new Error('Invalid model');
setModelSpy.mockRejectedValueOnce(configError);
switchModelSpy.mockRejectedValueOnce(configError);
await expect(
session.setModel({
sessionId: 'test-session-id',
modelId: 'invalid-model',
modelId: `invalid-model(${AuthType.USE_OPENAI})`,
}),
).rejects.toThrow('Invalid model');
});
@ -198,24 +209,14 @@ describe('Session', () => {
try {
await fs.writeFile(filePath, '# Test\n', 'utf8');
const readManyFilesTool = {
buildAndExecute: vi.fn().mockResolvedValue({
llmContent: 'file content',
returnDisplay: 'ok',
}),
};
const toolRegistry = {
getTool: vi.fn((name: string) =>
name === 'read_many_files' ? readManyFilesTool : undefined,
),
};
const fileService = {
shouldGitIgnoreFile: vi.fn().mockReturnValue(false),
};
const readManyFilesSpy = vi
.spyOn(core, 'readManyFiles')
.mockResolvedValue({
contentParts: 'file content',
files: [],
});
mockConfig.getTargetDir = vi.fn().mockReturnValue(tempDir);
mockConfig.getToolRegistry = vi.fn().mockReturnValue(toolRegistry);
mockConfig.getFileService = vi.fn().mockReturnValue(fileService);
mockChat.sendMessageStream = vi
.fn()
.mockResolvedValue((async function* () {})());
@ -234,10 +235,10 @@ describe('Session', () => {
await session.prompt(promptRequest);
expect(readManyFilesTool.buildAndExecute).toHaveBeenCalledWith(
{ paths: [fileName] },
expect.any(AbortSignal),
);
expect(readManyFilesSpy).toHaveBeenCalledWith(mockConfig, {
paths: [fileName],
signal: expect.any(AbortSignal),
});
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
}