refactor flows into canonical runtime architecture
This commit is contained in:
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,8 +2,8 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const { createAuthPageRecovery } = require('../content/auth-page-recovery.js');
|
||||
const source = fs.readFileSync('content/auth-page-recovery.js', 'utf8');
|
||||
const { createAuthPageRecovery } = require('../flows/openai/content/auth-page-recovery.js');
|
||||
const source = fs.readFileSync('flows/openai/content/auth-page-recovery.js', 'utf8');
|
||||
|
||||
function extractFunction(sourceText, name) {
|
||||
const start = sourceText.indexOf(`function ${name}(`);
|
||||
|
||||
@@ -311,10 +311,10 @@ async function runAutoSequenceFromStep() {
|
||||
10: 'completed',
|
||||
}),
|
||||
tabRegistry: {
|
||||
'signup-page': { tabId: 88, ready: true },
|
||||
'openai-auth': { tabId: 88, ready: true },
|
||||
},
|
||||
sourceLastUrls: {
|
||||
'signup-page': 'https://auth.openai.com/authorize',
|
||||
'openai-auth': 'https://auth.openai.com/authorize',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ return {
|
||||
assert.equal(api.normalizePersistentSettingValue('signupMethod', 'unknown'), 'email');
|
||||
assert.equal(api.normalizePersistentSettingValue('activeFlowId', 'codex'), 'openai');
|
||||
assert.equal(api.normalizePersistentSettingValue('activeFlowId', 'kiro'), 'kiro');
|
||||
assert.equal(api.normalizePersistentSettingValue('kiroTargetId', 'unknown'), 'kiro-rs');
|
||||
assert.equal(api.normalizePersistentSettingValue('targetId', 'sub2api'), 'sub2api');
|
||||
assert.equal(api.normalizePersistentSettingValue('kiroRsUrl', ''), '');
|
||||
assert.equal(api.normalizePersistentSettingValue('kiroRsKey', ' key-1 '), 'key-1');
|
||||
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', '5SIM'), '5sim');
|
||||
|
||||
@@ -120,7 +120,7 @@ const self = {
|
||||
},
|
||||
},
|
||||
};
|
||||
const DEFAULT_STATE = { panelMode: 'cpa' };
|
||||
const DEFAULT_STATE = { targetId: 'cpa' };
|
||||
const CONTRIBUTION_RUNTIME_DEFAULTS = {
|
||||
accountContributionEnabled: false,
|
||||
accountContributionExpected: false,
|
||||
@@ -150,7 +150,7 @@ return { buildAccountContributionState };
|
||||
`)();
|
||||
|
||||
const enabledState = api.buildAccountContributionState(true, {
|
||||
panelMode: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {
|
||||
@@ -166,12 +166,12 @@ return { buildAccountContributionState };
|
||||
openai: { enabled: true, adapterId: 'openai-oauth' },
|
||||
});
|
||||
assert.equal(enabledState.contributionSessionId, 'session-001');
|
||||
assert.equal(enabledState.panelMode, 'sub2api');
|
||||
assert.equal(enabledState.targetId, 'sub2api');
|
||||
assert.equal(enabledState.customPassword, '');
|
||||
assert.equal(enabledState.accountRunHistoryTextEnabled, false);
|
||||
|
||||
const disabledState = api.buildAccountContributionState(false, {
|
||||
panelMode: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {
|
||||
@@ -184,17 +184,17 @@ return { buildAccountContributionState };
|
||||
assert.equal(disabledState.contributionAdapterId, '');
|
||||
assert.deepStrictEqual(disabledState.flowContributionRuntime, {});
|
||||
assert.equal(disabledState.contributionSessionId, '');
|
||||
assert.equal(disabledState.panelMode, 'sub2api');
|
||||
assert.equal(disabledState.targetId, 'sub2api');
|
||||
assert.equal(disabledState.customPassword, 'Secret123!');
|
||||
|
||||
const plusContributionState = api.buildAccountContributionState(true, {
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
plusModeEnabled: true,
|
||||
customPassword: 'Secret123!',
|
||||
accountRunHistoryTextEnabled: true,
|
||||
}, {});
|
||||
assert.equal(plusContributionState.contributionTargetGroupName, 'openai-plus');
|
||||
assert.equal(plusContributionState.panelMode, 'sub2api');
|
||||
assert.equal(plusContributionState.targetId, 'sub2api');
|
||||
});
|
||||
|
||||
test('resetState preserves contribution runtime across reset', () => {
|
||||
@@ -289,7 +289,7 @@ test('message router handles contribution mode, start flow, and status polling m
|
||||
calls.push({ type: 'toggle', enabled });
|
||||
return {
|
||||
accountContributionEnabled: Boolean(enabled),
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
};
|
||||
},
|
||||
startFlowContribution: async (options) => {
|
||||
@@ -383,7 +383,7 @@ test('message router blocks AUTO_RUN and SCHEDULE_AUTO_RUN when shared auto-run
|
||||
getPendingAutoRunTimerPlan: () => null,
|
||||
getState: async () => ({
|
||||
activeFlowId: 'site-a',
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
signupMethod: 'phone',
|
||||
stepStatuses: {},
|
||||
}),
|
||||
@@ -947,7 +947,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
|
||||
const oauthUrl = await api.refreshOAuthUrlBeforeStep6({
|
||||
accountContributionEnabled: false,
|
||||
panelMode: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
email: 'user@example.com',
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ const fs = require('node:fs');
|
||||
const test = require('node:test');
|
||||
|
||||
function loadCpaSessionImportModule() {
|
||||
const source = fs.readFileSync('background/steps/cpa-session-import.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/cpa-session-import.js', 'utf8');
|
||||
return new Function('self', `${source}; return self.MultiPageBackgroundCpaSessionImport;`)({});
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ test('CPA session import step reads current ChatGPT session and completes node',
|
||||
assert.deepStrictEqual(ensureCalls[0].options.inject, [
|
||||
'content/utils.js',
|
||||
'content/operation-delay.js',
|
||||
'content/plus-checkout.js',
|
||||
'flows/openai/content/plus-checkout.js',
|
||||
]);
|
||||
assert.deepStrictEqual(sentMessages, [{
|
||||
tabId: 91,
|
||||
|
||||
@@ -120,20 +120,20 @@ function isPlusModeState(state = {}) {
|
||||
return Boolean(state?.plusModeEnabled);
|
||||
}
|
||||
function resolveCurrentFlowCapabilities(state = {}, options = {}) {
|
||||
const normalizedPanelMode = String(options.panelMode || '').trim().toLowerCase();
|
||||
const normalizedTargetId = String(options.targetId || '').trim().toLowerCase();
|
||||
const requestedStrategy = normalizePlusAccountAccessStrategy(state.plusAccountAccessStrategy);
|
||||
const effectiveStrategy = normalizedPanelMode === 'sub2api'
|
||||
const effectiveStrategy = normalizedTargetId === 'sub2api'
|
||||
? (requestedStrategy === 'sub2api_codex_session' ? 'sub2api_codex_session' : 'oauth')
|
||||
: (normalizedPanelMode === 'cpa'
|
||||
: (normalizedTargetId === 'cpa'
|
||||
? (requestedStrategy === 'cpa_codex_session' ? 'cpa_codex_session' : 'oauth')
|
||||
: 'oauth');
|
||||
return {
|
||||
effectivePanelMode: options.panelMode,
|
||||
effectiveTargetId: options.targetId,
|
||||
effectivePlusAccountAccessStrategy: effectiveStrategy,
|
||||
effectiveSignupMethod: 'email',
|
||||
stepDefinitionOptions: {
|
||||
activeFlowId: 'openai',
|
||||
panelMode: options.panelMode,
|
||||
targetId: options.targetId,
|
||||
plusModeEnabled: Boolean(state.plusModeEnabled),
|
||||
plusPaymentMethod: normalizePlusPaymentMethod(state.plusPaymentMethod),
|
||||
plusAccountAccessStrategy: effectiveStrategy,
|
||||
@@ -158,7 +158,7 @@ test('background step resolution keeps SUB2API session tail only when the effect
|
||||
const state = {
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
@@ -184,7 +184,7 @@ test('background step resolution keeps CPA session tail when the effective Plus
|
||||
const state = {
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||
@@ -210,7 +210,7 @@ test('background step resolution falls back to OAuth tail when the requested ses
|
||||
const state = {
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
|
||||
@@ -3,34 +3,42 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
function loadDesktopAuthorizeRunnerApi() {
|
||||
const stateSource = fs.readFileSync('background/kiro/state.js', 'utf8');
|
||||
const clientSource = fs.readFileSync('background/kiro/desktop-client.js', 'utf8');
|
||||
const runnerSource = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
|
||||
const stateSource = fs.readFileSync('flows/kiro/background/state.js', 'utf8');
|
||||
const clientSource = fs.readFileSync('flows/kiro/background/desktop-client.js', 'utf8');
|
||||
const runnerSource = fs.readFileSync('flows/kiro/background/desktop-authorize-runner.js', 'utf8');
|
||||
const globalScope = {};
|
||||
new Function('self', `${stateSource}; ${clientSource}; ${runnerSource}; return self;`)(globalScope);
|
||||
return globalScope.MultiPageBackgroundKiroDesktopAuthorizeRunner;
|
||||
}
|
||||
|
||||
function getKiroRuntime(state = {}) {
|
||||
return state?.runtimeState?.flowState?.kiro || {};
|
||||
}
|
||||
|
||||
function createDesktopAuthorizeState(overrides = {}) {
|
||||
return {
|
||||
kiroRuntime: {
|
||||
session: {
|
||||
desktopTabId: 91,
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
session: {
|
||||
desktopTabId: 91,
|
||||
},
|
||||
register: {
|
||||
email: 'kiro-user@example.com',
|
||||
},
|
||||
desktopAuth: {
|
||||
region: 'us-east-1',
|
||||
clientId: 'desktop-client-id',
|
||||
clientSecret: 'desktop-client-secret',
|
||||
state: 'desktop-state-001',
|
||||
codeVerifier: 'desktop-code-verifier',
|
||||
redirectUri: 'http://127.0.0.1:43121/oauth/callback',
|
||||
redirectPort: 43121,
|
||||
authorizeUrl: 'https://example.com/authorize',
|
||||
},
|
||||
upload: {},
|
||||
},
|
||||
},
|
||||
register: {
|
||||
email: 'kiro-user@example.com',
|
||||
},
|
||||
desktopAuth: {
|
||||
region: 'us-east-1',
|
||||
clientId: 'desktop-client-id',
|
||||
clientSecret: 'desktop-client-secret',
|
||||
state: 'desktop-state-001',
|
||||
codeVerifier: 'desktop-code-verifier',
|
||||
redirectUri: 'http://127.0.0.1:43121/oauth/callback',
|
||||
redirectPort: 43121,
|
||||
authorizeUrl: 'https://example.com/authorize',
|
||||
},
|
||||
upload: {},
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
@@ -73,7 +81,7 @@ test('parseDesktopCallbackUrl validates state and redirect port', () => {
|
||||
});
|
||||
|
||||
test('kiro desktop authorize runner uses a shared 3-minute page-load timeout budget', () => {
|
||||
const source = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/kiro/background/desktop-authorize-runner.js', 'utf8');
|
||||
assert.match(source, /DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS/);
|
||||
assert.match(source, /createTimeoutBudget/);
|
||||
assert.match(source, /resolveTimeoutBudget/);
|
||||
@@ -95,14 +103,14 @@ test('background wires the Kiro register injector into desktop authorization run
|
||||
});
|
||||
|
||||
test('kiro desktop authorization opens account page when no web session tab is tracked', () => {
|
||||
const source = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/kiro/background/desktop-authorize-runner.js', 'utf8');
|
||||
assert.match(source, /KIRO_WEB_ACCOUNT_URL = 'https:\/\/app\.kiro\.dev\/settings\/account'/);
|
||||
assert.match(source, /chrome\.tabs\.create\(\{\s*url: KIRO_WEB_ACCOUNT_URL,\s*active: true,/);
|
||||
assert.match(source, /未能从已打开页面确认 Kiro Web 登录态,正在打开 Kiro 账号页重新确认/);
|
||||
});
|
||||
|
||||
test('kiro desktop authorization is gated by completed Kiro Web sign-in', () => {
|
||||
const source = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/kiro/background/desktop-authorize-runner.js', 'utf8');
|
||||
assert.match(source, /restoreKiroWebSessionFromOpenTabs/);
|
||||
assert.match(source, /GET_KIRO_REGISTER_PAGE_STATE/);
|
||||
assert.match(source, /Kiro Web 登录态尚未建立/);
|
||||
@@ -112,12 +120,16 @@ test('kiro desktop authorization is gated by completed Kiro Web sign-in', () =>
|
||||
test('executeKiroStartDesktopAuthorize restores existing Kiro Web session before desktop auth', async () => {
|
||||
const api = loadDesktopAuthorizeRunnerApi();
|
||||
let currentState = {
|
||||
kiroRuntime: {
|
||||
session: {},
|
||||
register: {},
|
||||
webAuth: {},
|
||||
desktopAuth: {},
|
||||
upload: {},
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
session: {},
|
||||
register: {},
|
||||
webAuth: {},
|
||||
desktopAuth: {},
|
||||
upload: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const setStateCalls = [];
|
||||
@@ -178,7 +190,7 @@ test('executeKiroStartDesktopAuthorize restores existing Kiro Web session before
|
||||
getState: async () => currentState,
|
||||
getTabId: async () => null,
|
||||
isTabAlive: async () => false,
|
||||
KIRO_REGISTER_INJECT_FILES: ['content/kiro/register-page.js'],
|
||||
KIRO_REGISTER_INJECT_FILES: ['flows/kiro/content/register-page.js'],
|
||||
registerTab: async (source, tabId) => {
|
||||
if (source === 'kiro-desktop-authorize') {
|
||||
desktopRegisteredTabId = tabId;
|
||||
@@ -213,10 +225,10 @@ test('executeKiroStartDesktopAuthorize restores existing Kiro Web session before
|
||||
assert.equal(desktopRegisteredTabId, 88);
|
||||
assert.match(openedAuthorizeUrl, /client_id=restored-client-id/);
|
||||
assert.equal(currentState.email, 'restored@duck.com');
|
||||
assert.equal(currentState.kiroRuntime.register.email, 'restored@duck.com');
|
||||
assert.equal(currentState.kiroRuntime.register.status, 'completed');
|
||||
assert.equal(currentState.kiroRuntime.webAuth.status, 'signed_in');
|
||||
assert.equal(completedPayload?.kiroRuntime?.desktopAuth?.clientId, 'restored-client-id');
|
||||
assert.equal(getKiroRuntime(currentState).register.email, 'restored@duck.com');
|
||||
assert.equal(getKiroRuntime(currentState).register.status, 'completed');
|
||||
assert.equal(getKiroRuntime(currentState).webAuth.status, 'signed_in');
|
||||
assert.equal(getKiroRuntime(completedPayload).desktopAuth?.clientId, 'restored-client-id');
|
||||
assert.equal(
|
||||
logs.some(({ message }) => message.includes('检测到已有 Kiro Web 登录态,已恢复账号 restored@duck.com')),
|
||||
true
|
||||
@@ -227,12 +239,16 @@ test('executeKiroStartDesktopAuthorize restores existing Kiro Web session before
|
||||
test('executeKiroStartDesktopAuthorize opens Kiro account page to restore login state', async () => {
|
||||
const api = loadDesktopAuthorizeRunnerApi();
|
||||
let currentState = {
|
||||
kiroRuntime: {
|
||||
session: {},
|
||||
register: {},
|
||||
webAuth: {},
|
||||
desktopAuth: {},
|
||||
upload: {},
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
session: {},
|
||||
register: {},
|
||||
webAuth: {},
|
||||
desktopAuth: {},
|
||||
upload: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const logs = [];
|
||||
@@ -278,7 +294,7 @@ test('executeKiroStartDesktopAuthorize opens Kiro account page to restore login
|
||||
ensureContentScriptReadyOnTab: async (source, tabId, options = {}) => {
|
||||
assert.equal(source, 'kiro-register-page');
|
||||
assert.equal(tabId, 91);
|
||||
assert.deepEqual(options.inject, ['content/kiro/register-page.js']);
|
||||
assert.deepEqual(options.inject, ['flows/kiro/content/register-page.js']);
|
||||
assert.equal(options.injectSource, 'kiro-register-page');
|
||||
},
|
||||
fetchImpl: async () => ({
|
||||
@@ -291,7 +307,7 @@ test('executeKiroStartDesktopAuthorize opens Kiro account page to restore login
|
||||
getState: async () => currentState,
|
||||
getTabId: async () => null,
|
||||
isTabAlive: async () => false,
|
||||
KIRO_REGISTER_INJECT_FILES: ['content/kiro/register-page.js'],
|
||||
KIRO_REGISTER_INJECT_FILES: ['flows/kiro/content/register-page.js'],
|
||||
registerTab: async (source, tabId) => {
|
||||
registeredTabs.push({ source, tabId });
|
||||
},
|
||||
@@ -320,8 +336,8 @@ test('executeKiroStartDesktopAuthorize opens Kiro account page to restore login
|
||||
await runner.executeKiroStartDesktopAuthorize(currentState);
|
||||
|
||||
assert.equal(createdTabUrl, 'https://app.kiro.dev/settings/account');
|
||||
assert.equal(currentState.kiroRuntime.register.email, 'opened@duck.com');
|
||||
assert.equal(completedPayload?.kiroRuntime?.desktopAuth?.clientId, 'opened-client-id');
|
||||
assert.equal(getKiroRuntime(currentState).register.email, 'opened@duck.com');
|
||||
assert.equal(getKiroRuntime(completedPayload).desktopAuth?.clientId, 'opened-client-id');
|
||||
assert.deepEqual(registeredTabs, [
|
||||
{ source: 'kiro-register-page', tabId: 91 },
|
||||
{ source: 'kiro-desktop-authorize', tabId: 92 },
|
||||
@@ -391,31 +407,35 @@ test('executeKiroCompleteDesktopAuthorize finishes from callback page without wa
|
||||
|
||||
await runner.executeKiroCompleteDesktopAuthorize(currentState);
|
||||
|
||||
assert.equal(completedPayload?.kiroRuntime?.desktopAuth?.authorizationCode, 'auth-code-001');
|
||||
assert.equal(completedPayload?.kiroRuntime?.desktopAuth?.refreshToken, 'refresh-token-001');
|
||||
assert.equal(getKiroRuntime(completedPayload).desktopAuth?.authorizationCode, 'auth-code-001');
|
||||
assert.equal(getKiroRuntime(completedPayload).desktopAuth?.refreshToken, 'refresh-token-001');
|
||||
});
|
||||
|
||||
test('executeKiroCompleteDesktopAuthorize waits for callback after consent even if original tab disappears', async () => {
|
||||
const api = loadDesktopAuthorizeRunnerApi();
|
||||
let currentState = createDesktopAuthorizeState({
|
||||
kiroRuntime: {
|
||||
session: {
|
||||
desktopTabId: 91,
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
session: {
|
||||
desktopTabId: 91,
|
||||
},
|
||||
register: {
|
||||
email: 'kiro-user@example.com',
|
||||
},
|
||||
desktopAuth: {
|
||||
region: 'us-east-1',
|
||||
clientId: 'desktop-client-id',
|
||||
clientSecret: 'desktop-client-secret',
|
||||
state: 'desktop-state-002',
|
||||
codeVerifier: 'desktop-code-verifier',
|
||||
redirectUri: 'http://127.0.0.1:43121/oauth/callback',
|
||||
redirectPort: 43121,
|
||||
authorizeUrl: 'https://example.com/authorize',
|
||||
},
|
||||
upload: {},
|
||||
},
|
||||
},
|
||||
register: {
|
||||
email: 'kiro-user@example.com',
|
||||
},
|
||||
desktopAuth: {
|
||||
region: 'us-east-1',
|
||||
clientId: 'desktop-client-id',
|
||||
clientSecret: 'desktop-client-secret',
|
||||
state: 'desktop-state-002',
|
||||
codeVerifier: 'desktop-code-verifier',
|
||||
redirectUri: 'http://127.0.0.1:43121/oauth/callback',
|
||||
redirectPort: 43121,
|
||||
authorizeUrl: 'https://example.com/authorize',
|
||||
},
|
||||
upload: {},
|
||||
},
|
||||
});
|
||||
let completedPayload = null;
|
||||
@@ -505,6 +525,6 @@ test('executeKiroCompleteDesktopAuthorize waits for callback after consent even
|
||||
|
||||
await runner.executeKiroCompleteDesktopAuthorize(currentState);
|
||||
|
||||
assert.equal(completedPayload?.kiroRuntime?.desktopAuth?.authorizationCode, 'auth-code-002');
|
||||
assert.equal(completedPayload?.kiroRuntime?.desktopAuth?.refreshToken, 'refresh-token-002');
|
||||
assert.equal(getKiroRuntime(completedPayload).desktopAuth?.authorizationCode, 'auth-code-002');
|
||||
assert.equal(getKiroRuntime(completedPayload).desktopAuth?.refreshToken, 'refresh-token-002');
|
||||
});
|
||||
|
||||
@@ -3,13 +3,17 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
function loadPublisherApi() {
|
||||
const stateSource = fs.readFileSync('background/kiro/state.js', 'utf8');
|
||||
const publisherSource = fs.readFileSync('background/kiro/publisher-kiro-rs.js', 'utf8');
|
||||
const stateSource = fs.readFileSync('flows/kiro/background/state.js', 'utf8');
|
||||
const publisherSource = fs.readFileSync('flows/kiro/background/publisher-kiro-rs.js', 'utf8');
|
||||
const globalScope = {};
|
||||
new Function('self', `${stateSource}; ${publisherSource}; return self;`)(globalScope);
|
||||
return globalScope.MultiPageBackgroundKiroPublisherKiroRs;
|
||||
}
|
||||
|
||||
function getKiroRuntime(state = {}) {
|
||||
return state?.runtimeState?.flowState?.kiro || {};
|
||||
}
|
||||
|
||||
test('kiro publisher exposes a factory and upload payload helpers', () => {
|
||||
const api = loadPublisherApi();
|
||||
assert.equal(typeof api?.createKiroRsPublisher, 'function');
|
||||
@@ -20,7 +24,7 @@ test('kiro publisher exposes a factory and upload payload helpers', () => {
|
||||
test('kiro publisher builds kiro.rs payload from desktop auth runtime with BuilderId profileArn', async () => {
|
||||
const api = loadPublisherApi();
|
||||
const payload = api.buildKiroRsPayload({
|
||||
kiroTargetId: 'kiro-rs',
|
||||
targetId: 'kiro-rs',
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'demo-key',
|
||||
ipProxyEnabled: true,
|
||||
@@ -29,18 +33,22 @@ test('kiro publisher builds kiro.rs payload from desktop auth runtime with Build
|
||||
ipProxyProtocol: 'http',
|
||||
ipProxyUsername: 'proxy-user',
|
||||
ipProxyPassword: 'proxy-pass',
|
||||
kiroRuntime: {
|
||||
register: {
|
||||
email: 'aws-user@example.com',
|
||||
},
|
||||
desktopAuth: {
|
||||
region: 'us-east-1',
|
||||
clientId: 'client-001',
|
||||
clientSecret: 'secret-001',
|
||||
refreshToken: 'refresh-token-001',
|
||||
},
|
||||
upload: {
|
||||
targetId: 'kiro-rs',
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
register: {
|
||||
email: 'aws-user@example.com',
|
||||
},
|
||||
desktopAuth: {
|
||||
region: 'us-east-1',
|
||||
clientId: 'client-001',
|
||||
clientSecret: 'secret-001',
|
||||
refreshToken: 'refresh-token-001',
|
||||
},
|
||||
upload: {
|
||||
targetId: 'kiro-rs',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -69,22 +77,26 @@ test('kiro publisher reads latest kiro.rs key from background state instead of s
|
||||
const api = loadPublisherApi();
|
||||
const requests = [];
|
||||
let liveState = {
|
||||
kiroTargetId: 'kiro-rs',
|
||||
targetId: 'kiro-rs',
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'live-key',
|
||||
email: 'aws-user@example.com',
|
||||
kiroRuntime: {
|
||||
register: {
|
||||
email: 'aws-user@example.com',
|
||||
},
|
||||
desktopAuth: {
|
||||
region: 'us-east-1',
|
||||
clientId: 'client-001',
|
||||
clientSecret: 'secret-001',
|
||||
refreshToken: 'refresh-token-001',
|
||||
},
|
||||
upload: {
|
||||
targetId: 'kiro-rs',
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
register: {
|
||||
email: 'aws-user@example.com',
|
||||
},
|
||||
desktopAuth: {
|
||||
region: 'us-east-1',
|
||||
clientId: 'client-001',
|
||||
clientSecret: 'secret-001',
|
||||
refreshToken: 'refresh-token-001',
|
||||
},
|
||||
upload: {
|
||||
targetId: 'kiro-rs',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
settingsState: {
|
||||
@@ -180,19 +192,23 @@ test('kiro publisher routes step 9 through public contribution upload when contr
|
||||
flowId: 'kiro',
|
||||
accountContributionEnabled: true,
|
||||
contributionAdapterId: 'kiro-builder-id',
|
||||
kiroTargetId: 'kiro-rs',
|
||||
kiroRuntime: {
|
||||
register: {
|
||||
email: 'aws-user@example.com',
|
||||
},
|
||||
desktopAuth: {
|
||||
region: 'us-east-1',
|
||||
clientId: 'client-001',
|
||||
clientSecret: 'secret-001',
|
||||
refreshToken: 'refresh-token-001',
|
||||
},
|
||||
upload: {
|
||||
targetId: 'kiro-rs',
|
||||
targetId: 'kiro-rs',
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
register: {
|
||||
email: 'aws-user@example.com',
|
||||
},
|
||||
desktopAuth: {
|
||||
region: 'us-east-1',
|
||||
clientId: 'client-001',
|
||||
clientSecret: 'secret-001',
|
||||
refreshToken: 'refresh-token-001',
|
||||
},
|
||||
upload: {
|
||||
targetId: 'kiro-rs',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -224,10 +240,10 @@ test('kiro publisher routes step 9 through public contribution upload when contr
|
||||
assert.equal(requests.length, 0);
|
||||
assert.equal(completed.length, 1);
|
||||
assert.equal(completed[0].nodeId, 'kiro-upload-credential');
|
||||
assert.equal(completed[0].payload.kiroRuntime.upload.targetId, 'contribution');
|
||||
assert.equal(completed[0].payload.kiroRuntime.upload.status, 'uploaded');
|
||||
assert.equal(completed[0].payload.kiroRuntime.upload.credentialId, 'kiro-contribution-009');
|
||||
assert.equal(completed[0].payload.kiroRuntime.upload.lastMessage, '贡献链路成功:kiro-step-9');
|
||||
assert.equal(getKiroRuntime(completed[0].payload).upload.targetId, 'contribution');
|
||||
assert.equal(getKiroRuntime(completed[0].payload).upload.status, 'uploaded');
|
||||
assert.equal(getKiroRuntime(completed[0].payload).upload.credentialId, 'kiro-contribution-009');
|
||||
assert.equal(getKiroRuntime(completed[0].payload).upload.lastMessage, '贡献链路成功:kiro-step-9');
|
||||
});
|
||||
|
||||
test('kiro publisher trims api key and includes fallback Authorization header during connection check', async () => {
|
||||
|
||||
@@ -3,13 +3,17 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
function loadRegisterRunnerApi() {
|
||||
const stateSource = fs.readFileSync('background/kiro/state.js', 'utf8');
|
||||
const runnerSource = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
|
||||
const stateSource = fs.readFileSync('flows/kiro/background/state.js', 'utf8');
|
||||
const runnerSource = fs.readFileSync('flows/kiro/background/register-runner.js', 'utf8');
|
||||
const globalScope = {};
|
||||
new Function('self', `${stateSource}; ${runnerSource}; return self;`)(globalScope);
|
||||
return globalScope.MultiPageBackgroundKiroRegisterRunner;
|
||||
}
|
||||
|
||||
function getKiroRuntime(state = {}) {
|
||||
return state?.runtimeState?.flowState?.kiro || {};
|
||||
}
|
||||
|
||||
test('kiro register runner module exposes a factory and Kiro official sign-in entry', () => {
|
||||
const api = loadRegisterRunnerApi();
|
||||
assert.equal(typeof api?.createKiroRegisterRunner, 'function');
|
||||
@@ -17,7 +21,7 @@ test('kiro register runner module exposes a factory and Kiro official sign-in en
|
||||
});
|
||||
|
||||
test('kiro register runner removed the old AWS device authorization bootstrap', () => {
|
||||
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/kiro/background/register-runner.js', 'utf8');
|
||||
assert.doesNotMatch(source, /startBuilderIdDeviceLogin/);
|
||||
assert.doesNotMatch(source, /device_authorization/);
|
||||
assert.doesNotMatch(source, /verificationUriComplete/);
|
||||
@@ -25,7 +29,7 @@ test('kiro register runner removed the old AWS device authorization bootstrap',
|
||||
});
|
||||
|
||||
test('kiro register runner uses a shared 3-minute page-load timeout budget', () => {
|
||||
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/kiro/background/register-runner.js', 'utf8');
|
||||
assert.match(source, /DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS/);
|
||||
assert.match(source, /createTimeoutBudget/);
|
||||
assert.match(source, /resolveTimeoutBudget/);
|
||||
@@ -34,7 +38,7 @@ test('kiro register runner uses a shared 3-minute page-load timeout budget', ()
|
||||
});
|
||||
|
||||
test('kiro register consent step treats Kiro Web signed-in page as completion', () => {
|
||||
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/kiro/background/register-runner.js', 'utf8');
|
||||
assert.match(source, /readKiroRegisterPageState\(tabId, \{/);
|
||||
assert.match(source, /\['authorization_page', 'success_page', 'kiro_web_signed_in'\]\.includes\(landingResult\?\.state\)/);
|
||||
assert.match(source, /landingResult\?\.state === 'authorization_page'/);
|
||||
@@ -42,7 +46,7 @@ test('kiro register consent step treats Kiro Web signed-in page as completion',
|
||||
});
|
||||
|
||||
test('kiro register runner uses registration-only page states instead of shared OpenAI names', () => {
|
||||
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/kiro/background/register-runner.js', 'utf8');
|
||||
assert.match(source, /KIRO_REGISTER_PAGE_STATES/);
|
||||
assert.match(source, /'register_otp_page'/);
|
||||
assert.match(source, /'create_password_page'/);
|
||||
@@ -54,7 +58,7 @@ test('kiro register runner uses registration-only page states instead of shared
|
||||
});
|
||||
|
||||
test('kiro register runner fails existing-account login branches during registration', () => {
|
||||
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/kiro/background/register-runner.js', 'utf8');
|
||||
assert.match(source, /KIRO_REGISTER_EXISTING_ACCOUNT_STATES/);
|
||||
assert.match(source, /assertKiroRegistrationOnlyState\(landingResult, currentState, 2, resolvedEmail\)/);
|
||||
assert.match(source, /邮箱.*已进入 AWS Builder ID 登录页/);
|
||||
@@ -64,12 +68,16 @@ test('kiro register runner fails existing-account login branches during registra
|
||||
test('kiro submit-email stops immediately when AWS routes the email to login', async () => {
|
||||
const api = loadRegisterRunnerApi();
|
||||
const currentState = {
|
||||
kiroRuntime: {
|
||||
session: {
|
||||
registerTabId: 101,
|
||||
},
|
||||
register: {
|
||||
loginUrl: 'https://app.kiro.dev/signin',
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
session: {
|
||||
registerTabId: 101,
|
||||
},
|
||||
register: {
|
||||
loginUrl: 'https://app.kiro.dev/signin',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -119,18 +127,22 @@ test('kiro submit-email stops immediately when AWS routes the email to login', a
|
||||
|
||||
assert.equal(completed, false);
|
||||
assert.equal(sentMessages.some((message) => message.type === 'EXECUTE_NODE'), true);
|
||||
assert.equal(statePatches.some((patch) => /已进入 AWS Builder ID 登录页/.test(patch.kiroRuntime?.session?.lastError || '')), true);
|
||||
assert.equal(statePatches.some((patch) => /已进入 AWS Builder ID 登录页/.test(getKiroRuntime(patch).session?.lastError || '')), true);
|
||||
});
|
||||
|
||||
test('kiro submit-email can adopt an already-open registration OTP page without allocating a new mailbox', async () => {
|
||||
const api = loadRegisterRunnerApi();
|
||||
const currentState = {
|
||||
kiroRuntime: {
|
||||
session: {
|
||||
registerTabId: 102,
|
||||
},
|
||||
register: {
|
||||
loginUrl: 'https://app.kiro.dev/signin',
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
session: {
|
||||
registerTabId: 102,
|
||||
},
|
||||
register: {
|
||||
loginUrl: 'https://app.kiro.dev/signin',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -163,8 +175,8 @@ test('kiro submit-email can adopt an already-open registration OTP page without
|
||||
|
||||
await runner.executeKiroSubmitEmail({ nodeId: 'kiro-submit-email', ...currentState });
|
||||
|
||||
assert.equal(completedPayload?.kiroRuntime?.register?.email, 'manual-user@duck.com');
|
||||
assert.equal(completedPayload?.kiroRuntime?.register?.status, 'waiting_otp');
|
||||
assert.equal(completedPayload?.kiroRuntime?.register?.verificationRequestedAt, 0);
|
||||
assert.equal(getKiroRuntime(completedPayload).register?.email, 'manual-user@duck.com');
|
||||
assert.equal(getKiroRuntime(completedPayload).register?.status, 'waiting_otp');
|
||||
assert.equal(getKiroRuntime(completedPayload).register?.verificationRequestedAt, 0);
|
||||
assert.equal(sentMessages.some((message) => message.type === 'EXECUTE_NODE'), false);
|
||||
});
|
||||
|
||||
@@ -3,109 +3,128 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
function loadKiroStateApi() {
|
||||
const source = fs.readFileSync('background/kiro/state.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/kiro/background/state.js', 'utf8');
|
||||
const globalScope = {};
|
||||
return new Function('self', `${source}; return self.MultiPageBackgroundKiroState;`)(globalScope);
|
||||
}
|
||||
|
||||
test('background imports kiro state module and routes Kiro runtime through dedicated helpers', () => {
|
||||
function getKiroRuntime(state = {}) {
|
||||
return state?.runtimeState?.flowState?.kiro || {};
|
||||
}
|
||||
|
||||
test('background imports kiro state module for Kiro runtime projection and reset helpers', () => {
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
assert.match(source, /background\/kiro\/state\.js/);
|
||||
assert.match(source, /flows\/kiro\/background\/state\.js/);
|
||||
assert.match(source, /const kiroStateHelpers = self\.MultiPageBackgroundKiroState/);
|
||||
assert.match(source, /kiroStateHelpers\?\.buildStateView/);
|
||||
assert.match(source, /kiroStateHelpers\?\.buildSessionStatePatch/);
|
||||
assert.match(source, /kiroStateHelpers\?\.buildDownstreamResetPatch/);
|
||||
assert.match(source, /kiroStateHelpers\?\.applyNodeCompletionPayload/);
|
||||
assert.doesNotMatch(source, /migrateLegacyKiroRuntimeState/);
|
||||
assert.equal(/kiroStateHelpers\?\.buildSessionStatePatch/.test(source), false);
|
||||
assert.equal(/kiroRuntime:\s*kiroStateHelpers\?\.buildDefaultRuntimeState/.test(source), false);
|
||||
});
|
||||
|
||||
test('kiro state module exposes canonical nested kiroRuntime view', () => {
|
||||
test('kiro state module exposes canonical nested runtimeState view', () => {
|
||||
const api = loadKiroStateApi();
|
||||
const view = api.buildStateView({
|
||||
kiroTargetId: 'kiro-rs',
|
||||
kiroRuntime: {
|
||||
session: {
|
||||
currentStage: 'desktop-authorize',
|
||||
registerTabId: 88,
|
||||
pageState: 'name_entry',
|
||||
pageUrl: 'https://view.awsapps.com/start',
|
||||
},
|
||||
register: {
|
||||
email: 'aws-user@example.com',
|
||||
fullName: 'Ada Lovelace',
|
||||
loginUrl: 'https://app.kiro.dev/signin',
|
||||
status: 'waiting_name',
|
||||
},
|
||||
webAuth: {
|
||||
status: 'signin_started',
|
||||
hasAccessToken: false,
|
||||
hasSessionToken: false,
|
||||
},
|
||||
desktopAuth: {
|
||||
clientId: 'client-001',
|
||||
clientSecret: 'secret-001',
|
||||
refreshToken: 'refresh-001',
|
||||
status: 'authorized',
|
||||
},
|
||||
upload: {
|
||||
targetId: 'kiro-rs',
|
||||
status: 'ready_to_upload',
|
||||
credentialId: 321,
|
||||
},
|
||||
const runtimeState = api.buildRuntimeStatePatch({}, {
|
||||
session: {
|
||||
currentStage: 'desktop-authorize',
|
||||
registerTabId: 88,
|
||||
pageState: 'name_entry',
|
||||
pageUrl: 'https://view.awsapps.com/start',
|
||||
},
|
||||
register: {
|
||||
email: 'aws-user@example.com',
|
||||
fullName: 'Ada Lovelace',
|
||||
loginUrl: 'https://app.kiro.dev/signin',
|
||||
status: 'waiting_name',
|
||||
},
|
||||
webAuth: {
|
||||
status: 'signin_started',
|
||||
hasAccessToken: false,
|
||||
hasSessionToken: false,
|
||||
},
|
||||
desktopAuth: {
|
||||
clientId: 'client-001',
|
||||
clientSecret: 'secret-001',
|
||||
refreshToken: 'refresh-001',
|
||||
status: 'authorized',
|
||||
},
|
||||
upload: {
|
||||
targetId: 'kiro-rs',
|
||||
status: 'ready_to_upload',
|
||||
credentialId: 321,
|
||||
},
|
||||
}).runtimeState;
|
||||
const view = api.buildStateView({
|
||||
targetId: 'kiro-rs',
|
||||
runtimeState,
|
||||
});
|
||||
|
||||
assert.equal(view.kiroTargetId, 'kiro-rs');
|
||||
assert.equal(view.kiroRuntime.session.currentStage, 'desktop-authorize');
|
||||
assert.equal(view.kiroRuntime.session.registerTabId, 88);
|
||||
assert.equal(view.kiroRuntime.register.email, 'aws-user@example.com');
|
||||
assert.equal(view.kiroRuntime.register.loginUrl, 'https://app.kiro.dev/signin');
|
||||
assert.equal(view.kiroRuntime.webAuth.status, 'signin_started');
|
||||
assert.equal(view.kiroRuntime.desktopAuth.clientId, 'client-001');
|
||||
assert.equal(view.kiroRuntime.desktopAuth.refreshToken, 'refresh-001');
|
||||
assert.equal(view.kiroRuntime.upload.status, 'ready_to_upload');
|
||||
assert.equal(view.kiroRuntime.upload.credentialId, 321);
|
||||
assert.equal(view.targetId, 'kiro-rs');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(view, 'kiroRuntime'), false);
|
||||
assert.equal(getKiroRuntime(view).session.currentStage, 'desktop-authorize');
|
||||
assert.equal(getKiroRuntime(view).session.registerTabId, 88);
|
||||
assert.equal(getKiroRuntime(view).register.email, 'aws-user@example.com');
|
||||
assert.equal(getKiroRuntime(view).register.loginUrl, 'https://app.kiro.dev/signin');
|
||||
assert.equal(getKiroRuntime(view).webAuth.status, 'signin_started');
|
||||
assert.equal(getKiroRuntime(view).desktopAuth.clientId, 'client-001');
|
||||
assert.equal(getKiroRuntime(view).desktopAuth.refreshToken, 'refresh-001');
|
||||
assert.equal(getKiroRuntime(view).upload.status, 'ready_to_upload');
|
||||
assert.equal(getKiroRuntime(view).upload.credentialId, 321);
|
||||
assert.equal(view.runtimeState.flowState.kiro.session.currentStage, 'desktop-authorize');
|
||||
assert.equal(view.runtimeState.flowState.kiro.register.email, 'aws-user@example.com');
|
||||
assert.equal(view.runtimeState.flowState.kiro.upload.credentialId, 321);
|
||||
});
|
||||
|
||||
test('kiro state session patch accepts canonical nested runtime updates', () => {
|
||||
const api = loadKiroStateApi();
|
||||
const patch = api.buildSessionStatePatch({
|
||||
kiroRuntime: api.buildDefaultRuntimeState(),
|
||||
runtimeState: api.buildRuntimeStatePatch({}, api.buildDefaultRuntimeState()).runtimeState,
|
||||
}, {
|
||||
kiroRuntime: {
|
||||
session: {
|
||||
currentStage: 'register',
|
||||
pageState: 'register_otp_page',
|
||||
pageUrl: 'https://signin.aws/register',
|
||||
},
|
||||
register: {
|
||||
email: 'aws-user@example.com',
|
||||
fullName: 'Ada Lovelace',
|
||||
verificationRequestedAt: 1700000000000,
|
||||
},
|
||||
desktopAuth: {
|
||||
status: 'waiting_callback',
|
||||
},
|
||||
upload: {
|
||||
status: 'waiting_register',
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
session: {
|
||||
currentStage: 'register',
|
||||
pageState: 'register_otp_page',
|
||||
pageUrl: 'https://signin.aws/register',
|
||||
},
|
||||
register: {
|
||||
email: 'aws-user@example.com',
|
||||
fullName: 'Ada Lovelace',
|
||||
verificationRequestedAt: 1700000000000,
|
||||
},
|
||||
desktopAuth: {
|
||||
status: 'waiting_callback',
|
||||
},
|
||||
upload: {
|
||||
status: 'waiting_register',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(patch.kiroRuntime.session.currentStage, 'register');
|
||||
assert.equal(patch.kiroRuntime.session.pageState, 'register_otp_page');
|
||||
assert.equal(patch.kiroRuntime.session.pageUrl, 'https://signin.aws/register');
|
||||
assert.equal(patch.kiroRuntime.register.email, 'aws-user@example.com');
|
||||
assert.equal(patch.kiroRuntime.register.fullName, 'Ada Lovelace');
|
||||
assert.equal(patch.kiroRuntime.register.verificationRequestedAt, 1700000000000);
|
||||
assert.equal(patch.kiroRuntime.desktopAuth.status, 'waiting_callback');
|
||||
assert.equal(patch.kiroRuntime.upload.status, 'waiting_register');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(patch, 'kiroRuntime'), false);
|
||||
assert.equal(getKiroRuntime(patch).session.currentStage, 'register');
|
||||
assert.equal(getKiroRuntime(patch).session.pageState, 'register_otp_page');
|
||||
assert.equal(getKiroRuntime(patch).session.pageUrl, 'https://signin.aws/register');
|
||||
assert.equal(getKiroRuntime(patch).register.email, 'aws-user@example.com');
|
||||
assert.equal(getKiroRuntime(patch).register.fullName, 'Ada Lovelace');
|
||||
assert.equal(getKiroRuntime(patch).register.verificationRequestedAt, 1700000000000);
|
||||
assert.equal(getKiroRuntime(patch).desktopAuth.status, 'waiting_callback');
|
||||
assert.equal(getKiroRuntime(patch).upload.status, 'waiting_register');
|
||||
assert.equal(patch.runtimeState.flowState.kiro.session.currentStage, 'register');
|
||||
assert.equal(patch.runtimeState.flowState.kiro.register.email, 'aws-user@example.com');
|
||||
assert.equal(patch.runtimeState.flowState.kiro.desktopAuth.status, 'waiting_callback');
|
||||
});
|
||||
|
||||
test('kiro state reset helpers clear downstream runtime and fresh keep-state preserves only target selection', () => {
|
||||
const api = loadKiroStateApi();
|
||||
const currentState = {
|
||||
kiroTargetId: 'kiro-rs',
|
||||
kiroRuntime: {
|
||||
targetId: 'kiro-rs',
|
||||
runtimeState: api.buildRuntimeStatePatch({}, {
|
||||
session: {
|
||||
currentStage: 'upload',
|
||||
registerTabId: 88,
|
||||
@@ -126,21 +145,26 @@ test('kiro state reset helpers clear downstream runtime and fresh keep-state pre
|
||||
status: 'uploaded',
|
||||
credentialId: 321,
|
||||
},
|
||||
},
|
||||
}).runtimeState,
|
||||
};
|
||||
|
||||
const resetPatch = api.buildDownstreamResetPatch('kiro-submit-email', currentState);
|
||||
assert.equal(resetPatch.kiroRuntime.session.currentStage, 'register');
|
||||
assert.equal(resetPatch.kiroRuntime.register.email, '');
|
||||
assert.equal(resetPatch.kiroRuntime.register.fullName, '');
|
||||
assert.equal(resetPatch.kiroRuntime.desktopAuth.refreshToken, '');
|
||||
assert.equal(resetPatch.kiroRuntime.upload.status, '');
|
||||
assert.equal(resetPatch.kiroRuntime.upload.credentialId, null);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(resetPatch, 'kiroRuntime'), false);
|
||||
assert.equal(getKiroRuntime(resetPatch).session.currentStage, 'register');
|
||||
assert.equal(getKiroRuntime(resetPatch).register.email, '');
|
||||
assert.equal(getKiroRuntime(resetPatch).register.fullName, '');
|
||||
assert.equal(getKiroRuntime(resetPatch).desktopAuth.refreshToken, '');
|
||||
assert.equal(getKiroRuntime(resetPatch).upload.status, '');
|
||||
assert.equal(getKiroRuntime(resetPatch).upload.credentialId, null);
|
||||
assert.equal(resetPatch.runtimeState.flowState.kiro.register.email, '');
|
||||
assert.equal(resetPatch.runtimeState.flowState.kiro.desktopAuth.refreshToken, '');
|
||||
|
||||
const keepState = api.buildFreshKeepState(currentState);
|
||||
assert.equal(keepState.kiroTargetId, 'kiro-rs');
|
||||
assert.equal(keepState.kiroRuntime.register.email, '');
|
||||
assert.equal(keepState.kiroRuntime.desktopAuth.refreshToken, '');
|
||||
assert.equal(keepState.kiroRuntime.upload.status, '');
|
||||
assert.equal(keepState.kiroRuntime.upload.targetId, 'kiro-rs');
|
||||
assert.equal(keepState.targetId, 'kiro-rs');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(keepState, 'kiroRuntime'), false);
|
||||
assert.equal(getKiroRuntime(keepState).register.email, '');
|
||||
assert.equal(getKiroRuntime(keepState).desktopAuth.refreshToken, '');
|
||||
assert.equal(getKiroRuntime(keepState).upload.status, '');
|
||||
assert.equal(getKiroRuntime(keepState).upload.targetId, 'kiro-rs');
|
||||
assert.equal(keepState.runtimeState.flowState.kiro.upload.targetId, 'kiro-rs');
|
||||
});
|
||||
|
||||
@@ -4,11 +4,11 @@ const fs = require('node:fs');
|
||||
|
||||
test('background imports logging/status module', () => {
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
assert.match(source, /background\/logging-status\.js/);
|
||||
assert.match(source, /core\/flow-kernel\/logging-status\.js/);
|
||||
});
|
||||
|
||||
test('logging/status module exposes a factory', () => {
|
||||
const source = fs.readFileSync('background/logging-status.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/logging-status.js', 'utf8');
|
||||
const globalScope = {};
|
||||
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundLoggingStatus;`)(globalScope);
|
||||
@@ -17,7 +17,7 @@ test('logging/status module exposes a factory', () => {
|
||||
});
|
||||
|
||||
test('logging/status add-phone detection ignores step 2 phone-entry switch failures', () => {
|
||||
const source = fs.readFileSync('background/logging-status.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/logging-status.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundLoggingStatus;`)(globalScope);
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ test('signup flow helper allocates mail2925 account before generating alias emai
|
||||
calls.setEmail.push(email);
|
||||
},
|
||||
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
waitForTabUrlMatch: async () => null,
|
||||
});
|
||||
|
||||
@@ -87,7 +87,7 @@ test('signup flow helper skips mail2925 account allocation when account pool swi
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setEmailState: async () => {},
|
||||
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
waitForTabUrlMatch: async () => null,
|
||||
});
|
||||
|
||||
|
||||
@@ -459,7 +459,7 @@ test('SAVE_SETTING rebuilds Plus node statuses when panel mode forces the effect
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
|
||||
const broadcasts = [];
|
||||
let state = {
|
||||
panelMode: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
@@ -486,12 +486,12 @@ test('SAVE_SETTING rebuilds Plus node statuses when panel mode forces the effect
|
||||
const router = api.createMessageRouter({
|
||||
addLog: async () => {},
|
||||
buildLuckmailSessionSettingsPayload: () => ({}),
|
||||
buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'panelMode')
|
||||
? { panelMode: input.panelMode }
|
||||
buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'targetId')
|
||||
? { targetId: input.targetId }
|
||||
: {},
|
||||
broadcastDataUpdate: (payload) => broadcasts.push(payload),
|
||||
getNodeIdsForState: (nextState = {}) => (
|
||||
String(nextState.panelMode || '').trim() === 'sub2api'
|
||||
String(nextState.targetId || '').trim() === 'sub2api'
|
||||
&& String(nextState.plusAccountAccessStrategy || '').trim() === 'sub2api_codex_session'
|
||||
? [
|
||||
'open-chatgpt',
|
||||
@@ -525,12 +525,12 @@ test('SAVE_SETTING rebuilds Plus node statuses when panel mode forces the effect
|
||||
const response = await router.handleMessage({
|
||||
type: 'SAVE_SETTING',
|
||||
payload: {
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(response.ok, true);
|
||||
assert.equal(state.panelMode, 'cpa');
|
||||
assert.equal(state.targetId, 'cpa');
|
||||
assert.equal(state.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(state.currentNodeId, '');
|
||||
assert.equal(state.oauthUrl, null);
|
||||
@@ -552,7 +552,7 @@ test('SAVE_SETTING rebuilds Plus node statuses when panel mode forces the effect
|
||||
});
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(state.nodeStatuses, 'sub2api-session-import'), false);
|
||||
assert.deepStrictEqual(broadcasts.at(-1), {
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
signupMethod: 'email',
|
||||
oauthUrl: null,
|
||||
localhostUrl: null,
|
||||
@@ -595,7 +595,7 @@ test('SAVE_SETTING mirrors activeFlowId into flowId when switching to kiro flow'
|
||||
const globalScope = { console };
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
|
||||
const broadcasts = [];
|
||||
let state = { activeFlowId: 'openai', flowId: 'openai', panelMode: 'cpa', plusModeEnabled: false, plusPaymentMethod: 'paypal' };
|
||||
let state = { activeFlowId: 'openai', flowId: 'openai', targetId: 'cpa', plusModeEnabled: false, plusPaymentMethod: 'paypal' };
|
||||
|
||||
const router = api.createMessageRouter({
|
||||
addLog: async () => {},
|
||||
@@ -683,7 +683,7 @@ test('SAVE_SETTING syncs canonical kiro settingsState back into session state',
|
||||
let state = {
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
kiroTargetId: 'kiro-rs',
|
||||
targetId: 'kiro-rs',
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: '',
|
||||
settingsSchemaVersion: 4,
|
||||
@@ -718,7 +718,7 @@ test('SAVE_SETTING syncs canonical kiro settingsState back into session state',
|
||||
setPersistentSettings: async () => ({
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
kiroTargetId: 'kiro-rs',
|
||||
targetId: 'kiro-rs',
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'live-key',
|
||||
settingsSchemaVersion: 4,
|
||||
@@ -751,7 +751,7 @@ test('CHECK_KIRO_RS_CONNECTION prefers current sidepanel payload over stale save
|
||||
getState: async () => ({
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
kiroTargetId: 'kiro-rs',
|
||||
targetId: 'kiro-rs',
|
||||
kiroRsUrl: 'https://old.example.com/admin',
|
||||
kiroRsKey: 'old-key',
|
||||
settingsState: {
|
||||
@@ -808,7 +808,7 @@ test('AUTO_RUN applies current flow selection from payload before starting loop'
|
||||
let state = {
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: 'paypal',
|
||||
};
|
||||
@@ -829,7 +829,7 @@ test('AUTO_RUN applies current flow selection from payload before starting loop'
|
||||
validations.push({
|
||||
activeFlowId: validationState?.activeFlowId,
|
||||
flowId: validationState?.flowId,
|
||||
kiroTargetId: validationState?.kiroTargetId,
|
||||
targetId: validationState?.targetId,
|
||||
optionActiveFlowId: options?.activeFlowId,
|
||||
});
|
||||
return { ok: true, errors: [] };
|
||||
@@ -848,14 +848,14 @@ test('AUTO_RUN applies current flow selection from payload before starting loop'
|
||||
assert.equal(response.ok, true);
|
||||
assert.equal(state.activeFlowId, 'kiro');
|
||||
assert.equal(state.flowId, 'kiro');
|
||||
assert.equal(state.kiroTargetId, 'kiro-rs');
|
||||
assert.equal(state.targetId, 'kiro-rs');
|
||||
assert.deepStrictEqual(calls, [
|
||||
{
|
||||
type: 'setState',
|
||||
updates: {
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
kiroTargetId: 'kiro-rs',
|
||||
targetId: 'kiro-rs',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -877,7 +877,7 @@ test('AUTO_RUN applies current flow selection from payload before starting loop'
|
||||
{
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
kiroTargetId: 'kiro-rs',
|
||||
targetId: 'kiro-rs',
|
||||
optionActiveFlowId: 'kiro',
|
||||
},
|
||||
]);
|
||||
@@ -891,18 +891,18 @@ test('SAVE_SETTING re-resolves signup method when panel mode changes', async ()
|
||||
signupMethod: 'phone',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: false,
|
||||
panelMode: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
};
|
||||
|
||||
const router = api.createMessageRouter({
|
||||
addLog: async () => {},
|
||||
buildLuckmailSessionSettingsPayload: () => ({}),
|
||||
buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'panelMode')
|
||||
? { panelMode: input.panelMode }
|
||||
buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'targetId')
|
||||
? { targetId: input.targetId }
|
||||
: {},
|
||||
broadcastDataUpdate: () => {},
|
||||
getState: async () => ({ ...state }),
|
||||
resolveSignupMethod: (nextState = {}) => nextState.panelMode === 'cpa' ? 'email' : 'phone',
|
||||
resolveSignupMethod: (nextState = {}) => nextState.targetId === 'cpa' ? 'email' : 'phone',
|
||||
setPersistentSettings: async (updates) => ({ ...updates }),
|
||||
setState: async (updates) => {
|
||||
state = { ...state, ...updates };
|
||||
@@ -911,11 +911,11 @@ test('SAVE_SETTING re-resolves signup method when panel mode changes', async ()
|
||||
|
||||
const response = await router.handleMessage({
|
||||
type: 'SAVE_SETTING',
|
||||
payload: { panelMode: 'cpa' },
|
||||
payload: { targetId: 'cpa' },
|
||||
});
|
||||
|
||||
assert.equal(response.ok, true);
|
||||
assert.equal(state.panelMode, 'cpa');
|
||||
assert.equal(state.targetId, 'cpa');
|
||||
assert.equal(state.signupMethod, 'email');
|
||||
});
|
||||
|
||||
@@ -929,7 +929,7 @@ test('SAVE_SETTING applies shared mode-switch normalization before persisting in
|
||||
signupMethod: 'email',
|
||||
phoneVerificationEnabled: false,
|
||||
plusModeEnabled: false,
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
};
|
||||
|
||||
const router = api.createMessageRouter({
|
||||
|
||||
@@ -426,7 +426,7 @@ test('message router finalizes step 3 before marking it completed', async () =>
|
||||
const response = await router.handleMessage({
|
||||
type: 'NODE_COMPLETE',
|
||||
nodeId: 'fill-password',
|
||||
source: 'signup-page',
|
||||
source: 'openai-auth',
|
||||
payload: {
|
||||
nodeId: 'fill-password',
|
||||
email: 'user@example.com',
|
||||
@@ -548,7 +548,7 @@ test('message router marks step 3 failed when post-submit finalize fails', async
|
||||
const response = await router.handleMessage({
|
||||
type: 'NODE_COMPLETE',
|
||||
nodeId: 'fill-password',
|
||||
source: 'signup-page',
|
||||
source: 'openai-auth',
|
||||
payload: {
|
||||
nodeId: 'fill-password',
|
||||
email: 'user@example.com',
|
||||
@@ -581,7 +581,7 @@ test('message router does not duplicate step 3 mismatch failure log after finali
|
||||
const response = await router.handleMessage({
|
||||
type: 'NODE_ERROR',
|
||||
nodeId: 'fill-password',
|
||||
source: 'signup-page',
|
||||
source: 'openai-auth',
|
||||
payload: { nodeId: 'fill-password' },
|
||||
error: mismatchError,
|
||||
}, {});
|
||||
@@ -604,7 +604,7 @@ test('message router stops the flow and surfaces cloudflare security block error
|
||||
const response = await router.handleMessage({
|
||||
type: 'NODE_ERROR',
|
||||
nodeId: 'oauth-login',
|
||||
source: 'signup-page',
|
||||
source: 'openai-auth',
|
||||
payload: { nodeId: 'oauth-login' },
|
||||
error: 'CF_SECURITY_BLOCKED::您已触发Cloudflare 安全防护系统',
|
||||
}, {});
|
||||
|
||||
@@ -68,7 +68,7 @@ test('navigation utils support codex2api mode and url normalization', () => {
|
||||
utils.normalizeCodex2ApiUrl('https://codex-admin.example.com/'),
|
||||
'https://codex-admin.example.com/admin/accounts'
|
||||
);
|
||||
assert.equal(utils.getPanelMode({ panelMode: 'codex2api' }), 'codex2api');
|
||||
assert.equal(utils.getPanelMode({ targetId: 'codex2api' }), 'codex2api');
|
||||
assert.equal(utils.getPanelModeLabel('codex2api'), 'Codex2API');
|
||||
});
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ const fs = require('node:fs');
|
||||
|
||||
test('confirm-oauth and platform-verify stay free of operation delay gate calls', () => {
|
||||
for (const file of [
|
||||
'background/steps/confirm-oauth.js',
|
||||
'background/steps/platform-verify.js',
|
||||
'flows/openai/background/steps/confirm-oauth.js',
|
||||
'flows/openai/background/steps/platform-verify.js',
|
||||
'background/panel-bridge.js',
|
||||
'content/sub2api-panel.js',
|
||||
'flows/openai/content/sub2api-panel.js',
|
||||
]) {
|
||||
const source = fs.readFileSync(file, 'utf8');
|
||||
assert.doesNotMatch(source, /performOperationWithDelay\(/, `${file} must not call the operation delay gate`);
|
||||
|
||||
@@ -3,7 +3,7 @@ const fs = require('node:fs');
|
||||
const test = require('node:test');
|
||||
|
||||
test('platform verify module supports codex2api protocol callback exchange', async () => {
|
||||
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (url, options = {}) => {
|
||||
assert.equal(url, 'http://localhost:8080/api/admin/oauth/exchange-code');
|
||||
@@ -81,7 +81,7 @@ test('platform verify module supports codex2api protocol callback exchange', asy
|
||||
});
|
||||
|
||||
test('platform verify retries transient SUB2API oauth/token exchange errors before succeeding', async () => {
|
||||
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
|
||||
|
||||
const logs = [];
|
||||
@@ -150,7 +150,7 @@ test('platform verify retries transient SUB2API oauth/token exchange errors befo
|
||||
});
|
||||
|
||||
test('platform verify retries transient SUB2API token_exchange_user_error before succeeding', async () => {
|
||||
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
|
||||
|
||||
const logs = [];
|
||||
|
||||
@@ -44,7 +44,7 @@ function createDeps(overrides = {}) {
|
||||
|
||||
function loadStep10WithSub2Api() {
|
||||
const sub2apiSource = fs.readFileSync('background/sub2api-api.js', 'utf8');
|
||||
const step10Source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
|
||||
const step10Source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
|
||||
return new Function('self', `${sub2apiSource}\n${step10Source}; return self.MultiPageBackgroundStep10;`)({});
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ function createSub2ApiResponse(payload, status = 200) {
|
||||
}
|
||||
|
||||
test('platform verify module submits CPA callback via management API first', async () => {
|
||||
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
|
||||
const originalFetch = globalThis.fetch;
|
||||
let uiCalled = false;
|
||||
globalThis.fetch = async (url, options = {}) => {
|
||||
@@ -114,7 +114,7 @@ test('platform verify module submits CPA callback via management API first', asy
|
||||
});
|
||||
|
||||
test('platform verify module prefers cpaManagementOrigin when provided', async () => {
|
||||
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (url, options = {}) => {
|
||||
assert.equal(url, 'http://localhost:9999/v0/management/oauth-callback');
|
||||
@@ -149,7 +149,7 @@ test('platform verify module prefers cpaManagementOrigin when provided', async (
|
||||
});
|
||||
|
||||
test('platform verify module fails fast when CPA API submit fails', async () => {
|
||||
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () => ({
|
||||
ok: false,
|
||||
@@ -185,7 +185,7 @@ test('platform verify module fails fast when CPA API submit fails', async () =>
|
||||
});
|
||||
|
||||
test('platform verify module requires management key for CPA API-only flow', async () => {
|
||||
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
|
||||
const originalFetch = globalThis.fetch;
|
||||
let fetchCalled = false;
|
||||
globalThis.fetch = async () => {
|
||||
@@ -221,7 +221,7 @@ test('platform verify module requires management key for CPA API-only flow', asy
|
||||
});
|
||||
|
||||
test('platform verify module rejects callback when cpa oauth state mismatches', async () => {
|
||||
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
|
||||
const originalFetch = globalThis.fetch;
|
||||
let fetchCalled = false;
|
||||
globalThis.fetch = async () => {
|
||||
|
||||
@@ -3,14 +3,14 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
function loadRuntimeStateApi() {
|
||||
const source = fs.readFileSync('background/runtime-state.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/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, /core\/flow-kernel\/runtime-state\.js/);
|
||||
assert.match(source, /createRuntimeStateHelpers/);
|
||||
assert.match(source, /buildStateViewWithRuntimeState/);
|
||||
assert.match(source, /buildStatePatchWithRuntimeState/);
|
||||
@@ -46,10 +46,10 @@ test('runtime-state view preserves canonical flow metadata from node state', ()
|
||||
phoneNumber: '+447700900123',
|
||||
},
|
||||
tabRegistry: {
|
||||
'signup-page': { tabId: 12 },
|
||||
'openai-auth': { tabId: 12 },
|
||||
},
|
||||
sourceLastUrls: {
|
||||
'signup-page': 'https://auth.example.com/start',
|
||||
'openai-auth': 'https://auth.example.com/start',
|
||||
},
|
||||
flowStartTime: 12345,
|
||||
});
|
||||
@@ -72,10 +72,10 @@ test('runtime-state view preserves canonical flow metadata from node state', ()
|
||||
});
|
||||
assert.deepStrictEqual(view.sharedState, {
|
||||
tabRegistry: {
|
||||
'signup-page': { tabId: 12 },
|
||||
'openai-auth': { tabId: 12 },
|
||||
},
|
||||
sourceLastUrls: {
|
||||
'signup-page': 'https://auth.example.com/start',
|
||||
'openai-auth': 'https://auth.example.com/start',
|
||||
},
|
||||
flowStartTime: 12345,
|
||||
});
|
||||
@@ -171,3 +171,53 @@ test('runtime-state patch prefers explicit activeFlowId over stale legacy flowId
|
||||
'kiro-open-register-page': 'running',
|
||||
});
|
||||
});
|
||||
|
||||
test('runtime-state patch preserves canonical nested kiro flow state without projecting a top-level alias', () => {
|
||||
const api = loadRuntimeStateApi();
|
||||
const helpers = api.createRuntimeStateHelpers({
|
||||
DEFAULT_ACTIVE_FLOW_ID: 'openai',
|
||||
defaultNodeStatuses: {
|
||||
'open-chatgpt': 'pending',
|
||||
'kiro-open-register-page': 'pending',
|
||||
},
|
||||
});
|
||||
|
||||
const patch = helpers.buildSessionStatePatch({
|
||||
runtimeState: {
|
||||
activeFlowId: 'kiro',
|
||||
flowState: {
|
||||
kiro: {
|
||||
session: {
|
||||
currentStage: 'register',
|
||||
},
|
||||
register: {
|
||||
email: 'old-user@example.com',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
register: {
|
||||
email: 'aws-user@example.com',
|
||||
},
|
||||
upload: {
|
||||
status: 'uploaded',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(patch, 'kiroRuntime'), false);
|
||||
assert.equal(patch.runtimeState.flowState.kiro.session.currentStage, 'register');
|
||||
assert.equal(patch.runtimeState.flowState.kiro.register.email, 'aws-user@example.com');
|
||||
assert.equal(patch.runtimeState.flowState.kiro.upload.status, 'uploaded');
|
||||
|
||||
const view = helpers.buildStateView(patch);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(view, 'kiroRuntime'), false);
|
||||
assert.equal(view.flowState.kiro.register.email, 'aws-user@example.com');
|
||||
assert.equal(view.flowState.kiro.upload.status, 'uploaded');
|
||||
});
|
||||
|
||||
@@ -52,12 +52,24 @@ test('importSettingsBundle normalizes unsupported capability flags before persis
|
||||
const api = new Function(`
|
||||
const SETTINGS_EXPORT_SCHEMA_VERSION = 1;
|
||||
const DEFAULT_REGISTRATION_EMAIL_STATE = { emailHistory: [] };
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const self = {
|
||||
MultiPageLegacySettingsImporter: {
|
||||
createSettingsImporter() {
|
||||
return {
|
||||
importSettings(settings = {}) {
|
||||
return { ...settings };
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
let persistedUpdates = null;
|
||||
let stateUpdates = null;
|
||||
let broadcastPayload = null;
|
||||
let currentState = {
|
||||
activeFlowId: 'site-a',
|
||||
panelMode: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
signupMethod: 'phone',
|
||||
plusModeEnabled: false,
|
||||
phoneVerificationEnabled: false,
|
||||
@@ -74,7 +86,7 @@ function validateModeSwitchState() {
|
||||
ok: false,
|
||||
errors: [{ code: 'panel_mode_unsupported', message: '当前 flow 不支持 SUB2API 面板模式。' }],
|
||||
normalizedUpdates: {
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
plusModeEnabled: false,
|
||||
phoneVerificationEnabled: false,
|
||||
signupMethod: 'email',
|
||||
@@ -84,6 +96,9 @@ function validateModeSwitchState() {
|
||||
function resolveSignupMethod(state = {}) {
|
||||
return String(state?.signupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email';
|
||||
}
|
||||
function getSettingsSchemaApi() {
|
||||
return null;
|
||||
}
|
||||
async function setPersistentSettings(updates) {
|
||||
persistedUpdates = { ...updates };
|
||||
}
|
||||
@@ -109,7 +124,7 @@ return {
|
||||
const result = await api.importSettingsBundle({
|
||||
schemaVersion: 1,
|
||||
settings: {
|
||||
panelMode: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
plusModeEnabled: true,
|
||||
phoneVerificationEnabled: true,
|
||||
signupMethod: 'phone',
|
||||
@@ -117,16 +132,164 @@ return {
|
||||
});
|
||||
|
||||
assert.deepEqual(api.getPersistedUpdates(), {
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
plusModeEnabled: false,
|
||||
phoneVerificationEnabled: false,
|
||||
signupMethod: 'email',
|
||||
});
|
||||
assert.equal(api.getStateUpdates().panelMode, 'cpa');
|
||||
assert.equal(api.getStateUpdates().targetId, 'cpa');
|
||||
assert.equal(api.getStateUpdates().plusModeEnabled, false);
|
||||
assert.equal(api.getStateUpdates().phoneVerificationEnabled, false);
|
||||
assert.equal(api.getStateUpdates().signupMethod, 'email');
|
||||
assert.equal(api.getBroadcastPayload().panelMode, 'cpa');
|
||||
assert.equal(api.getBroadcastPayload().targetId, 'cpa');
|
||||
assert.equal(api.getBroadcastPayload().signupMethod, 'email');
|
||||
assert.equal(result.signupMethod, 'email');
|
||||
});
|
||||
|
||||
test('importSettingsBundle routes legacy settings through the legacy importer before persisting', 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 persistedUpdates = 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: '' },
|
||||
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: 'imported-key',
|
||||
},
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 9 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
async function ensureManualInteractionAllowed() {
|
||||
return currentState;
|
||||
}
|
||||
function buildPersistentSettingsPayload(settings = {}) {
|
||||
return {
|
||||
activeFlowId: settings.settingsState.activeFlowId,
|
||||
targetId: 'cpa',
|
||||
signupMethod: 'email',
|
||||
targetId: 'kiro-rs',
|
||||
kiroRsUrl: settings.settingsState.flows.kiro.targets['kiro-rs'].baseUrl,
|
||||
kiroRsKey: settings.settingsState.flows.kiro.targets['kiro-rs'].apiKey,
|
||||
settingsSchemaVersion: settings.settingsSchemaVersion,
|
||||
settingsState: settings.settingsState,
|
||||
};
|
||||
}
|
||||
function validateModeSwitchState() {
|
||||
return { normalizedUpdates: {} };
|
||||
}
|
||||
function resolveSignupMethod() {
|
||||
return 'email';
|
||||
}
|
||||
function getSettingsSchemaApi() {
|
||||
return null;
|
||||
}
|
||||
async function setPersistentSettings(updates) {
|
||||
persistedUpdates = { ...updates };
|
||||
return updates;
|
||||
}
|
||||
async function setState(updates) {
|
||||
currentState = { ...currentState, ...updates };
|
||||
}
|
||||
function broadcastDataUpdate() {}
|
||||
async function getState() {
|
||||
return currentState;
|
||||
}
|
||||
${extractFunction('importSettingsBundle')}
|
||||
return {
|
||||
importSettingsBundle,
|
||||
getImporterInput: () => importerInput,
|
||||
getPersistedUpdates: () => persistedUpdates,
|
||||
};
|
||||
`)();
|
||||
|
||||
await api.importSettingsBundle({
|
||||
schemaVersion: 1,
|
||||
settings: {
|
||||
targetId: 'sub2api',
|
||||
kiroRuntime: {
|
||||
upload: {
|
||||
status: 'uploaded',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(api.getImporterInput(), {
|
||||
targetId: 'sub2api',
|
||||
kiroRuntime: {
|
||||
upload: {
|
||||
status: 'uploaded',
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.equal(api.getPersistedUpdates().activeFlowId, 'kiro');
|
||||
assert.equal(api.getPersistedUpdates().settingsSchemaVersion, 5);
|
||||
assert.equal(api.getPersistedUpdates().settingsState.flows.kiro.targets['kiro-rs'].apiKey, 'imported-key');
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
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 = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||
const settingsSchemaSource = fs.readFileSync('shared/settings-schema.js', 'utf8');
|
||||
const flowRegistrySource = readFlowRegistryBundle();
|
||||
const settingsSchemaSource = fs.readFileSync('core/flow-kernel/settings-schema.js', 'utf8');
|
||||
const backgroundSource = fs.readFileSync('background.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
@@ -59,9 +60,7 @@ const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const DEFAULT_SUB2API_GROUP_NAMES = ['codex', 'openai-plus'];
|
||||
const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([
|
||||
'activeFlowId',
|
||||
'openaiIntegrationTargetId',
|
||||
'panelMode',
|
||||
'kiroTargetId',
|
||||
'targetId',
|
||||
'vpsUrl',
|
||||
'vpsPassword',
|
||||
'localCpaStep9Mode',
|
||||
@@ -92,7 +91,7 @@ const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([
|
||||
const SETTINGS_SCHEMA_VIEW_KEY_SET = new Set(SETTINGS_SCHEMA_VIEW_KEYS);
|
||||
const PERSISTED_SETTING_DEFAULTS = {
|
||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
signupMethod: 'email',
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: 'paypal',
|
||||
@@ -102,7 +101,6 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
ipProxyEnabled: false,
|
||||
ipProxyService: '711proxy',
|
||||
ipProxyMode: 'account',
|
||||
kiroTargetId: 'kiro-rs',
|
||||
kiroRsUrl: '',
|
||||
kiroRsKey: '',
|
||||
stepExecutionRangeByFlow: {},
|
||||
@@ -203,13 +201,13 @@ test('buildPersistentSettingsPayload writes canonical settings schema into persi
|
||||
}, { fillDefaults: true });
|
||||
|
||||
assert.equal(payload.activeFlowId, 'kiro');
|
||||
assert.equal(payload.kiroTargetId, 'kiro-rs');
|
||||
assert.equal(payload.targetId, 'kiro-rs');
|
||||
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(payload.kiroRsKey, 'secret-key');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
|
||||
assert.equal(payload.settingsSchemaVersion, 4);
|
||||
assert.equal(payload.settingsSchemaVersion, 5);
|
||||
assert.equal(payload.settingsState.activeFlowId, 'kiro');
|
||||
assert.equal(payload.settingsState.flows.kiro.targetId, 'kiro-rs');
|
||||
assert.equal(payload.settingsState.flows.kiro.selectedTargetId, 'kiro-rs');
|
||||
assert.equal(
|
||||
payload.settingsState.flows.kiro.targets['kiro-rs'].baseUrl,
|
||||
'https://kiro.example.com/admin'
|
||||
@@ -220,7 +218,7 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
|
||||
const api = buildHarness();
|
||||
|
||||
const payload = api.buildPersistentSettingsPayload({
|
||||
settingsSchemaVersion: 4,
|
||||
settingsSchemaVersion: 5,
|
||||
settingsState: {
|
||||
activeFlowId: 'kiro',
|
||||
services: {
|
||||
@@ -230,8 +228,8 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
|
||||
},
|
||||
flows: {
|
||||
openai: {
|
||||
integrationTargetId: 'cpa',
|
||||
integrationTargets: {
|
||||
selectedTargetId: 'cpa',
|
||||
targets: {
|
||||
cpa: {
|
||||
vpsUrl: '',
|
||||
vpsPassword: '',
|
||||
@@ -266,7 +264,7 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
|
||||
},
|
||||
},
|
||||
kiro: {
|
||||
targetId: 'kiro-rs',
|
||||
selectedTargetId: 'kiro-rs',
|
||||
targets: {
|
||||
'kiro-rs': {
|
||||
baseUrl: 'https://kiro.example.com/admin',
|
||||
@@ -282,11 +280,11 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
|
||||
}, { requireKnownKeys: true });
|
||||
|
||||
assert.equal(payload.activeFlowId, 'kiro');
|
||||
assert.equal(payload.kiroTargetId, 'kiro-rs');
|
||||
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, 4);
|
||||
assert.equal(payload.settingsSchemaVersion, 5);
|
||||
assert.equal(payload.settingsState.flows.openai.plus.plusAccountAccessStrategy, 'oauth');
|
||||
});
|
||||
|
||||
@@ -313,7 +311,7 @@ function getRequestedKeys() {
|
||||
assert.ok(api.getRequestedKeys().includes('settingsSchemaVersion'));
|
||||
assert.ok(api.getRequestedKeys().includes('settingsState'));
|
||||
assert.ok(api.getRequestedKeys().includes('plusAccountAccessStrategy'));
|
||||
assert.equal(state.settingsSchemaVersion, 4);
|
||||
assert.equal(state.settingsSchemaVersion, 5);
|
||||
assert.equal(state.settingsState.activeFlowId, 'openai');
|
||||
});
|
||||
|
||||
@@ -324,7 +322,7 @@ const chrome = {
|
||||
local: {
|
||||
async get() {
|
||||
return {
|
||||
settingsSchemaVersion: 4,
|
||||
settingsSchemaVersion: 5,
|
||||
settingsState: {
|
||||
activeFlowId: 'kiro',
|
||||
services: {
|
||||
@@ -334,8 +332,8 @@ const chrome = {
|
||||
},
|
||||
flows: {
|
||||
openai: {
|
||||
integrationTargetId: 'sub2api',
|
||||
integrationTargets: {
|
||||
selectedTargetId: 'sub2api',
|
||||
targets: {
|
||||
cpa: {
|
||||
vpsUrl: '',
|
||||
vpsPassword: '',
|
||||
@@ -370,7 +368,7 @@ const chrome = {
|
||||
},
|
||||
},
|
||||
kiro: {
|
||||
targetId: 'kiro-rs',
|
||||
selectedTargetId: 'kiro-rs',
|
||||
targets: {
|
||||
'kiro-rs': {
|
||||
baseUrl: 'https://kiro.example.com/admin',
|
||||
@@ -393,7 +391,8 @@ const chrome = {
|
||||
const state = await api.getPersistedSettings();
|
||||
|
||||
assert.equal(state.activeFlowId, 'kiro');
|
||||
assert.equal(state.panelMode, 'sub2api');
|
||||
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');
|
||||
@@ -435,7 +434,7 @@ function getRemovedKeys() {
|
||||
`);
|
||||
|
||||
const persisted = await api.setPersistentSettings({
|
||||
settingsSchemaVersion: 4,
|
||||
settingsSchemaVersion: 5,
|
||||
settingsState: {
|
||||
activeFlowId: 'kiro',
|
||||
services: {
|
||||
@@ -445,8 +444,8 @@ function getRemovedKeys() {
|
||||
},
|
||||
flows: {
|
||||
openai: {
|
||||
integrationTargetId: 'cpa',
|
||||
integrationTargets: {
|
||||
selectedTargetId: 'cpa',
|
||||
targets: {
|
||||
cpa: {
|
||||
vpsUrl: '',
|
||||
vpsPassword: '',
|
||||
@@ -481,7 +480,7 @@ function getRemovedKeys() {
|
||||
},
|
||||
},
|
||||
kiro: {
|
||||
targetId: 'kiro-rs',
|
||||
selectedTargetId: 'kiro-rs',
|
||||
targets: {
|
||||
'kiro-rs': {
|
||||
baseUrl: 'https://kiro.example.com/admin',
|
||||
@@ -499,21 +498,20 @@ function getRemovedKeys() {
|
||||
const write = api.getPersistedWrites().at(-1);
|
||||
|
||||
assert.equal(persisted.activeFlowId, 'kiro');
|
||||
assert.equal(persisted.kiroTargetId, 'kiro-rs');
|
||||
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, 4);
|
||||
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, 4);
|
||||
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.targetId, 'kiro-rs');
|
||||
assert.ok(api.getRemovedKeys().includes('panelMode'));
|
||||
assert.equal(write.settingsState.flows.kiro.selectedTargetId, 'kiro-rs');
|
||||
assert.ok(api.getRemovedKeys().includes('kiroRsUrl'));
|
||||
});
|
||||
|
||||
@@ -526,7 +524,7 @@ const chrome = {
|
||||
local: {
|
||||
async get() {
|
||||
return {
|
||||
settingsSchemaVersion: 4,
|
||||
settingsSchemaVersion: 5,
|
||||
settingsState: {
|
||||
activeFlowId: 'openai',
|
||||
services: {
|
||||
@@ -575,7 +573,7 @@ const chrome = {
|
||||
local: {
|
||||
async get() {
|
||||
return {
|
||||
settingsSchemaVersion: 4,
|
||||
settingsSchemaVersion: 5,
|
||||
settingsState: {
|
||||
activeFlowId: 'openai',
|
||||
services: {
|
||||
@@ -585,8 +583,8 @@ const chrome = {
|
||||
},
|
||||
flows: {
|
||||
openai: {
|
||||
integrationTargetId: 'cpa',
|
||||
integrationTargets: {
|
||||
selectedTargetId: 'cpa',
|
||||
targets: {
|
||||
cpa: {
|
||||
vpsUrl: 'https://old-cpa.example.com',
|
||||
vpsPassword: 'old-vps-password',
|
||||
@@ -621,7 +619,7 @@ const chrome = {
|
||||
},
|
||||
},
|
||||
kiro: {
|
||||
targetId: 'kiro-rs',
|
||||
selectedTargetId: 'kiro-rs',
|
||||
targets: {
|
||||
'kiro-rs': {
|
||||
baseUrl: 'https://kiro.example.com/admin',
|
||||
@@ -654,7 +652,7 @@ function getRemovedKeys() {
|
||||
`);
|
||||
|
||||
const persisted = await api.setPersistentSettings({
|
||||
panelMode: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
mailProvider: 'cloudflare-temp-email',
|
||||
ipProxyEnabled: true,
|
||||
ipProxyMode: 'api',
|
||||
@@ -664,7 +662,7 @@ function getRemovedKeys() {
|
||||
});
|
||||
const write = api.getPersistedWrites().at(-1);
|
||||
|
||||
assert.equal(persisted.panelMode, 'sub2api');
|
||||
assert.equal(persisted.targetId, 'sub2api');
|
||||
assert.equal(persisted.mailProvider, 'cloudflare-temp-email');
|
||||
assert.equal(persisted.ipProxyEnabled, true);
|
||||
assert.equal(persisted.ipProxyMode, 'api');
|
||||
@@ -673,7 +671,7 @@ function getRemovedKeys() {
|
||||
fromStep: 2,
|
||||
toStep: 4,
|
||||
});
|
||||
assert.equal(write.settingsState.flows.openai.integrationTargetId, 'sub2api');
|
||||
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');
|
||||
@@ -682,8 +680,8 @@ function getRemovedKeys() {
|
||||
fromStep: 2,
|
||||
toStep: 4,
|
||||
});
|
||||
assert.equal(write.settingsState.flows.openai.integrationTargets.sub2api.sub2apiUrl, 'https://sub2api.example.com');
|
||||
assert.equal(write.settingsState.flows.openai.integrationTargets.sub2api.sub2apiEmail, 'owner@example.com');
|
||||
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);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const step2Source = fs.readFileSync('background/steps/submit-signup-email.js', 'utf8');
|
||||
const step2Source = fs.readFileSync('flows/openai/background/steps/submit-signup-email.js', 'utf8');
|
||||
const step2GlobalScope = {};
|
||||
const step2Api = new Function('self', `${step2Source}; return self.MultiPageBackgroundStep2;`)(step2GlobalScope);
|
||||
|
||||
@@ -33,7 +33,7 @@ test('step 2 completes with password step skipped when landing on email verifica
|
||||
isTabAlive: async () => true,
|
||||
resolveSignupEmailForFlow: async () => 'user@example.com',
|
||||
sendToContentScriptResilient: async () => ({ submitted: true }),
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
});
|
||||
|
||||
await executor.executeStep2({ email: 'user@example.com' });
|
||||
@@ -72,7 +72,7 @@ test('step 2 keeps password flow when landing on password page', async () => {
|
||||
isTabAlive: async () => true,
|
||||
resolveSignupEmailForFlow: async () => 'user@example.com',
|
||||
sendToContentScriptResilient: async () => ({ submitted: true }),
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
});
|
||||
|
||||
await executor.executeStep2({ email: 'user@example.com' });
|
||||
@@ -150,7 +150,7 @@ test('step 2 uses phone activation when resolved signup method is phone', async
|
||||
sentPayloads.push(message.payload);
|
||||
return { submitted: true };
|
||||
},
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
});
|
||||
|
||||
await executor.executeStep2({ signupMethod: 'phone' });
|
||||
@@ -233,7 +233,7 @@ test('step 2 reuses existing signup phone activation without acquiring a new num
|
||||
sentPayloads.push(message.payload);
|
||||
return { submitted: true };
|
||||
},
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
});
|
||||
|
||||
await executor.executeStep2({
|
||||
@@ -290,7 +290,7 @@ test('step 2 submits manual signup phone without acquiring a number', async () =
|
||||
sentPayloads.push(message.payload);
|
||||
return { submitted: true };
|
||||
},
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
});
|
||||
|
||||
await executor.executeStep2({
|
||||
@@ -354,7 +354,7 @@ test('step 2 stops with an explicit error instead of silently skipping 3/4/5 on
|
||||
isTabAlive: async () => true,
|
||||
resolveSignupEmailForFlow: async () => 'user@example.com',
|
||||
sendToContentScriptResilient: async () => ({ submitted: true }),
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
@@ -405,7 +405,7 @@ test('step 2 does not force auth-entry retry on logged-out chatgpt home when con
|
||||
sentPayloads.push(message.payload);
|
||||
return { submitted: true };
|
||||
},
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
});
|
||||
|
||||
await executor.executeStep2({ email: 'user@example.com' });
|
||||
@@ -472,7 +472,7 @@ test('step 2 waits for the existing signup tab to settle before probing the entr
|
||||
}
|
||||
return { submitted: true };
|
||||
},
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
waitForTabStableComplete: async (_tabId, options) => {
|
||||
events.push({ type: 'wait-stable', options });
|
||||
return { id: 17, url: 'https://chatgpt.com/', status: 'complete' };
|
||||
@@ -543,7 +543,7 @@ test('signup flow helper recognizes email verification page as post-email landin
|
||||
},
|
||||
setEmailState: async () => {},
|
||||
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
waitForTabUrlMatch: async () => ({
|
||||
id: 21,
|
||||
url: 'https://auth.openai.com/email-verification',
|
||||
@@ -600,7 +600,7 @@ test('signup flow helper waits for the signup entry tab to settle for step 2 bef
|
||||
},
|
||||
setEmailState: async () => {},
|
||||
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
waitForTabStableComplete: async (_tabId, options) => {
|
||||
events.push({ type: 'wait-stable', options });
|
||||
return { id: 23, url: 'https://chatgpt.com/', status: 'complete' };
|
||||
@@ -680,7 +680,7 @@ test('signup flow helper accepts phone signup landing on login password page', a
|
||||
},
|
||||
setEmailState: async () => {},
|
||||
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
waitForTabUrlMatch: async (_tabId, predicate) => {
|
||||
const url = 'https://auth.openai.com/log-in/password';
|
||||
return predicate(url) ? { id: 22, url } : null;
|
||||
@@ -724,7 +724,7 @@ test('signup flow helper reuses existing managed alias email when it is still co
|
||||
setEmailCalls += 1;
|
||||
},
|
||||
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
waitForTabUrlMatch: async () => null,
|
||||
});
|
||||
|
||||
@@ -767,7 +767,7 @@ test('signup flow helper can generate an email on demand when add-email starts f
|
||||
setStateCalls.push(updates);
|
||||
},
|
||||
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
waitForTabUrlMatch: async () => null,
|
||||
});
|
||||
|
||||
@@ -836,7 +836,7 @@ test('signup flow helper delegates preserved phone identity email sync to the sh
|
||||
setStateCalls += 1;
|
||||
},
|
||||
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
waitForTabUrlMatch: async () => null,
|
||||
});
|
||||
|
||||
@@ -901,7 +901,7 @@ test('signup flow helper finalizes step 3 submit by reusing signup verification
|
||||
},
|
||||
setEmailState: async () => {},
|
||||
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||
SIGNUP_PAGE_INJECT_FILES: ['content/utils.js', 'content/signup-page.js'],
|
||||
OPENAI_AUTH_INJECT_FILES: ['content/utils.js', 'flows/openai/content/openai-auth.js'],
|
||||
waitForTabUrlMatch: async () => null,
|
||||
});
|
||||
|
||||
@@ -943,11 +943,11 @@ test('signup flow helper rewrites retryable step 3 finalize transport timeout in
|
||||
isSignupPasswordPageUrl: () => true,
|
||||
reuseOrCreateTab: async () => 31,
|
||||
sendToContentScriptResilient: async () => {
|
||||
throw new Error('Content script on signup-page did not respond in 45s. Try refreshing the tab and retry.');
|
||||
throw new Error('Content script on openai-auth did not respond in 45s. Try refreshing the tab and retry.');
|
||||
},
|
||||
setEmailState: async () => {},
|
||||
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||
SIGNUP_PAGE_INJECT_FILES: ['content/utils.js', 'content/signup-page.js'],
|
||||
OPENAI_AUTH_INJECT_FILES: ['content/utils.js', 'flows/openai/content/openai-auth.js'],
|
||||
waitForTabUrlMatch: async () => null,
|
||||
});
|
||||
|
||||
|
||||
@@ -6,22 +6,22 @@ test('background imports workflow step modules including rebuilt Kiro modules',
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
|
||||
[
|
||||
'background/steps/open-chatgpt.js',
|
||||
'background/steps/submit-signup-email.js',
|
||||
'background/steps/fill-password.js',
|
||||
'background/steps/fetch-signup-code.js',
|
||||
'background/steps/fill-profile.js',
|
||||
'background/steps/wait-registration-success.js',
|
||||
'background/steps/oauth-login.js',
|
||||
'background/steps/fetch-login-code.js',
|
||||
'background/steps/confirm-oauth.js',
|
||||
'background/steps/platform-verify.js',
|
||||
'flows/openai/background/steps/open-chatgpt.js',
|
||||
'flows/openai/background/steps/submit-signup-email.js',
|
||||
'flows/openai/background/steps/fill-password.js',
|
||||
'flows/openai/background/steps/fetch-signup-code.js',
|
||||
'flows/openai/background/steps/fill-profile.js',
|
||||
'flows/openai/background/steps/wait-registration-success.js',
|
||||
'flows/openai/background/steps/oauth-login.js',
|
||||
'flows/openai/background/steps/fetch-login-code.js',
|
||||
'flows/openai/background/steps/confirm-oauth.js',
|
||||
'flows/openai/background/steps/platform-verify.js',
|
||||
'shared/kiro-timeouts.js',
|
||||
'background/kiro/state.js',
|
||||
'background/kiro/register-runner.js',
|
||||
'background/kiro/desktop-client.js',
|
||||
'background/kiro/desktop-authorize-runner.js',
|
||||
'background/kiro/publisher-kiro-rs.js',
|
||||
'flows/kiro/background/state.js',
|
||||
'flows/kiro/background/register-runner.js',
|
||||
'flows/kiro/background/desktop-client.js',
|
||||
'flows/kiro/background/desktop-authorize-runner.js',
|
||||
'flows/kiro/background/publisher-kiro-rs.js',
|
||||
].forEach((path) => {
|
||||
assert.match(source, new RegExp(path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
test('background node registry preserves node metadata even before an executor is registered', () => {
|
||||
const source = fs.readFileSync('background/steps/registry.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/step-registry.js', 'utf8');
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStepRegistry;`)({});
|
||||
const registry = api.createNodeRegistry([
|
||||
{
|
||||
@@ -27,7 +27,7 @@ test('background node registry preserves node metadata even before an executor i
|
||||
});
|
||||
|
||||
test('background node registry executes registered nodes in display order', async () => {
|
||||
const source = fs.readFileSync('background/steps/registry.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/step-registry.js', 'utf8');
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStepRegistry;`)({});
|
||||
const events = [];
|
||||
const registry = api.createNodeRegistry([
|
||||
|
||||
@@ -4,19 +4,19 @@ const fs = require('node:fs');
|
||||
|
||||
test('background imports node registry and wires the rebuilt Kiro executors', () => {
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
assert.match(source, /background\/steps\/registry\.js/);
|
||||
assert.match(source, /core\/flow-kernel\/step-registry\.js/);
|
||||
assert.match(source, /data\/step-definitions\.js/);
|
||||
assert.match(source, /background\/workflow-engine\.js/);
|
||||
assert.match(source, /core\/flow-kernel\/workflow-engine\.js/);
|
||||
assert.match(source, /MultiPageStepDefinitions\?\.getNodes/);
|
||||
assert.match(source, /buildNodeRegistry\(definitions/);
|
||||
assert.match(source, /const stepRegistryCache = new Map\(\);/);
|
||||
assert.match(source, /const definitions = getNodeDefinitionsForState\(state\);/);
|
||||
assert.match(source, /stepRegistryCache\.set\(cacheKey, buildStepRegistry\(definitions\)\)/);
|
||||
|
||||
assert.match(source, /background\/kiro\/register-runner\.js/);
|
||||
assert.match(source, /background\/kiro\/desktop-client\.js/);
|
||||
assert.match(source, /background\/kiro\/desktop-authorize-runner\.js/);
|
||||
assert.match(source, /background\/kiro\/publisher-kiro-rs\.js/);
|
||||
assert.match(source, /flows\/kiro\/background\/register-runner\.js/);
|
||||
assert.match(source, /flows\/kiro\/background\/desktop-client\.js/);
|
||||
assert.match(source, /flows\/kiro\/background\/desktop-authorize-runner\.js/);
|
||||
assert.match(source, /flows\/kiro\/background\/publisher-kiro-rs\.js/);
|
||||
assert.doesNotMatch(source, /background\/steps\/kiro-device-auth\.js/);
|
||||
|
||||
assert.match(source, /const kiroRegisterRunner = self\.MultiPageBackgroundKiroRegisterRunner\?\.createKiroRegisterRunner\(/);
|
||||
|
||||
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
function loadStep1Module() {
|
||||
const source = fs.readFileSync('background/steps/open-chatgpt.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/open-chatgpt.js', 'utf8');
|
||||
const globalScope = {};
|
||||
return new Function('self', `${source}; return self.MultiPageBackgroundStep1;`)(globalScope);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background/steps/fill-password.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/fill-password.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep3;`)(globalScope);
|
||||
|
||||
@@ -26,7 +26,7 @@ test('step 3 reuses existing generated password when rerunning the same email fl
|
||||
events.passwordStates.push(password);
|
||||
},
|
||||
setState: async () => {},
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
});
|
||||
|
||||
await executor.executeStep3({
|
||||
@@ -80,7 +80,7 @@ test('step 3 supports phone-only signup identity when password page is present',
|
||||
setState: async (updates) => {
|
||||
events.stateUpdates.push(updates);
|
||||
},
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
});
|
||||
|
||||
await executor.executeStep3({
|
||||
@@ -149,7 +149,7 @@ test('step 3 phone signup intent does not fall back to a stale email identity',
|
||||
setState: async (updates) => {
|
||||
events.stateUpdates.push(updates);
|
||||
},
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
@@ -188,7 +188,7 @@ test('step 3 respects resolved email fallback when phone signup is unavailable',
|
||||
},
|
||||
setPasswordState: async () => {},
|
||||
setState: async () => {},
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
OPENAI_AUTH_INJECT_FILES: [],
|
||||
});
|
||||
|
||||
await executor.executeStep3({
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background/steps/fetch-signup-code.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/fetch-signup-code.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep4;`)(globalScope);
|
||||
|
||||
@@ -427,7 +427,7 @@ test('step 4 prepare retries transport by recovering retry page without replayin
|
||||
}
|
||||
sendToContentScriptCalls += 1;
|
||||
if (sendToContentScriptCalls === 1) {
|
||||
throw new Error('Content script on signup-page did not respond in 30s. Try refreshing the tab and retry.');
|
||||
throw new Error('Content script on openai-auth did not respond in 30s. Try refreshing the tab and retry.');
|
||||
}
|
||||
return { ready: true };
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background/steps/fill-profile.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/fill-profile.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep5;`)(globalScope);
|
||||
|
||||
@@ -28,7 +28,7 @@ test('step 5 forwards generated profile data and relies on completion signal flo
|
||||
|
||||
assert.deepStrictEqual(events.messages, [
|
||||
{
|
||||
source: 'signup-page',
|
||||
source: 'openai-auth',
|
||||
message: {
|
||||
type: 'EXECUTE_NODE',
|
||||
nodeId: 'fill-profile',
|
||||
|
||||
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
test('step 6 waits for registration success and completes from background', async () => {
|
||||
const source = fs.readFileSync('background/steps/wait-registration-success.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/wait-registration-success.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep6;`)(globalScope);
|
||||
|
||||
@@ -33,7 +33,7 @@ test('step 6 waits for registration success and completes from background', asyn
|
||||
});
|
||||
|
||||
test('step 6 only clears cookies when cleanup switch is enabled', async () => {
|
||||
const source = fs.readFileSync('background/steps/wait-registration-success.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/wait-registration-success.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep6;`)(globalScope);
|
||||
|
||||
@@ -90,7 +90,7 @@ test('step 6 only clears cookies when cleanup switch is enabled', async () => {
|
||||
});
|
||||
|
||||
test('step 7 retries up to configured limit and then fails', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -138,7 +138,7 @@ test('step 7 retries up to configured limit and then fails', async () => {
|
||||
});
|
||||
|
||||
test('step 7 hands add-phone to the dedicated post-login phone node without internal retry', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -196,7 +196,7 @@ test('step 7 hands add-phone to the dedicated post-login phone node without inte
|
||||
});
|
||||
|
||||
test('step 7 no longer runs shared phone verification inside oauth-login', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -218,7 +218,7 @@ test('step 7 no longer runs shared phone verification inside oauth-login', async
|
||||
password: 'secret',
|
||||
phoneVerificationEnabled: true,
|
||||
}),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 91 : 0),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 91 : 0),
|
||||
isStep6RecoverableResult: (result) => result?.step6Outcome === 'recoverable',
|
||||
isStep6SuccessResult: (result) => result?.step6Outcome === 'success',
|
||||
phoneVerificationHelpers: {
|
||||
@@ -261,7 +261,7 @@ test('step 7 no longer runs shared phone verification inside oauth-login', async
|
||||
});
|
||||
|
||||
test('step 7 add-phone handoff does not depend on phone verification being enabled', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -302,7 +302,7 @@ test('step 7 add-phone handoff does not depend on phone verification being enabl
|
||||
});
|
||||
|
||||
test('step 7 ignores obsolete shared add-phone verifier during handoff', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -343,7 +343,7 @@ test('step 7 ignores obsolete shared add-phone verifier during handoff', async (
|
||||
});
|
||||
|
||||
test('step 7 starts a new oauth timeout window for each refreshed oauth url', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -396,7 +396,7 @@ test('step 7 starts a new oauth timeout window for each refreshed oauth url', as
|
||||
});
|
||||
|
||||
test('step 7 forwards direct OAuth consent skip metadata when completing', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -445,7 +445,7 @@ test('step 7 forwards direct OAuth consent skip metadata when completing', async
|
||||
});
|
||||
|
||||
test('step 7 forwards phone login identity payload when account identifier is phone', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -535,7 +535,7 @@ test('step 7 forwards phone login identity payload when account identifier is ph
|
||||
});
|
||||
|
||||
test('step 7 keeps Plus email login even when phone sms runtime exists', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -589,7 +589,7 @@ test('step 7 keeps Plus email login even when phone sms runtime exists', async (
|
||||
});
|
||||
|
||||
test('step 7 keeps relogin-bound-email as the active node id', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -655,7 +655,7 @@ test('step 7 keeps relogin-bound-email as the active node id', async () => {
|
||||
});
|
||||
|
||||
test('step 7 keeps phone login after step 8 stores an unbound email for phone signup', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -711,7 +711,7 @@ test('step 7 keeps phone login after step 8 stores an unbound email for phone si
|
||||
});
|
||||
|
||||
test('step 7 can infer phone login from an available phone signup configuration before step 2 finishes', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -757,7 +757,7 @@ test('step 7 can infer phone login from an available phone signup configuration
|
||||
});
|
||||
|
||||
test('step 7 can start from a manually filled signup phone without completed step 2 or step 3 state', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -822,7 +822,7 @@ test('step 7 can start from a manually filled signup phone without completed ste
|
||||
});
|
||||
|
||||
test('step 7 stops immediately when management secret is missing', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
@@ -867,7 +867,7 @@ test('step 7 stops immediately when management secret is missing', async () => {
|
||||
});
|
||||
|
||||
test('step 7 stops immediately when management secret is invalid', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background/steps/fetch-login-code.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/fetch-login-code.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep8;`)(globalScope);
|
||||
|
||||
@@ -44,7 +44,7 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
@@ -447,7 +447,7 @@ test('step 8 defers add-email page to the dedicated bind-email node in phone mod
|
||||
password: 'secret',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
}),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
@@ -991,7 +991,7 @@ test('step 8 does not submit or recover add-email inside fetch-login-code', asyn
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ ...runtimeState }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
@@ -1102,7 +1102,7 @@ test('step 8 rejects add-email page in email login mode', async () => {
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ ...runtimeState }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
@@ -1290,7 +1290,7 @@ test('Plus login-code step reuses step 8 verification logic but completes visibl
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret', plusModeEnabled: true }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
@@ -1371,7 +1371,7 @@ test('bound-email relogin code step points recovery to the relogin step in Plus
|
||||
signupMethod: 'phone',
|
||||
phoneSignupReloginAfterBindEmailEnabled: true,
|
||||
}),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
@@ -1449,7 +1449,7 @@ test('step 8 completes directly when auth page is already on OAuth consent page'
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
@@ -1533,7 +1533,7 @@ test('step 8 retries in-place when polling fails but auth page still stays on ve
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: (error) => /页面通信异常|did not respond/i.test(String(error?.message || error || '')),
|
||||
@@ -1595,7 +1595,7 @@ test('step 8 keeps resend cooldown timestamp across in-place retries to avoid re
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret', loginVerificationRequestedAt: null }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: (error) => /页面通信异常|did not respond/i.test(String(error?.message || error || '')),
|
||||
@@ -1677,7 +1677,7 @@ test('step 8 completes when polling fails but recovery probe shows oauth consent
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: (error) => /页面通信异常|did not respond/i.test(String(error?.message || error || '')),
|
||||
@@ -1759,7 +1759,7 @@ test('step 8 uses a fixed 10-minute lookback window and plans 2925 polling as 2/
|
||||
{ id: 'acc-1', email: 'pool-user@2925.com' },
|
||||
],
|
||||
}),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
@@ -1839,7 +1839,7 @@ test('step 8 falls back to the run email when the verification page does not exp
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
@@ -1896,7 +1896,7 @@ test('step 8 does not rerun step 7 when verification submit lands on add-phone',
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
@@ -1953,7 +1953,7 @@ test('step 8 checks iCloud session before polling iCloud mailbox', async () => {
|
||||
navigateOnReuse: true,
|
||||
}),
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
|
||||
@@ -16,7 +16,7 @@ function loadSub2ApiApiModule() {
|
||||
}
|
||||
|
||||
function loadSub2ApiSessionImportModule() {
|
||||
const source = fs.readFileSync('background/steps/sub2api-session-import.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/sub2api-session-import.js', 'utf8');
|
||||
return new Function('self', `${source}; return self.MultiPageBackgroundSub2ApiSessionImport;`)({});
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ test('session import step reads current ChatGPT session and completes node', asy
|
||||
assert.deepStrictEqual(ensureCalls[0].options.inject, [
|
||||
'content/utils.js',
|
||||
'content/operation-delay.js',
|
||||
'content/plus-checkout.js',
|
||||
'flows/openai/content/plus-checkout.js',
|
||||
]);
|
||||
assert.deepStrictEqual(sentMessages, [{
|
||||
tabId: 91,
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const { readSourceRegistryBundle } = require('./helpers/script-bundles.js');
|
||||
|
||||
const registrySource = readSourceRegistryBundle();
|
||||
|
||||
test('background imports tab runtime module', () => {
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
assert.match(source, /background\/tab-runtime\.js/);
|
||||
assert.match(source, /core\/flow-kernel\/tab-runtime\.js/);
|
||||
});
|
||||
|
||||
test('tab runtime module exposes a factory', () => {
|
||||
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
const globalScope = {};
|
||||
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
|
||||
@@ -16,9 +19,8 @@ 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');
|
||||
test('tab runtime accepts canonical openai-auth readiness for queued openai-auth commands', async () => {
|
||||
const runtimeSource = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
const runtimeApi = new Function('self', `${runtimeSource}; return self.MultiPageBackgroundTabRuntime;`)({});
|
||||
const registryApi = new Function('self', `${registrySource}; return self.MultiPageSourceRegistry;`)({});
|
||||
const sourceRegistry = registryApi.createSourceRegistry();
|
||||
@@ -26,10 +28,10 @@ test('tab runtime accepts canonical openai-auth readiness for queued signup-page
|
||||
const sentMessages = [];
|
||||
let currentState = {
|
||||
tabRegistry: {
|
||||
'signup-page': { tabId: 91, ready: true },
|
||||
'openai-auth': { tabId: 91, ready: true },
|
||||
},
|
||||
sourceLastUrls: {
|
||||
'signup-page': 'https://auth.openai.com/authorize',
|
||||
'openai-auth': 'https://auth.openai.com/authorize',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -73,21 +75,21 @@ test('tab runtime accepts canonical openai-auth readiness for queued signup-page
|
||||
sourceLastUrls: {},
|
||||
};
|
||||
|
||||
const queued = runtime.queueCommand('signup-page', { type: 'STEP2_TEST' }, 1000);
|
||||
const queued = runtime.queueCommand('openai-auth', { 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, {
|
||||
await runtime.ensureContentScriptReadyOnTab('openai-auth', 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);
|
||||
assert.deepEqual(Object.keys(currentState.tabRegistry), ['openai-auth']);
|
||||
});
|
||||
|
||||
test('tab runtime caps per-attempt response timeout to the remaining resilient timeout budget', () => {
|
||||
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
|
||||
|
||||
@@ -122,7 +124,7 @@ test('tab runtime caps per-attempt response timeout to the remaining resilient t
|
||||
});
|
||||
|
||||
test('tab runtime gives step 5 profile submit enough response time for slow page transitions', () => {
|
||||
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
|
||||
|
||||
@@ -153,7 +155,7 @@ test('tab runtime gives step 5 profile submit enough response time for slow page
|
||||
});
|
||||
|
||||
test('tab runtime replays retryable transport recovery hook and surfaces a localized timeout error', async () => {
|
||||
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
|
||||
|
||||
@@ -207,7 +209,7 @@ test('tab runtime replays retryable transport recovery hook and surfaces a local
|
||||
});
|
||||
|
||||
test('tab runtime waitForTabComplete waits until tab status becomes complete', async () => {
|
||||
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
|
||||
|
||||
@@ -250,7 +252,7 @@ test('tab runtime waitForTabComplete waits until tab status becomes complete', a
|
||||
});
|
||||
|
||||
test('tab runtime waitForTabComplete aborts promptly when stop is requested', async () => {
|
||||
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
|
||||
|
||||
@@ -290,7 +292,7 @@ test('tab runtime waitForTabComplete aborts promptly when stop is requested', as
|
||||
});
|
||||
|
||||
test('tab runtime waitForTabStableComplete waits through a late navigation after an initial complete state', async () => {
|
||||
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
|
||||
|
||||
@@ -345,7 +347,7 @@ test('tab runtime waitForTabStableComplete waits through a late navigation after
|
||||
});
|
||||
|
||||
test('tab runtime opens new automation tabs in the locked window', async () => {
|
||||
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
|
||||
const created = [];
|
||||
@@ -377,14 +379,14 @@ test('tab runtime opens new automation tabs in the locked window', async () => {
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await runtime.reuseOrCreateTab('signup-page', 'https://example.com');
|
||||
await runtime.reuseOrCreateTab('openai-auth', 'https://example.com');
|
||||
|
||||
assert.equal(created.length, 1);
|
||||
assert.equal(created[0].windowId, 100);
|
||||
});
|
||||
|
||||
test('tab runtime force-new opens replacement before removing the active stale source tab', async () => {
|
||||
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
|
||||
const events = [];
|
||||
@@ -413,18 +415,18 @@ test('tab runtime force-new opens replacement before removing the active stale s
|
||||
getSourceLabel: (sourceName) => sourceName || 'unknown',
|
||||
getState: async () => ({
|
||||
automationWindowId: 100,
|
||||
sourceLastUrls: { 'signup-page': 'https://chatgpt.com/' },
|
||||
sourceLastUrls: { 'openai-auth': 'https://chatgpt.com/' },
|
||||
tabRegistry: {},
|
||||
}),
|
||||
matchesSourceUrlFamily: (sourceName, candidateUrl) => (
|
||||
sourceName === 'signup-page'
|
||||
sourceName === 'openai-auth'
|
||||
&& /chatgpt\.com|auth\.openai\.com/.test(String(candidateUrl || ''))
|
||||
),
|
||||
setState: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const tabId = await runtime.reuseOrCreateTab('signup-page', 'https://auth.openai.com/authorize', {
|
||||
const tabId = await runtime.reuseOrCreateTab('openai-auth', 'https://auth.openai.com/authorize', {
|
||||
forceNew: true,
|
||||
});
|
||||
|
||||
@@ -439,7 +441,7 @@ test('tab runtime force-new opens replacement before removing the active stale s
|
||||
});
|
||||
|
||||
test('tab runtime scopes tab queries to the locked automation window', async () => {
|
||||
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
|
||||
const queries = [];
|
||||
@@ -472,7 +474,7 @@ test('tab runtime scopes tab queries to the locked automation window', async ()
|
||||
});
|
||||
|
||||
test('tab runtime does not create tabs outside an unavailable locked window', async () => {
|
||||
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
|
||||
const created = [];
|
||||
@@ -512,7 +514,7 @@ test('tab runtime does not create tabs outside an unavailable locked window', as
|
||||
});
|
||||
|
||||
test('tab runtime does not query all windows when the locked window is unavailable', async () => {
|
||||
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
|
||||
const queries = [];
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
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 = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||
const flowRegistrySource = readFlowRegistryBundle();
|
||||
const contributionRegistrySource = fs.readFileSync('shared/contribution-registry.js', 'utf8');
|
||||
|
||||
function loadApi() {
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const { readFlowCapabilitiesBundle } = require('./helpers/script-bundles.js');
|
||||
|
||||
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||
const contributionRegistrySource = fs.readFileSync('shared/contribution-registry.js', 'utf8');
|
||||
const settingsSchemaSource = fs.readFileSync('shared/settings-schema.js', 'utf8');
|
||||
const source = fs.readFileSync('shared/flow-capabilities.js', 'utf8');
|
||||
const source = readFlowCapabilitiesBundle();
|
||||
|
||||
function loadApi() {
|
||||
const scope = {};
|
||||
return new Function(
|
||||
'self',
|
||||
`${flowRegistrySource}; ${contributionRegistrySource}; ${settingsSchemaSource}; ${source}; return self.MultiPageFlowCapabilities;`
|
||||
)(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', () => {
|
||||
@@ -22,7 +16,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
|
||||
const enabledState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
openaiIntegrationTargetId: 'cpa',
|
||||
targetId: 'cpa',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: false,
|
||||
accountContributionEnabled: false,
|
||||
@@ -38,7 +32,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
|
||||
const plusLockedState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
openaiIntegrationTargetId: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
accountContributionEnabled: false,
|
||||
@@ -59,7 +53,7 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab
|
||||
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'site-a',
|
||||
openaiIntegrationTargetId: 'codex2api',
|
||||
targetId: 'codex2api',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
accountContributionEnabled: true,
|
||||
@@ -73,7 +67,7 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab
|
||||
assert.equal(capabilityState.canShowLuckmail, false);
|
||||
assert.equal(capabilityState.canUsePhoneSignup, false);
|
||||
assert.equal(capabilityState.effectiveSignupMethod, 'email');
|
||||
assert.equal(capabilityState.panelMode, 'codex2api');
|
||||
assert.equal(capabilityState.effectiveTargetId, 'codex2api');
|
||||
assert.deepEqual(capabilityState.supportedTargetIds, []);
|
||||
});
|
||||
|
||||
@@ -84,8 +78,7 @@ test('flow capability registry exposes Kiro as an independent flow with its own
|
||||
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'kiro',
|
||||
kiroTargetId: 'kiro-rs',
|
||||
openaiIntegrationTargetId: 'sub2api',
|
||||
targetId: 'kiro-rs',
|
||||
signupMethod: 'phone',
|
||||
plusModeEnabled: true,
|
||||
phoneVerificationEnabled: true,
|
||||
@@ -120,7 +113,7 @@ test('flow capability registry exposes shared auto-run validation for phone lock
|
||||
const plusLockedResult = registry.validateAutoRunStart({
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
openaiIntegrationTargetId: 'cpa',
|
||||
targetId: 'cpa',
|
||||
signupMethod: 'phone',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
@@ -134,7 +127,7 @@ test('flow capability registry exposes shared auto-run validation for phone lock
|
||||
const unsupportedPanelResult = registry.validateAutoRunStart({
|
||||
state: {
|
||||
activeFlowId: 'site-a',
|
||||
openaiIntegrationTargetId: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
signupMethod: 'email',
|
||||
},
|
||||
});
|
||||
@@ -158,14 +151,14 @@ test('flow capability registry normalizes unsupported mode switches back to the
|
||||
const validation = registry.validateModeSwitch({
|
||||
state: {
|
||||
activeFlowId: 'site-a',
|
||||
openaiIntegrationTargetId: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
signupMethod: 'phone',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
accountContributionEnabled: true,
|
||||
},
|
||||
changedKeys: [
|
||||
'openaiIntegrationTargetId',
|
||||
'targetId',
|
||||
'signupMethod',
|
||||
'phoneVerificationEnabled',
|
||||
'plusModeEnabled',
|
||||
@@ -175,9 +168,7 @@ test('flow capability registry normalizes unsupported mode switches back to the
|
||||
|
||||
assert.equal(validation.ok, false);
|
||||
assert.deepEqual(validation.normalizedUpdates, {
|
||||
panelMode: 'cpa',
|
||||
openaiIntegrationTargetId: 'cpa',
|
||||
kiroTargetId: 'cpa',
|
||||
targetId: 'cpa',
|
||||
signupMethod: 'email',
|
||||
phoneVerificationEnabled: false,
|
||||
plusModeEnabled: false,
|
||||
@@ -203,7 +194,7 @@ test('flow capability registry exposes editable Plus account access strategies f
|
||||
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
openaiIntegrationTargetId: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
signupMethod: 'email',
|
||||
plusModeEnabled: true,
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
@@ -227,7 +218,7 @@ test('flow capability registry maps session import to the current source target'
|
||||
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
openaiIntegrationTargetId: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
signupMethod: 'email',
|
||||
plusModeEnabled: true,
|
||||
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||
@@ -250,7 +241,7 @@ test('flow capability registry exposes editable Plus account access strategies f
|
||||
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
openaiIntegrationTargetId: 'cpa',
|
||||
targetId: 'cpa',
|
||||
signupMethod: 'email',
|
||||
plusModeEnabled: true,
|
||||
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||
@@ -274,7 +265,7 @@ test('flow capability registry falls back to OAuth when the current source canno
|
||||
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
openaiIntegrationTargetId: 'codex2api',
|
||||
targetId: 'codex2api',
|
||||
signupMethod: 'email',
|
||||
plusModeEnabled: true,
|
||||
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||
@@ -298,7 +289,7 @@ test('flow capability registry forces SUB2API session import only for contributi
|
||||
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
openaiIntegrationTargetId: 'cpa',
|
||||
targetId: 'cpa',
|
||||
signupMethod: 'email',
|
||||
plusModeEnabled: true,
|
||||
accountContributionEnabled: true,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const { readFlowRegistryBundle, readBundle } = require('./helpers/script-bundles.js');
|
||||
|
||||
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||
const settingsSchemaSource = fs.readFileSync('shared/settings-schema.js', 'utf8');
|
||||
const flowRegistrySource = readFlowRegistryBundle();
|
||||
const settingsSchemaSource = readBundle(['core/flow-kernel/settings-schema.js']);
|
||||
|
||||
function loadApis() {
|
||||
const scope = {};
|
||||
@@ -57,7 +57,7 @@ test('settings schema normalizes view input into canonical nested namespaces', (
|
||||
|
||||
const normalized = schema.normalizeSettingsState({
|
||||
activeFlowId: 'kiro',
|
||||
panelMode: 'sub2api',
|
||||
targetId: 'kiro-rs',
|
||||
mailProvider: 'hotmail',
|
||||
ipProxyEnabled: true,
|
||||
ipProxyService: '711proxy',
|
||||
@@ -75,9 +75,9 @@ test('settings schema normalizes view input into canonical nested namespaces', (
|
||||
assert.equal(normalized.services.email.provider, 'hotmail');
|
||||
assert.equal(normalized.services.proxy.enabled, true);
|
||||
assert.equal(normalized.services.account.customPassword, 'SharedSecret123!');
|
||||
assert.equal(normalized.flows.openai.integrationTargetId, 'sub2api');
|
||||
assert.equal(normalized.flows.openai.selectedTargetId, 'cpa');
|
||||
assert.equal(normalized.flows.openai.plus.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(normalized.flows.kiro.targetId, 'kiro-rs');
|
||||
assert.equal(normalized.flows.kiro.selectedTargetId, 'kiro-rs');
|
||||
assert.equal(normalized.flows.kiro.targets['kiro-rs'].baseUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(normalized.flows.kiro.targets['kiro-rs'].apiKey, 'secret-key');
|
||||
assert.deepEqual(normalized.flows.kiro.autoRun.stepExecutionRange, {
|
||||
@@ -116,7 +116,7 @@ test('settings schema can project canonical state into a read view without legac
|
||||
const schema = settingsSchema.createSettingsSchema();
|
||||
const normalized = schema.normalizeSettingsState({
|
||||
activeFlowId: 'kiro',
|
||||
kiroTargetId: 'kiro-rs',
|
||||
targetId: 'kiro-rs',
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'key-123',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
@@ -124,13 +124,11 @@ test('settings schema can project canonical state into a read view without legac
|
||||
const view = schema.buildSettingsView(normalized);
|
||||
|
||||
assert.equal(view.activeFlowId, 'kiro');
|
||||
assert.equal(view.openaiIntegrationTargetId, 'cpa');
|
||||
assert.equal(view.kiroTargetId, 'kiro-rs');
|
||||
assert.equal(view.panelMode, 'cpa');
|
||||
assert.equal(view.targetId, 'kiro-rs');
|
||||
assert.equal(view.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(view.kiroRsKey, 'key-123');
|
||||
assert.equal(view.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(view.settingsSchemaVersion, 4);
|
||||
assert.equal(view.settingsSchemaVersion, 5);
|
||||
assert.equal(view.settingsState.activeFlowId, 'kiro');
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background/steps/gopay-approve.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/gopay-approve.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
|
||||
const source = fs.readFileSync('content/gopay-flow.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/gopay-flow.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background/steps/gopay-manual-confirm.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/gopay-manual-confirm.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundGoPayManualConfirm;`)(globalScope);
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
const fs = require('node:fs');
|
||||
|
||||
const FLOW_DEFINITION_FILES = Object.freeze([
|
||||
'flows/openai/index.js',
|
||||
'flows/kiro/index.js',
|
||||
'flows/index.js',
|
||||
]);
|
||||
|
||||
const FLOW_WORKFLOW_FILES = Object.freeze([
|
||||
'flows/openai/workflow.js',
|
||||
'flows/kiro/workflow.js',
|
||||
]);
|
||||
|
||||
function readBundle(files = []) {
|
||||
return files
|
||||
.map((file) => fs.readFileSync(file, 'utf8'))
|
||||
.join(';\n');
|
||||
}
|
||||
|
||||
function readFlowRegistryBundle(extraFiles = []) {
|
||||
return readBundle([
|
||||
...FLOW_DEFINITION_FILES,
|
||||
'core/flow-kernel/flow-registry.js',
|
||||
...extraFiles,
|
||||
]);
|
||||
}
|
||||
|
||||
function readSourceRegistryBundle(extraFiles = []) {
|
||||
return readBundle([
|
||||
...FLOW_DEFINITION_FILES,
|
||||
'core/flow-kernel/flow-registry.js',
|
||||
'core/flow-kernel/source-registry.js',
|
||||
...extraFiles,
|
||||
]);
|
||||
}
|
||||
|
||||
function readStepDefinitionsBundle(extraFiles = []) {
|
||||
return readBundle([
|
||||
...FLOW_DEFINITION_FILES,
|
||||
...FLOW_WORKFLOW_FILES,
|
||||
'data/step-definitions.js',
|
||||
...extraFiles,
|
||||
]);
|
||||
}
|
||||
|
||||
function readFlowCapabilitiesBundle(extraFiles = []) {
|
||||
return readBundle([
|
||||
...FLOW_DEFINITION_FILES,
|
||||
'core/flow-kernel/flow-registry.js',
|
||||
'shared/contribution-registry.js',
|
||||
'core/flow-kernel/settings-schema.js',
|
||||
'core/flow-kernel/flow-capabilities.js',
|
||||
...extraFiles,
|
||||
]);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FLOW_DEFINITION_FILES,
|
||||
FLOW_WORKFLOW_FILES,
|
||||
readBundle,
|
||||
readFlowCapabilitiesBundle,
|
||||
readFlowRegistryBundle,
|
||||
readSourceRegistryBundle,
|
||||
readStepDefinitionsBundle,
|
||||
};
|
||||
@@ -3,14 +3,18 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
function loadKiroContributionModules() {
|
||||
const stateSource = fs.readFileSync('background/kiro/state.js', 'utf8');
|
||||
const artifactSource = fs.readFileSync('background/kiro/credential-artifact.js', 'utf8');
|
||||
const stateSource = fs.readFileSync('flows/kiro/background/state.js', 'utf8');
|
||||
const artifactSource = fs.readFileSync('flows/kiro/background/credential-artifact.js', 'utf8');
|
||||
const adapterSource = fs.readFileSync('background/contribution/adapters/kiro-builder-id.js', 'utf8');
|
||||
const globalScope = {};
|
||||
new Function('self', `${stateSource}; ${artifactSource}; ${adapterSource}; return self;`)(globalScope);
|
||||
return globalScope;
|
||||
}
|
||||
|
||||
function getKiroRuntime(state = {}) {
|
||||
return state?.runtimeState?.flowState?.kiro || {};
|
||||
}
|
||||
|
||||
function buildAuthorizedKiroState(overrides = {}) {
|
||||
return {
|
||||
activeFlowId: 'kiro',
|
||||
@@ -19,20 +23,24 @@ function buildAuthorizedKiroState(overrides = {}) {
|
||||
contributionAdapterId: 'kiro-builder-id',
|
||||
contributionNickname: '贡献者',
|
||||
contributionQq: '123456',
|
||||
kiroRuntime: {
|
||||
register: {
|
||||
email: 'kiro-user@example.com',
|
||||
},
|
||||
desktopAuth: {
|
||||
region: 'us-east-1',
|
||||
clientId: 'client-id-001',
|
||||
clientSecret: 'client-secret-super-long',
|
||||
refreshToken: 'refresh-token-super-secret',
|
||||
tokenSource: 'desktop_authorization_code_pkce',
|
||||
authorizedAt: 1760000000000,
|
||||
},
|
||||
upload: {
|
||||
targetId: 'kiro-rs',
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
register: {
|
||||
email: 'kiro-user@example.com',
|
||||
},
|
||||
desktopAuth: {
|
||||
region: 'us-east-1',
|
||||
clientId: 'client-id-001',
|
||||
clientSecret: 'client-secret-super-long',
|
||||
refreshToken: 'refresh-token-super-secret',
|
||||
tokenSource: 'desktop_authorization_code_pkce',
|
||||
authorizedAt: 1760000000000,
|
||||
},
|
||||
upload: {
|
||||
targetId: 'kiro-rs',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...overrides,
|
||||
@@ -65,11 +73,15 @@ test('Kiro Builder ID artifact builder rejects missing required fields', () => {
|
||||
|
||||
assert.throws(
|
||||
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
|
||||
kiroRuntime: {
|
||||
...buildAuthorizedKiroState().kiroRuntime,
|
||||
desktopAuth: {
|
||||
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
|
||||
refreshToken: '',
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
...getKiroRuntime(buildAuthorizedKiroState()),
|
||||
desktopAuth: {
|
||||
...getKiroRuntime(buildAuthorizedKiroState()).desktopAuth,
|
||||
refreshToken: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
@@ -77,11 +89,15 @@ test('Kiro Builder ID artifact builder rejects missing required fields', () => {
|
||||
);
|
||||
assert.throws(
|
||||
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
|
||||
kiroRuntime: {
|
||||
...buildAuthorizedKiroState().kiroRuntime,
|
||||
desktopAuth: {
|
||||
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
|
||||
clientId: '',
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
...getKiroRuntime(buildAuthorizedKiroState()),
|
||||
desktopAuth: {
|
||||
...getKiroRuntime(buildAuthorizedKiroState()).desktopAuth,
|
||||
clientId: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
@@ -89,11 +105,15 @@ test('Kiro Builder ID artifact builder rejects missing required fields', () => {
|
||||
);
|
||||
assert.throws(
|
||||
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
|
||||
kiroRuntime: {
|
||||
...buildAuthorizedKiroState().kiroRuntime,
|
||||
desktopAuth: {
|
||||
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
|
||||
clientSecret: '',
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
...getKiroRuntime(buildAuthorizedKiroState()),
|
||||
desktopAuth: {
|
||||
...getKiroRuntime(buildAuthorizedKiroState()).desktopAuth,
|
||||
clientSecret: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
@@ -101,9 +121,13 @@ test('Kiro Builder ID artifact builder rejects missing required fields', () => {
|
||||
);
|
||||
assert.throws(
|
||||
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
|
||||
kiroRuntime: {
|
||||
...buildAuthorizedKiroState().kiroRuntime,
|
||||
register: { email: '' },
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
...getKiroRuntime(buildAuthorizedKiroState()),
|
||||
register: { email: '' },
|
||||
},
|
||||
},
|
||||
},
|
||||
email: '',
|
||||
accountIdentifier: '',
|
||||
@@ -167,11 +191,15 @@ test('Kiro contribution adapter skips invalid artifacts without sending secrets'
|
||||
});
|
||||
|
||||
const result = await adapter.maybeSubmitFlowContribution(buildAuthorizedKiroState({
|
||||
kiroRuntime: {
|
||||
...buildAuthorizedKiroState().kiroRuntime,
|
||||
desktopAuth: {
|
||||
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
|
||||
refreshToken: '',
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
kiro: {
|
||||
...getKiroRuntime(buildAuthorizedKiroState()),
|
||||
desktopAuth: {
|
||||
...getKiroRuntime(buildAuthorizedKiroState()).desktopAuth,
|
||||
refreshToken: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
@@ -211,8 +239,8 @@ test('Kiro contribution adapter redacts server errors that echo submitted secret
|
||||
});
|
||||
|
||||
test('Kiro public contribution is triggered from step 9 instead of step 8', () => {
|
||||
const desktopSource = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
|
||||
const publisherSource = fs.readFileSync('background/kiro/publisher-kiro-rs.js', 'utf8');
|
||||
const desktopSource = fs.readFileSync('flows/kiro/background/desktop-authorize-runner.js', 'utf8');
|
||||
const publisherSource = fs.readFileSync('flows/kiro/background/publisher-kiro-rs.js', 'utf8');
|
||||
assert.doesNotMatch(desktopSource, /trigger:\s*'kiro-step-8'/);
|
||||
assert.match(publisherSource, /maybeSubmitFlowContribution/);
|
||||
assert.match(publisherSource, /trigger:\s*'kiro-step-9'/);
|
||||
|
||||
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
|
||||
const source = fs.readFileSync('content/kiro/register-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/kiro/content/register-page.js', 'utf8');
|
||||
|
||||
function createTextNode(textContent = '') {
|
||||
return { textContent };
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const accountRunHistorySource = fs.readFileSync('background/account-run-history.js', 'utf8');
|
||||
const accountRecordsImporterSource = fs.readFileSync('imports/legacy/account-records-importer.js', 'utf8');
|
||||
|
||||
function loadImporterApi() {
|
||||
const scope = {};
|
||||
return new Function('self', `
|
||||
${accountRunHistorySource}
|
||||
${accountRecordsImporterSource}
|
||||
return self.MultiPageLegacyAccountRecordsImporter;
|
||||
`)(scope);
|
||||
}
|
||||
|
||||
test('legacy account records importer normalizes old history records into canonical record shape', () => {
|
||||
const importer = loadImporterApi();
|
||||
const importerApi = importer.createAccountRecordsImporter({
|
||||
getNodeIdByStepForState: () => '',
|
||||
});
|
||||
|
||||
const records = importerApi.importAccountRecords([
|
||||
{
|
||||
email: 'LegacyUser@example.com',
|
||||
password: 'secret',
|
||||
status: 'success',
|
||||
recordedAt: '2026-04-17T00:12:00.000Z',
|
||||
plusModeEnabled: 1,
|
||||
},
|
||||
{
|
||||
phone: '+6612345',
|
||||
finalStatus: 'stopped',
|
||||
reason: '用户停止',
|
||||
failedStep: 7,
|
||||
flowId: 'kiro',
|
||||
},
|
||||
]);
|
||||
|
||||
assert.equal(records.length, 2);
|
||||
assert.deepEqual(records[0], {
|
||||
recordId: 'legacyuser@example.com',
|
||||
flowId: '',
|
||||
runId: '',
|
||||
accountIdentifierType: 'email',
|
||||
accountIdentifier: 'legacyuser@example.com',
|
||||
email: 'legacyuser@example.com',
|
||||
phoneNumber: '',
|
||||
password: 'secret',
|
||||
finalStatus: 'success',
|
||||
finishedAt: '2026-04-17T00:12:00.000Z',
|
||||
retryCount: 0,
|
||||
failureLabel: '流程完成',
|
||||
failureDetail: '',
|
||||
failedNodeId: '',
|
||||
failedStep: null,
|
||||
source: 'manual',
|
||||
autoRunContext: null,
|
||||
plusModeEnabled: true,
|
||||
accountContributionEnabled: false,
|
||||
});
|
||||
assert.equal(records[1].recordId, 'phone:+6612345');
|
||||
assert.equal(records[1].flowId, 'kiro');
|
||||
assert.equal(records[1].accountIdentifierType, 'phone');
|
||||
assert.equal(records[1].phoneNumber, '+6612345');
|
||||
assert.equal(records[1].finalStatus, 'stopped');
|
||||
assert.equal(records[1].failedStep, 7);
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
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 settingsImporterSource = fs.readFileSync('imports/legacy/settings-importer.js', 'utf8');
|
||||
|
||||
function loadImporterApi() {
|
||||
const scope = {};
|
||||
return new Function('self', `
|
||||
${flowRegistrySource}
|
||||
${settingsSchemaSource}
|
||||
${settingsImporterSource}
|
||||
return {
|
||||
flowRegistry: self.MultiPageFlowRegistry,
|
||||
settingsSchema: self.MultiPageSettingsSchema,
|
||||
importer: self.MultiPageLegacySettingsImporter,
|
||||
};
|
||||
`)(scope);
|
||||
}
|
||||
|
||||
test('legacy settings importer converts flat legacy keys into canonical settingsState', () => {
|
||||
const { flowRegistry, settingsSchema, importer } = loadImporterApi();
|
||||
const importerApi = importer.createSettingsImporter({
|
||||
flowRegistry,
|
||||
settingsSchemaApi: settingsSchema.createSettingsSchema({ flowRegistry }),
|
||||
});
|
||||
|
||||
const imported = importerApi.importSettings({
|
||||
activeFlowId: 'kiro',
|
||||
panelMode: 'sub2api',
|
||||
kiroTargetId: 'kiro-rs',
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'secret-key',
|
||||
mailProvider: 'hotmail',
|
||||
stepExecutionRangeByFlow: {
|
||||
kiro: { enabled: true, fromStep: 2, toStep: 9 },
|
||||
},
|
||||
kiroRuntime: {
|
||||
upload: {
|
||||
status: 'uploaded',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(imported.settingsSchemaVersion, 5);
|
||||
assert.equal(imported.settingsState.activeFlowId, 'kiro');
|
||||
assert.equal(imported.settingsState.flows.openai.selectedTargetId, 'sub2api');
|
||||
assert.equal(imported.settingsState.flows.kiro.selectedTargetId, 'kiro-rs');
|
||||
assert.equal(imported.settingsState.flows.kiro.targets['kiro-rs'].baseUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(imported.settingsState.flows.kiro.targets['kiro-rs'].apiKey, 'secret-key');
|
||||
assert.deepEqual(imported.settingsState.flows.kiro.autoRun.stepExecutionRange, {
|
||||
enabled: true,
|
||||
fromStep: 2,
|
||||
toStep: 9,
|
||||
});
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(imported, 'kiroRuntime'), false);
|
||||
assert.deepEqual(imported.legacyFieldHits.sort(), [
|
||||
'kiroRuntime',
|
||||
'kiroTargetId',
|
||||
'panelMode',
|
||||
'stepExecutionRangeByFlow',
|
||||
]);
|
||||
});
|
||||
|
||||
test('legacy settings importer preserves canonical settingsState without reintroducing old fields', () => {
|
||||
const { flowRegistry, settingsSchema, importer } = loadImporterApi();
|
||||
const schema = settingsSchema.createSettingsSchema({ flowRegistry });
|
||||
const importerApi = importer.createSettingsImporter({
|
||||
flowRegistry,
|
||||
settingsSchemaApi: schema,
|
||||
});
|
||||
const canonicalState = schema.normalizeSettingsState({
|
||||
settingsState: schema.buildDefaultSettingsState(),
|
||||
});
|
||||
|
||||
const imported = importerApi.importSettings({
|
||||
settingsSchemaVersion: 5,
|
||||
settingsState: canonicalState,
|
||||
});
|
||||
|
||||
assert.deepEqual(imported.settingsState, canonicalState);
|
||||
assert.deepEqual(imported.legacyFieldHits, []);
|
||||
});
|
||||
@@ -78,6 +78,6 @@ test('mail polling bundles do not load operation delay module', () => {
|
||||
});
|
||||
|
||||
test('WhatsApp code reader remains polling-only and delay-free', () => {
|
||||
const source = fs.readFileSync('content/whatsapp-flow.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/whatsapp-flow.js', 'utf8');
|
||||
assert.doesNotMatch(source, /performOperationWithDelay\(/);
|
||||
});
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
@@ -2,7 +2,7 @@ const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
|
||||
const helperSource = fs.readFileSync('background.js', 'utf8');
|
||||
const tabRuntimeSource = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const tabRuntimeSource = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
|
||||
function extractFunction(source, name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
@@ -145,14 +145,14 @@ return {
|
||||
|
||||
(async () => {
|
||||
assert.strictEqual(
|
||||
api.matchesSourceUrlFamily('signup-page', 'https://chatgpt.com/', 'https://chatgpt.com/'),
|
||||
api.matchesSourceUrlFamily('openai-auth', 'https://chatgpt.com/', 'https://chatgpt.com/'),
|
||||
true,
|
||||
'signup-page family should include chatgpt.com'
|
||||
'openai-auth family should include chatgpt.com'
|
||||
);
|
||||
assert.strictEqual(
|
||||
api.matchesSourceUrlFamily('signup-page', 'https://chat.openai.com/', 'https://auth.openai.com/authorize'),
|
||||
api.matchesSourceUrlFamily('openai-auth', 'https://chat.openai.com/', 'https://auth.openai.com/authorize'),
|
||||
true,
|
||||
'signup-page family should include legacy chat.openai.com'
|
||||
'openai-auth family should include legacy chat.openai.com'
|
||||
);
|
||||
assert.strictEqual(
|
||||
api.matchesSourceUrlFamily('mail-163', 'https://mail.126.com/js6/main.jsp', 'https://mail.163.com/js6/main.jsp'),
|
||||
@@ -169,15 +169,15 @@ return {
|
||||
],
|
||||
state: {
|
||||
sourceLastUrls: {
|
||||
'signup-page': 'https://chatgpt.com/',
|
||||
'openai-auth': 'https://chatgpt.com/',
|
||||
},
|
||||
tabRegistry: {
|
||||
'signup-page': { tabId: 3, ready: true },
|
||||
'openai-auth': { tabId: 3, ready: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await api.closeConflictingTabsForSource('signup-page', 'https://auth.openai.com/authorize', {
|
||||
await api.closeConflictingTabsForSource('openai-auth', 'https://auth.openai.com/authorize', {
|
||||
excludeTabIds: [3],
|
||||
});
|
||||
|
||||
@@ -195,19 +195,19 @@ return {
|
||||
],
|
||||
state: {
|
||||
sourceLastUrls: {
|
||||
'signup-page': 'https://auth.openai.com/authorize?client_id=test',
|
||||
'openai-auth': 'https://auth.openai.com/authorize?client_id=test',
|
||||
},
|
||||
tabRegistry: {
|
||||
'signup-page': { tabId: 11, ready: true },
|
||||
'openai-auth': { tabId: 11, ready: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await api.closeConflictingTabsForSource('signup-page', 'https://chatgpt.com/');
|
||||
await api.closeConflictingTabsForSource('openai-auth', 'https://chatgpt.com/');
|
||||
|
||||
snapshot = api.snapshot();
|
||||
assert.deepStrictEqual(snapshot.removedBatches, [[11, 12]]);
|
||||
assert.strictEqual(snapshot.currentState.tabRegistry['signup-page'], null);
|
||||
assert.strictEqual(snapshot.currentState.tabRegistry['openai-auth'], null);
|
||||
|
||||
api.reset({
|
||||
tabs: [
|
||||
@@ -216,19 +216,19 @@ return {
|
||||
],
|
||||
state: {
|
||||
sourceLastUrls: {
|
||||
'signup-page': 'https://auth.openai.com/authorize?client_id=test',
|
||||
'openai-auth': 'https://auth.openai.com/authorize?client_id=test',
|
||||
},
|
||||
tabRegistry: {},
|
||||
},
|
||||
});
|
||||
|
||||
await api.closeConflictingTabsForSource('signup-page', 'https://chatgpt.com/');
|
||||
await api.closeConflictingTabsForSource('openai-auth', 'https://chatgpt.com/');
|
||||
|
||||
snapshot = api.snapshot();
|
||||
assert.deepStrictEqual(snapshot.removedBatches, [[21, 22]]);
|
||||
assert.deepStrictEqual(snapshot.currentTabs, []);
|
||||
|
||||
console.log('signup page tab cleanup tests passed');
|
||||
console.log('openai auth tab cleanup tests passed');
|
||||
})().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
@@ -10,10 +10,10 @@ function assertOrdered(list, before, after) {
|
||||
|
||||
test('manifest loads operation delay after utils only for covered auth/provider bundles', () => {
|
||||
const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));
|
||||
const authBundle = manifest.content_scripts.find((entry) => entry.js.includes('content/signup-page.js')).js;
|
||||
const authBundle = manifest.content_scripts.find((entry) => entry.js.includes('flows/openai/content/openai-auth.js')).js;
|
||||
assertOrdered(authBundle, 'content/utils.js', 'content/operation-delay.js');
|
||||
assertOrdered(authBundle, 'content/operation-delay.js', 'content/auth-page-recovery.js');
|
||||
assertOrdered(authBundle, 'content/operation-delay.js', 'content/signup-page.js');
|
||||
assertOrdered(authBundle, 'content/operation-delay.js', 'flows/openai/content/auth-page-recovery.js');
|
||||
assertOrdered(authBundle, 'content/operation-delay.js', 'flows/openai/content/openai-auth.js');
|
||||
|
||||
const duckBundle = manifest.content_scripts.find((entry) => entry.js.includes('content/duck-mail.js')).js;
|
||||
assertOrdered(duckBundle, 'content/utils.js', 'content/operation-delay.js');
|
||||
@@ -27,11 +27,11 @@ test('manifest loads operation delay after utils only for covered auth/provider
|
||||
|
||||
test('dynamic covered injections load operation delay after utils', () => {
|
||||
const expectations = [
|
||||
['background.js', 'SIGNUP_PAGE_INJECT_FILES'],
|
||||
['background/steps/create-plus-checkout.js', 'PLUS_CHECKOUT_INJECT_FILES'],
|
||||
['background/steps/fill-plus-checkout.js', 'PLUS_CHECKOUT_INJECT_FILES'],
|
||||
['background/steps/paypal-approve.js', 'PAYPAL_INJECT_FILES'],
|
||||
['background/steps/gopay-approve.js', 'GOPAY_INJECT_FILES'],
|
||||
['background.js', 'OPENAI_AUTH_INJECT_FILES'],
|
||||
['flows/openai/background/steps/create-plus-checkout.js', 'PLUS_CHECKOUT_INJECT_FILES'],
|
||||
['flows/openai/background/steps/fill-plus-checkout.js', 'PLUS_CHECKOUT_INJECT_FILES'],
|
||||
['flows/openai/background/steps/paypal-approve.js', 'PAYPAL_INJECT_FILES'],
|
||||
['flows/openai/background/steps/gopay-approve.js', 'GOPAY_INJECT_FILES'],
|
||||
['background/mail-2925-session.js', 'MAIL2925_INJECT'],
|
||||
];
|
||||
for (const [file, constantName] of expectations) {
|
||||
@@ -40,8 +40,8 @@ test('dynamic covered injections load operation delay after utils', () => {
|
||||
assert.ok(match, `missing ${constantName} in ${file}`);
|
||||
const block = match[1];
|
||||
assert.match(block, /'content\/utils\.js'[\s\S]*'content\/operation-delay\.js'/, `${file} must inject operation delay after utils`);
|
||||
if (constantName === 'SIGNUP_PAGE_INJECT_FILES') {
|
||||
assert.match(block, /'content\/operation-delay\.js'[\s\S]*'content\/auth-page-recovery\.js'/, 'auth recovery must load after operation delay');
|
||||
if (constantName === 'OPENAI_AUTH_INJECT_FILES') {
|
||||
assert.match(block, /'content\/operation-delay\.js'[\s\S]*'flows\/openai\/content\/auth-page-recovery\.js'/, 'auth recovery must load after operation delay');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -56,7 +56,7 @@ test('2925 provider reuse path also injects operation delay', () => {
|
||||
});
|
||||
|
||||
test('excluded platform verification paths do not load operation delay', () => {
|
||||
for (const file of ['background/steps/platform-verify.js', 'background/panel-bridge.js']) {
|
||||
for (const file of ['flows/openai/background/steps/platform-verify.js', 'background/panel-bridge.js']) {
|
||||
const source = fs.readFileSync(file, 'utf8');
|
||||
assert.doesNotMatch(source, /content\/operation-delay\.js/);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
|
||||
const source = fs.readFileSync('background/steps/paypal-approve.js', 'utf8');
|
||||
const paypalContentSource = fs.readFileSync('content/paypal-flow.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/paypal-approve.js', 'utf8');
|
||||
const paypalContentSource = fs.readFileSync('flows/openai/content/paypal-flow.js', 'utf8');
|
||||
|
||||
function loadModule() {
|
||||
const self = {};
|
||||
|
||||
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
|
||||
const source = fs.readFileSync('content/paypal-flow.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/paypal-flow.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/phone-auth.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/phone-auth.js', 'utf8');
|
||||
const globalScope = { navigator: { language: 'zh-CN' } };
|
||||
const api = new Function('self', `${source}; return self.MultiPagePhoneAuth;`)(globalScope);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
function loadPhoneCountryUtils() {
|
||||
const source = fs.readFileSync('content/phone-country-utils.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/phone-country-utils.js', 'utf8');
|
||||
const root = {};
|
||||
return new Function('self', `${source}; return self.MultiPagePhoneCountryUtils;`)(root);
|
||||
}
|
||||
@@ -43,21 +43,21 @@ test('phone country utils is loaded before phone auth content scripts', () => {
|
||||
));
|
||||
|
||||
assert.ok(authScript, 'missing auth content script');
|
||||
assert.ok(authScript.js.includes('content/phone-country-utils.js'));
|
||||
assert.ok(authScript.js.includes('flows/openai/content/phone-country-utils.js'));
|
||||
assert.ok(
|
||||
authScript.js.indexOf('content/phone-country-utils.js') < authScript.js.indexOf('content/phone-auth.js'),
|
||||
authScript.js.indexOf('flows/openai/content/phone-country-utils.js') < authScript.js.indexOf('flows/openai/content/phone-auth.js'),
|
||||
'phone-country-utils.js must load before phone-auth.js'
|
||||
);
|
||||
assert.ok(
|
||||
authScript.js.indexOf('content/phone-country-utils.js') < authScript.js.indexOf('content/signup-page.js'),
|
||||
'phone-country-utils.js must load before signup-page.js'
|
||||
authScript.js.indexOf('flows/openai/content/phone-country-utils.js') < authScript.js.indexOf('flows/openai/content/openai-auth.js'),
|
||||
'phone-country-utils.js must load before openai-auth.js'
|
||||
);
|
||||
|
||||
const background = fs.readFileSync('background.js', 'utf8');
|
||||
const injectLine = background.match(/const\s+SIGNUP_PAGE_INJECT_FILES\s*=\s*\[[^\n]+\]/)?.[0] || '';
|
||||
assert.ok(injectLine.includes("'content/phone-country-utils.js'"));
|
||||
const injectLine = background.match(/const\s+OPENAI_AUTH_INJECT_FILES\s*=\s*\[[^\n]+\]/)?.[0] || '';
|
||||
assert.ok(injectLine.includes("'flows/openai/content/phone-country-utils.js'"));
|
||||
assert.ok(
|
||||
injectLine.indexOf("'content/phone-country-utils.js'") < injectLine.indexOf("'content/phone-auth.js'"),
|
||||
injectLine.indexOf("'flows/openai/content/phone-country-utils.js'") < injectLine.indexOf("'flows/openai/content/phone-auth.js'"),
|
||||
'dynamic signup injection must load phone-country-utils.js before phone-auth.js'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
|
||||
|
||||
function createExecutor(overrides = {}) {
|
||||
|
||||
@@ -82,7 +82,7 @@ function isGpcAutoModePermissionDenied() {
|
||||
return false;
|
||||
}
|
||||
function getSelectedPanelMode() {
|
||||
return latestState?.panelMode || 'cpa';
|
||||
return latestState?.targetId || 'cpa';
|
||||
}
|
||||
function resolveCurrentSidepanelCapabilities() {
|
||||
return ${capabilityStateSource};
|
||||
@@ -141,7 +141,7 @@ test('sidepanel keeps requested Plus account strategy while contribution mode fo
|
||||
}`,
|
||||
`{
|
||||
activeFlowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
plusPaymentMethod: 'paypal',
|
||||
accountContributionEnabled: true,
|
||||
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||
@@ -168,7 +168,7 @@ test('sidepanel maps generic session import to SUB2API when the current source i
|
||||
}`,
|
||||
`{
|
||||
activeFlowId: 'openai',
|
||||
panelMode: 'sub2api',
|
||||
targetId: 'sub2api',
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
}`
|
||||
@@ -190,12 +190,12 @@ test('sidepanel maps generic session import to CPA when the current source is CP
|
||||
runtimeLocks: { plusModeEnabled: true },
|
||||
canEditPlusAccountAccessStrategy: true,
|
||||
availablePlusAccountAccessStrategies: ['oauth', 'cpa_codex_session'],
|
||||
effectivePanelMode: 'cpa',
|
||||
effectiveTargetId: 'cpa',
|
||||
effectivePlusAccountAccessStrategy: 'cpa_codex_session',
|
||||
}`,
|
||||
`{
|
||||
activeFlowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||
}`
|
||||
@@ -217,12 +217,12 @@ test('sidepanel falls back to OAuth when the current source cannot import sessio
|
||||
runtimeLocks: { plusModeEnabled: true },
|
||||
canEditPlusAccountAccessStrategy: false,
|
||||
availablePlusAccountAccessStrategies: ['oauth'],
|
||||
effectivePanelMode: 'codex2api',
|
||||
effectiveTargetId: 'codex2api',
|
||||
effectivePlusAccountAccessStrategy: 'oauth',
|
||||
}`,
|
||||
`{
|
||||
activeFlowId: 'openai',
|
||||
panelMode: 'codex2api',
|
||||
targetId: 'codex2api',
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||
}`
|
||||
|
||||
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
|
||||
const source = fs.readFileSync('content/plus-checkout.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/plus-checkout.js', 'utf8');
|
||||
|
||||
test('plus checkout content script can be injected repeatedly on the same page', () => {
|
||||
const attrs = new Map();
|
||||
|
||||
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
function loadPlusCheckoutBillingModule() {
|
||||
const source = fs.readFileSync('background/steps/fill-plus-checkout.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/fill-plus-checkout.js', 'utf8');
|
||||
const globalScope = {};
|
||||
return new Function('self', `${source}; return self.MultiPageBackgroundPlusCheckoutBilling;`)(globalScope);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
|
||||
const source = fs.readFileSync('background/steps/create-plus-checkout.js', 'utf8');
|
||||
const plusCheckoutSource = fs.readFileSync('content/plus-checkout.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/create-plus-checkout.js', 'utf8');
|
||||
const plusCheckoutSource = fs.readFileSync('flows/openai/content/plus-checkout.js', 'utf8');
|
||||
const gopayUtilsSource = fs.readFileSync('gopay-utils.js', 'utf8');
|
||||
const globalScope = {};
|
||||
new Function('self', `${gopayUtilsSource};`)(globalScope);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background/steps/plus-return-confirm.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/background/steps/plus-return-confirm.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundPlusReturnConfirm;`)(globalScope);
|
||||
|
||||
|
||||
@@ -67,8 +67,7 @@ const latestState = {
|
||||
accountContributionEnabled: false,
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
kiroTargetId: 'kiro-rs',
|
||||
targetId: 'cpa',
|
||||
};
|
||||
const inputAutoSkipFailures = { checked: false };
|
||||
const inputContributionNickname = { value: 'tester' };
|
||||
@@ -79,7 +78,7 @@ const inputAutoDelayMinutes = { value: '30' };
|
||||
const btnAutoRun = { disabled: false, innerHTML: '' };
|
||||
const inputRunCount = { disabled: false };
|
||||
const selectFlow = { value: latestState.activeFlowId };
|
||||
const selectPanelMode = { value: latestState.panelMode };
|
||||
const selectPanelMode = { value: latestState.targetId };
|
||||
let runCountValue = ${Math.max(1, Number(runCount) || 1)};
|
||||
let pendingAutoRunStartTotalRuns = 0;
|
||||
let pendingAutoRunStartExpiresAt = 0;
|
||||
@@ -121,8 +120,8 @@ function getSelectedFlowId(state = latestState) {
|
||||
function getSelectedTargetId(flowId = getSelectedFlowId()) {
|
||||
return String(
|
||||
flowId === 'kiro'
|
||||
? (selectPanelMode.value || latestState.kiroTargetId || 'kiro-rs')
|
||||
: normalizePanelMode(selectPanelMode.value || latestState.panelMode || 'cpa')
|
||||
? (selectPanelMode.value || latestState.targetId || 'kiro-rs')
|
||||
: normalizePanelMode(selectPanelMode.value || latestState.targetId || 'cpa')
|
||||
).trim().toLowerCase() || (flowId === 'kiro' ? 'kiro-rs' : 'cpa');
|
||||
}
|
||||
function shouldOfferAutoModeChoice() { return false; }
|
||||
@@ -239,7 +238,7 @@ test('startAutoRunFromCurrentSettings sends current flow selection with auto run
|
||||
selectPanelMode.value = 'kiro-rs';
|
||||
latestState.activeFlowId = 'openai';
|
||||
latestState.flowId = 'openai';
|
||||
latestState.kiroTargetId = 'kiro-rs';
|
||||
latestState.targetId = 'kiro-rs';
|
||||
events.push({ type: 'flow-switch-race' });
|
||||
}`,
|
||||
});
|
||||
@@ -264,7 +263,7 @@ test('startAutoRunFromCurrentSettings blocks when shared flow capability validat
|
||||
const events = [];
|
||||
const latestState = {
|
||||
activeFlowId: 'site-a',
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
signupMethod: 'phone',
|
||||
accountContributionEnabled: false,
|
||||
phoneVerificationEnabled: true,
|
||||
|
||||
@@ -2,9 +2,10 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
const { readFlowRegistryBundle } = require('./helpers/script-bundles.js');
|
||||
|
||||
const source = fs.readFileSync('sidepanel/contribution-mode.js', 'utf8');
|
||||
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||
const flowRegistrySource = readFlowRegistryBundle();
|
||||
const contributionRegistrySource = fs.readFileSync('shared/contribution-registry.js', 'utf8');
|
||||
|
||||
function createElement() {
|
||||
|
||||
@@ -339,7 +339,8 @@ return {
|
||||
|
||||
api.setLatestState({ accountContributionEnabled: false });
|
||||
const normalPayload = api.collectSettingsPayload();
|
||||
assert.equal(normalPayload.panelMode, 'cpa');
|
||||
assert.equal('panelMode' in normalPayload, false);
|
||||
assert.equal(normalPayload.targetId, 'cpa');
|
||||
assert.equal(normalPayload.customPassword, 'Secret123!');
|
||||
assert.equal(normalPayload.accountRunHistoryTextEnabled, true);
|
||||
assert.equal(normalPayload.accountRunHistoryHelperBaseUrl, 'http://127.0.0.1:17373');
|
||||
|
||||
@@ -170,9 +170,11 @@ return {
|
||||
activeFlowId: 'kiro',
|
||||
flowId: 'kiro',
|
||||
nodeStatuses: { 'open-chatgpt': 'completed' },
|
||||
targetId: 'kiro-rs',
|
||||
});
|
||||
assert.equal(api.getCalls()[0].activeFlowId, 'kiro');
|
||||
assert.equal(api.getCalls()[0].flowId, 'kiro');
|
||||
assert.equal(api.getCalls()[0].targetId, 'kiro-rs');
|
||||
});
|
||||
|
||||
test('updatePanelModeUI reapplies dynamic Plus and phone visibility after flow group visibility', () => {
|
||||
@@ -185,7 +187,7 @@ const calls = [];
|
||||
let latestState = {
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
};
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const selectFlow = { value: '' };
|
||||
@@ -220,8 +222,6 @@ function updatePhoneVerificationSettingsUI() {
|
||||
function resolveCurrentSidepanelCapabilities() {
|
||||
return {
|
||||
visibleGroupIds: ['service-account', 'openai-plus', 'openai-phone'],
|
||||
effectivePanelMode: 'cpa',
|
||||
panelMode: 'cpa',
|
||||
effectiveTargetId: 'cpa',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -254,11 +254,11 @@ test('sidepanel source wires runtime signup phone field to background sync messa
|
||||
test('sidepanel warns once before using phone signup with CPA source', async () => {
|
||||
assert.match(
|
||||
sidepanelSource,
|
||||
/signupMethodButtons\.forEach\(\(button\) => \{[\s\S]*await confirmCpaPhoneSignupIfNeeded\(\{[\s\S]*signupMethod: nextSignupMethod,[\s\S]*panelMode: getSelectedPanelMode\(\),/
|
||||
/signupMethodButtons\.forEach\(\(button\) => \{[\s\S]*await confirmCpaPhoneSignupIfNeeded\(\{[\s\S]*signupMethod: nextSignupMethod,[\s\S]*targetId: getSelectedPanelMode\(latestState\),/
|
||||
);
|
||||
assert.match(
|
||||
sidepanelSource,
|
||||
/selectPanelMode\.addEventListener\('change', async \(\) => \{[\s\S]*await confirmCpaPhoneSignupIfNeeded\(\{[\s\S]*signupMethod: getSelectedSignupMethod\(\),[\s\S]*panelMode: nextPanelMode,/
|
||||
/selectPanelMode\.addEventListener\('change', async \(\) => \{[\s\S]*await confirmCpaPhoneSignupIfNeeded\(\{[\s\S]*signupMethod: getSelectedSignupMethod\(\),[\s\S]*targetId: nextPanelMode,/
|
||||
);
|
||||
|
||||
const bundle = [
|
||||
@@ -277,6 +277,7 @@ test('sidepanel warns once before using phone signup with CPA source', async ()
|
||||
const SIGNUP_METHOD_PHONE = 'phone';
|
||||
const SIGNUP_METHOD_EMAIL = 'email';
|
||||
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const CPA_PHONE_SIGNUP_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-cpa-phone-signup-prompt-dismissed';
|
||||
const CPA_PHONE_SIGNUP_WARNING_MESSAGE = '请确保打开手机接码设置中的“绑定后重登”开关,不然可能无法使用(有些版本无需开启)';
|
||||
const storage = new Map();
|
||||
@@ -291,6 +292,11 @@ const localStorage = {
|
||||
storage.delete(key);
|
||||
},
|
||||
};
|
||||
let latestState = {
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
targetId: 'cpa',
|
||||
};
|
||||
let selectedSignupMethod = 'phone';
|
||||
let selectedPanelMode = 'cpa';
|
||||
let capturedOptions = null;
|
||||
@@ -301,6 +307,12 @@ function getSelectedSignupMethod() {
|
||||
function getSelectedPanelMode() {
|
||||
return selectedPanelMode;
|
||||
}
|
||||
function getSelectedTargetId() {
|
||||
return selectedPanelMode;
|
||||
}
|
||||
function getSelectedTargetIdForState() {
|
||||
return selectedPanelMode;
|
||||
}
|
||||
async function openConfirmModalWithOption(options) {
|
||||
capturedOptions = options;
|
||||
return modalResult;
|
||||
@@ -326,7 +338,7 @@ return {
|
||||
assert.equal(api.shouldWarnCpaPhoneSignup('phone', 'sub2api'), false);
|
||||
assert.equal(api.shouldWarnCpaPhoneSignup('phone', 'codex2api'), false);
|
||||
|
||||
const firstResult = await api.confirmCpaPhoneSignupIfNeeded({ signupMethod: 'phone', panelMode: 'cpa' });
|
||||
const firstResult = await api.confirmCpaPhoneSignupIfNeeded({ signupMethod: 'phone', targetId: 'cpa' });
|
||||
assert.equal(firstResult, true);
|
||||
assert.equal(api.getCapturedOptions().title, 'CPA 手机号注册提醒');
|
||||
assert.equal(api.getCapturedOptions().message, '请确保打开手机接码设置中的“绑定后重登”开关,不然可能无法使用(有些版本无需开启)');
|
||||
@@ -335,7 +347,7 @@ return {
|
||||
assert.equal(api.getDismissed(), null);
|
||||
|
||||
api.setModalResult({ confirmed: false, optionChecked: true });
|
||||
const secondResult = await api.confirmCpaPhoneSignupIfNeeded({ signupMethod: 'phone', panelMode: 'cpa' });
|
||||
const secondResult = await api.confirmCpaPhoneSignupIfNeeded({ signupMethod: 'phone', targetId: 'cpa' });
|
||||
assert.equal(secondResult, false);
|
||||
assert.equal(api.getDismissed(), '1');
|
||||
assert.equal(api.shouldWarnCpaPhoneSignup('phone', 'cpa'), false);
|
||||
@@ -354,11 +366,11 @@ const window = {
|
||||
MultiPageFlowCapabilities: {
|
||||
createFlowCapabilityRegistry() {
|
||||
return {
|
||||
resolveSidepanelCapabilities({ state = {}, panelMode = 'cpa', signupMethod = 'email' } = {}) {
|
||||
resolveSidepanelCapabilities({ state = {}, targetId = 'cpa', signupMethod = 'email' } = {}) {
|
||||
const phoneAllowed = String(state?.activeFlowId || '').trim().toLowerCase() === 'openai';
|
||||
return {
|
||||
canSelectPhoneSignup: phoneAllowed,
|
||||
shouldWarnCpaPhoneSignup: phoneAllowed && signupMethod === 'phone' && panelMode === 'cpa',
|
||||
shouldWarnCpaPhoneSignup: phoneAllowed && signupMethod === 'phone' && targetId === 'cpa',
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -368,11 +380,14 @@ const window = {
|
||||
let latestState = {
|
||||
activeFlowId: 'site-a',
|
||||
accountContributionEnabled: false,
|
||||
panelMode: 'cpa',
|
||||
targetId: 'cpa',
|
||||
};
|
||||
const inputPhoneVerificationEnabled = { checked: true };
|
||||
const inputPlusModeEnabled = { checked: false };
|
||||
function getSelectedFlowId() { return latestState.activeFlowId; }
|
||||
function getSelectedPanelMode() { return 'cpa'; }
|
||||
function getSelectedTargetId() { return 'cpa'; }
|
||||
function getSelectedTargetIdForState() { return 'cpa'; }
|
||||
function getSelectedSignupMethod() { return 'phone'; }
|
||||
function isCpaPhoneSignupPromptDismissed() { return false; }
|
||||
${bundle}
|
||||
@@ -1059,6 +1074,7 @@ function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
|
||||
function normalizeAutoStepDelaySeconds(value) { return value === '' ? null : Number(value); }
|
||||
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
|
||||
function normalizePlusAccountAccessStrategy(value = '') { return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; }
|
||||
function resolvePlusAccountAccessStrategyForTarget(value = '') { return normalizePlusAccountAccessStrategy(value); }
|
||||
${extractFunction('normalizePhoneSmsProvider')}
|
||||
${extractFunction('normalizePhoneSmsProviderValue')}
|
||||
${extractFunction('normalizeFiveSimCountryCode')}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const { readFlowRegistryBundle } = require('./helpers/script-bundles.js');
|
||||
|
||||
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||
const source = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
|
||||
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||
const flowRegistrySource = readFlowRegistryBundle();
|
||||
|
||||
test('sidepanel exposes SUB2API account priority below group setting', () => {
|
||||
assert.match(html, /id="row-sub2api-account-priority"/);
|
||||
@@ -25,6 +26,10 @@ test('sidepanel exposes SUB2API account priority below group setting', () => {
|
||||
});
|
||||
|
||||
test('sidepanel persists and locks SUB2API account priority setting', () => {
|
||||
const flowRegistryApi = new Function(
|
||||
'self',
|
||||
`${flowRegistrySource}; return self.MultiPageFlowRegistry;`
|
||||
)({});
|
||||
assert.match(
|
||||
source,
|
||||
/const rowSub2ApiAccountPriority = document\.getElementById\('row-sub2api-account-priority'\);/
|
||||
@@ -41,7 +46,10 @@ test('sidepanel persists and locks SUB2API account priority setting', () => {
|
||||
/inputSub2ApiAccountPriority\.value = String\(normalizeSub2ApiAccountPriorityValue\(state\?\.sub2apiAccountPriority\)\);/
|
||||
);
|
||||
assert.match(source, /applyFlowSettingsGroupVisibility\(visibleGroupIds\);/);
|
||||
assert.match(flowRegistrySource, /'openai-target-sub2api': \{[\s\S]*'row-sub2api-account-priority'/);
|
||||
assert.deepStrictEqual(
|
||||
flowRegistryApi.getSettingsGroupDefinition('openai-target-sub2api')?.rowIds?.includes('row-sub2api-account-priority'),
|
||||
true
|
||||
);
|
||||
assert.match(source, /inputSub2ApiAccountPriority\.disabled = locked;/);
|
||||
assert.match(
|
||||
source,
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const { readSourceRegistryBundle } = require('./helpers/script-bundles.js');
|
||||
|
||||
function loadSourceRegistry() {
|
||||
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
|
||||
const sourceRegistrySource = fs.readFileSync('shared/source-registry.js', 'utf8');
|
||||
const globalScope = {};
|
||||
new Function('self', `${flowRegistrySource}; ${sourceRegistrySource}; return self;`)(globalScope);
|
||||
new Function('self', `${readSourceRegistryBundle()}; return self;`)(globalScope);
|
||||
return globalScope.MultiPageSourceRegistry.createSourceRegistry();
|
||||
}
|
||||
|
||||
test('background imports shared source registry module', () => {
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
assert.match(source, /shared\/flow-registry\.js/);
|
||||
assert.match(source, /shared\/settings-schema\.js/);
|
||||
assert.match(source, /shared\/source-registry\.js/);
|
||||
assert.match(source, /core\/flow-kernel\/flow-registry\.js/);
|
||||
assert.match(source, /core\/flow-kernel\/settings-schema\.js/);
|
||||
assert.match(source, /core\/flow-kernel\/source-registry\.js/);
|
||||
assert.match(source, /shared\/kiro-timeouts\.js/);
|
||||
});
|
||||
|
||||
@@ -23,10 +22,10 @@ test('manifest loads shared source registry before content utils in static bundl
|
||||
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.includes('core/flow-kernel/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'
|
||||
scripts.indexOf('core/flow-kernel/source-registry.js') < scripts.indexOf('content/utils.js'),
|
||||
'core/flow-kernel/source-registry.js must load before content/utils.js'
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -35,8 +34,8 @@ test('manifest no longer ships a static Kiro content bundle', () => {
|
||||
const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));
|
||||
const hasStaticKiroBundle = (manifest.content_scripts || []).some((entry) => {
|
||||
const scripts = Array.isArray(entry.js) ? entry.js : [];
|
||||
return scripts.includes('content/kiro/register-page.js')
|
||||
|| scripts.includes('content/kiro/desktop-authorize-page.js');
|
||||
return scripts.includes('flows/kiro/content/register-page.js')
|
||||
|| scripts.includes('flows/kiro/content/desktop-authorize-page.js');
|
||||
});
|
||||
|
||||
assert.equal(hasStaticKiroBundle, false);
|
||||
@@ -46,19 +45,20 @@ test('background injects shared Kiro timeout module before Kiro content scripts'
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
assert.match(
|
||||
source,
|
||||
/const KIRO_REGISTER_INJECT_FILES = \['shared\/source-registry\.js', 'shared\/kiro-timeouts\.js', 'content\/utils\.js', 'content\/kiro\/register-page\.js'\];/
|
||||
/const KIRO_REGISTER_INJECT_FILES = \['flows\/openai\/index\.js', 'flows\/kiro\/index\.js', 'flows\/index\.js', 'core\/flow-kernel\/flow-registry\.js', 'core\/flow-kernel\/source-registry\.js', 'shared\/kiro-timeouts\.js', 'content\/utils\.js', 'flows\/kiro\/content\/register-page\.js'\];/
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/const KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = \['shared\/source-registry\.js', 'shared\/kiro-timeouts\.js', 'content\/utils\.js', 'content\/kiro\/desktop-authorize-page\.js'\];/
|
||||
/const KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = \['flows\/openai\/index\.js', 'flows\/kiro\/index\.js', 'flows\/index\.js', 'core\/flow-kernel\/flow-registry\.js', 'core\/flow-kernel\/source-registry\.js', 'shared\/kiro-timeouts\.js', 'content\/utils\.js', 'flows\/kiro\/content\/desktop-authorize-page\.js'\];/
|
||||
);
|
||||
});
|
||||
|
||||
test('shared source registry exposes canonical Kiro sources and drivers', () => {
|
||||
const registry = loadSourceRegistry();
|
||||
|
||||
assert.equal(registry.resolveCanonicalSource('signup-page'), 'openai-auth');
|
||||
assert.deepEqual(registry.getSourceKeys('signup-page'), ['openai-auth', 'signup-page']);
|
||||
assert.equal(registry.resolveCanonicalSource('openai-auth'), 'openai-auth');
|
||||
assert.deepEqual(registry.getSourceKeys('openai-auth'), ['openai-auth']);
|
||||
assert.equal(registry.resolveCanonicalSource('signup-page'), 'signup-page');
|
||||
assert.equal(registry.getSourceLabel('openai-auth'), '认证页');
|
||||
|
||||
assert.equal(
|
||||
@@ -132,9 +132,9 @@ test('shared source registry exposes canonical Kiro sources and drivers', () =>
|
||||
assert.equal(registry.driverAcceptsCommand('openai-auth', 'submit-signup-email'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('content/platform-panel', 'platform-verify'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('openai-auth', 'platform-verify'), false);
|
||||
assert.equal(registry.driverAcceptsCommand('content/kiro/register-page', 'kiro-submit-password'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('content/kiro/desktop-authorize-page', 'kiro-complete-desktop-authorize'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('background/kiro-register', 'kiro-open-register-page'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('background/kiro-desktop-authorize', 'kiro-start-desktop-authorize'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('background/kiro-publisher-kiro-rs', 'kiro-upload-credential'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('flows/kiro/content/register-page', 'kiro-submit-password'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('flows/kiro/content/desktop-authorize-page', 'kiro-complete-desktop-authorize'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('flows/kiro/background/register-runner', 'kiro-open-register-page'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('flows/kiro/background/desktop-authorize-runner', 'kiro-start-desktop-authorize'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('flows/kiro/background/publisher-kiro-rs', 'kiro-upload-credential'), true);
|
||||
});
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const { readStepDefinitionsBundle } = require('./helpers/script-bundles.js');
|
||||
|
||||
test('step definitions module exposes ordered normal and Plus step metadata', () => {
|
||||
const source = fs.readFileSync('data/step-definitions.js', 'utf8');
|
||||
const globalScope = {};
|
||||
|
||||
const api = new Function('self', `${source}; return self.MultiPageStepDefinitions;`)(globalScope);
|
||||
const api = new Function('self', `${readStepDefinitionsBundle()}; return self.MultiPageStepDefinitions;`)(globalScope);
|
||||
const steps = api.getSteps();
|
||||
const phoneSteps = api.getSteps({ signupMethod: 'phone' });
|
||||
const phoneReloginSteps = api.getSteps({
|
||||
@@ -180,7 +179,7 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
]
|
||||
);
|
||||
assert.equal(kiroSteps.every((step) => step.flowId === 'kiro'), true);
|
||||
assert.equal(kiroSteps[0].driverId, 'background/kiro-register');
|
||||
assert.equal(kiroSteps[0].driverId, 'flows/kiro/background/register-runner');
|
||||
assert.equal(kiroSteps[8].sourceId, 'kiro-rs-admin');
|
||||
assert.equal(kiroSteps[0].title, '打开注册页');
|
||||
assert.equal(kiroSteps[1].title, '获取邮箱并继续');
|
||||
@@ -286,9 +285,8 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
});
|
||||
|
||||
test('Plus session strategy swaps the OAuth tail for a single SUB2API import node', () => {
|
||||
const source = fs.readFileSync('data/step-definitions.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageStepDefinitions;`)(globalScope);
|
||||
const api = new Function('self', `${readStepDefinitionsBundle()}; return self.MultiPageStepDefinitions;`)(globalScope);
|
||||
const forbiddenTailKeys = [
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
@@ -360,9 +358,8 @@ test('Plus session strategy swaps the OAuth tail for a single SUB2API import nod
|
||||
});
|
||||
|
||||
test('Plus phone signup never switches to SUB2API session tail even if the requested strategy is session import', () => {
|
||||
const source = fs.readFileSync('data/step-definitions.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageStepDefinitions;`)(globalScope);
|
||||
const api = new Function('self', `${readStepDefinitionsBundle()}; return self.MultiPageStepDefinitions;`)(globalScope);
|
||||
const steps = api.getSteps({
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
@@ -377,9 +374,8 @@ test('Plus phone signup never switches to SUB2API session tail even if the reque
|
||||
});
|
||||
|
||||
test('Plus session strategy swaps the OAuth tail for a single CPA import node', () => {
|
||||
const source = fs.readFileSync('data/step-definitions.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageStepDefinitions;`)(globalScope);
|
||||
const api = new Function('self', `${readStepDefinitionsBundle()}; return self.MultiPageStepDefinitions;`)(globalScope);
|
||||
const forbiddenTailKeys = [
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,8 +2,8 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const phoneAuthSource = fs.readFileSync('content/phone-auth.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
const phoneAuthSource = fs.readFileSync('flows/openai/content/phone-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const backgroundSource = fs.readFileSync('background.js', 'utf8');
|
||||
const step8Source = fs.readFileSync('background/steps/fetch-login-code.js', 'utf8');
|
||||
const step8Source = fs.readFileSync('flows/openai/background/steps/fetch-login-code.js', 'utf8');
|
||||
const step8GlobalScope = {};
|
||||
const step8Api = new Function('self', `${step8Source}; return self.MultiPageBackgroundStep8;`)(step8GlobalScope);
|
||||
|
||||
|
||||
@@ -34,13 +34,13 @@ const bundle = [
|
||||
const api = new Function(`${bundle}; return { isRetryableContentScriptTransportError };`)();
|
||||
|
||||
assert.strictEqual(
|
||||
api.isRetryableContentScriptTransportError(new Error('Content script on signup-page did not respond in 2s. Try refreshing the tab and retry.')),
|
||||
api.isRetryableContentScriptTransportError(new Error('Content script on openai-auth did not respond in 2s. Try refreshing the tab and retry.')),
|
||||
true,
|
||||
'Step 8 状态探测短超时应被视为可重试错误'
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
api.isRetryableContentScriptTransportError(new Error('Content script on signup-page did not respond in 30s. Try refreshing the tab and retry.')),
|
||||
api.isRetryableContentScriptTransportError(new Error('Content script on openai-auth did not respond in 30s. Try refreshing the tab and retry.')),
|
||||
true,
|
||||
'普通内容脚本超时也应沿用可重试分支'
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const helperSource = fs.readFileSync('background.js', 'utf8');
|
||||
const step8ModuleSource = fs.readFileSync('background/steps/confirm-oauth.js', 'utf8');
|
||||
const step8ModuleSource = fs.readFileSync('flows/openai/background/steps/confirm-oauth.js', 'utf8');
|
||||
|
||||
function extractFunction(source, name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
@@ -178,11 +178,11 @@ async function isTabAlive() {
|
||||
}
|
||||
async function ensureStep8SignupPageReady() {}
|
||||
async function prepareStep8DebuggerClick() {
|
||||
sentMessages.push({ source: 'signup-page', type: 'STEP8_FIND_AND_CLICK' });
|
||||
sentMessages.push({ source: 'openai-auth', type: 'STEP8_FIND_AND_CLICK' });
|
||||
return { rect: { centerX: 10, centerY: 20 } };
|
||||
}
|
||||
async function triggerStep8ContentStrategy() {
|
||||
sentMessages.push({ source: 'signup-page', type: 'STEP8_TRIGGER_CONTINUE' });
|
||||
sentMessages.push({ source: 'openai-auth', type: 'STEP8_TRIGGER_CONTINUE' });
|
||||
}
|
||||
async function waitForStep8ClickEffect() {
|
||||
return { progressed: false, reason: 'no_effect' };
|
||||
|
||||
@@ -3,7 +3,7 @@ const fs = require('fs');
|
||||
|
||||
const helperSource = fs.readFileSync('background.js', 'utf8');
|
||||
const messageRouterSource = fs.readFileSync('background/message-router.js', 'utf8');
|
||||
const tabRuntimeSource = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||
const tabRuntimeSource = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
|
||||
|
||||
function extractFunction(source, name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
@@ -68,7 +68,7 @@ const MAIL_2925_MODE_RECEIVE = 'receive';
|
||||
const DEFAULT_MAIL_2925_MODE = MAIL_2925_MODE_PROVIDE;
|
||||
let currentState = {
|
||||
tabRegistry: {
|
||||
'signup-page': { tabId: 1, ready: true },
|
||||
'openai-auth': { tabId: 1, ready: true },
|
||||
'vps-panel': { tabId: 99, ready: true },
|
||||
},
|
||||
};
|
||||
@@ -205,7 +205,7 @@ return {
|
||||
{ id: 4, url: authCallbackUrl },
|
||||
],
|
||||
tabRegistry: {
|
||||
'signup-page': { tabId: 1, ready: true },
|
||||
'openai-auth': { tabId: 1, ready: true },
|
||||
'vps-panel': { tabId: 99, ready: true },
|
||||
},
|
||||
});
|
||||
@@ -213,7 +213,7 @@ return {
|
||||
await api.handleStepData(10, { localhostUrl: codexCallbackUrl });
|
||||
let snapshot = api.snapshot();
|
||||
assert.deepStrictEqual(snapshot.removedBatches, [[1], [2]]);
|
||||
assert.strictEqual(snapshot.currentState.tabRegistry['signup-page'], null);
|
||||
assert.strictEqual(snapshot.currentState.tabRegistry['openai-auth'], null);
|
||||
assert.deepStrictEqual(snapshot.currentState.tabRegistry['vps-panel'], { tabId: 99, ready: true });
|
||||
|
||||
api.reset({
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const step9ModuleSource = fs.readFileSync('background/steps/confirm-oauth.js', 'utf8');
|
||||
const step9ModuleSource = fs.readFileSync('flows/openai/background/steps/confirm-oauth.js', 'utf8');
|
||||
|
||||
test('step9 observes disabled oauth timeout while waiting for localhost callback', async () => {
|
||||
const api = new Function('step9ModuleSource', `
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/vps-panel.js', 'utf8');
|
||||
const source = fs.readFileSync('flows/openai/content/vps-panel.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
|
||||
const step9ModuleSource = fs.readFileSync('background/steps/confirm-oauth.js', 'utf8');
|
||||
const step9ModuleSource = fs.readFileSync('flows/openai/background/steps/confirm-oauth.js', 'utf8');
|
||||
|
||||
const api = new Function('step9ModuleSource', `
|
||||
const self = {};
|
||||
|
||||
@@ -131,7 +131,7 @@ function createSub2ApiPanelContext(fetchCalls = []) {
|
||||
};
|
||||
|
||||
vm.createContext(context);
|
||||
vm.runInContext(fs.readFileSync('content/sub2api-panel.js', 'utf8'), context);
|
||||
vm.runInContext(fs.readFileSync('flows/openai/content/sub2api-panel.js', 'utf8'), context);
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
@@ -1569,7 +1569,7 @@ test('verification flow notifies onResendRequestedAt when resend is triggered',
|
||||
);
|
||||
});
|
||||
|
||||
test('verification flow uses resilient signup-page transport when submitting verification code', async () => {
|
||||
test('verification flow uses resilient openai-auth transport when submitting verification code', async () => {
|
||||
const resilientCalls = [];
|
||||
|
||||
const helpers = api.createVerificationFlowHelpers({
|
||||
|
||||
Reference in New Issue
Block a user