feat: 增强步骤 3 的手机号注册逻辑,确保手机号身份优先处理并添加相关测试用例
This commit is contained in:
@@ -77,6 +77,7 @@ test('background account history settings are normalized independently from hotm
|
||||
extractFunction('normalizeFiveSimOperator'),
|
||||
extractFunction('normalizeFiveSimMaxPrice'),
|
||||
extractFunction('normalizeFiveSimCountryFallback'),
|
||||
extractFunction('normalizeSub2ApiGroupNames'),
|
||||
extractFunction('normalizePersistentSettingValue'),
|
||||
].join('\n');
|
||||
|
||||
@@ -247,6 +248,10 @@ return {
|
||||
api.normalizePersistentSettingValue('sub2apiDefaultProxyName', ' proxy-a '),
|
||||
'proxy-a'
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
api.normalizePersistentSettingValue('sub2apiGroupNames', [' codex ', 'openai-plus', 'CODEX']),
|
||||
['codex', 'openai-plus']
|
||||
);
|
||||
assert.equal(
|
||||
api.normalizePersistentSettingValue('codex2apiUrl', 'localhost:8080/admin'),
|
||||
'http://localhost:8080/admin/accounts'
|
||||
|
||||
@@ -512,6 +512,73 @@ test('signup flow helper reuses existing managed alias email when it is still co
|
||||
assert.equal(setEmailCalls, 0);
|
||||
});
|
||||
|
||||
test('signup flow helper can generate an email on demand when add-email starts from phone signup', async () => {
|
||||
const fetchedStates = [];
|
||||
const setStateCalls = [];
|
||||
|
||||
const helpers = signupFlowApi.createSignupFlowHelpers({
|
||||
buildGeneratedAliasEmail: () => '',
|
||||
chrome: { tabs: { get: async () => ({ id: 21, url: 'https://auth.openai.com/create-account/password' }) } },
|
||||
ensureContentScriptReadyOnTab: async () => {},
|
||||
ensureHotmailAccountForFlow: async () => ({}),
|
||||
ensureLuckmailPurchaseForFlow: async () => ({}),
|
||||
fetchGeneratedEmail: async (state, options) => {
|
||||
fetchedStates.push({ state, options });
|
||||
return 'duck.generated@example.com';
|
||||
},
|
||||
isGeneratedAliasProvider: () => false,
|
||||
isReusableGeneratedAliasEmail: () => false,
|
||||
isHotmailProvider: () => false,
|
||||
isLuckmailProvider: () => false,
|
||||
isSignupEmailVerificationPageUrl: () => false,
|
||||
isSignupPasswordPageUrl: () => true,
|
||||
reuseOrCreateTab: async () => 21,
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setEmailState: async () => {
|
||||
throw new Error('fetchGeneratedEmail already persists the generated email');
|
||||
},
|
||||
setState: async (updates) => {
|
||||
setStateCalls.push(updates);
|
||||
},
|
||||
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
waitForTabUrlMatch: async () => null,
|
||||
});
|
||||
|
||||
const email = await helpers.resolveSignupEmailForFlow({
|
||||
email: '',
|
||||
emailGenerator: 'duck',
|
||||
accountIdentifierType: 'phone',
|
||||
accountIdentifier: '+447780579093',
|
||||
signupPhoneNumber: '+447780579093',
|
||||
signupPhoneCompletedActivation: {
|
||||
activationId: 'signup-completed',
|
||||
phoneNumber: '+447780579093',
|
||||
},
|
||||
}, {
|
||||
preserveAccountIdentity: true,
|
||||
});
|
||||
|
||||
assert.equal(email, 'duck.generated@example.com');
|
||||
assert.equal(fetchedStates.length, 1);
|
||||
assert.equal(fetchedStates[0].options.preserveAccountIdentity, true);
|
||||
assert.deepStrictEqual(setStateCalls, [
|
||||
{
|
||||
email: 'duck.generated@example.com',
|
||||
accountIdentifierType: 'phone',
|
||||
accountIdentifier: '+447780579093',
|
||||
signupPhoneNumber: '+447780579093',
|
||||
signupPhoneActivation: null,
|
||||
signupPhoneCompletedActivation: {
|
||||
activationId: 'signup-completed',
|
||||
phoneNumber: '+447780579093',
|
||||
},
|
||||
signupPhoneVerificationRequestedAt: null,
|
||||
signupPhoneVerificationPurpose: '',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('signup flow helper finalizes step 3 submit by reusing signup verification preparation', async () => {
|
||||
let ensureCalls = 0;
|
||||
const messages = [];
|
||||
|
||||
@@ -122,3 +122,81 @@ test('step 3 supports phone-only signup identity when password page is present',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('step 3 phone signup intent does not fall back to a stale email identity', async () => {
|
||||
const events = {
|
||||
passwordStates: [],
|
||||
messages: [],
|
||||
stateUpdates: [],
|
||||
};
|
||||
|
||||
const executor = api.createStep3Executor({
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
ensureContentScriptReadyOnTab: async () => {},
|
||||
generatePassword: () => 'Generated123!',
|
||||
getTabId: async () => 88,
|
||||
isTabAlive: async () => true,
|
||||
resolveSignupMethod: () => 'phone',
|
||||
sendToContentScript: async (_source, message) => {
|
||||
events.messages.push(message);
|
||||
},
|
||||
setPasswordState: async (password) => {
|
||||
events.passwordStates.push(password);
|
||||
},
|
||||
setState: async (updates) => {
|
||||
events.stateUpdates.push(updates);
|
||||
},
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executeStep3({
|
||||
email: 'stale@example.com',
|
||||
signupMethod: 'phone',
|
||||
resolvedSignupMethod: 'phone',
|
||||
accountIdentifierType: null,
|
||||
accountIdentifier: '',
|
||||
customPassword: 'PhoneSecret123!',
|
||||
accounts: [],
|
||||
}),
|
||||
/缺少注册手机号,请先完成步骤 2 或在侧栏填写注册手机号后再执行步骤 3。/
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(events.passwordStates, []);
|
||||
assert.deepStrictEqual(events.stateUpdates, []);
|
||||
assert.deepStrictEqual(events.messages, []);
|
||||
});
|
||||
|
||||
test('step 3 respects resolved email fallback when phone signup is unavailable', async () => {
|
||||
const events = {
|
||||
messages: [],
|
||||
};
|
||||
|
||||
const executor = api.createStep3Executor({
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
ensureContentScriptReadyOnTab: async () => {},
|
||||
generatePassword: () => 'Generated123!',
|
||||
getTabId: async () => 88,
|
||||
isTabAlive: async () => true,
|
||||
resolveSignupMethod: () => 'email',
|
||||
sendToContentScript: async (_source, message) => {
|
||||
events.messages.push(message);
|
||||
},
|
||||
setPasswordState: async () => {},
|
||||
setState: async () => {},
|
||||
SIGNUP_PAGE_INJECT_FILES: [],
|
||||
});
|
||||
|
||||
await executor.executeStep3({
|
||||
email: 'fallback@example.com',
|
||||
signupMethod: 'phone',
|
||||
resolvedSignupMethod: 'email',
|
||||
customPassword: 'EmailSecret123!',
|
||||
accounts: [],
|
||||
});
|
||||
|
||||
assert.equal(events.messages[0].payload.accountIdentifierType, 'email');
|
||||
assert.equal(events.messages[0].payload.accountIdentifier, 'fallback@example.com');
|
||||
});
|
||||
|
||||
@@ -300,8 +300,9 @@ test('step 8 submits add-email before polling the email verification code', asyn
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||
resolveSignupEmailForFlow: async (state) => {
|
||||
resolveSignupEmailForFlow: async (state, options = {}) => {
|
||||
calls.resolvedStates.push(state);
|
||||
calls.resolveOptions = options;
|
||||
return 'new.user@example.com';
|
||||
},
|
||||
resolveVerificationStep: async (_step, state, _mail, options) => {
|
||||
@@ -336,6 +337,7 @@ test('step 8 submits add-email before polling the email verification code', asyn
|
||||
|
||||
assert.equal(calls.contentMessages.length, 1);
|
||||
assert.equal(calls.resolvedStates.length, 1);
|
||||
assert.equal(calls.resolveOptions.preserveAccountIdentity, true);
|
||||
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');
|
||||
|
||||
@@ -141,6 +141,9 @@ test('sidepanel html contains account records overlay and manager script', () =>
|
||||
assert.match(html, /id="btn-toggle-account-records-selection"/);
|
||||
assert.match(html, /id="btn-delete-selected-account-records"/);
|
||||
assert.match(html, /id="input-sub2api-default-proxy"/);
|
||||
assert.match(html, /id="sub2api-group-picker"/);
|
||||
assert.match(html, /id="input-sub2api-group" value="codex"/);
|
||||
assert.match(html, /id="btn-add-sub2api-group"/);
|
||||
assert.notEqual(managerIndex, -1);
|
||||
assert.notEqual(sidepanelIndex, -1);
|
||||
assert.ok(managerIndex < sidepanelIndex);
|
||||
|
||||
@@ -512,6 +512,7 @@ function updateAccountRunHistorySettingsUI() {}
|
||||
function updatePhoneVerificationSettingsUI() {}
|
||||
function updatePanelModeUI() {}
|
||||
function updateMailProviderUI() { calls.push({ target: selectIcloudTargetMailboxType.value, provider: selectIcloudForwardMailProvider.value }); }
|
||||
function renderSub2ApiGroupOptions() {}
|
||||
function isLuckmailProvider() { return false; }
|
||||
function updateButtonStates() {}
|
||||
${bundle}
|
||||
|
||||
@@ -136,6 +136,7 @@ test('sidepanel html exposes phone verification toggle and multi-provider SMS ro
|
||||
|
||||
test('sidepanel source wires runtime signup phone field to background sync messages', () => {
|
||||
assert.match(sidepanelSource, /function getRuntimeSignupPhoneValue\(state = latestState\)/);
|
||||
assert.match(sidepanelSource, /function shouldExecuteStep3WithSignupPhoneIdentity\(state = latestState\)/);
|
||||
assert.match(sidepanelSource, /function shouldPreserveSignupPhoneInputValue\(stateSignupPhone = ''\)/);
|
||||
assert.match(sidepanelSource, /function syncSignupPhoneInputFromState\(state = latestState\)/);
|
||||
assert.match(sidepanelSource, /async function persistSignupPhoneInputForAction\(\)/);
|
||||
@@ -143,10 +144,40 @@ test('sidepanel source wires runtime signup phone field to background sync messa
|
||||
assert.match(sidepanelSource, /final \? 'SAVE_SIGNUP_PHONE' : 'SET_SIGNUP_PHONE_STATE'/);
|
||||
assert.match(sidepanelSource, /message\.payload\.signupPhoneNumber !== undefined/);
|
||||
assert.match(sidepanelSource, /await persistSignupPhoneInputForAction\(\);\s*await saveSettings/);
|
||||
assert.match(sidepanelSource, /if \(shouldExecuteStep3WithSignupPhoneIdentity\(latestState\)\)[\s\S]*payload: \{ step \}/);
|
||||
assert.match(sidepanelSource, /async function handleSkipStep\(step\)[\s\S]*await persistCurrentSettingsForAction\(\);/);
|
||||
assert.match(sidepanelSource, /inputSignupPhone\.addEventListener\('input'[\s\S]*signupPhoneInputDirty = true/);
|
||||
});
|
||||
|
||||
test('manual step 3 uses phone identity without requiring registration email', () => {
|
||||
const api = new Function(`
|
||||
let latestState = { signupMethod: 'phone', phoneVerificationEnabled: true, signupPhoneNumber: '+441111111111', accountIdentifierType: 'phone', accountIdentifier: '+441111111111' };
|
||||
const DEFAULT_SIGNUP_METHOD = 'email';
|
||||
const SIGNUP_METHOD_PHONE = 'phone';
|
||||
function getSelectedSignupMethod() { return 'phone'; }
|
||||
${extractFunction('normalizeSignupMethod')}
|
||||
${extractFunction('getRuntimeSignupPhoneValue')}
|
||||
${extractFunction('shouldExecuteStep3WithSignupPhoneIdentity')}
|
||||
return { shouldExecuteStep3WithSignupPhoneIdentity };
|
||||
`)();
|
||||
|
||||
assert.equal(api.shouldExecuteStep3WithSignupPhoneIdentity({
|
||||
signupMethod: 'phone',
|
||||
phoneVerificationEnabled: true,
|
||||
accountIdentifierType: 'phone',
|
||||
accountIdentifier: '+441111111111',
|
||||
signupPhoneNumber: '+441111111111',
|
||||
email: '',
|
||||
}), true);
|
||||
assert.equal(api.shouldExecuteStep3WithSignupPhoneIdentity({
|
||||
signupMethod: 'email',
|
||||
accountIdentifierType: 'email',
|
||||
accountIdentifier: 'user@example.com',
|
||||
signupPhoneNumber: '',
|
||||
email: 'user@example.com',
|
||||
}), false);
|
||||
});
|
||||
|
||||
test('runtime signup phone sync preserves active manual input until it is saved', () => {
|
||||
const api = new Function(`
|
||||
let latestState = { signupMethod: 'phone', phoneVerificationEnabled: true, signupPhoneNumber: '+441111111111' };
|
||||
|
||||
Reference in New Issue
Block a user