Files
FlowPilot/tests/background-settings-schema-persistence.test.js
T
2026-05-29 00:44:30 +08:00

924 lines
32 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { readFlowRegistryBundle } = require('./helpers/script-bundles.js');
const flowRegistrySource = readFlowRegistryBundle();
const settingsSchemaSource = fs.readFileSync('core/flow-kernel/settings-schema.js', 'utf8');
const backgroundSource = fs.readFileSync('background.js', 'utf8');
const DEFAULT_MADAO_BASE_URL_FOR_TEST = 'http://127.0.0.1:7822';
const DEFAULT_MADAO_MODE_FOR_TEST = 'routing_plan';
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
const start = markers
.map((marker) => backgroundSource.indexOf(marker))
.find((index) => index >= 0);
if (start < 0) {
throw new Error(`missing function ${name}`);
}
let parenDepth = 0;
let signatureEnded = false;
let braceStart = -1;
for (let i = start; i < backgroundSource.length; i += 1) {
const ch = backgroundSource[i];
if (ch === '(') parenDepth += 1;
if (ch === ')') {
parenDepth -= 1;
if (parenDepth === 0) signatureEnded = true;
}
if (ch === '{' && signatureEnded) {
braceStart = i;
break;
}
}
if (braceStart < 0) {
throw new Error(`missing body for function ${name}`);
}
let depth = 0;
let end = braceStart;
for (; end < backgroundSource.length; end += 1) {
const ch = backgroundSource[end];
if (ch === '{') depth += 1;
if (ch === '}') {
depth -= 1;
if (depth === 0) {
end += 1;
break;
}
}
}
return backgroundSource.slice(start, end);
}
function buildHarness(extra = '') {
return new Function(`
const self = {};
${flowRegistrySource}
${settingsSchemaSource}
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
const DEFAULT_SUB2API_GROUP_NAMES = ['codex', 'openai-plus'];
const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([
'activeFlowId',
'targetId',
'vpsUrl',
'vpsPassword',
'localCpaStep9Mode',
'sub2apiUrl',
'sub2apiEmail',
'sub2apiPassword',
'sub2apiGroupName',
'sub2apiGroupNames',
'sub2apiAccountPriority',
'sub2apiDefaultProxyName',
'codex2apiUrl',
'codex2apiAdminKey',
'customPassword',
'signupMethod',
'phoneVerificationEnabled',
'phoneSignupReloginAfterBindEmailEnabled',
'plusModeEnabled',
'plusPaymentMethod',
'plusAccountAccessStrategy',
'mailProvider',
'customMailReceiveMode',
'customMailHelperBaseUrl',
'ipProxyEnabled',
'ipProxyService',
'ipProxyMode',
'kiroRsUrl',
'kiroRsKey',
'stepExecutionRangeByFlow',
]);
const SETTINGS_SCHEMA_VIEW_KEY_SET = new Set(SETTINGS_SCHEMA_VIEW_KEYS);
const DEFAULT_MADAO_BASE_URL = 'http://127.0.0.1:7822';
const DEFAULT_MADAO_MODE = 'routing_plan';
const PERSISTED_SETTING_DEFAULTS = {
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
targetId: 'cpa',
signupMethod: 'email',
plusModeEnabled: false,
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'oauth',
phoneVerificationEnabled: false,
mailProvider: '163',
customMailReceiveMode: 'manual',
customMailHelperBaseUrl: 'http://127.0.0.1:17374',
ipProxyEnabled: false,
ipProxyService: '711proxy',
ipProxyMode: 'account',
kiroRsUrl: '',
kiroRsKey: '',
phoneSmsProvider: 'hero-sms',
madaoBaseUrl: DEFAULT_MADAO_BASE_URL,
madaoHttpSecret: '',
madaoMode: DEFAULT_MADAO_MODE,
madaoRoutingPlanId: '',
madaoProviderId: '',
madaoCountry: '',
madaoAutoPickCountry: true,
madaoReusePhone: true,
madaoMinPrice: '',
madaoMaxPrice: '',
stepExecutionRangeByFlow: {},
};
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
const PERSISTED_SETTINGS_SCHEMA_KEYS = ['settingsSchemaVersion', 'settingsState'];
const LEGACY_AUTO_STEP_DELAY_KEYS = [];
const LEGACY_VERIFICATION_RESEND_COUNT_KEYS = [];
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
function isPlainObjectValue(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function normalizePanelMode(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized === 'sub2api' || normalized === 'codex2api' ? normalized : 'cpa';
}
function normalizeSignupMethod(value = '') {
return String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email';
}
function normalizePlusPaymentMethod(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized === 'gopay' || normalized === 'gpc-helper' ? normalized : 'paypal';
}
${extractFunction('normalizePlusAccountAccessStrategy')}
function normalizeSub2ApiGroupNames(value) {
return Array.isArray(value) ? value.map((entry) => String(entry || '').trim()).filter(Boolean) : [];
}
function normalizeCloudflareDomains(value) { return Array.isArray(value) ? value : []; }
function normalizeCloudflareTempEmailDomains(value) { return Array.isArray(value) ? value : []; }
function normalizeCloudMailDomains(value) { return Array.isArray(value) ? value : []; }
function normalizeMailProvider(value = '') { return String(value || '163').trim().toLowerCase() || '163'; }
function normalizeCustomMailReceiveMode(value = '') { return String(value || '').trim().toLowerCase() === 'helper' ? 'helper' : 'manual'; }
function normalizeCustomMailHelperBaseUrl(value = '') {
const trimmed = String(value || '').trim();
const candidate = trimmed || 'http://127.0.0.1:17374';
try {
const parsed = new URL(candidate);
if (!['http:', 'https:'].includes(parsed.protocol)) {
return 'http://127.0.0.1:17374';
}
parsed.hash = '';
parsed.search = '';
parsed.pathname = parsed.pathname.replace(/[/]+$/, '');
const path = parsed.pathname === '/' ? '' : parsed.pathname;
return (parsed.origin + path) || 'http://127.0.0.1:17374';
} catch {
return 'http://127.0.0.1:17374';
}
}
function normalizeStepExecutionRangeByFlow(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; }
function normalizePhoneSmsProvider(value = '') {
const normalized = String(value || '').trim().toLowerCase();
if (normalized === '5sim' || normalized === 'nexsms' || normalized === 'madao') {
return normalized;
}
return 'hero-sms';
}
function normalizePhoneSmsProviderOrder(value = []) {
const source = Array.isArray(value) ? value : String(value || '').split(/[\\r\\n,]+/);
const seen = new Set();
return source
.map((entry) => normalizePhoneSmsProvider(entry))
.filter((provider) => {
if (!provider || seen.has(provider)) return false;
seen.add(provider);
return true;
})
.slice(0, 4);
}
function normalizeLocalHttpBaseUrl(value = '', fallback = DEFAULT_MADAO_BASE_URL) {
const rawValue = String(value || fallback).trim();
try {
const parsed = new URL(rawValue);
if (!['http:', 'https:'].includes(parsed.protocol)) {
return fallback;
}
return parsed.toString().replace(/\\/$/, '');
} catch {
return fallback;
}
}
function normalizeMaDaoBaseUrl(value = '') {
const normalized = normalizeLocalHttpBaseUrl(value, DEFAULT_MADAO_BASE_URL);
try {
const parsed = new URL(normalized);
parsed.pathname = parsed.pathname.replace(/\\/api\\/(?:acquire|poll|release|routing\\/replace)$/i, '');
parsed.search = '';
parsed.hash = '';
return parsed.toString().replace(/\\/$/, '');
} catch {
return DEFAULT_MADAO_BASE_URL;
}
}
function normalizeMaDaoMode(value = '') { return String(value || '').trim().toLowerCase() === 'direct' ? 'direct' : DEFAULT_MADAO_MODE; }
function normalizeMaDaoIdentifier(value = '') { return String(value || '').trim(); }
function normalizeMaDaoProviderId(value = '') { return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, ''); }
function normalizeMaDaoCountry(value = '') {
const trimmed = String(value || '').trim();
if (!trimmed) return '';
const lowered = trimmed.toLowerCase();
if (lowered === 'any' || lowered === 'local') return lowered;
if (/^[a-z]{2}$/i.test(trimmed)) return trimmed.toUpperCase();
return lowered.replace(/[^a-z0-9_-]+/g, '');
}
function normalizeHeroSmsMaxPrice(value = '') {
const rawValue = String(value ?? '').trim();
if (!rawValue) return '';
const numeric = Number(rawValue);
if (!Number.isFinite(numeric) || numeric <= 0) return '';
return String(Math.round(numeric * 10000) / 10000);
}
function normalizeMaDaoPrice(value = '') { return normalizeHeroSmsMaxPrice(value); }
function normalizeIpProxyProviderValue(value) { return String(value || '711proxy').trim() || '711proxy'; }
function normalizeIpProxyMode(value) { return String(value || 'account').trim() || 'account'; }
function normalizeIpProxyServiceProfiles(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; }
function buildIpProxyServiceProfileFromState() {
return {
mode: 'account',
apiUrl: '',
accountList: '',
accountSessionPrefix: '',
accountLifeMinutes: '',
poolTargetCount: '20',
host: '',
port: '',
protocol: 'http',
username: '',
password: '',
region: '',
};
}
function normalizeIpProxyAccountList(value) { return String(value || ''); }
function normalizeIpProxyAccountSessionPrefix(value) { return String(value || ''); }
function normalizeIpProxyAccountLifeMinutes(value) { return String(value || ''); }
function normalizeIpProxyPoolTargetCount(value) { return String(value || '20'); }
function normalizeIpProxyPort(value) { return String(value || '').trim(); }
function normalizeIpProxyProtocol(value) { return String(value || 'http').trim() || 'http'; }
function resolveSignupMethod(state = {}) {
const activeFlowId = String(state?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID;
if (activeFlowId === 'kiro') {
return 'email';
}
return String(state?.signupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email';
}
function resolveLegacyAutoStepDelaySeconds() { return undefined; }
${extractFunction('normalizePersistentSettingValue')}
${extractFunction('getSettingsSchemaApi')}
${extractFunction('projectSettingsSchemaView')}
${extractFunction('setSettingsStatePatchValue')}
${extractFunction('mergeSettingsStatePatch')}
${extractFunction('buildSettingsStatePatchFromFlatUpdates')}
${extractFunction('buildPersistedSettingsStoragePayload')}
${extractFunction('buildPersistentSettingsPayload')}
${extractFunction('getPersistedSettings')}
${extractFunction('setPersistentSettings')}
${extra}
return {
buildPersistentSettingsPayload,
getPersistedSettings,
setPersistentSettings,
getRequestedKeys: typeof getRequestedKeys === 'function' ? getRequestedKeys : () => [],
getPersistedWrites: typeof getPersistedWrites === 'function' ? getPersistedWrites : () => [],
getRemovedKeys: typeof getRemovedKeys === 'function' ? getRemovedKeys : () => [],
};
`)();
}
test('buildPersistentSettingsPayload writes canonical settings schema into persisted payloads when defaults are materialized', () => {
const api = buildHarness();
const payload = api.buildPersistentSettingsPayload({
activeFlowId: 'kiro',
kiroRsUrl: 'https://kiro.example.com/admin',
kiroRsKey: 'secret-key',
}, { fillDefaults: true });
assert.equal(payload.activeFlowId, 'kiro');
assert.equal(payload.targetId, 'kiro-rs');
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(payload.kiroRsKey, 'secret-key');
assert.equal(payload.phoneSmsProvider, 'hero-sms');
assert.equal(payload.madaoBaseUrl, DEFAULT_MADAO_BASE_URL_FOR_TEST);
assert.equal(payload.madaoMode, DEFAULT_MADAO_MODE_FOR_TEST);
assert.equal(payload.madaoAutoPickCountry, true);
assert.equal(payload.madaoReusePhone, true);
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
assert.equal(payload.settingsSchemaVersion, 5);
assert.equal(payload.settingsState.activeFlowId, 'kiro');
assert.equal(payload.settingsState.flows.kiro.selectedTargetId, 'kiro-rs');
assert.equal(
payload.settingsState.flows.kiro.targets['kiro-rs'].baseUrl,
'https://kiro.example.com/admin'
);
});
test('buildPersistentSettingsPayload accepts schema-only input when requireKnownKeys is enabled', () => {
const api = buildHarness();
const payload = api.buildPersistentSettingsPayload({
settingsSchemaVersion: 5,
settingsState: {
activeFlowId: 'kiro',
services: {
account: { customPassword: '' },
email: { provider: '163' },
proxy: { enabled: false, provider: '711proxy', mode: 'account' },
},
flows: {
openai: {
selectedTargetId: 'cpa',
targets: {
cpa: {
vpsUrl: '',
vpsPassword: '',
localCpaStep9Mode: 'submit',
},
sub2api: {
sub2apiUrl: '',
sub2apiEmail: '',
sub2apiPassword: '',
sub2apiGroupName: 'codex',
sub2apiGroupNames: ['codex', 'openai-plus'],
sub2apiAccountPriority: 1,
sub2apiDefaultProxyName: '',
},
codex2api: {
codex2apiUrl: '',
codex2apiAdminKey: '',
},
},
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-only-key',
},
},
autoRun: {
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 9 },
},
},
},
},
}, { requireKnownKeys: true });
assert.equal(payload.activeFlowId, 'kiro');
assert.equal(payload.targetId, 'kiro-rs');
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(payload.kiroRsKey, 'schema-only-key');
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
assert.equal(payload.settingsSchemaVersion, 5);
assert.equal(payload.settingsState.flows.openai.plus.plusAccountAccessStrategy, 'oauth');
});
test('getPersistedSettings reads schema keys alongside legacy flat settings keys', async () => {
const api = buildHarness(`
let requestedKeys = [];
const chrome = {
storage: {
local: {
async get(keys) {
requestedKeys = Array.isArray(keys) ? [...keys] : [];
return {};
},
},
},
};
function getRequestedKeys() {
return requestedKeys;
}
`);
const state = await api.getPersistedSettings();
assert.ok(api.getRequestedKeys().includes('settingsSchemaVersion'));
assert.ok(api.getRequestedKeys().includes('settingsState'));
assert.ok(api.getRequestedKeys().includes('plusAccountAccessStrategy'));
assert.equal(state.settingsSchemaVersion, 5);
assert.equal(state.settingsState.activeFlowId, 'openai');
assert.ok(api.getRequestedKeys().includes('madaoBaseUrl'));
assert.ok(api.getRequestedKeys().includes('madaoMode'));
});
test('getPersistedSettings can project schema-only storage back into legacy flat settings', async () => {
const api = buildHarness(`
const chrome = {
storage: {
local: {
async get() {
return {
settingsSchemaVersion: 5,
settingsState: {
activeFlowId: 'kiro',
services: {
account: { customPassword: '' },
email: { provider: 'hotmail' },
proxy: { enabled: true, provider: '711proxy', mode: 'account' },
},
flows: {
openai: {
selectedTargetId: 'sub2api',
targets: {
cpa: {
vpsUrl: '',
vpsPassword: '',
localCpaStep9Mode: 'submit',
},
sub2api: {
sub2apiUrl: '',
sub2apiEmail: '',
sub2apiPassword: '',
sub2apiGroupName: 'codex',
sub2apiGroupNames: ['codex', 'openai-plus'],
sub2apiAccountPriority: 1,
sub2apiDefaultProxyName: '',
},
codex2api: {
codex2apiUrl: '',
codex2apiAdminKey: '',
},
},
signup: {
signupMethod: 'email',
phoneVerificationEnabled: false,
phoneSignupReloginAfterBindEmailEnabled: false,
},
plus: {
plusModeEnabled: false,
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'sub2api_codex_session',
},
autoRun: {
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 11 },
},
},
kiro: {
selectedTargetId: 'kiro-rs',
targets: {
'kiro-rs': {
baseUrl: 'https://kiro.example.com/admin',
apiKey: 'stored-key',
},
},
autoRun: {
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 9 },
},
},
},
},
};
},
},
},
};
`);
const state = await api.getPersistedSettings();
assert.equal(state.activeFlowId, 'kiro');
assert.equal(state.targetId, 'kiro-rs');
assert.equal(state.settingsState.flows.openai.selectedTargetId, 'sub2api');
assert.equal(state.mailProvider, 'hotmail');
assert.equal(state.ipProxyEnabled, true);
assert.equal(state.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(state.kiroRsKey, 'stored-key');
assert.equal(state.plusAccountAccessStrategy, 'sub2api_codex_session');
assert.equal(Object.prototype.hasOwnProperty.call(state, 'kiroRegion'), false);
assert.deepEqual(state.stepExecutionRangeByFlow.kiro, {
enabled: true,
fromStep: 1,
toStep: 9,
});
});
test('setPersistentSettings materializes canonical schema keys for schema-only updates', async () => {
const api = buildHarness(`
const persistedWrites = [];
const removedKeys = [];
const chrome = {
storage: {
local: {
async get() {
return {};
},
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({
settingsSchemaVersion: 5,
settingsState: {
activeFlowId: 'kiro',
services: {
account: { customPassword: '' },
email: { provider: '163' },
proxy: { enabled: false, provider: '711proxy', mode: 'account' },
},
flows: {
openai: {
selectedTargetId: 'cpa',
targets: {
cpa: {
vpsUrl: '',
vpsPassword: '',
localCpaStep9Mode: 'submit',
},
sub2api: {
sub2apiUrl: '',
sub2apiEmail: '',
sub2apiPassword: '',
sub2apiGroupName: 'codex',
sub2apiGroupNames: ['codex', 'openai-plus'],
sub2apiAccountPriority: 1,
sub2apiDefaultProxyName: '',
},
codex2api: {
codex2apiUrl: '',
codex2apiAdminKey: '',
},
},
signup: {
signupMethod: 'email',
phoneVerificationEnabled: false,
phoneSignupReloginAfterBindEmailEnabled: false,
},
plus: {
plusModeEnabled: false,
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'sub2api_codex_session',
},
autoRun: {
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 11 },
},
},
kiro: {
selectedTargetId: 'kiro-rs',
targets: {
'kiro-rs': {
baseUrl: 'https://kiro.example.com/admin',
apiKey: 'nested-only-key',
},
},
autoRun: {
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 9 },
},
},
},
},
});
const write = api.getPersistedWrites().at(-1);
assert.equal(persisted.activeFlowId, 'kiro');
assert.equal(persisted.targetId, 'kiro-rs');
assert.equal(persisted.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(persisted.kiroRsKey, 'nested-only-key');
assert.equal(persisted.plusAccountAccessStrategy, 'sub2api_codex_session');
assert.equal(Object.prototype.hasOwnProperty.call(persisted, 'kiroRegion'), false);
assert.equal(persisted.settingsSchemaVersion, 5);
assert.equal(Object.prototype.hasOwnProperty.call(write, 'activeFlowId'), false);
assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRsUrl'), false);
assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRsKey'), false);
assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRegion'), false);
assert.equal(write.settingsSchemaVersion, 5);
assert.equal(write.settingsState.activeFlowId, 'kiro');
assert.equal(write.settingsState.flows.openai.plus.plusAccountAccessStrategy, 'sub2api_codex_session');
assert.equal(write.settingsState.flows.kiro.selectedTargetId, 'kiro-rs');
assert.ok(api.getRemovedKeys().includes('kiroRsUrl'));
});
test('setPersistentSettings mirrors flat mail provider updates into canonical settingsState', async () => {
const api = buildHarness(`
const persistedWrites = [];
const removedKeys = [];
const chrome = {
storage: {
local: {
async get() {
return {
settingsSchemaVersion: 5,
settingsState: {
activeFlowId: 'openai',
services: {
account: { customPassword: '' },
email: { provider: '163' },
proxy: { enabled: false, provider: '711proxy', mode: 'account' },
},
flows: {},
},
};
},
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({
mailProvider: 'cloudflare-temp-email',
});
const write = api.getPersistedWrites().at(-1);
assert.equal(persisted.mailProvider, 'cloudflare-temp-email');
assert.equal(persisted.settingsState.services.email.provider, 'cloudflare-temp-email');
assert.equal(write.settingsState.services.email.provider, 'cloudflare-temp-email');
assert.equal(Object.prototype.hasOwnProperty.call(write, 'mailProvider'), false);
});
test('setPersistentSettings mirrors custom mail helper mode into canonical email settings', async () => {
const api = buildHarness(`
const persistedWrites = [];
const removedKeys = [];
const chrome = {
storage: {
local: {
async get() {
return {};
},
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({
mailProvider: 'custom',
customMailReceiveMode: 'helper',
customMailHelperBaseUrl: 'http://127.0.0.1:17374/',
});
const write = api.getPersistedWrites().at(-1);
assert.equal(persisted.mailProvider, 'custom');
assert.equal(persisted.customMailReceiveMode, 'helper');
assert.equal(persisted.customMailHelperBaseUrl, 'http://127.0.0.1:17374');
assert.equal(write.settingsState.services.email.provider, 'custom');
assert.equal(write.settingsState.services.email.customReceiveMode, 'helper');
assert.equal(write.settingsState.services.email.customHelperBaseUrl, 'http://127.0.0.1:17374');
assert.equal(Object.prototype.hasOwnProperty.call(write, 'customMailReceiveMode'), false);
});
test('buildPersistentSettingsPayload persists normalized MaDao flat settings outside canonical settingsState', () => {
const api = buildHarness();
const payload = api.buildPersistentSettingsPayload({
phoneSmsProvider: 'MaDao',
madaoBaseUrl: 'http://127.0.0.1:7822/api/acquire?x=1',
madaoHttpSecret: ' secret-token ',
madaoMode: 'direct',
madaoRoutingPlanId: ' rp-openai ',
madaoProviderId: ' Upstream A! ',
madaoCountry: ' gb ',
madaoAutoPickCountry: 0,
madaoReusePhone: 1,
madaoMinPrice: '0.123456',
madaoMaxPrice: '-1',
});
assert.equal(payload.phoneSmsProvider, 'madao');
assert.equal(payload.madaoBaseUrl, DEFAULT_MADAO_BASE_URL_FOR_TEST);
assert.equal(payload.madaoHttpSecret, ' secret-token ');
assert.equal(payload.madaoMode, 'direct');
assert.equal(payload.madaoRoutingPlanId, 'rp-openai');
assert.equal(payload.madaoProviderId, 'upstreama');
assert.equal(payload.madaoCountry, 'GB');
assert.equal(payload.madaoAutoPickCountry, false);
assert.equal(payload.madaoReusePhone, true);
assert.equal(payload.madaoMinPrice, '0.1235');
assert.equal(payload.madaoMaxPrice, '');
assert.equal(Object.prototype.hasOwnProperty.call(payload.settingsState || {}, 'madaoBaseUrl'), false);
});
test('setPersistentSettings mirrors flat schema updates without resetting other canonical settings', async () => {
const api = buildHarness(`
const persistedWrites = [];
const removedKeys = [];
const chrome = {
storage: {
local: {
async get() {
return {
settingsSchemaVersion: 5,
settingsState: {
activeFlowId: 'openai',
services: {
account: { customPassword: 'old-password' },
email: { provider: '163' },
proxy: { enabled: false, provider: '711proxy', mode: 'account' },
},
flows: {
openai: {
selectedTargetId: 'cpa',
targets: {
cpa: {
vpsUrl: 'https://old-cpa.example.com',
vpsPassword: 'old-vps-password',
localCpaStep9Mode: 'submit',
},
sub2api: {
sub2apiUrl: 'https://sub2api.example.com',
sub2apiEmail: 'owner@example.com',
sub2apiPassword: 'sub2api-secret',
sub2apiGroupName: 'kept-group',
sub2apiGroupNames: ['kept-group'],
sub2apiAccountPriority: 7,
sub2apiDefaultProxyName: 'proxy-a',
},
codex2api: {
codex2apiUrl: 'https://codex2api.example.com',
codex2apiAdminKey: 'codex-key',
},
},
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: 'kiro-key',
},
},
autoRun: {
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 9 },
},
},
},
},
};
},
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({
targetId: 'sub2api',
mailProvider: 'cloudflare-temp-email',
ipProxyEnabled: true,
ipProxyMode: 'api',
stepExecutionRangeByFlow: {
openai: { enabled: true, fromStep: 2, toStep: 4 },
},
});
const write = api.getPersistedWrites().at(-1);
assert.equal(persisted.targetId, 'sub2api');
assert.equal(persisted.mailProvider, 'cloudflare-temp-email');
assert.equal(persisted.ipProxyEnabled, true);
assert.equal(persisted.ipProxyMode, 'api');
assert.deepEqual(persisted.stepExecutionRangeByFlow.openai, {
enabled: true,
fromStep: 2,
toStep: 4,
});
assert.equal(write.settingsState.flows.openai.selectedTargetId, 'sub2api');
assert.equal(write.settingsState.services.email.provider, 'cloudflare-temp-email');
assert.equal(write.settingsState.services.proxy.enabled, true);
assert.equal(write.settingsState.services.proxy.mode, 'api');
assert.deepEqual(write.settingsState.flows.openai.autoRun.stepExecutionRange, {
enabled: true,
fromStep: 2,
toStep: 4,
});
assert.equal(write.settingsState.flows.openai.targets.sub2api.sub2apiUrl, 'https://sub2api.example.com');
assert.equal(write.settingsState.flows.openai.targets.sub2api.sub2apiEmail, 'owner@example.com');
assert.equal(write.settingsState.flows.kiro.targets['kiro-rs'].apiKey, 'kiro-key');
assert.equal(Object.prototype.hasOwnProperty.call(write, 'mailProvider'), false);
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'));
});