拆分 OAuth 后置验证流程
This commit is contained in:
@@ -137,7 +137,7 @@ test('step 7 retries up to configured limit and then fails', async () => {
|
||||
assert.equal(events.completed, 0);
|
||||
});
|
||||
|
||||
test('step 7 exits internal retry loop immediately when add-phone is detected', 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 globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
@@ -145,7 +145,7 @@ test('step 7 exits internal retry loop immediately when add-phone is detected',
|
||||
const events = {
|
||||
refreshCalls: 0,
|
||||
sendCalls: 0,
|
||||
completed: 0,
|
||||
completions: [],
|
||||
logs: [],
|
||||
};
|
||||
|
||||
@@ -153,8 +153,8 @@ test('step 7 exits internal retry loop immediately when add-phone is detected',
|
||||
addLog: async (message, level = 'info') => {
|
||||
events.logs.push({ message, level });
|
||||
},
|
||||
completeNodeFromBackground: async () => {
|
||||
events.completed += 1;
|
||||
completeNodeFromBackground: async (step, payload) => {
|
||||
events.completions.push({ step, payload });
|
||||
},
|
||||
getErrorMessage: (error) => error?.message || String(error || ''),
|
||||
getLoginAuthStateLabel: (state) => state || 'unknown',
|
||||
@@ -174,21 +174,28 @@ test('step 7 exits internal retry loop immediately when add-phone is detected',
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executeStep7({ email: 'user@example.com', password: 'secret' }),
|
||||
/add-phone/
|
||||
);
|
||||
await executor.executeStep7({ email: 'user@example.com', password: 'secret' });
|
||||
|
||||
assert.equal(events.refreshCalls, 1, 'add-phone should stop further OAuth refresh attempts');
|
||||
assert.equal(events.sendCalls, 1, 'add-phone should stop after the first failed login attempt');
|
||||
assert.equal(events.completed, 0);
|
||||
assert.deepStrictEqual(events.completions, [
|
||||
{
|
||||
step: 'oauth-login',
|
||||
payload: {
|
||||
loginVerificationRequestedAt: null,
|
||||
skipLoginVerificationStep: true,
|
||||
addPhonePage: true,
|
||||
directOAuthConsentPage: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
assert.ok(
|
||||
!events.logs.some(({ message }) => /准备重试/.test(message)),
|
||||
'add-phone failure should not be logged as an internal retryable attempt'
|
||||
);
|
||||
});
|
||||
|
||||
test('step 7 hands direct add-phone to shared phone verification when enabled', async () => {
|
||||
test('step 7 no longer runs shared phone verification inside oauth-login', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
@@ -239,36 +246,21 @@ test('step 7 hands direct add-phone to shared phone verification when enabled',
|
||||
});
|
||||
|
||||
assert.equal(events.refreshCalls, 1);
|
||||
assert.deepStrictEqual(events.phoneCalls, [
|
||||
{
|
||||
tabId: 91,
|
||||
pageState: {
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
state: 'add_phone_page',
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
},
|
||||
options: {
|
||||
step: 7,
|
||||
visibleStep: 7,
|
||||
},
|
||||
},
|
||||
]);
|
||||
assert.deepStrictEqual(events.phoneCalls, []);
|
||||
assert.deepStrictEqual(events.completions, [
|
||||
{
|
||||
step: 'oauth-login',
|
||||
payload: {
|
||||
loginVerificationRequestedAt: null,
|
||||
skipLoginVerificationStep: true,
|
||||
directOAuthConsentPage: true,
|
||||
phoneVerification: true,
|
||||
loginPhoneVerification: true,
|
||||
addPhonePage: true,
|
||||
directOAuthConsentPage: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('step 7 direct add-phone stays fatal when phone verification is disabled', 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 globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
@@ -304,15 +296,12 @@ test('step 7 direct add-phone stays fatal when phone verification is disabled',
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executeStep7({ email: 'user@example.com', password: 'secret', phoneVerificationEnabled: false }),
|
||||
/手机号页面.*接码|phone verification/i
|
||||
);
|
||||
await executor.executeStep7({ email: 'user@example.com', password: 'secret', phoneVerificationEnabled: false });
|
||||
assert.equal(events.phoneCalls, 0);
|
||||
assert.equal(events.completions, 0);
|
||||
assert.equal(events.completions, 1);
|
||||
});
|
||||
|
||||
test('step 7 propagates fatal errors from shared add-phone verification', async () => {
|
||||
test('step 7 ignores obsolete shared add-phone verifier during handoff', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
@@ -348,12 +337,9 @@ test('step 7 propagates fatal errors from shared add-phone verification', async
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executeStep7({ email: 'user@example.com', password: 'secret', phoneVerificationEnabled: true }),
|
||||
/没有可用手机号/
|
||||
);
|
||||
assert.equal(events.phoneCalls, 1);
|
||||
assert.equal(events.completions, 0);
|
||||
await executor.executeStep7({ email: 'user@example.com', password: 'secret', phoneVerificationEnabled: true });
|
||||
assert.equal(events.phoneCalls, 0);
|
||||
assert.equal(events.completions, 1);
|
||||
});
|
||||
|
||||
test('step 7 starts a new oauth timeout window for each refreshed oauth url', async () => {
|
||||
@@ -382,6 +368,7 @@ test('step 7 starts a new oauth timeout window for each refreshed oauth url', as
|
||||
reuseOrCreateTab: async () => {},
|
||||
sendToContentScriptResilient: async (_source, _message, options) => ({
|
||||
step6Outcome: 'success',
|
||||
state: 'verification_page',
|
||||
usedTimeoutMs: options.timeoutMs,
|
||||
}),
|
||||
startOAuthFlowTimeoutWindow: async (payload) => {
|
||||
|
||||
@@ -96,7 +96,7 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
||||
assert.equal(calls.resolveOptions.completionStep, 8);
|
||||
});
|
||||
|
||||
test('step 8 keeps phone-registered accounts on email-code flow when page is email verification', async () => {
|
||||
test('step 8 rejects ordinary email verification page in phone login mode', async () => {
|
||||
const calls = {
|
||||
getMailConfigCalls: 0,
|
||||
helperCalls: [],
|
||||
@@ -159,18 +159,21 @@ test('step 8 keeps phone-registered accounts on email-code flow when page is ema
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executeStep8({
|
||||
visibleStep: 8,
|
||||
accountIdentifierType: 'phone',
|
||||
signupPhoneCompletedActivation: {
|
||||
activationId: 'signup-done',
|
||||
phoneNumber: '66959916439',
|
||||
},
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
await assert.rejects(
|
||||
() => executor.executeStep8({
|
||||
visibleStep: 8,
|
||||
accountIdentifierType: 'phone',
|
||||
signupPhoneCompletedActivation: {
|
||||
activationId: 'signup-done',
|
||||
phoneNumber: '66959916439',
|
||||
},
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
}),
|
||||
/手机号注册模式只允许处理手机登录验证码/
|
||||
);
|
||||
|
||||
assert.equal(calls.getMailConfigCalls, 1);
|
||||
assert.equal(calls.resolveCalls, 1);
|
||||
assert.equal(calls.getMailConfigCalls, 0);
|
||||
assert.equal(calls.resolveCalls, 0);
|
||||
assert.deepStrictEqual(calls.helperCalls, []);
|
||||
assert.deepStrictEqual(calls.completions, []);
|
||||
});
|
||||
@@ -260,9 +263,154 @@ test('step 8 routes only a real phone verification page through sms helper', asy
|
||||
]);
|
||||
});
|
||||
|
||||
test('step 8 submits add-email before polling the email verification code', async () => {
|
||||
test('post-login phone verification completes only on phone pages', async () => {
|
||||
const calls = {
|
||||
helperCalls: [],
|
||||
completions: [],
|
||||
};
|
||||
|
||||
const executor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async (step, payload) => {
|
||||
calls.completions.push({ step, payload });
|
||||
},
|
||||
ensureStep8VerificationPageReady: async () => {
|
||||
throw new Error('post-login phone step should inspect auth state directly');
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getState: async () => ({}),
|
||||
getTabId: async () => 1,
|
||||
phoneVerificationHelpers: {
|
||||
completePhoneVerificationFlow: async (tabId, pageState, options) => {
|
||||
calls.helperCalls.push({ tabId, pageState, options });
|
||||
return { code: '112233' };
|
||||
},
|
||||
},
|
||||
reuseOrCreateTab: async () => 1,
|
||||
sendToContentScriptResilient: async () => ({
|
||||
state: 'add_phone_page',
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
}),
|
||||
setState: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executePostLoginPhoneVerification({
|
||||
visibleStep: 9,
|
||||
nodeId: 'post-login-phone-verification',
|
||||
phoneVerificationEnabled: true,
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(calls.helperCalls, [
|
||||
{
|
||||
tabId: 1,
|
||||
pageState: {
|
||||
state: 'add_phone_page',
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
},
|
||||
options: {
|
||||
step: 9,
|
||||
visibleStep: 9,
|
||||
},
|
||||
},
|
||||
]);
|
||||
assert.deepStrictEqual(calls.completions, [
|
||||
{
|
||||
step: 'post-login-phone-verification',
|
||||
payload: {
|
||||
phoneVerification: true,
|
||||
postLoginPhoneVerification: true,
|
||||
code: '112233',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('post-login phone verification skips on OAuth consent and errors when disabled', async () => {
|
||||
const completions = [];
|
||||
const executor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async (step, payload) => {
|
||||
completions.push({ step, payload });
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getTabId: async () => 1,
|
||||
phoneVerificationHelpers: {
|
||||
completePhoneVerificationFlow: async () => {
|
||||
throw new Error('OAuth consent should not call phone helper');
|
||||
},
|
||||
},
|
||||
reuseOrCreateTab: async () => 1,
|
||||
sendToContentScriptResilient: async () => ({ state: 'oauth_consent_page' }),
|
||||
setState: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executePostLoginPhoneVerification({
|
||||
visibleStep: 9,
|
||||
nodeId: 'post-login-phone-verification',
|
||||
phoneVerificationEnabled: true,
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(completions, [
|
||||
{
|
||||
step: 'post-login-phone-verification',
|
||||
payload: {
|
||||
directOAuthConsentPage: true,
|
||||
phoneVerification: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const disabledExecutor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getTabId: async () => 1,
|
||||
phoneVerificationHelpers: {
|
||||
completePhoneVerificationFlow: async () => {
|
||||
throw new Error('disabled phone verification should not call helper');
|
||||
},
|
||||
},
|
||||
reuseOrCreateTab: async () => 1,
|
||||
sendToContentScriptResilient: async () => ({
|
||||
state: 'phone_verification_page',
|
||||
url: 'https://auth.openai.com/phone-verification',
|
||||
}),
|
||||
setState: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => disabledExecutor.executePostLoginPhoneVerification({
|
||||
visibleStep: 9,
|
||||
phoneVerificationEnabled: false,
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
}),
|
||||
/手机接码未开启/
|
||||
);
|
||||
});
|
||||
|
||||
test('step 8 defers add-email page to the dedicated bind-email node in phone mode', async () => {
|
||||
const calls = {
|
||||
contentMessages: [],
|
||||
completions: [],
|
||||
resolvedStates: [],
|
||||
setStates: [],
|
||||
mailStates: [],
|
||||
@@ -277,6 +425,9 @@ test('step 8 submits add-email before polling the email verification code', asyn
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeNodeFromBackground: async (step, payload) => {
|
||||
calls.completions.push({ step, payload });
|
||||
},
|
||||
confirmCustomVerificationStepBypass: async () => {},
|
||||
ensureStep8VerificationPageReady: async () => ({ state: 'add_email_page', url: 'https://auth.openai.com/add-email' }),
|
||||
getOAuthFlowRemainingMs: async () => 5000,
|
||||
@@ -339,29 +490,320 @@ test('step 8 submits add-email before polling the email verification code', asyn
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
|
||||
assert.equal(calls.contentMessages.length, 1);
|
||||
assert.equal(calls.resolvedStates.length, 1);
|
||||
assert.equal(calls.resolveOptions.preserveAccountIdentity, true);
|
||||
assert.equal(calls.persistCalls.length, 1);
|
||||
assert.equal(calls.persistCalls[0].email, 'new.user@example.com');
|
||||
assert.equal(calls.persistCalls[0].options.preserveAccountIdentity, true);
|
||||
assert.equal(calls.persistCalls[0].options.source, 'step8_add_email');
|
||||
assert.equal(calls.mailStates[0].email, 'new.user@example.com');
|
||||
assert.equal(calls.resolvedVerification.state.email, 'new.user@example.com');
|
||||
assert.equal(calls.resolvedVerification.options.targetEmail, 'new.user@example.com');
|
||||
assert.equal(calls.contentMessages.length, 0);
|
||||
assert.equal(calls.resolvedStates.length, 0);
|
||||
assert.equal(calls.persistCalls.length, 0);
|
||||
assert.equal(calls.mailStates.length, 0);
|
||||
assert.deepStrictEqual(calls.setStates, [
|
||||
{
|
||||
step8VerificationTargetEmail: 'new.user@example.com',
|
||||
step8VerificationTargetEmail: '',
|
||||
loginVerificationRequestedAt: null,
|
||||
},
|
||||
]);
|
||||
assert.deepStrictEqual(calls.completions, [
|
||||
{
|
||||
step: 'fetch-login-code',
|
||||
payload: {
|
||||
loginVerificationRequestedAt: null,
|
||||
skipLoginVerificationStep: true,
|
||||
addEmailPage: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('step 8 reruns step 7 with preserved phone login identity after add-email verification failure', async () => {
|
||||
test('bind-email submits add-email and requires an email verification page', async () => {
|
||||
const calls = {
|
||||
contentMessages: [],
|
||||
completions: [],
|
||||
persistCalls: [],
|
||||
setStates: [],
|
||||
};
|
||||
let runtimeState = {
|
||||
email: '',
|
||||
password: 'secret',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
};
|
||||
|
||||
const executor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async (step, payload) => {
|
||||
calls.completions.push({ step, payload });
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getState: async () => ({ ...runtimeState }),
|
||||
getTabId: async () => 1,
|
||||
persistRegistrationEmailState: async (state, email, options) => {
|
||||
calls.persistCalls.push({ state, email, options });
|
||||
runtimeState = {
|
||||
...runtimeState,
|
||||
email,
|
||||
};
|
||||
},
|
||||
resolveSignupEmailForFlow: async (_state, options = {}) => {
|
||||
assert.equal(options.preserveAccountIdentity, true);
|
||||
return 'bind.user@example.com';
|
||||
},
|
||||
reuseOrCreateTab: async () => 1,
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
if (message.type === 'GET_LOGIN_AUTH_STATE') {
|
||||
return { state: 'add_email_page', url: 'https://auth.openai.com/add-email' };
|
||||
}
|
||||
calls.contentMessages.push(message);
|
||||
assert.equal(message.type, 'SUBMIT_ADD_EMAIL');
|
||||
assert.equal(message.payload.email, 'bind.user@example.com');
|
||||
return {
|
||||
submitted: true,
|
||||
displayedEmail: 'bind.user@example.com',
|
||||
url: 'https://auth.openai.com/email-verification',
|
||||
};
|
||||
},
|
||||
setState: async (payload) => {
|
||||
calls.setStates.push(payload);
|
||||
runtimeState = {
|
||||
...runtimeState,
|
||||
...payload,
|
||||
};
|
||||
},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executeBindEmail({
|
||||
visibleStep: 9,
|
||||
nodeId: 'bind-email',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
|
||||
assert.equal(calls.contentMessages.length, 1);
|
||||
assert.equal(calls.persistCalls.length, 1);
|
||||
assert.equal(calls.persistCalls[0].options.source, 'bind_email');
|
||||
assert.deepStrictEqual(calls.completions, [
|
||||
{
|
||||
step: 'bind-email',
|
||||
payload: {
|
||||
bindEmailSubmitted: true,
|
||||
email: 'bind.user@example.com',
|
||||
step8VerificationTargetEmail: 'bind.user@example.com',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('bind-email skips on OAuth consent and rejects direct OAuth after submit', async () => {
|
||||
const completions = [];
|
||||
const skipExecutor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async (step, payload) => {
|
||||
completions.push({ step, payload });
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getTabId: async () => 1,
|
||||
reuseOrCreateTab: async () => 1,
|
||||
sendToContentScriptResilient: async () => ({ state: 'oauth_consent_page' }),
|
||||
setState: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await skipExecutor.executeBindEmail({
|
||||
visibleStep: 9,
|
||||
nodeId: 'bind-email',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
assert.deepStrictEqual(completions, [
|
||||
{
|
||||
step: 'bind-email',
|
||||
payload: {
|
||||
directOAuthConsentPage: true,
|
||||
bindEmailSubmitted: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const directOauthExecutor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getState: async () => ({ email: '', oauthUrl: 'https://oauth.example/latest' }),
|
||||
getTabId: async () => 1,
|
||||
resolveSignupEmailForFlow: async () => 'bind.user@example.com',
|
||||
reuseOrCreateTab: async () => 1,
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
if (message.type === 'GET_LOGIN_AUTH_STATE') {
|
||||
return { state: 'add_email_page', url: 'https://auth.openai.com/add-email' };
|
||||
}
|
||||
return {
|
||||
submitted: true,
|
||||
directOAuthConsentPage: true,
|
||||
url: 'https://auth.openai.com/authorize',
|
||||
};
|
||||
},
|
||||
setState: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => directOauthExecutor.executeBindEmail({
|
||||
visibleStep: 9,
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
}),
|
||||
/绑定邮箱提交后必须进入邮箱验证码页/
|
||||
);
|
||||
});
|
||||
|
||||
test('fetch-bind-email-code polls only after bind-email submitted', async () => {
|
||||
const calls = {
|
||||
resolveOptions: null,
|
||||
setStates: [],
|
||||
};
|
||||
const realDateNow = Date.now;
|
||||
Date.now = () => 222000;
|
||||
|
||||
const executor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
confirmCustomVerificationStepBypass: async () => {},
|
||||
getOAuthFlowRemainingMs: async () => 9000,
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getMailConfig: () => ({
|
||||
provider: 'qq',
|
||||
label: 'QQ 邮箱',
|
||||
source: 'mail-qq',
|
||||
url: 'https://mail.qq.com',
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getTabId: async () => 1,
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||
resolveVerificationStep: async (_step, _state, _mail, options) => {
|
||||
calls.resolveOptions = options;
|
||||
},
|
||||
reuseOrCreateTab: async () => 1,
|
||||
sendToContentScriptResilient: async () => ({
|
||||
state: 'verification_page',
|
||||
displayedEmail: 'bind.user@example.com',
|
||||
url: 'https://auth.openai.com/email-verification',
|
||||
}),
|
||||
setState: async (payload) => {
|
||||
calls.setStates.push(payload);
|
||||
},
|
||||
shouldUseCustomRegistrationEmail: () => false,
|
||||
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
try {
|
||||
await executor.executeFetchBindEmailCode({
|
||||
visibleStep: 10,
|
||||
nodeId: 'fetch-bind-email-code',
|
||||
bindEmailSubmitted: true,
|
||||
email: 'bind.user@example.com',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
} finally {
|
||||
Date.now = realDateNow;
|
||||
}
|
||||
|
||||
assert.equal(calls.resolveOptions.completionStep, 10);
|
||||
assert.equal(calls.resolveOptions.sessionKey, '10:222000');
|
||||
assert.equal(calls.resolveOptions.targetEmail, 'bind.user@example.com');
|
||||
assert.deepStrictEqual(calls.setStates, [
|
||||
{
|
||||
step8VerificationTargetEmail: 'bind.user@example.com',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('fetch-bind-email-code rejects unexpected pages after bind-email submitted', async () => {
|
||||
const executor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getTabId: async () => 1,
|
||||
reuseOrCreateTab: async () => 1,
|
||||
sendToContentScriptResilient: async () => ({
|
||||
state: 'oauth_consent_page',
|
||||
url: 'https://auth.openai.com/authorize',
|
||||
}),
|
||||
setState: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executeFetchBindEmailCode({
|
||||
visibleStep: 10,
|
||||
bindEmailSubmitted: true,
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
}),
|
||||
/绑定邮箱提交后不应直接进入 OAuth 授权页/
|
||||
);
|
||||
|
||||
const notSubmittedCompletions = [];
|
||||
const skipExecutor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async (step, payload) => {
|
||||
notSubmittedCompletions.push({ step, payload });
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getTabId: async () => 1,
|
||||
reuseOrCreateTab: async () => 1,
|
||||
sendToContentScriptResilient: async () => ({ state: 'oauth_consent_page' }),
|
||||
setState: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await skipExecutor.executeFetchBindEmailCode({
|
||||
visibleStep: 10,
|
||||
nodeId: 'fetch-bind-email-code',
|
||||
bindEmailSubmitted: false,
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
assert.deepStrictEqual(notSubmittedCompletions, [
|
||||
{
|
||||
step: 'fetch-bind-email-code',
|
||||
payload: {
|
||||
directOAuthConsentPage: true,
|
||||
bindEmailCodeSkipped: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('step 8 does not submit or recover add-email inside fetch-login-code', async () => {
|
||||
const calls = {
|
||||
ensureCalls: 0,
|
||||
resolveCalls: 0,
|
||||
rerunStates: [],
|
||||
contentMessages: [],
|
||||
completions: [],
|
||||
};
|
||||
let runtimeState = {
|
||||
visibleStep: 8,
|
||||
@@ -388,6 +830,9 @@ test('step 8 reruns step 7 with preserved phone login identity after add-email v
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeNodeFromBackground: async (step, payload) => {
|
||||
calls.completions.push({ step, payload });
|
||||
},
|
||||
confirmCustomVerificationStepBypass: async () => {},
|
||||
ensureStep8VerificationPageReady: async () => {
|
||||
calls.ensureCalls += 1;
|
||||
@@ -463,16 +908,23 @@ test('step 8 reruns step 7 with preserved phone login identity after add-email v
|
||||
|
||||
await executor.executeStep8({ ...runtimeState });
|
||||
|
||||
assert.equal(calls.contentMessages.length, 1);
|
||||
assert.equal(calls.resolveCalls, 2);
|
||||
assert.equal(calls.rerunStates.length, 1);
|
||||
assert.equal(calls.rerunStates[0].email, 'new.user@example.com');
|
||||
assert.equal(calls.rerunStates[0].accountIdentifierType, 'phone');
|
||||
assert.equal(calls.rerunStates[0].accountIdentifier, '+447780579093');
|
||||
assert.equal(calls.rerunStates[0].signupPhoneNumber, '+447780579093');
|
||||
assert.equal(calls.ensureCalls, 1);
|
||||
assert.equal(calls.contentMessages.length, 0);
|
||||
assert.equal(calls.resolveCalls, 0);
|
||||
assert.equal(calls.rerunStates.length, 0);
|
||||
assert.deepStrictEqual(calls.completions, [
|
||||
{
|
||||
step: 'fetch-login-code',
|
||||
payload: {
|
||||
loginVerificationRequestedAt: null,
|
||||
skipLoginVerificationStep: true,
|
||||
addEmailPage: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('step 8 add-email rereads persisted phone identity before rerunning step 7', async () => {
|
||||
test('step 8 rejects add-email page in email login mode', async () => {
|
||||
const calls = {
|
||||
resolveCalls: 0,
|
||||
rerunStates: [],
|
||||
@@ -563,16 +1015,14 @@ test('step 8 add-email rereads persisted phone identity before rerunning step 7'
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executeStep8({ ...runtimeState }),
|
||||
/STEP8_RESTART_STEP7::/
|
||||
/邮箱注册模式不应进入添加邮箱页/
|
||||
);
|
||||
|
||||
assert.equal(calls.resolveCalls, 2);
|
||||
assert.equal(calls.rerunStates.length, 1);
|
||||
assert.equal(calls.rerunStates[0].accountIdentifierType, 'phone');
|
||||
assert.equal(calls.rerunStates[0].signupPhoneNumber, '+447780579093');
|
||||
assert.equal(calls.resolveCalls, 0);
|
||||
assert.equal(calls.rerunStates.length, 0);
|
||||
});
|
||||
|
||||
test('step 8 email_in_use recovery preserves the previous registration baseline', async () => {
|
||||
test('step 8 does not run add-email email_in_use recovery in email login mode', async () => {
|
||||
const calls = {
|
||||
contentCalls: 0,
|
||||
setStates: [],
|
||||
@@ -588,15 +1038,6 @@ test('step 8 email_in_use recovery preserves the previous registration baseline'
|
||||
},
|
||||
},
|
||||
},
|
||||
buildRegistrationEmailStateUpdates: () => ({
|
||||
email: null,
|
||||
registrationEmailState: {
|
||||
current: '',
|
||||
previous: 'old.user@example.com',
|
||||
source: 'step8_recovery',
|
||||
updatedAt: 123,
|
||||
},
|
||||
}),
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
confirmCustomVerificationStepBypass: async () => {},
|
||||
ensureStep8VerificationPageReady: async () => ({
|
||||
@@ -653,30 +1094,24 @@ test('step 8 email_in_use recovery preserves the previous registration baseline'
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executeStep8({
|
||||
email: 'old.user@example.com',
|
||||
registrationEmailState: {
|
||||
current: 'old.user@example.com',
|
||||
previous: 'old.user@example.com',
|
||||
source: 'generated:duck',
|
||||
updatedAt: 1,
|
||||
},
|
||||
oauthUrl: 'https://auth.openai.com/add-email',
|
||||
password: 'secret',
|
||||
visibleStep: 8,
|
||||
});
|
||||
await assert.rejects(
|
||||
() => executor.executeStep8({
|
||||
email: 'old.user@example.com',
|
||||
registrationEmailState: {
|
||||
current: 'old.user@example.com',
|
||||
previous: 'old.user@example.com',
|
||||
source: 'generated:duck',
|
||||
updatedAt: 1,
|
||||
},
|
||||
oauthUrl: 'https://auth.openai.com/add-email',
|
||||
password: 'secret',
|
||||
visibleStep: 8,
|
||||
}),
|
||||
/邮箱注册模式不应进入添加邮箱页/
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(calls.setStates[0], {
|
||||
email: null,
|
||||
registrationEmailState: {
|
||||
current: '',
|
||||
previous: 'old.user@example.com',
|
||||
source: 'step8_recovery',
|
||||
updatedAt: 123,
|
||||
},
|
||||
step8VerificationTargetEmail: '',
|
||||
loginVerificationRequestedAt: null,
|
||||
});
|
||||
assert.equal(calls.contentCalls, 0);
|
||||
assert.deepStrictEqual(calls.setStates, []);
|
||||
});
|
||||
|
||||
test('Plus login-code step reuses step 8 verification logic but completes visible step 11', async () => {
|
||||
|
||||
@@ -192,15 +192,15 @@ test('5sim provider rejects maxPrice with custom operator before buying', async
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => provider.requestActivation({
|
||||
fiveSimApiKey: 'demo-key',
|
||||
fiveSimCountryId: 'vietnam',
|
||||
fiveSimCountryLabel: '瓒婂崡 (Vietnam)',
|
||||
fiveSimMaxPrice: '12',
|
||||
fiveSimOperator: 'virtual21',
|
||||
}),
|
||||
/maxPrice only works when operator is "any"/
|
||||
);
|
||||
() => provider.requestActivation({
|
||||
fiveSimApiKey: 'demo-key',
|
||||
fiveSimCountryId: 'vietnam',
|
||||
fiveSimCountryLabel: '瓒婂崡 (Vietnam)',
|
||||
fiveSimMaxPrice: '12',
|
||||
fiveSimOperator: 'virtual21',
|
||||
}),
|
||||
/价格上限仅支持运营商为 "any"/
|
||||
);
|
||||
assert.deepStrictEqual(requests, []);
|
||||
});
|
||||
|
||||
|
||||
@@ -40,5 +40,5 @@ test('phone sms provider registry normalizes ids, order and labels consistently'
|
||||
registry.createProvider('5sim', { foo: 1 }),
|
||||
{ provider: '5sim', deps: { foo: 1 } }
|
||||
);
|
||||
assert.throws(() => registry.createProvider('nexsms'), /Phone SMS provider module is not loaded: nexsms/);
|
||||
assert.throws(() => registry.createProvider('nexsms'), /接码平台模块未加载:nexsms/);
|
||||
});
|
||||
|
||||
@@ -1634,7 +1634,7 @@ test('phone verification helper fails fast when HeroSMS country list is empty',
|
||||
heroSmsCountryId: 0,
|
||||
heroSmsCountryFallback: [],
|
||||
}),
|
||||
/HeroSMS countries are empty/i
|
||||
/HeroSMS 未选择国家/
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1968,7 +1968,7 @@ test('phone verification helper rejects HeroSMS WRONG_MAX_PRICE below configured
|
||||
|
||||
await assert.rejects(
|
||||
helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key', heroSmsMinPrice: '0.07' }),
|
||||
/below configured minPrice=0\.07/i
|
||||
/低于当前配置的最低购买价 0\.07/
|
||||
);
|
||||
|
||||
const actions = requests.map((requestUrl) => `${requestUrl.searchParams.get('action')}:${requestUrl.searchParams.get('maxPrice') || ''}`);
|
||||
@@ -2002,7 +2002,7 @@ test('phone verification helper rejects reversed price range before fetching pri
|
||||
heroSmsMinPrice: '0.2',
|
||||
heroSmsMaxPrice: '0.1',
|
||||
}),
|
||||
/price range is invalid/i
|
||||
/价格区间无效/
|
||||
);
|
||||
assert.equal(fetchCalled, false);
|
||||
});
|
||||
@@ -2039,7 +2039,7 @@ test('phone verification helper stops when WRONG_MAX_PRICE exceeds configured ma
|
||||
|
||||
await assert.rejects(
|
||||
helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '0.05' }),
|
||||
/exceeds configured maxPrice=0\.05/i
|
||||
/超过当前配置的价格上限 0\.05/
|
||||
);
|
||||
|
||||
const actions = requests.map((requestUrl) => `${requestUrl.searchParams.get('action')}:${requestUrl.searchParams.get('maxPrice') || ''}`);
|
||||
@@ -2403,16 +2403,16 @@ test('phone verification helper rejects 5sim maxPrice with custom operator befor
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => helpers.requestPhoneActivation({
|
||||
phoneSmsProvider: '5sim',
|
||||
fiveSimApiKey: 'five-token',
|
||||
fiveSimCountryOrder: ['vietnam'],
|
||||
fiveSimOperator: 'virtual21',
|
||||
fiveSimMaxPrice: '0.1',
|
||||
heroSmsActivationRetryRounds: 1,
|
||||
}),
|
||||
/maxPrice only works when operator is "any"/
|
||||
await assert.rejects(
|
||||
() => helpers.requestPhoneActivation({
|
||||
phoneSmsProvider: '5sim',
|
||||
fiveSimApiKey: 'five-token',
|
||||
fiveSimCountryOrder: ['vietnam'],
|
||||
fiveSimOperator: 'virtual21',
|
||||
fiveSimMaxPrice: '0.1',
|
||||
heroSmsActivationRetryRounds: 1,
|
||||
}),
|
||||
/价格上限仅支持运营商为 "any"/
|
||||
);
|
||||
assert.deepStrictEqual(requests, []);
|
||||
});
|
||||
@@ -8105,14 +8105,14 @@ test('phone verification helper logs no-supply diagnostics with consecutive stre
|
||||
phoneVerificationPage: false,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
}),
|
||||
/all provider candidates failed to acquire number/i
|
||||
/所有接码平台候选均未获取到手机号/
|
||||
);
|
||||
|
||||
await runOnce();
|
||||
await runOnce();
|
||||
|
||||
const diagnosticsLogs = logs
|
||||
.filter((entry) => String(entry.message || '').includes('diagnostics: 无号连续失败'));
|
||||
.filter((entry) => String(entry.message || '').includes('步骤 9 诊断:无号连续失败'));
|
||||
|
||||
assert.equal(diagnosticsLogs.length >= 2, true);
|
||||
assert.equal(diagnosticsLogs.every((entry) => entry.options?.step === 9), true);
|
||||
@@ -8120,15 +8120,15 @@ test('phone verification helper logs no-supply diagnostics with consecutive stre
|
||||
assert.equal(diagnosticsLogs.some((entry) => entry.message.includes('无号连续失败 1 次')), true);
|
||||
assert.equal(diagnosticsLogs.some((entry) => entry.message.includes('无号连续失败 2 次')), true);
|
||||
assert.equal(
|
||||
diagnosticsLogs.some((entry) => entry.message.includes('priceRange=0.04~0.06')),
|
||||
diagnosticsLogs.some((entry) => entry.message.includes('价格区间=0.04~0.06')),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
diagnosticsLogs.some((entry) => entry.message.includes('minPrice=0.04')),
|
||||
diagnosticsLogs.some((entry) => entry.message.includes('最低价=0.04')),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
diagnosticsLogs.some((entry) => entry.message.includes('maxPrice=0.06')),
|
||||
diagnosticsLogs.some((entry) => entry.message.includes('最高价=0.06')),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
@@ -8139,6 +8139,63 @@ test('phone verification helper logs no-supply diagnostics with consecutive stre
|
||||
assert.equal(requests.some((entry) => entry.searchParams.get('action') === 'getNumber'), true);
|
||||
});
|
||||
|
||||
test('phone verification helper localizes HeroSMS BAD_KEY acquisition failure', async () => {
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'bad-key',
|
||||
heroSmsCountryId: 52,
|
||||
heroSmsCountryLabel: 'Thailand',
|
||||
heroSmsCountryFallback: [],
|
||||
currentPhoneActivation: null,
|
||||
reusablePhoneActivation: null,
|
||||
phoneVerificationReplacementLimit: 1,
|
||||
};
|
||||
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url) => {
|
||||
const action = new URL(url).searchParams.get('action');
|
||||
if (action === 'getPrices') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload({ country: '52', cost: 0.05, count: 20 }),
|
||||
};
|
||||
}
|
||||
if (action === 'getNumber' || action === 'getNumberV2') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => 'BAD_KEY',
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getState: async () => ({ ...currentState }),
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
throw new Error(`Unexpected content-script message: ${message.type}`);
|
||||
},
|
||||
setState: async (updates) => {
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
helpers.completePhoneVerificationFlow(1, {
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
}),
|
||||
(error) => {
|
||||
assert.match(error.message, /步骤 9:所有接码平台候选均未获取到手机号/);
|
||||
assert.match(error.message, /HeroSMS:获取手机号失败:API Key 无效(BAD_KEY)/);
|
||||
assert.doesNotMatch(error.message, /all provider candidates failed|failed to acquire number|HeroSMS getNumber failed/i);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('phone verification helper routes 5sim buy, check, and finish by current activation provider', async () => {
|
||||
const requests = [];
|
||||
let currentState = {
|
||||
|
||||
@@ -54,6 +54,9 @@ test('shared source registry exposes canonical source, alias, detection, and rea
|
||||
assert.equal(registry.shouldReportReadyForFrame('unknown-source', false), false);
|
||||
assert.equal(registry.getCleanupOwnerSource('oauth-localhost-callback'), 'openai-auth');
|
||||
assert.equal(registry.driverAcceptsCommand('openai-auth', 'submit-signup-email'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('openai-auth', 'post-login-phone-verification'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('openai-auth', 'bind-email'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('openai-auth', 'fetch-bind-email-code'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('content/platform-panel', 'platform-verify'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('openai-auth', 'platform-verify'), false);
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
const gpcSteps = api.getSteps({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' });
|
||||
|
||||
assert.equal(Array.isArray(steps), true);
|
||||
assert.equal(steps.length, 10);
|
||||
assert.equal(steps.length, 11);
|
||||
assert.equal(steps.every((step) => step.flowId === 'openai'), true);
|
||||
assert.deepStrictEqual(
|
||||
steps.map((step) => step.order),
|
||||
@@ -32,6 +32,7 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
'wait-registration-success',
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'post-login-phone-verification',
|
||||
'confirm-oauth',
|
||||
'platform-verify',
|
||||
]
|
||||
@@ -40,6 +41,23 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
assert.equal(steps[5].title, '等待注册成功');
|
||||
assert.equal(phoneSteps[1].title, '注册并输入手机号');
|
||||
assert.equal(phoneSteps[3].title, '获取手机验证码');
|
||||
assert.deepStrictEqual(
|
||||
phoneSteps.map((step) => step.key),
|
||||
[
|
||||
'open-chatgpt',
|
||||
'submit-signup-email',
|
||||
'fill-password',
|
||||
'fetch-signup-code',
|
||||
'fill-profile',
|
||||
'wait-registration-success',
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'bind-email',
|
||||
'fetch-bind-email-code',
|
||||
'confirm-oauth',
|
||||
'platform-verify',
|
||||
]
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
plusSteps.map((step) => step.key),
|
||||
@@ -55,6 +73,7 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
'plus-checkout-return',
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'post-login-phone-verification',
|
||||
'confirm-oauth',
|
||||
'platform-verify',
|
||||
]
|
||||
@@ -64,11 +83,33 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
assert.equal(plusSteps.find((step) => step.key === 'paypal-approve')?.title, 'PayPal 登录与授权');
|
||||
assert.equal(plusPhoneSteps[1].title, '注册并输入手机号');
|
||||
assert.equal(plusPhoneSteps[3].title, '获取手机验证码');
|
||||
assert.deepStrictEqual(
|
||||
plusPhoneSteps.map((step) => step.key),
|
||||
[
|
||||
'open-chatgpt',
|
||||
'submit-signup-email',
|
||||
'fill-password',
|
||||
'fetch-signup-code',
|
||||
'fill-profile',
|
||||
'plus-checkout-create',
|
||||
'plus-checkout-billing',
|
||||
'paypal-approve',
|
||||
'plus-checkout-return',
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'bind-email',
|
||||
'fetch-bind-email-code',
|
||||
'confirm-oauth',
|
||||
'platform-verify',
|
||||
]
|
||||
);
|
||||
assert.equal(goPaySteps.some((step) => step.key === 'paypal-approve'), false);
|
||||
assert.equal(api.getStepById(8, { plusModeEnabled: true, plusPaymentMethod: 'gopay' }), null);
|
||||
assert.equal(api.getPlusPaymentStepTitle({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }), '');
|
||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]);
|
||||
assert.equal(api.getLastStepId({ plusModeEnabled: true }), 13);
|
||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]);
|
||||
assert.equal(api.getLastStepId({ plusModeEnabled: true }), 14);
|
||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, signupMethod: 'phone' }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
|
||||
assert.equal(api.getLastStepId({ plusModeEnabled: true, signupMethod: 'phone' }), 15);
|
||||
assert.equal(api.hasFlow('openai'), true);
|
||||
assert.equal(api.hasFlow('site-a'), false);
|
||||
assert.deepStrictEqual(api.getRegisteredFlowIds(), ['openai']);
|
||||
@@ -89,12 +130,13 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
'gopay-subscription-confirm',
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'post-login-phone-verification',
|
||||
'confirm-oauth',
|
||||
'platform-verify',
|
||||
]
|
||||
);
|
||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }), [1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13]);
|
||||
assert.equal(api.getLastStepId({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }), 13);
|
||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }), [1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14]);
|
||||
assert.equal(api.getLastStepId({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }), 14);
|
||||
assert.equal(goPaySteps[5].title, '打开 GoPay 订阅页');
|
||||
assert.equal(goPaySteps[6].title, '等待 GoPay 订阅确认');
|
||||
|
||||
@@ -110,12 +152,13 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
'plus-checkout-billing',
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'post-login-phone-verification',
|
||||
'confirm-oauth',
|
||||
'platform-verify',
|
||||
]
|
||||
);
|
||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), [1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13]);
|
||||
assert.equal(api.getLastStepId({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), 13);
|
||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), [1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14]);
|
||||
assert.equal(api.getLastStepId({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), 14);
|
||||
assert.equal(gpcSteps[5].title, '创建 GPC 订单');
|
||||
assert.equal(gpcSteps[6].title, '等待 GPC 任务完成');
|
||||
});
|
||||
|
||||
@@ -241,7 +241,7 @@ return {
|
||||
assert.match(String(result?.url || ''), /chatgpt\.com/);
|
||||
});
|
||||
|
||||
test('step 8 ready check completes phone verification flow before waiting for OAuth consent', async () => {
|
||||
test('step 8 ready check rejects add-phone instead of completing phone verification', async () => {
|
||||
const api = new Function(`
|
||||
let pollCount = 0;
|
||||
const phoneVerificationCalls = [];
|
||||
@@ -292,28 +292,13 @@ return {
|
||||
};
|
||||
`)();
|
||||
|
||||
const { result, phoneVerificationCalls } = await api.run();
|
||||
|
||||
assert.deepStrictEqual(phoneVerificationCalls, [
|
||||
{
|
||||
tabId: 88,
|
||||
pageState: {
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
consentReady: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
assert.deepStrictEqual(result, {
|
||||
url: 'https://auth.openai.com/authorize',
|
||||
addPhonePage: false,
|
||||
phoneVerificationPage: false,
|
||||
consentReady: true,
|
||||
});
|
||||
await assert.rejects(
|
||||
() => api.run(),
|
||||
/自动确认 OAuth 只处理 OAuth 授权页/
|
||||
);
|
||||
});
|
||||
|
||||
test('step 8 ready check blocks phone verification flow when sms toggle is disabled', async () => {
|
||||
test('step 8 ready check rejects phone pages before OAuth confirmation', async () => {
|
||||
const api = new Function(`
|
||||
const phoneVerificationCalls = [];
|
||||
|
||||
@@ -358,6 +343,6 @@ return {
|
||||
|
||||
const { error, phoneVerificationCalls } = await api.run();
|
||||
|
||||
assert.match(String(error?.message || ''), /未开启接码功能/);
|
||||
assert.match(String(error?.message || ''), /自动确认 OAuth 只处理 OAuth 授权页/);
|
||||
assert.deepStrictEqual(phoneVerificationCalls, []);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user