mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-29 12:11:09 +00:00
refactor(settings): sequential settings migration
This commit is contained in:
parent
ac5a0c68e5
commit
ae8c0d3d4e
18 changed files with 3527 additions and 944 deletions
164
packages/cli/src/config/migration/scheduler.test.ts
Normal file
164
packages/cli/src/config/migration/scheduler.test.ts
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { MigrationScheduler } from './scheduler.js';
|
||||
|
||||
import type { SettingsMigration } from './types.js';
|
||||
|
||||
describe('MigrationScheduler', () => {
|
||||
// Mock migration for testing
|
||||
const createMockMigration = (
|
||||
fromVersion: number,
|
||||
toVersion: number,
|
||||
shouldMigrateResult: boolean,
|
||||
): SettingsMigration => ({
|
||||
fromVersion,
|
||||
toVersion,
|
||||
shouldMigrate: vi.fn().mockReturnValue(shouldMigrateResult),
|
||||
migrate: vi.fn((settings) => ({
|
||||
settings: {
|
||||
...(settings as Record<string, unknown>),
|
||||
$version: toVersion,
|
||||
},
|
||||
warnings: [],
|
||||
})),
|
||||
});
|
||||
|
||||
it('should execute migrations in order when shouldMigrate returns true', () => {
|
||||
const migration1 = createMockMigration(1, 2, true);
|
||||
const migration2 = createMockMigration(2, 3, true);
|
||||
|
||||
const scheduler = new MigrationScheduler([migration1, migration2], 'user');
|
||||
const result = scheduler.migrate({ $version: 1, someKey: 'value' });
|
||||
|
||||
expect(migration1.shouldMigrate).toHaveBeenCalledTimes(1);
|
||||
expect(migration1.migrate).toHaveBeenCalledTimes(1);
|
||||
expect(migration2.shouldMigrate).toHaveBeenCalledTimes(1);
|
||||
expect(migration2.migrate).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(result.executedMigrations).toHaveLength(2);
|
||||
expect(result.executedMigrations[0]).toEqual({
|
||||
fromVersion: 1,
|
||||
toVersion: 2,
|
||||
});
|
||||
expect(result.executedMigrations[1]).toEqual({
|
||||
fromVersion: 2,
|
||||
toVersion: 3,
|
||||
});
|
||||
expect(result.finalVersion).toBe(3);
|
||||
});
|
||||
|
||||
it('should skip migrations when shouldMigrate returns false', () => {
|
||||
const migration1 = createMockMigration(1, 2, false);
|
||||
const migration2 = createMockMigration(2, 3, true);
|
||||
|
||||
const scheduler = new MigrationScheduler([migration1, migration2], 'user');
|
||||
const result = scheduler.migrate({ $version: 2, someKey: 'value' });
|
||||
|
||||
expect(migration1.shouldMigrate).toHaveBeenCalledTimes(1);
|
||||
expect(migration1.migrate).not.toHaveBeenCalled();
|
||||
expect(migration2.shouldMigrate).toHaveBeenCalledTimes(1);
|
||||
expect(migration2.migrate).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(result.executedMigrations).toHaveLength(1);
|
||||
expect(result.executedMigrations[0]).toEqual({
|
||||
fromVersion: 2,
|
||||
toVersion: 3,
|
||||
});
|
||||
});
|
||||
|
||||
it('should be idempotent - running migrations twice produces same result', () => {
|
||||
// Create a migration that checks the version to determine if migration is needed
|
||||
const migration1: SettingsMigration = {
|
||||
fromVersion: 1,
|
||||
toVersion: 2,
|
||||
shouldMigrate: vi.fn((settings) => {
|
||||
const s = settings as Record<string, unknown>;
|
||||
return s['$version'] !== 2;
|
||||
}),
|
||||
migrate: vi.fn((settings) => ({
|
||||
settings: {
|
||||
...(settings as Record<string, unknown>),
|
||||
$version: 2,
|
||||
},
|
||||
warnings: [],
|
||||
})),
|
||||
};
|
||||
|
||||
const scheduler = new MigrationScheduler([migration1], 'user');
|
||||
const input = { theme: 'dark' };
|
||||
|
||||
const result1 = scheduler.migrate(input);
|
||||
const result2 = scheduler.migrate(result1.settings);
|
||||
|
||||
expect(result1.executedMigrations).toHaveLength(1);
|
||||
expect(result2.executedMigrations).toHaveLength(0);
|
||||
expect(result1.finalVersion).toBe(result2.finalVersion);
|
||||
});
|
||||
|
||||
it('should pass updated settings to each migration', () => {
|
||||
const migration1: SettingsMigration = {
|
||||
fromVersion: 1,
|
||||
toVersion: 2,
|
||||
shouldMigrate: vi.fn().mockReturnValue(true),
|
||||
migrate: vi.fn(() => ({
|
||||
settings: { $version: 2, transformed: true },
|
||||
warnings: [],
|
||||
})),
|
||||
};
|
||||
|
||||
const migration2: SettingsMigration = {
|
||||
fromVersion: 2,
|
||||
toVersion: 3,
|
||||
shouldMigrate: vi.fn().mockReturnValue(true),
|
||||
migrate: vi.fn((s) => ({ settings: s, warnings: [] })),
|
||||
};
|
||||
|
||||
const scheduler = new MigrationScheduler([migration1, migration2], 'user');
|
||||
scheduler.migrate({ $version: 1 });
|
||||
|
||||
expect(migration2.shouldMigrate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ $version: 2, transformed: true }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty migrations array', () => {
|
||||
const scheduler = new MigrationScheduler([], 'user');
|
||||
const result = scheduler.migrate({ $version: 1, key: 'value' });
|
||||
|
||||
expect(result.executedMigrations).toHaveLength(0);
|
||||
expect(result.finalVersion).toBe(1);
|
||||
expect(result.settings).toEqual({ $version: 1, key: 'value' });
|
||||
});
|
||||
|
||||
it('should throw error when migration fails', () => {
|
||||
const migration1: SettingsMigration = {
|
||||
fromVersion: 1,
|
||||
toVersion: 2,
|
||||
shouldMigrate: vi.fn().mockReturnValue(true),
|
||||
migrate: vi.fn().mockImplementation(() => {
|
||||
throw new Error('Migration failed');
|
||||
}),
|
||||
};
|
||||
|
||||
const scheduler = new MigrationScheduler([migration1], 'user');
|
||||
|
||||
expect(() => scheduler.migrate({ $version: 1 })).toThrow(
|
||||
'Migration failed',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle settings without version field', () => {
|
||||
const migration1 = createMockMigration(1, 2, true);
|
||||
|
||||
const scheduler = new MigrationScheduler([migration1], 'user');
|
||||
const result = scheduler.migrate({ theme: 'dark' });
|
||||
|
||||
expect(result.finalVersion).toBe(2);
|
||||
expect(result.executedMigrations).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue