From 4b739d91b5f71699b24640902a9d8e7db56fbc16 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Thu, 28 May 2026 23:27:52 +0800 Subject: [PATCH] fix: restore full settings import coverage --- background.js | 50 ++++-- ...nd-settings-import-mode-validation.test.js | 150 ++++++++++++++++++ ...ground-settings-schema-persistence.test.js | 54 +++++++ 3 files changed, 244 insertions(+), 10 deletions(-) diff --git a/background.js b/background.js index c8694e3..f78a657 100644 --- a/background.js +++ b/background.js @@ -4242,8 +4242,11 @@ async function setState(updates) { } } -async function setPersistentSettings(updates) { - const currentSettings = await getPersistedSettings(); +async function setPersistentSettings(updates, options = {}) { + const replaceExisting = Boolean(options?.replaceExisting || options?.replace); + const currentSettings = replaceExisting + ? buildPersistentSettingsPayload({}, { fillDefaults: true }) + : await getPersistedSettings(); const nextUpdates = updates && typeof updates === 'object' && !Array.isArray(updates) ? updates : {}; @@ -4270,11 +4273,26 @@ async function setPersistentSettings(updates) { activeFlowId: currentSettings?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID, } ); - mergedSettingsState = isPlainObjectValue(nextUpdates.settingsState) - ? (typeof settingsSchemaApi.mergeSettingsState === 'function' - ? settingsSchemaApi.mergeSettingsState(currentSettingsState, nextUpdates.settingsState) - : nextUpdates.settingsState) - : currentSettingsState; + if (isPlainObjectValue(nextUpdates.settingsState)) { + mergedSettingsState = replaceExisting + ? settingsSchemaApi.normalizeSettingsState({ + activeFlowId: nextUpdates.activeFlowId + || nextUpdates.settingsState.activeFlowId + || currentSettings?.activeFlowId + || DEFAULT_ACTIVE_FLOW_ID, + settingsState: nextUpdates.settingsState, + }, { + activeFlowId: nextUpdates.activeFlowId + || nextUpdates.settingsState.activeFlowId + || currentSettings?.activeFlowId + || DEFAULT_ACTIVE_FLOW_ID, + }) + : (typeof settingsSchemaApi.mergeSettingsState === 'function' + ? settingsSchemaApi.mergeSettingsState(currentSettingsState, nextUpdates.settingsState) + : nextUpdates.settingsState); + } else { + mergedSettingsState = currentSettingsState; + } mergedSettingsState = mergeSettingsStatePatch( mergedSettingsState, buildSettingsStatePatchFromFlatUpdates(explicitFlatUpdates) @@ -4303,7 +4321,16 @@ async function setPersistentSettings(updates) { ? buildPersistedSettingsStoragePayload(persistedUpdates) : persistedUpdates; if (hasSchemaApi && chrome.storage?.local?.remove) { - await chrome.storage.local.remove(SETTINGS_SCHEMA_VIEW_KEYS); + const removedKeys = replaceExisting + ? Array.from(new Set([ + ...PERSISTED_SETTING_KEYS, + ...PERSISTED_SETTINGS_SCHEMA_KEYS, + ...SETTINGS_SCHEMA_VIEW_KEYS, + ...LEGACY_AUTO_STEP_DELAY_KEYS, + ...LEGACY_VERIFICATION_RESEND_COUNT_KEYS, + ])) + : SETTINGS_SCHEMA_VIEW_KEYS; + await chrome.storage.local.remove(removedKeys); } await chrome.storage.local.set(storagePayload); } @@ -4353,7 +4380,10 @@ async function importSettingsBundle(configBundle) { defaultFlowId: DEFAULT_ACTIVE_FLOW_ID, }) || null; const importedSettingsSource = typeof settingsImporter?.importSettings === 'function' - ? settingsImporter.importSettings(configBundle.settings) + ? { + ...configBundle.settings, + ...settingsImporter.importSettings(configBundle.settings), + } : configBundle.settings; const importedSettings = buildPersistentSettingsPayload(importedSettingsSource, { fillDefaults: true, @@ -4384,7 +4414,7 @@ async function importSettingsBundle(configBundle) { }); } - const persistedSettings = await setPersistentSettings(importedSettings) || importedSettings; + const persistedSettings = await setPersistentSettings(importedSettings, { replaceExisting: true }) || importedSettings; const sessionUpdates = { ...persistedSettings, diff --git a/tests/background-settings-import-mode-validation.test.js b/tests/background-settings-import-mode-validation.test.js index d509e81..1401b5b 100644 --- a/tests/background-settings-import-mode-validation.test.js +++ b/tests/background-settings-import-mode-validation.test.js @@ -293,3 +293,153 @@ return { assert.equal(api.getPersistedUpdates().settingsSchemaVersion, 5); assert.equal(api.getPersistedUpdates().settingsState.flows.kiro.targets['kiro-rs'].apiKey, 'imported-key'); }); + +test('importSettingsBundle keeps exported non-schema settings while applying legacy schema migration', async () => { + const api = new Function(` +const SETTINGS_EXPORT_SCHEMA_VERSION = 1; +const DEFAULT_REGISTRATION_EMAIL_STATE = { emailHistory: [] }; +const DEFAULT_ACTIVE_FLOW_ID = 'openai'; +let importerInput = null; +let payloadInput = null; +let persistedUpdates = null; +let persistOptions = null; +let currentState = { + activeFlowId: 'openai', + nodeStatuses: {}, +}; +const self = { + MultiPageFlowRegistry: { + DEFAULT_FLOW_ID: 'openai', + }, + MultiPageLegacySettingsImporter: { + createSettingsImporter() { + return { + importSettings(settings = {}) { + importerInput = JSON.parse(JSON.stringify(settings)); + return { + settingsSchemaVersion: 5, + settingsState: { + schemaVersion: 5, + activeFlowId: 'kiro', + services: { + account: { customPassword: 'schema-password' }, + email: { provider: 'hotmail' }, + proxy: { enabled: true, provider: '711proxy', mode: 'api' }, + }, + flows: { + openai: { + selectedTargetId: 'sub2api', + targets: {}, + signup: { + signupMethod: 'email', + phoneVerificationEnabled: false, + phoneSignupReloginAfterBindEmailEnabled: false, + }, + plus: { + plusModeEnabled: false, + plusPaymentMethod: 'paypal', + plusAccountAccessStrategy: 'oauth', + }, + autoRun: { + stepExecutionRange: { enabled: false, fromStep: 1, toStep: 11 }, + }, + }, + kiro: { + selectedTargetId: 'kiro-rs', + targets: { + 'kiro-rs': { + baseUrl: 'https://kiro.example.com/admin', + apiKey: 'schema-key', + }, + }, + autoRun: { + stepExecutionRange: { enabled: true, fromStep: 2, toStep: 9 }, + }, + }, + }, + }, + }; + }, + }; + }, + }, +}; +async function ensureManualInteractionAllowed() { + return currentState; +} +function buildPersistentSettingsPayload(settings = {}) { + payloadInput = JSON.parse(JSON.stringify(settings)); + return { ...settings }; +} +function validateModeSwitchState() { + return { normalizedUpdates: {} }; +} +function resolveSignupMethod(state = {}) { + return String(state?.signupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email'; +} +function getSettingsSchemaApi() { + return null; +} +async function setPersistentSettings(updates, options = {}) { + persistedUpdates = JSON.parse(JSON.stringify(updates)); + persistOptions = { ...options }; + return updates; +} +async function setState(updates) { + currentState = { ...currentState, ...updates }; +} +function broadcastDataUpdate() {} +async function getState() { + return currentState; +} +${extractFunction('importSettingsBundle')} +return { + importSettingsBundle, + getImporterInput: () => importerInput, + getPayloadInput: () => payloadInput, + getPersistedUpdates: () => persistedUpdates, + getPersistOptions: () => persistOptions, +}; +`)(); + + await api.importSettingsBundle({ + schemaVersion: 1, + settings: { + activeFlowId: 'openai', + targetId: 'sub2api', + cloudflareTempEmailBaseUrl: 'https://mail.example.com', + cloudflareTempEmailAdminAuth: 'admin-secret', + cloudMailToken: 'cloud-token', + gopayHelperApiKey: 'gpc-key', + heroSmsApiKey: 'hero-key', + hotmailAccounts: [ + { id: 'hotmail-1', email: 'owner@example.com', password: 'mail-pass' }, + ], + ipProxyServiceProfiles: { + '711proxy': { + mode: 'api', + apiUrl: 'https://proxy.example.com', + }, + }, + paypalAccounts: [ + { id: 'paypal-1', email: 'paypal@example.com', password: 'paypal-pass' }, + ], + }, + }); + + assert.equal(api.getImporterInput().cloudflareTempEmailAdminAuth, 'admin-secret'); + assert.equal(api.getPayloadInput().settingsState.activeFlowId, 'kiro'); + assert.equal(api.getPayloadInput().cloudflareTempEmailBaseUrl, 'https://mail.example.com'); + assert.equal(api.getPayloadInput().cloudflareTempEmailAdminAuth, 'admin-secret'); + assert.equal(api.getPayloadInput().cloudMailToken, 'cloud-token'); + assert.equal(api.getPayloadInput().gopayHelperApiKey, 'gpc-key'); + assert.equal(api.getPayloadInput().heroSmsApiKey, 'hero-key'); + assert.deepEqual(api.getPayloadInput().hotmailAccounts, [ + { id: 'hotmail-1', email: 'owner@example.com', password: 'mail-pass' }, + ]); + assert.equal(api.getPayloadInput().ipProxyServiceProfiles['711proxy'].apiUrl, 'https://proxy.example.com'); + assert.equal(api.getPayloadInput().paypalAccounts[0].email, 'paypal@example.com'); + assert.equal(api.getPersistedUpdates().settingsState.flows.kiro.targets['kiro-rs'].apiKey, 'schema-key'); + assert.equal(api.getPersistedUpdates().cloudflareTempEmailAdminAuth, 'admin-secret'); + assert.deepEqual(api.getPersistOptions(), { replaceExisting: true }); +}); diff --git a/tests/background-settings-schema-persistence.test.js b/tests/background-settings-schema-persistence.test.js index dade66f..e8c2a39 100644 --- a/tests/background-settings-schema-persistence.test.js +++ b/tests/background-settings-schema-persistence.test.js @@ -752,3 +752,57 @@ function getRemovedKeys() { assert.equal(Object.prototype.hasOwnProperty.call(write, 'panelMode'), false); assert.equal(Object.prototype.hasOwnProperty.call(write, 'ipProxyMode'), false); }); + +test('setPersistentSettings replace mode does not retain previous non-schema settings', async () => { + const api = buildHarness(` +const persistedWrites = []; +const removedKeys = []; +const chrome = { + storage: { + local: { + async get() { + throw new Error('replace mode should not read existing settings'); + }, + async remove(keys) { + removedKeys.push(...(Array.isArray(keys) ? keys : [keys])); + }, + async set(payload) { + persistedWrites.push(JSON.parse(JSON.stringify(payload))); + }, + }, + }, +}; +function getPersistedWrites() { + return persistedWrites; +} +function getRemovedKeys() { + return removedKeys; +} +`); + + const persisted = await api.setPersistentSettings({ + activeFlowId: 'kiro', + targetId: 'kiro-rs', + mailProvider: 'hotmail', + ipProxyEnabled: true, + ipProxyMode: 'api', + kiroRsUrl: 'https://kiro.example.com/admin', + kiroRsKey: 'imported-key', + }, { replaceExisting: true }); + const write = api.getPersistedWrites().at(-1); + + assert.equal(persisted.activeFlowId, 'kiro'); + assert.equal(persisted.targetId, 'kiro-rs'); + assert.equal(persisted.mailProvider, 'hotmail'); + assert.equal(persisted.ipProxyEnabled, true); + assert.equal(persisted.ipProxyMode, 'api'); + assert.equal(persisted.kiroRsUrl, 'https://kiro.example.com/admin'); + assert.equal(persisted.kiroRsKey, 'imported-key'); + assert.equal(write.settingsState.activeFlowId, 'kiro'); + assert.equal(write.settingsState.services.email.provider, 'hotmail'); + assert.equal(write.settingsState.services.proxy.mode, 'api'); + assert.equal(write.settingsState.flows.kiro.targets['kiro-rs'].apiKey, 'imported-key'); + assert.ok(api.getRemovedKeys().includes('settingsState')); + assert.ok(api.getRemovedKeys().includes('mailProvider')); + assert.ok(api.getRemovedKeys().includes('kiroRsKey')); +});