fix: restore full settings import coverage

This commit is contained in:
QLHazyCoder
2026-05-28 23:27:52 +08:00
parent b84732e506
commit 4b739d91b5
3 changed files with 244 additions and 10 deletions
+40 -10
View File
@@ -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,
@@ -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 });
});
@@ -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'));
});