feat: lay groundwork for multi-flow registries

This commit is contained in:
QLHazyCoder
2026-05-13 04:46:56 +08:00
parent 04f89620be
commit a85ed0ce89
28 changed files with 2775 additions and 92 deletions
+3
View File
@@ -731,6 +731,9 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
'async function getPersistedAliasState() {',
' return {};',
'}',
'function buildStatePatchWithRuntimeState(_currentState, updates) {',
' return updates;',
'}',
'const chrome = {',
' storage: {',
' session: {',
@@ -0,0 +1,93 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
test('background imports mail rule registry and OpenAI mail rules modules', () => {
const source = fs.readFileSync('background.js', 'utf8');
assert.match(source, /background\/mail-rule-registry\.js/);
assert.match(source, /flows\/openai\/mail-rules\.js/);
});
test('mail rule registry exposes canonical OpenAI verification poll payloads', () => {
const registrySource = fs.readFileSync('background/mail-rule-registry.js', 'utf8');
const openAiSource = fs.readFileSync('flows/openai/mail-rules.js', 'utf8');
const registryApi = new Function('self', `${registrySource}; return self.MultiPageBackgroundMailRuleRegistry;`)({});
const openAiApi = new Function('self', `${openAiSource}; return self.MultiPageOpenAiMailRules;`)({});
const openAiMailRules = openAiApi.createOpenAiMailRules({
getHotmailVerificationRequestTimestamp: (step) => (step === 4 ? 123 : 456),
MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
});
const registry = registryApi.createMailRuleRegistry({
defaultFlowId: 'openai',
flowBuilders: {
openai: openAiMailRules,
},
});
assert.deepEqual(
registry.buildVerificationPollPayload(
4,
{
activeFlowId: 'openai',
email: 'user@example.com',
mailProvider: '2925',
mail2925Mode: 'receive',
},
{ excludeCodes: ['111111'] }
),
{
flowId: 'openai',
ruleId: 'openai-signup-code',
step: 4,
artifactType: 'code',
filterAfterTimestamp: 0,
senderFilters: ['openai', 'noreply', 'verify', 'auth', 'duckduckgo', 'forward'],
subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm'],
targetEmail: 'user@example.com',
mail2925MatchTargetEmail: true,
maxAttempts: 15,
intervalMs: 15000,
excludeCodes: ['111111'],
}
);
assert.deepEqual(
registry.buildVerificationPollPayload(8, {
activeFlowId: 'openai',
email: 'user@example.com',
step8VerificationTargetEmail: 'login@example.com',
}),
{
flowId: 'openai',
ruleId: 'openai-login-code',
step: 8,
artifactType: 'code',
filterAfterTimestamp: 456,
senderFilters: ['openai', 'noreply', 'verify', 'auth', 'chatgpt', 'duckduckgo', 'forward'],
subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm', 'login'],
targetEmail: 'login@example.com',
mail2925MatchTargetEmail: false,
maxAttempts: 5,
intervalMs: 3000,
}
);
});
test('mail rule registry rejects unknown active flow ids instead of silently using OpenAI rules', () => {
const registrySource = fs.readFileSync('background/mail-rule-registry.js', 'utf8');
const registryApi = new Function('self', `${registrySource}; return self.MultiPageBackgroundMailRuleRegistry;`)({});
const registry = registryApi.createMailRuleRegistry({
defaultFlowId: 'openai',
flowBuilders: {},
});
assert.throws(
() => registry.buildVerificationPollPayload(4, {
activeFlowId: 'site-a',
email: 'user@example.com',
}),
/未找到 flow=site-a 的邮件轮询规则构造器/
);
});
@@ -162,3 +162,39 @@ test('SAVE_SETTING broadcasts operation delay setting without background success
assert.deepStrictEqual(broadcasts.at(-1), { operationDelayEnabled: false });
assert.equal(logs.length, 0);
});
test('SAVE_SETTING re-resolves signup method when panel mode changes', async () => {
const source = fs.readFileSync('background/message-router.js', 'utf8');
const globalScope = { console };
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
let state = {
signupMethod: 'phone',
phoneVerificationEnabled: true,
plusModeEnabled: false,
panelMode: 'sub2api',
};
const router = api.createMessageRouter({
addLog: async () => {},
buildLuckmailSessionSettingsPayload: () => ({}),
buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'panelMode')
? { panelMode: input.panelMode }
: {},
broadcastDataUpdate: () => {},
getState: async () => ({ ...state }),
resolveSignupMethod: (nextState = {}) => nextState.panelMode === 'cpa' ? 'email' : 'phone',
setPersistentSettings: async () => {},
setState: async (updates) => {
state = { ...state, ...updates };
},
});
const response = await router.handleMessage({
type: 'SAVE_SETTING',
payload: { panelMode: 'cpa' },
});
assert.equal(response.ok, true);
assert.equal(state.panelMode, 'cpa');
assert.equal(state.signupMethod, 'email');
});
@@ -29,6 +29,7 @@ test('navigation utils recognize signup password pages for email and phone signu
assert.equal(utils.isSignupPasswordPageUrl('https://auth.openai.com/create-account/password'), true);
assert.equal(utils.isSignupPasswordPageUrl('https://auth.openai.com/log-in/password'), true);
assert.equal(utils.isSignupPasswordPageUrl('https://auth.openai.com/log-in'), false);
assert.equal(utils.isSignupEntryHost('www.chatgpt.com'), true);
});
test('navigation utils treat 126 mail hosts as part of the shared NetEase mail family', () => {
@@ -0,0 +1,163 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadRuntimeStateApi() {
const source = fs.readFileSync('background/runtime-state.js', 'utf8');
const globalScope = {};
return new Function('self', `${source}; return self.MultiPageBackgroundRuntimeState;`)(globalScope);
}
test('background imports runtime-state module and wires state view helpers', () => {
const source = fs.readFileSync('background.js', 'utf8');
assert.match(source, /background\/runtime-state\.js/);
assert.match(source, /createRuntimeStateHelpers/);
assert.match(source, /buildStateViewWithRuntimeState/);
assert.match(source, /buildStatePatchWithRuntimeState/);
assert.match(source, /runtimeState:/);
});
test('runtime-state module exposes a factory', () => {
const api = loadRuntimeStateApi();
assert.equal(typeof api?.createRuntimeStateHelpers, 'function');
});
test('runtime-state view derives canonical flow metadata from legacy step state', () => {
const api = loadRuntimeStateApi();
const helpers = api.createRuntimeStateHelpers({
DEFAULT_ACTIVE_FLOW_ID: 'openai',
defaultStepStatuses: {
1: 'pending',
2: 'pending',
10: 'pending',
},
getStepDefinitionForState(step) {
return {
1: { id: 1, key: 'open-chatgpt' },
2: { id: 2, key: 'submit-signup-email' },
10: { id: 10, key: 'oauth-login' },
}[Number(step)] || null;
},
});
const view = helpers.buildStateView({
currentStep: 2,
stepStatuses: {
1: 'completed',
2: 'running',
},
oauthUrl: 'https://auth.example.com/start',
plusCheckoutTabId: 88,
currentPhoneActivation: {
activationId: 'active-1',
phoneNumber: '+447700900123',
},
tabRegistry: {
'signup-page': { tabId: 12 },
},
sourceLastUrls: {
'signup-page': 'https://auth.example.com/start',
},
flowStartTime: 12345,
});
assert.equal(view.activeFlowId, 'openai');
assert.equal(view.currentNodeId, 'submit-signup-email');
assert.deepStrictEqual(view.legacyStepCompat, {
currentStep: 2,
stepStatuses: {
1: 'completed',
2: 'running',
10: 'pending',
},
});
assert.deepStrictEqual(view.nodeStatuses, {
'open-chatgpt': 'completed',
'submit-signup-email': 'running',
'oauth-login': 'pending',
});
assert.equal(view.runtimeState.flowState.openai.auth.oauthUrl, 'https://auth.example.com/start');
assert.equal(view.runtimeState.flowState.openai.plus.plusCheckoutTabId, 88);
assert.deepStrictEqual(view.runtimeState.flowState.openai.phoneVerification.currentPhoneActivation, {
activationId: 'active-1',
phoneNumber: '+447700900123',
});
assert.deepStrictEqual(view.sharedState, {
tabRegistry: {
'signup-page': { tabId: 12 },
},
sourceLastUrls: {
'signup-page': 'https://auth.example.com/start',
},
flowStartTime: 12345,
});
});
test('runtime-state patch accepts nested flow updates while keeping legacy compatibility fields in sync', () => {
const api = loadRuntimeStateApi();
const helpers = api.createRuntimeStateHelpers({
DEFAULT_ACTIVE_FLOW_ID: 'openai',
defaultStepStatuses: {
1: 'pending',
2: 'pending',
10: 'pending',
},
getStepDefinitionForState(step) {
return {
1: { id: 1, key: 'open-chatgpt' },
2: { id: 2, key: 'submit-signup-email' },
10: { id: 10, key: 'oauth-login' },
}[Number(step)] || null;
},
});
const patch = helpers.buildSessionStatePatch({
currentStep: 1,
stepStatuses: {
1: 'running',
2: 'pending',
10: 'pending',
},
oauthUrl: 'https://old.example.com/start',
}, {
runtimeState: {
activeRunId: 'run-001',
flowState: {
openai: {
auth: {
oauthUrl: 'https://new.example.com/start',
},
plus: {
plusCheckoutTabId: 99,
},
},
},
legacyStepCompat: {
currentStep: 10,
stepStatuses: {
1: 'completed',
10: 'running',
},
},
},
});
assert.equal(patch.activeFlowId, 'openai');
assert.equal(patch.activeRunId, 'run-001');
assert.equal(patch.currentNodeId, 'oauth-login');
assert.equal(patch.oauthUrl, 'https://new.example.com/start');
assert.equal(patch.plusCheckoutTabId, 99);
assert.equal(patch.currentStep, 10);
assert.deepStrictEqual(patch.stepStatuses, {
1: 'completed',
2: 'pending',
10: 'running',
});
assert.deepStrictEqual(patch.nodeStatuses, {
'open-chatgpt': 'completed',
'submit-signup-email': 'pending',
'oauth-login': 'running',
});
assert.equal(patch.runtimeState.flowState.openai.auth.oauthUrl, 'https://new.example.com/start');
assert.equal(patch.runtimeState.flowState.openai.plus.plusCheckoutTabId, 99);
});
@@ -95,6 +95,51 @@ return {
assert.equal(api.logs.some((entry) => /固定为邮箱注册/.test(entry.message)), true);
});
test('signup method resolution respects the shared flow capability registry when available', () => {
const api = new Function(`
const self = {
MultiPageFlowCapabilities: {
createFlowCapabilityRegistry() {
return {
resolveSidepanelCapabilities({ state = {}, signupMethod = 'email' } = {}) {
const phoneAllowed = String(state?.activeFlowId || '').trim().toLowerCase() === 'openai';
return {
canUsePhoneSignup: phoneAllowed,
effectiveSignupMethod: signupMethod === 'phone' && phoneAllowed ? 'phone' : 'email',
};
},
};
},
},
};
const SIGNUP_METHOD_EMAIL = 'email';
const SIGNUP_METHOD_PHONE = 'phone';
${extractFunction('normalizeSignupMethod')}
${extractFunction('canUsePhoneSignup')}
${extractFunction('resolveSignupMethod')}
return {
canUsePhoneSignup,
resolveSignupMethod,
};
`)();
assert.equal(api.canUsePhoneSignup({
activeFlowId: 'site-a',
phoneVerificationEnabled: true,
signupMethod: 'phone',
}), false);
assert.equal(api.resolveSignupMethod({
activeFlowId: 'site-a',
phoneVerificationEnabled: true,
signupMethod: 'phone',
}), 'email');
assert.equal(api.resolveSignupMethod({
activeFlowId: 'openai',
phoneVerificationEnabled: true,
signupMethod: 'phone',
}), 'phone');
});
test('background step definitions resolve titles from the frozen signup method', () => {
const api = new Function(`
const captured = [];
@@ -16,6 +16,76 @@ test('tab runtime module exposes a factory', () => {
assert.equal(typeof api?.createTabRuntime, 'function');
});
test('tab runtime accepts canonical openai-auth readiness for queued signup-page commands', async () => {
const runtimeSource = fs.readFileSync('background/tab-runtime.js', 'utf8');
const registrySource = fs.readFileSync('shared/source-registry.js', 'utf8');
const runtimeApi = new Function('self', `${runtimeSource}; return self.MultiPageBackgroundTabRuntime;`)({});
const registryApi = new Function('self', `${registrySource}; return self.MultiPageSourceRegistry;`)({});
const sourceRegistry = registryApi.createSourceRegistry();
const sentMessages = [];
let currentState = {
tabRegistry: {
'signup-page': { tabId: 91, ready: true },
},
sourceLastUrls: {
'signup-page': 'https://auth.openai.com/authorize',
},
};
const runtime = runtimeApi.createTabRuntime({
LOG_PREFIX: '[test]',
addLog: async () => {},
chrome: {
tabs: {
get: async (tabId) => ({
id: tabId,
windowId: 1,
url: 'https://auth.openai.com/authorize',
status: 'complete',
}),
query: async () => [],
sendMessage: async (tabId, message) => {
if (message.type === 'PING') {
return { ok: true, source: 'openai-auth' };
}
sentMessages.push({ tabId, message });
return { ok: true };
},
},
},
getSourceLabel: (source) => source || 'unknown',
getState: async () => currentState,
matchesSourceUrlFamily: (source, candidateUrl, referenceUrl) => (
sourceRegistry.matchesSourceUrlFamily(source, candidateUrl, referenceUrl)
),
setState: async (updates) => {
currentState = { ...currentState, ...updates };
},
sourceRegistry,
throwIfStopped: () => {},
});
assert.equal(await runtime.getTabId('openai-auth'), 91);
currentState = {
tabRegistry: {},
sourceLastUrls: {},
};
const queued = runtime.queueCommand('signup-page', { type: 'STEP2_TEST' }, 1000);
runtime.flushCommand('openai-auth', 55);
await assert.doesNotReject(queued);
assert.deepEqual(sentMessages, [{ tabId: 55, message: { type: 'STEP2_TEST' } }]);
await runtime.ensureContentScriptReadyOnTab('signup-page', 77, {
timeoutMs: 100,
});
assert.deepEqual(currentState.tabRegistry['openai-auth'], { tabId: 77, ready: true, windowId: 1 });
assert.equal(Object.prototype.hasOwnProperty.call(currentState.tabRegistry, 'signup-page'), false);
});
test('tab runtime caps per-attempt response timeout to the remaining resilient timeout budget', () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const globalScope = {};
+34
View File
@@ -81,6 +81,38 @@ return { detectScriptSource };
);
});
test('detectScriptSource maps OpenAI auth hosts to canonical openai-auth source', () => {
const bundle = [extractFunction('detectScriptSource')].join('\n');
const api = new Function(`
${bundle}
return { detectScriptSource };
`)();
assert.equal(
api.detectScriptSource({
url: 'https://auth.openai.com/create-account',
hostname: 'auth.openai.com',
}),
'openai-auth'
);
});
test('detectScriptSource returns unknown-source for unrecognized pages', () => {
const bundle = [extractFunction('detectScriptSource')].join('\n');
const api = new Function(`
${bundle}
return { detectScriptSource };
`)();
assert.equal(
api.detectScriptSource({
url: 'https://example.com/',
hostname: 'example.com',
}),
'unknown-source'
);
});
test('shouldReportReadyForFrame suppresses noisy plus checkout child frame ready logs', () => {
const bundle = [extractFunction('shouldReportReadyForFrame')].join('\n');
const api = new Function(`
@@ -91,6 +123,8 @@ return { shouldReportReadyForFrame };
assert.equal(api.shouldReportReadyForFrame('plus-checkout', true), false);
assert.equal(api.shouldReportReadyForFrame('plus-checkout', false), true);
assert.equal(api.shouldReportReadyForFrame('paypal-flow', true), true);
assert.equal(api.shouldReportReadyForFrame('unknown-source', false), true);
assert.equal(api.shouldReportReadyForFrame('unknown-source', true), false);
});
test('getRuntimeScriptSource follows injected source overrides after utils is already loaded', () => {
+72
View File
@@ -0,0 +1,72 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('shared/flow-capabilities.js', 'utf8');
function loadApi() {
const scope = {};
return new Function('self', `${source}; return self.MultiPageFlowCapabilities;`)(scope);
}
test('flow capability registry keeps OpenAI phone signup available only when runtime locks allow it', () => {
const api = loadApi();
const registry = api.createFlowCapabilityRegistry();
const enabledState = registry.resolveSidepanelCapabilities({
state: {
activeFlowId: 'openai',
panelMode: 'cpa',
phoneVerificationEnabled: true,
plusModeEnabled: false,
contributionMode: false,
signupMethod: 'phone',
},
});
assert.equal(enabledState.canUsePhoneSignup, true);
assert.equal(enabledState.effectiveSignupMethod, 'phone');
assert.equal(enabledState.shouldWarnCpaPhoneSignup, true);
assert.deepEqual(enabledState.effectiveSignupMethods, ['email', 'phone']);
const plusLockedState = registry.resolveSidepanelCapabilities({
state: {
activeFlowId: 'openai',
panelMode: 'sub2api',
phoneVerificationEnabled: true,
plusModeEnabled: true,
contributionMode: false,
signupMethod: 'phone',
},
});
assert.equal(plusLockedState.canUsePhoneSignup, false);
assert.equal(plusLockedState.effectiveSignupMethod, 'email');
assert.equal(plusLockedState.shouldWarnCpaPhoneSignup, false);
assert.deepEqual(plusLockedState.effectiveSignupMethods, ['email']);
});
test('flow capability registry defaults unknown flows to minimal non-phone capabilities', () => {
const api = loadApi();
const registry = api.createFlowCapabilityRegistry();
const capabilityState = registry.resolveSidepanelCapabilities({
state: {
activeFlowId: 'site-a',
panelMode: 'codex2api',
phoneVerificationEnabled: true,
plusModeEnabled: true,
contributionMode: true,
signupMethod: 'phone',
},
});
assert.equal(capabilityState.activeFlowId, 'site-a');
assert.equal(capabilityState.canShowPhoneSettings, false);
assert.equal(capabilityState.canShowPlusSettings, false);
assert.equal(capabilityState.canShowLuckmail, false);
assert.equal(capabilityState.canUsePhoneSignup, false);
assert.equal(capabilityState.effectiveSignupMethod, 'email');
assert.equal(capabilityState.panelMode, 'codex2api');
assert.deepEqual(capabilityState.supportedPanelModes, []);
});
@@ -294,6 +294,58 @@ return {
assert.equal(api.shouldWarnCpaPhoneSignup('phone', 'cpa'), false);
});
test('sidepanel phone signup gating can follow the shared flow capability registry', () => {
const bundle = [
extractFunction('normalizeSignupMethod'),
extractFunction('normalizePanelMode'),
extractFunction('canSelectPhoneSignupMethod'),
extractFunction('shouldWarnCpaPhoneSignup'),
].join('\n');
const api = new Function(`
const window = {
MultiPageFlowCapabilities: {
createFlowCapabilityRegistry() {
return {
resolveSidepanelCapabilities({ state = {}, panelMode = 'cpa', signupMethod = 'email' } = {}) {
const phoneAllowed = String(state?.activeFlowId || '').trim().toLowerCase() === 'openai';
return {
canSelectPhoneSignup: phoneAllowed,
shouldWarnCpaPhoneSignup: phoneAllowed && signupMethod === 'phone' && panelMode === 'cpa',
};
},
};
},
},
};
let latestState = {
activeFlowId: 'site-a',
contributionMode: false,
panelMode: 'cpa',
};
const inputPhoneVerificationEnabled = { checked: true };
const inputPlusModeEnabled = { checked: false };
function getSelectedPanelMode() { return 'cpa'; }
function getSelectedSignupMethod() { return 'phone'; }
function isCpaPhoneSignupPromptDismissed() { return false; }
${bundle}
return {
canSelectPhoneSignupMethod,
shouldWarnCpaPhoneSignup,
setFlow(flowId) {
latestState.activeFlowId = flowId;
},
};
`)();
assert.equal(api.canSelectPhoneSignupMethod(), false);
assert.equal(api.shouldWarnCpaPhoneSignup('phone', 'cpa'), false);
api.setFlow('openai');
assert.equal(api.canSelectPhoneSignupMethod(), true);
assert.equal(api.shouldWarnCpaPhoneSignup('phone', 'cpa'), true);
});
test('manual step 3 uses phone identity without requiring registration email', () => {
const api = new Function(`
let latestState = { signupMethod: 'phone', phoneVerificationEnabled: true, signupPhoneNumber: '+441111111111', accountIdentifierType: 'phone', accountIdentifier: '+441111111111' };
+58 -2
View File
@@ -124,8 +124,8 @@ test('sidepanel signup method UI syncs shared step definitions with the selected
test('sidepanel applies restored signup method when rebuilding shared step definitions on load', () => {
const source = extractFunction('applySettingsState');
assert.match(source, /syncStepDefinitionsForMode\(Boolean\(state\?\.plusModeEnabled\),\s*\{/);
assert.match(source, /signupMethod:\s*state\?\.signupMethod/);
assert.match(source, /resolveStepDefinitionCapabilityState\(state/);
assert.match(source, /signupMethod:\s*stepDefinitionState\.signupMethod/);
});
test('sidepanel Plus UI hides PayPal account selector while GoPay is selected', () => {
@@ -165,6 +165,62 @@ return { updatePlusModeUI, selectPlusPaymentMethod, rowPayPalAccount };
assert.equal(api.rowPayPalAccount.style.display, '');
});
test('sidepanel Plus UI can hide Plus controls when the shared flow capability registry disables them', () => {
const bundle = [
extractFunction('normalizePlusPaymentMethod'),
extractFunction('getSelectedPlusPaymentMethod'),
extractFunction('normalizeGpcHelperPhoneModeValue'),
extractFunction('getGpcHelperAutoModeEnabled'),
extractFunction('normalizeGpcAutoModePermissionValue'),
extractFunction('getGpcAutoModePermissionFromPayload'),
extractFunction('shouldPreserveSelectedGpcAutoMode'),
extractFunction('hasGpcAutoModePermissionField'),
extractFunction('isGpcAutoModePermissionDenied'),
extractFunction('normalizeGpcOtpChannelValue'),
extractFunction('updatePlusModeUI'),
].join('\n');
const api = new Function(`
const window = {
MultiPageFlowCapabilities: {
createFlowCapabilityRegistry() {
return {
resolveSidepanelCapabilities() {
return {
canShowPlusSettings: false,
runtimeLocks: { plusModeEnabled: false },
};
},
};
},
},
};
let latestState = { plusPaymentMethod: 'paypal' };
const inputPlusModeEnabled = { checked: true };
const rowPlusMode = { style: { display: '' } };
const selectPlusPaymentMethod = { value: 'paypal', style: { display: '' } };
const rowPlusPaymentMethod = { style: { display: '' } };
const rowPayPalAccount = { style: { display: '' } };
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
${bundle}
return {
rowPlusMode,
rowPlusPaymentMethod,
rowPayPalAccount,
selectPlusPaymentMethod,
updatePlusModeUI,
};
`)();
api.updatePlusModeUI();
assert.equal(api.rowPlusMode.style.display, 'none');
assert.equal(api.rowPlusPaymentMethod.style.display, 'none');
assert.equal(api.rowPayPalAccount.style.display, 'none');
assert.equal(api.selectPlusPaymentMethod.style.display, 'none');
});
test('sidepanel step definitions keep GPC helper mode distinct', () => {
const bundle = [
extractFunction('normalizeSignupMethod'),
+56
View File
@@ -0,0 +1,56 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
test('background imports shared source registry module', () => {
const source = fs.readFileSync('background.js', 'utf8');
assert.match(source, /shared\/source-registry\.js/);
});
test('manifest loads shared source registry before content utils in static bundles', () => {
const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));
for (const entry of manifest.content_scripts || []) {
const scripts = Array.isArray(entry.js) ? entry.js : [];
if (!scripts.includes('content/utils.js')) continue;
assert.ok(scripts.includes('shared/source-registry.js'));
assert.ok(
scripts.indexOf('shared/source-registry.js') < scripts.indexOf('content/utils.js'),
'shared/source-registry.js must load before content/utils.js'
);
}
});
test('shared source registry exposes canonical source, alias, detection, and ready policies', () => {
const source = fs.readFileSync('shared/source-registry.js', 'utf8');
const api = new Function('self', `${source}; return self.MultiPageSourceRegistry;`)({});
const registry = api.createSourceRegistry();
assert.equal(registry.resolveCanonicalSource('signup-page'), 'openai-auth');
assert.deepEqual(registry.getSourceKeys('signup-page'), ['openai-auth', 'signup-page']);
assert.equal(registry.getSourceLabel('openai-auth'), '认证页');
assert.equal(
registry.matchesSourceUrlFamily(
'openai-auth',
'https://chatgpt.com/',
'https://auth.openai.com/authorize?client_id=test'
),
true
);
assert.equal(
registry.detectSourceFromLocation({
url: 'https://auth.openai.com/create-account',
hostname: 'auth.openai.com',
}),
'openai-auth'
);
assert.equal(
registry.detectSourceFromLocation({
url: 'https://example.com/',
hostname: 'example.com',
}),
'unknown-source'
);
assert.equal(registry.shouldReportReadyForFrame('mail-163', true), false);
assert.equal(registry.shouldReportReadyForFrame('unknown-source', false), false);
assert.equal(registry.getCleanupOwnerSource('oauth-localhost-callback'), 'openai-auth');
});
+37
View File
@@ -9,6 +9,7 @@ const api = new Function('self', `${source}; return self.MultiPageBackgroundVeri
function createVerificationFlowTestHelpers(overrides = {}) {
return api.createVerificationFlowHelpers({
addLog: async () => {},
buildVerificationPollPayload: null,
chrome: {
tabs: {
update: async () => {},
@@ -43,6 +44,42 @@ function createVerificationFlowTestHelpers(overrides = {}) {
});
}
test('verification flow prefers injected verification poll payload builder when provided', () => {
const helpers = createVerificationFlowTestHelpers({
buildVerificationPollPayload: (step, state, overrides = {}) => ({
flowId: state.activeFlowId || 'openai',
ruleId: 'custom-rule',
step,
senderFilters: ['custom-sender'],
subjectFilters: ['custom-subject'],
targetEmail: state.email,
maxAttempts: 9,
intervalMs: 4321,
...overrides,
}),
});
assert.deepStrictEqual(
helpers.getVerificationPollPayload(4, {
activeFlowId: 'openai',
email: 'user@example.com',
}, {
excludeCodes: ['111111'],
}),
{
flowId: 'openai',
ruleId: 'custom-rule',
step: 4,
senderFilters: ['custom-sender'],
subjectFilters: ['custom-subject'],
targetEmail: 'user@example.com',
maxAttempts: 9,
intervalMs: 4321,
excludeCodes: ['111111'],
}
);
});
test('verification flow keeps 2925 polling cadence in the default payload', () => {
const helpers = api.createVerificationFlowHelpers({
addLog: async () => {},