Skip to content
Merged
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
16 changes: 8 additions & 8 deletions integration-tests/cli/acp-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,17 +508,17 @@ function setupAcpTest(
expect(modelOption).toBeDefined();
expect(modelOption!.currentValue).toBeTruthy();

// Test: Set model using set_config_option
// Use openai model to avoid auth issues
const openaiModel = newSession.models.availableModels.find((model) =>
model.modelId.includes('openai'),
);
expect(openaiModel).toBeDefined();
const modelId = modelOption!.currentValue;
expect(
newSession.models.availableModels.some(
(model) => model.modelId === modelId,
),
).toBe(true);
Comment on lines +511 to +516

const setModelResult = (await sendRequest('session/set_config_option', {
sessionId: newSession.sessionId,
configId: 'model',
value: openaiModel!.modelId,
value: modelId,
})) as {
configOptions: Array<{
id: string;
Expand All @@ -535,7 +535,7 @@ function setupAcpTest(
(opt) => opt.id === 'model',
);
expect(updatedModelOption).toBeDefined();
expect(updatedModelOption!.currentValue).toBe(openaiModel!.modelId);
expect(updatedModelOption!.currentValue).toBe(modelId);
} catch (e) {
if (stderr.length) {
console.error('Agent stderr:', stderr.join(''));
Expand Down
6 changes: 4 additions & 2 deletions integration-tests/cli/file-system.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ describe('file-system', () => {
await rig.setup('should correctly handle file paths with spaces');
const fileName = 'my test file.txt';

const result = await rig.run(`write "hello" to "${fileName}"`);
const result = await rig.run(
`Use write_file to write exactly "hello" to "${fileName}".`,
);

const foundToolCall = await rig.waitForToolCall('write_file');
if (!foundToolCall) {
Expand All @@ -108,7 +110,7 @@ describe('file-system', () => {
).toBeTruthy();

const newFileContent = rig.readFile(fileName);
expect(newFileContent).toBe('hello');
expect(newFileContent.trimEnd()).toBe('hello');
Comment on lines 112 to +113
});

it('should perform a read-then-write sequence', async () => {
Expand Down
22 changes: 14 additions & 8 deletions integration-tests/cli/qwen-config-dir.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import {
} from 'node:fs';
import { join, resolve } from 'node:path';

const CURRENT_SETTINGS_VERSION = 5;
Comment on lines 31 to +33

// Helper: list files under a directory recursively, returning relative paths
function listFilesRecursive(dir: string, base = dir): string[] {
if (!existsSync(dir)) return [];
Expand Down Expand Up @@ -217,9 +219,7 @@ describe('QWEN_HOME environment variable', () => {
);
const migrated = JSON.parse(migratedRaw) as Record<string, unknown>;

// Migration should have bumped the version to the current SETTINGS_VERSION
// (packages/cli/src/config/settings.ts). Update this when the schema bumps.
expect(migrated['$version']).toBe(4);
expect(migrated['$version']).toBe(CURRENT_SETTINGS_VERSION);
});
});

Expand Down Expand Up @@ -249,10 +249,16 @@ describe('QWEN_HOME environment variable', () => {
process.env['QWEN_HOME'] = customConfigDir;

// Seed QWEN_HOME with the current schema version so it shouldn't migrate.
// Bump alongside SETTINGS_VERSION in packages/cli/src/config/settings.ts.
writeFileSync(
join(customConfigDir, 'settings.json'),
JSON.stringify({ $version: 4, customKey: 'in-global-dir' }, null, 2),
JSON.stringify(
{
$version: CURRENT_SETTINGS_VERSION,
customKey: 'in-global-dir',
},
null,
2,
),
);

// Overwrite the workspace settings.json with V1 format so migration is observable
Expand Down Expand Up @@ -284,14 +290,14 @@ describe('QWEN_HOME environment variable', () => {
}

// The workspace settings.json must have been migrated to the current
// SETTINGS_VERSION — proving the CLI read it from the workspace dir, not
// from QWEN_HOME. Update the version when the schema bumps.
// settings version, proving the CLI read it from the workspace dir, not
// from QWEN_HOME.
const workspaceRaw = readFileSync(workspaceSettingsPath, 'utf-8');
const workspaceSettings = JSON.parse(workspaceRaw) as Record<
string,
unknown
>;
expect(workspaceSettings['$version']).toBe(4);
expect(workspaceSettings['$version']).toBe(CURRENT_SETTINGS_VERSION);
expect(workspaceSettings['customWorkspaceKey']).toBe('workspace-value');

// The QWEN_HOME settings.json must be unchanged (still at the version we wrote)
Expand Down
40 changes: 17 additions & 23 deletions integration-tests/cli/settings-migration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,16 @@ const {
v3GitCoAuthorBooleanSettings,
} = workspacesSettings;

const CURRENT_SETTINGS_VERSION = 5;

/**
* Integration tests for settings migration chain (V1 -> V2 -> V3 -> V4)
* Integration tests for settings migration chain.
*
* These tests verify that:
* 1. V1 settings are automatically migrated to V4 on CLI startup
* 2. V2 settings are automatically migrated to V4 on CLI startup
* 3. V3 settings are automatically migrated to V4 on CLI startup
* 1. V1 settings are automatically migrated to current settings on CLI startup
* 2. V2 settings are automatically migrated to current settings on CLI startup
* 3. V3 settings are automatically migrated to current settings on CLI startup
* 4. Migration is idempotent (running multiple times produces same result)
*
* The numeric assertions use the literal `4` to match
* `SETTINGS_VERSION`; bump that constant and the literal together
* when adding a future migration.
*/
describe('settings-migration', () => {
let rig: TestRig;
Expand Down Expand Up @@ -99,8 +97,7 @@ describe('settings-migration', () => {
// Read migrated settings
const migratedSettings = readSettingsFile(rig);

// Verify migration to V4 (current SETTINGS_VERSION)
expect(migratedSettings['$version']).toBe(4);
expect(migratedSettings['$version']).toBe(CURRENT_SETTINGS_VERSION);
expect(migratedSettings['ui']).toEqual({
theme: 'dark',
hideTips: false,
Expand Down Expand Up @@ -142,7 +139,7 @@ describe('settings-migration', () => {
const migratedSettings = readSettingsFile(rig);

// Expected output based on stable test output
expect(migratedSettings['$version']).toBe(4);
expect(migratedSettings['$version']).toBe(CURRENT_SETTINGS_VERSION);
expect(migratedSettings['tools']).toEqual({ autoAccept: false });
expect(migratedSettings['context']).toEqual({ includeDirectories: [] });
expect(migratedSettings['model']).toEqual({ name: ['gemini', 'claude'] });
Expand All @@ -166,8 +163,7 @@ describe('settings-migration', () => {
// Read migrated settings
const migratedSettings = readSettingsFile(rig);

// Should be migrated to V4
expect(migratedSettings['$version']).toBe(4);
expect(migratedSettings['$version']).toBe(CURRENT_SETTINGS_VERSION);
// Legacy string values for ui/general should be preserved as-is (user data)
expect(migratedSettings['ui']).toBe('legacy-ui-string');
expect(migratedSettings['general']).toBe('legacy-general-string');
Expand All @@ -194,7 +190,7 @@ describe('settings-migration', () => {
const migratedSettings = readSettingsFile(rig);

// Expected output based on stable test output
expect(migratedSettings['$version']).toBe(4);
expect(migratedSettings['$version']).toBe(CURRENT_SETTINGS_VERSION);
expect(migratedSettings['model']).toEqual({ name: 'qwen-plus' });
expect(migratedSettings['ui']).toEqual({
hideWindowTitle: true,
Expand Down Expand Up @@ -230,8 +226,7 @@ describe('settings-migration', () => {
// Read migrated settings
const migratedSettings = readSettingsFile(rig);

// Verify migration to V4 (current SETTINGS_VERSION)
expect(migratedSettings['$version']).toBe(4);
expect(migratedSettings['$version']).toBe(CURRENT_SETTINGS_VERSION);

// Verify disable* -> enable* conversion with inversion
expect(
Expand Down Expand Up @@ -307,8 +302,7 @@ describe('settings-migration', () => {
// Read migrated settings
const migratedSettings = readSettingsFile(rig);

// Should be updated to V4 version
expect(migratedSettings['$version']).toBe(4);
expect(migratedSettings['$version']).toBe(CURRENT_SETTINGS_VERSION);
// Other settings should remain unchanged
expect(migratedSettings['ui']).toEqual({ theme: 'dark' });
expect(migratedSettings['model']).toEqual({ name: 'gemini' });
Expand All @@ -335,7 +329,7 @@ describe('settings-migration', () => {
const migratedSettings = readSettingsFile(rig);

// Version metadata should still be normalized to current version
expect(migratedSettings['$version']).toBe(4);
expect(migratedSettings['$version']).toBe(CURRENT_SETTINGS_VERSION);
// Existing user content should be preserved
expect(migratedSettings['customOnlyKey']).toBe('value');
});
Expand Down Expand Up @@ -377,7 +371,7 @@ describe('settings-migration', () => {
const migratedSettings = readSettingsFile(rig);

// Coercible strings are migrated; invalid disable* values are removed.
expect(migratedSettings['$version']).toBe(4);
expect(migratedSettings['$version']).toBe(CURRENT_SETTINGS_VERSION);
expect(migratedSettings['general']).toEqual({
enableAutoUpdate: false,
});
Expand Down Expand Up @@ -442,7 +436,7 @@ describe('settings-migration', () => {
const migratedSettings = readSettingsFile(rig);

// Expected output based on stable test output
expect(migratedSettings['$version']).toBe(4);
expect(migratedSettings['$version']).toBe(CURRENT_SETTINGS_VERSION);
// Migration converts disable* to enable* by inverting the value
// disableAutoUpdate: false -> enableAutoUpdate: true (inverted)
// But disableUpdateNag: true may affect the consolidation
Expand Down Expand Up @@ -509,7 +503,7 @@ describe('settings-migration', () => {
// V3 → V4 migration bumps the version; V3→V4 only touches
// general.gitCoAuthor, so unrelated legacy disable* keys remain as-is
// (V2→V3 ran on original V3 load, not re-applied here).
expect(finalSettings['$version']).toBe(4);
expect(finalSettings['$version']).toBe(CURRENT_SETTINGS_VERSION);
expect(
(finalSettings['general'] as Record<string, unknown>)?.[
'disableAutoUpdate'
Expand Down Expand Up @@ -563,7 +557,7 @@ describe('settings-migration', () => {

const finalSettings = readSettingsFile(rig);

expect(finalSettings['$version']).toBe(4);
expect(finalSettings['$version']).toBe(CURRENT_SETTINGS_VERSION);
expect(
(finalSettings['general'] as Record<string, unknown>)?.['gitCoAuthor'],
).toEqual({ commit: false, pr: false });
Expand Down
21 changes: 17 additions & 4 deletions packages/cli/src/config/migration/versions/v4-to-v5.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,19 @@ describe('V4ToV5Migration', () => {
).toBe(false);
});

it('returns false for V4 settings without modelProviders', () => {
expect(migration.shouldMigrate({ $version: 4 })).toBe(false);
it('returns true for V4 settings without modelProviders', () => {
expect(migration.shouldMigrate({ $version: 4 })).toBe(true);
});

it('returns false for V4 settings with already-object modelProviders', () => {
it('returns true for V4 settings with already-object modelProviders', () => {
expect(
migration.shouldMigrate({
$version: 4,
modelProviders: {
openai: { protocol: 'openai', models: [{ id: 'gpt-4o' }] },
},
}),
).toBe(false);
).toBe(true);
});

it('returns false for non-object input', () => {
Expand Down Expand Up @@ -92,6 +92,19 @@ describe('V4ToV5Migration', () => {
expect(warnings).toEqual([]);
});

it('bumps V4 settings without modelProviders to V5', () => {
const { settings, warnings } = migration.migrate(
{ $version: 4, custom: true },
'user',
) as {
settings: Record<string, unknown>;
warnings: string[];
};

expect(settings).toEqual({ $version: 5, custom: true });
expect(warnings).toEqual([]);
});

it.each([
['openai', 'openai'],
['qwen-oauth', 'qwen-oauth'],
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/config/migration/versions/v4-to-v5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export class V4ToV5Migration implements SettingsMigration {
if (s['$version'] !== 4 && s['$version'] !== undefined) {
return false;
}
if (s['$version'] === 4) {
return true;
}
const modelProviders = s['modelProviders'];
if (typeof modelProviders !== 'object' || modelProviders === null) {
return false;
Expand Down
Loading