Merge branch 'dev' of https://github.com/QLHazyCoder/codex-oauth-automation-extension into dev
This commit is contained in:
@@ -114,6 +114,12 @@ let currentState = {
|
||||
inbucketMailbox: '',
|
||||
cloudflareDomain: '',
|
||||
cloudflareDomains: [],
|
||||
reusablePhoneActivation: {
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
successfulUses: 1,
|
||||
maxUses: 3,
|
||||
},
|
||||
tabRegistry: {},
|
||||
sourceLastUrls: {},
|
||||
};
|
||||
@@ -329,6 +335,16 @@ return {
|
||||
assert.strictEqual(snapshot.currentState.autoRunSessionId, 0, 'session id should be cleared after completion');
|
||||
assert.strictEqual(snapshot.currentState.gmailBaseEmail, 'demo@gmail.com', 'gmail base email should survive fresh-attempt reset');
|
||||
assert.strictEqual(snapshot.currentState.mail2925BaseEmail, 'demo@2925.com', '2925 base email should survive fresh-attempt reset');
|
||||
assert.deepStrictEqual(
|
||||
snapshot.currentState.reusablePhoneActivation,
|
||||
{
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
successfulUses: 1,
|
||||
maxUses: 3,
|
||||
},
|
||||
'reusable phone activation should survive fresh-attempt reset'
|
||||
);
|
||||
|
||||
console.log('auto-run fresh attempt reset tests passed');
|
||||
})().catch((error) => {
|
||||
|
||||
@@ -713,6 +713,7 @@ function broadcastDataUpdate() {}
|
||||
function isLocalhostOAuthCallbackUrl() {
|
||||
return true;
|
||||
}
|
||||
async function finalizePhoneActivationAfterSuccessfulFlow() {}
|
||||
async function finalizeIcloudAliasAfterSuccessfulFlow() {}
|
||||
|
||||
${bundle}
|
||||
|
||||
@@ -12,6 +12,7 @@ function createRouter(overrides = {}) {
|
||||
stepStatuses: [],
|
||||
emailStates: [],
|
||||
finalizePayloads: [],
|
||||
phoneFinalizations: [],
|
||||
notifyCompletions: [],
|
||||
notifyErrors: [],
|
||||
securityBlocks: [],
|
||||
@@ -49,10 +50,13 @@ function createRouter(overrides = {}) {
|
||||
executeStepViaCompletionSignal: async () => {},
|
||||
exportSettingsBundle: async () => ({}),
|
||||
fetchGeneratedEmail: async () => '',
|
||||
finalizePhoneActivationAfterSuccessfulFlow: overrides.finalizePhoneActivationAfterSuccessfulFlow || (async (state) => {
|
||||
events.phoneFinalizations.push(state);
|
||||
}),
|
||||
finalizeStep3Completion: overrides.finalizeStep3Completion || (async (payload) => {
|
||||
events.finalizePayloads.push(payload);
|
||||
}),
|
||||
finalizeIcloudAliasAfterSuccessfulFlow: async () => {},
|
||||
finalizeIcloudAliasAfterSuccessfulFlow: overrides.finalizeIcloudAliasAfterSuccessfulFlow || (async () => {}),
|
||||
findHotmailAccount: async () => null,
|
||||
flushCommand: async () => {},
|
||||
getCurrentLuckmailPurchase: () => null,
|
||||
@@ -262,6 +266,68 @@ test('message router finalizes step 3 before marking it completed', async () =>
|
||||
assert.deepStrictEqual(response, { ok: true });
|
||||
});
|
||||
|
||||
test('message router finalizes pending phone activation on platform verify success', async () => {
|
||||
const state = {
|
||||
stepStatuses: { 10: 'pending' },
|
||||
reusablePhoneActivation: {
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
},
|
||||
pendingPhoneActivationConfirmation: {
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
},
|
||||
};
|
||||
const { router, events } = createRouter({
|
||||
state,
|
||||
getStepDefinitionForState: (step) => ({ id: step, key: step === 10 ? 'platform-verify' : '' }),
|
||||
});
|
||||
|
||||
await router.handleStepData(10, {
|
||||
localhostUrl: 'http://localhost:1455/auth/callback?code=ok',
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(events.phoneFinalizations, [state]);
|
||||
});
|
||||
|
||||
test('message router does not finalize pending phone activation when icloud finalization fails', async () => {
|
||||
const state = {
|
||||
stepStatuses: { 10: 'pending' },
|
||||
reusablePhoneActivation: {
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
},
|
||||
pendingPhoneActivationConfirmation: {
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
},
|
||||
};
|
||||
const { router, events } = createRouter({
|
||||
state,
|
||||
getStepDefinitionForState: (step) => ({ id: step, key: step === 10 ? 'platform-verify' : '' }),
|
||||
finalizeIcloudAliasAfterSuccessfulFlow: async () => {
|
||||
throw new Error('icloud finalize failed');
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => router.handleStepData(10, {
|
||||
localhostUrl: 'http://localhost:1455/auth/callback?code=ok',
|
||||
}),
|
||||
/icloud finalize failed/
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(events.phoneFinalizations, []);
|
||||
});
|
||||
|
||||
test('message router marks step 3 failed when post-submit finalize fails', async () => {
|
||||
const { router, events } = createRouter({
|
||||
finalizeStep3Completion: async () => {
|
||||
|
||||
@@ -310,6 +310,78 @@ test('step 8 retries in-place when polling fails but auth page still stays on ve
|
||||
assert.equal(events.ensureCalls >= 3, true);
|
||||
});
|
||||
|
||||
test('step 8 keeps resend cooldown timestamp across in-place retries to avoid repeated resend storms', async () => {
|
||||
const events = {
|
||||
resolveCalls: 0,
|
||||
resolveLastResendAts: [],
|
||||
sleepMs: [],
|
||||
};
|
||||
const realDateNow = Date.now;
|
||||
Date.now = () => 230000;
|
||||
|
||||
const executor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypass: async () => {},
|
||||
ensureStep8VerificationPageReady: async () => ({ state: 'verification_page', displayedEmail: 'user@example.com' }),
|
||||
rerunStep7ForStep8Recovery: async () => {},
|
||||
getOAuthFlowRemainingMs: async () => 9000,
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => Math.min(defaultTimeoutMs, 9000),
|
||||
getMailConfig: () => ({
|
||||
provider: 'qq',
|
||||
label: 'QQ 邮箱',
|
||||
source: 'mail-qq',
|
||||
url: 'https://mail.qq.com',
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret', loginVerificationRequestedAt: null }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: (error) => /页面通信异常|did not respond/i.test(String(error?.message || error || '')),
|
||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||
resolveVerificationStep: async (_step, _state, _mail, options) => {
|
||||
events.resolveCalls += 1;
|
||||
events.resolveLastResendAts.push(Number(options?.lastResendAt) || 0);
|
||||
if (events.resolveCalls === 1) {
|
||||
await options.onResendRequestedAt(222000);
|
||||
throw new Error('步骤 8:页面通信异常 did not respond in 1s');
|
||||
}
|
||||
},
|
||||
reuseOrCreateTab: async () => {},
|
||||
setState: async () => {},
|
||||
setStepStatus: async () => {},
|
||||
shouldUseCustomRegistrationEmail: () => false,
|
||||
sleepWithStop: async (ms) => {
|
||||
events.sleepMs.push(ms);
|
||||
},
|
||||
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS: 8,
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
try {
|
||||
await executor.executeStep8({
|
||||
email: 'user@example.com',
|
||||
password: 'secret',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
} finally {
|
||||
Date.now = realDateNow;
|
||||
}
|
||||
|
||||
assert.equal(events.resolveCalls, 2);
|
||||
assert.deepStrictEqual(events.resolveLastResendAts, [0, 222000]);
|
||||
assert.equal(events.sleepMs.length >= 1, true);
|
||||
assert.equal(events.sleepMs[0], 3000);
|
||||
});
|
||||
|
||||
test('step 8 completes when polling fails but recovery probe shows oauth consent page', async () => {
|
||||
const events = {
|
||||
ensureCalls: 0,
|
||||
|
||||
@@ -18,6 +18,107 @@ hotmail_helper = load_hotmail_helper()
|
||||
|
||||
|
||||
class HotmailHelperLoggingTest(unittest.TestCase):
|
||||
def test_select_latest_code_can_use_full_body_when_preview_is_truncated(self):
|
||||
css_prefix = (
|
||||
'Your temporary ChatGPT verification code '
|
||||
'@font-face { font-family: "Söhne"; src: url(https://cdn.openai.com/common/fonts/soehne/soehne-buch.woff2) format("woff2"); } '
|
||||
'.ExternalClass { width: 100%; } '
|
||||
'#bodyTable { width: 560px; } '
|
||||
'body { min-width: 100% !important; } '
|
||||
) * 8
|
||||
full_body = (
|
||||
css_prefix
|
||||
+ 'Enter this temporary verification code to continue: 272964 '
|
||||
+ 'Please ignore this email if this was not you.'
|
||||
)
|
||||
message = {
|
||||
"id": "imap-1",
|
||||
"mailbox": "INBOX",
|
||||
"subject": "Your temporary ChatGPT verification code",
|
||||
"from": {
|
||||
"emailAddress": {
|
||||
"address": "otp@tm1.openai.com",
|
||||
"name": "OpenAI",
|
||||
}
|
||||
},
|
||||
"bodyPreview": full_body[:500],
|
||||
"body": {
|
||||
"content": full_body,
|
||||
},
|
||||
"receivedTimestamp": 200,
|
||||
}
|
||||
|
||||
result = hotmail_helper.select_latest_code(
|
||||
[message],
|
||||
['openai', 'noreply', 'verify', 'auth', 'chatgpt', 'duckduckgo', 'forward'],
|
||||
['verify', 'verification', 'code', '验证码', 'confirm', 'login'],
|
||||
[],
|
||||
0,
|
||||
)
|
||||
|
||||
self.assertEqual(result["code"], "272964")
|
||||
self.assertEqual(result["message"]["id"], "imap-1")
|
||||
|
||||
def test_log_openai_messages_logs_full_body_when_available(self):
|
||||
messages = [{
|
||||
"mailbox": "INBOX",
|
||||
"subject": "Your verification code",
|
||||
"from": {
|
||||
"emailAddress": {
|
||||
"address": "account-security@openai.com",
|
||||
"name": "OpenAI",
|
||||
}
|
||||
},
|
||||
"bodyPreview": "Use 123456 to continue.",
|
||||
"body": {
|
||||
"content": "Hello there\nUse 123456 to continue.",
|
||||
},
|
||||
}]
|
||||
|
||||
output = io.StringIO()
|
||||
with redirect_stdout(output):
|
||||
hotmail_helper.log_openai_messages(messages, transport="imap")
|
||||
|
||||
rendered = output.getvalue()
|
||||
self.assertIn(
|
||||
"[HotmailHelper] openai mail received transport=imap mailbox=INBOX sender=account-security@openai.com senderName=OpenAI subject=Your verification code",
|
||||
rendered,
|
||||
)
|
||||
self.assertIn(
|
||||
"[HotmailHelper] openai mail full body start transport=imap mailbox=INBOX sender=account-security@openai.com senderName=OpenAI subject=Your verification code",
|
||||
rendered,
|
||||
)
|
||||
self.assertIn("Hello there\nUse 123456 to continue.", rendered)
|
||||
self.assertIn("[HotmailHelper] openai mail full body end", rendered)
|
||||
|
||||
def test_log_openai_messages_falls_back_to_preview_without_full_body(self):
|
||||
messages = [{
|
||||
"mailbox": "Junk",
|
||||
"subject": "Verify your sign in",
|
||||
"from": {
|
||||
"emailAddress": {
|
||||
"address": "noreply@tm.openai.com",
|
||||
"name": "ChatGPT",
|
||||
}
|
||||
},
|
||||
"bodyPreview": "Use 654321 to continue.",
|
||||
}]
|
||||
|
||||
output = io.StringIO()
|
||||
with redirect_stdout(output):
|
||||
hotmail_helper.log_openai_messages(messages, transport="graph")
|
||||
|
||||
rendered = output.getvalue()
|
||||
self.assertIn(
|
||||
"[HotmailHelper] openai mail received transport=graph mailbox=Junk sender=noreply@tm.openai.com senderName=ChatGPT subject=Verify your sign in",
|
||||
rendered,
|
||||
)
|
||||
self.assertIn(
|
||||
"[HotmailHelper] openai mail preview transport=graph mailbox=Junk sender=noreply@tm.openai.com senderName=ChatGPT subject=Verify your sign in preview=Use 654321 to continue.",
|
||||
rendered,
|
||||
)
|
||||
self.assertNotIn("openai mail full body start", rendered)
|
||||
|
||||
def test_refresh_access_token_logs_invalid_grant_and_direct_connection_refused_separately(self):
|
||||
failures = [
|
||||
{
|
||||
@@ -38,7 +139,8 @@ class HotmailHelperLoggingTest(unittest.TestCase):
|
||||
},
|
||||
]
|
||||
|
||||
with mock.patch.object(hotmail_helper, "try_refresh_access_token", side_effect=failures):
|
||||
with mock.patch.object(hotmail_helper, "try_refresh_access_token", side_effect=failures), \
|
||||
mock.patch.object(hotmail_helper, "get_proxy_debug_context", return_value="direct"):
|
||||
output = io.StringIO()
|
||||
with redirect_stdout(output):
|
||||
with self.assertRaises(RuntimeError):
|
||||
|
||||
@@ -32,7 +32,7 @@ function buildHeroSmsStatusV2Payload({ smsCode = '', smsText = '', callCode = ''
|
||||
});
|
||||
}
|
||||
|
||||
test('phone verification helper requests HeroSMS numbers with fixed OpenAI and Thailand parameters', async () => {
|
||||
test('phone verification helper requests HeroSMS numbers with manual maxPrice and fixed OpenAI/Thailand parameters', async () => {
|
||||
const requests = [];
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
@@ -40,26 +40,22 @@ test('phone verification helper requests HeroSMS numbers with fixed OpenAI and T
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (action === 'getPrices') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload(),
|
||||
};
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
||||
};
|
||||
},
|
||||
getState: async () => ({ heroSmsApiKey: 'demo-key' }),
|
||||
getState: async () => ({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '0.08' }),
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const activation = await helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key' });
|
||||
const activation = await helpers.requestPhoneActivation({
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsMaxPrice: '0.08',
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(activation, {
|
||||
activationId: '123456',
|
||||
@@ -70,111 +66,80 @@ test('phone verification helper requests HeroSMS numbers with fixed OpenAI and T
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
});
|
||||
assert.equal(requests.length, 2);
|
||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
||||
assert.equal(requests.length, 1);
|
||||
assert.equal(requests[0].searchParams.get('action'), 'getNumber');
|
||||
assert.equal(requests[0].searchParams.get('service'), 'dr');
|
||||
assert.equal(requests[0].searchParams.get('country'), '52');
|
||||
assert.equal(requests[0].searchParams.get('api_key'), 'demo-key');
|
||||
assert.equal(requests[1].searchParams.get('action'), 'getNumber');
|
||||
assert.equal(requests[1].searchParams.get('maxPrice'), '0.08');
|
||||
assert.equal(requests[1].searchParams.get('fixedPrice'), 'true');
|
||||
assert.equal(requests[1].searchParams.get('service'), 'dr');
|
||||
assert.equal(requests[1].searchParams.get('country'), '52');
|
||||
assert.equal(requests[1].searchParams.get('api_key'), 'demo-key');
|
||||
assert.equal(requests[0].searchParams.get('maxPrice'), '0.08');
|
||||
assert.equal(requests[0].searchParams.get('fixedPrice'), 'true');
|
||||
});
|
||||
|
||||
test('phone verification helper retries HeroSMS getPrices until it receives a usable lowest price', async () => {
|
||||
const requests = [];
|
||||
let getPricesAttempt = 0;
|
||||
test('phone verification helper requires manual HeroSMS maxPrice', async () => {
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (action === 'getPrices') {
|
||||
getPricesAttempt += 1;
|
||||
return getPricesAttempt < 3
|
||||
? {
|
||||
ok: true,
|
||||
text: async () => JSON.stringify({ unavailable: true }),
|
||||
}
|
||||
: {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload({ cost: 0.09 }),
|
||||
};
|
||||
}
|
||||
if (action === 'getNumber') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||
fetchImpl: async () => {
|
||||
throw new Error('should not request HeroSMS without maxPrice');
|
||||
},
|
||||
getState: async () => ({ heroSmsApiKey: 'demo-key' }),
|
||||
getState: async () => ({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '' }),
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key' });
|
||||
|
||||
assert.equal(requests.length, 4);
|
||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
||||
assert.equal(requests[1].searchParams.get('action'), 'getPrices');
|
||||
assert.equal(requests[2].searchParams.get('action'), 'getPrices');
|
||||
assert.equal(requests[3].searchParams.get('action'), 'getNumber');
|
||||
assert.equal(requests[3].searchParams.get('maxPrice'), '0.09');
|
||||
assert.equal(requests[3].searchParams.get('fixedPrice'), 'true');
|
||||
await assert.rejects(
|
||||
() => helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '' }),
|
||||
/HeroSMS maxPrice is missing/i
|
||||
);
|
||||
});
|
||||
|
||||
test('phone verification helper falls back to plain getNumber only after HeroSMS getPrices fails three times', async () => {
|
||||
test('phone verification helper still clears existing activation when maxPrice is missing', async () => {
|
||||
const requests = [];
|
||||
let getPricesAttempt = 0;
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsMaxPrice: '',
|
||||
currentPhoneActivation: {
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
},
|
||||
reusablePhoneActivation: null,
|
||||
pendingPhoneActivationConfirmation: null,
|
||||
};
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (action === 'getPrices') {
|
||||
getPricesAttempt += 1;
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => JSON.stringify({ unavailable: getPricesAttempt }),
|
||||
};
|
||||
}
|
||||
if (action === 'getNumber') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => 'ACCESS_CANCEL',
|
||||
};
|
||||
},
|
||||
getState: async () => ({ heroSmsApiKey: 'demo-key' }),
|
||||
getState: async () => currentState,
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setState: async () => {},
|
||||
setState: async (updates) => {
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key' });
|
||||
await assert.rejects(
|
||||
() => helpers.completePhoneVerificationFlow(1, { addPhonePage: true }),
|
||||
/HeroSMS maxPrice is missing/i
|
||||
);
|
||||
|
||||
assert.equal(requests.length, 4);
|
||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
||||
assert.equal(requests[1].searchParams.get('action'), 'getPrices');
|
||||
assert.equal(requests[2].searchParams.get('action'), 'getPrices');
|
||||
assert.equal(requests[2].searchParams.get('service'), 'dr');
|
||||
assert.equal(requests[2].searchParams.get('country'), '52');
|
||||
assert.equal(requests[2].searchParams.get('api_key'), 'demo-key');
|
||||
assert.equal(requests[3].searchParams.get('action'), 'getNumber');
|
||||
assert.equal(requests[3].searchParams.get('maxPrice'), null);
|
||||
assert.equal(requests[3].searchParams.get('fixedPrice'), null);
|
||||
assert.equal(requests.length, 1);
|
||||
assert.equal(requests[0].searchParams.get('action'), 'setStatus');
|
||||
assert.equal(requests[0].searchParams.get('id'), '123456');
|
||||
assert.equal(requests[0].searchParams.get('status'), '8');
|
||||
assert.equal(requests[0].searchParams.has('maxPrice'), false);
|
||||
assert.equal(currentState.currentPhoneActivation, null);
|
||||
});
|
||||
|
||||
test('phone verification helper retries with HeroSMS getNumberV2 when getNumber reports NO_NUMBERS', async () => {
|
||||
@@ -186,12 +151,6 @@ test('phone verification helper retries with HeroSMS getNumberV2 when getNumber
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (action === 'getPrices') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload({ country: '16' }),
|
||||
};
|
||||
}
|
||||
if (action === 'getNumber') {
|
||||
return {
|
||||
ok: true,
|
||||
@@ -209,7 +168,7 @@ test('phone verification helper retries with HeroSMS getNumberV2 when getNumber
|
||||
}
|
||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||
},
|
||||
getState: async () => ({ heroSmsApiKey: 'demo-key', heroSmsCountryId: 16 }),
|
||||
getState: async () => ({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '0.08', heroSmsCountryId: 16 }),
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
@@ -218,6 +177,7 @@ test('phone verification helper retries with HeroSMS getNumberV2 when getNumber
|
||||
|
||||
const activation = await helpers.requestPhoneActivation({
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsMaxPrice: '0.08',
|
||||
heroSmsCountryId: 16,
|
||||
});
|
||||
|
||||
@@ -231,17 +191,15 @@ test('phone verification helper retries with HeroSMS getNumberV2 when getNumber
|
||||
maxUses: 3,
|
||||
statusAction: 'getStatusV2',
|
||||
});
|
||||
assert.equal(requests.length, 3);
|
||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
||||
assert.equal(requests.length, 2);
|
||||
assert.equal(requests[0].searchParams.get('action'), 'getNumber');
|
||||
assert.equal(requests[0].searchParams.get('country'), '16');
|
||||
assert.equal(requests[1].searchParams.get('action'), 'getNumber');
|
||||
assert.equal(requests[0].searchParams.get('maxPrice'), '0.08');
|
||||
assert.equal(requests[0].searchParams.get('fixedPrice'), 'true');
|
||||
assert.equal(requests[1].searchParams.get('action'), 'getNumberV2');
|
||||
assert.equal(requests[1].searchParams.get('country'), '16');
|
||||
assert.equal(requests[1].searchParams.get('maxPrice'), '0.08');
|
||||
assert.equal(requests[1].searchParams.get('fixedPrice'), 'true');
|
||||
assert.equal(requests[2].searchParams.get('action'), 'getNumberV2');
|
||||
assert.equal(requests[2].searchParams.get('country'), '16');
|
||||
assert.equal(requests[2].searchParams.get('maxPrice'), '0.08');
|
||||
assert.equal(requests[2].searchParams.get('fixedPrice'), 'true');
|
||||
});
|
||||
|
||||
test('phone verification helper uses HeroSMS getStatusV2 after acquiring a number via getNumberV2', async () => {
|
||||
@@ -249,6 +207,7 @@ test('phone verification helper uses HeroSMS getStatusV2 after acquiring a numbe
|
||||
const stateUpdates = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsMaxPrice: '0.08',
|
||||
heroSmsCountryId: 16,
|
||||
heroSmsCountryLabel: 'United Kingdom',
|
||||
verificationResendCount: 0,
|
||||
@@ -264,12 +223,6 @@ test('phone verification helper uses HeroSMS getStatusV2 after acquiring a numbe
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (action === 'getPrices') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload({ country: '16' }),
|
||||
};
|
||||
}
|
||||
if (action === 'getNumber') {
|
||||
return {
|
||||
ok: true,
|
||||
@@ -361,7 +314,19 @@ test('phone verification helper uses HeroSMS getStatusV2 after acquiring a numbe
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 16,
|
||||
successfulUses: 1,
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
statusAction: 'getStatusV2',
|
||||
},
|
||||
},
|
||||
{
|
||||
pendingPhoneActivationConfirmation: {
|
||||
activationId: '654321',
|
||||
phoneNumber: '447911123456',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 16,
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
statusAction: 'getStatusV2',
|
||||
},
|
||||
@@ -372,7 +337,6 @@ test('phone verification helper uses HeroSMS getStatusV2 after acquiring a numbe
|
||||
]);
|
||||
const actions = requests.map((url) => url.searchParams.get('action'));
|
||||
assert.deepStrictEqual(actions, [
|
||||
'getPrices',
|
||||
'getNumber',
|
||||
'getNumberV2',
|
||||
'getStatusV2',
|
||||
@@ -381,117 +345,32 @@ test('phone verification helper uses HeroSMS getStatusV2 after acquiring a numbe
|
||||
]);
|
||||
});
|
||||
|
||||
test('phone verification helper refreshes maxPrice when HeroSMS returns WRONG_MAX_PRICE', async () => {
|
||||
const requests = [];
|
||||
let getNumberAttempt = 0;
|
||||
test('phone verification helper keeps the user-provided maxPrice and surfaces HeroSMS price errors', async () => {
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (action === 'getPrices') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload(),
|
||||
};
|
||||
}
|
||||
if (action === 'getNumber') {
|
||||
getNumberAttempt += 1;
|
||||
return getNumberAttempt === 1
|
||||
? {
|
||||
ok: false,
|
||||
text: async () => 'WRONG_MAX_PRICE:0.09',
|
||||
}
|
||||
: {
|
||||
ok: true,
|
||||
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
||||
};
|
||||
return {
|
||||
ok: false,
|
||||
text: async () => 'WRONG_MAX_PRICE:0.09',
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||
},
|
||||
getState: async () => ({ heroSmsApiKey: 'demo-key' }),
|
||||
getState: async () => ({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '0.08' }),
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const activation = await helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key' });
|
||||
|
||||
assert.deepStrictEqual(activation, {
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
});
|
||||
assert.equal(requests.length, 3);
|
||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
||||
assert.equal(requests[1].searchParams.get('action'), 'getNumber');
|
||||
assert.equal(requests[1].searchParams.get('maxPrice'), '0.08');
|
||||
assert.equal(requests[2].searchParams.get('action'), 'getNumber');
|
||||
assert.equal(requests[2].searchParams.get('maxPrice'), '0.09');
|
||||
assert.equal(requests[2].searchParams.get('fixedPrice'), 'true');
|
||||
});
|
||||
|
||||
test('phone verification helper falls back to plain getNumber when priced request fails to fetch', async () => {
|
||||
const requests = [];
|
||||
let getNumberAttempt = 0;
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (action === 'getPrices') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload(),
|
||||
};
|
||||
}
|
||||
if (action === 'getNumber') {
|
||||
getNumberAttempt += 1;
|
||||
if (getNumberAttempt === 1) {
|
||||
throw new TypeError('Failed to fetch');
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||
},
|
||||
getState: async () => ({ heroSmsApiKey: 'demo-key' }),
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const activation = await helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key' });
|
||||
|
||||
assert.deepStrictEqual(activation, {
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
});
|
||||
assert.equal(requests.length, 3);
|
||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
||||
assert.equal(requests[1].searchParams.get('action'), 'getNumber');
|
||||
assert.equal(requests[1].searchParams.get('maxPrice'), '0.08');
|
||||
assert.equal(requests[1].searchParams.get('fixedPrice'), 'true');
|
||||
assert.equal(requests[2].searchParams.get('action'), 'getNumber');
|
||||
assert.equal(requests[2].searchParams.get('maxPrice'), null);
|
||||
assert.equal(requests[2].searchParams.get('fixedPrice'), null);
|
||||
await assert.rejects(
|
||||
() => helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '0.08' }),
|
||||
/WRONG_MAX_PRICE:0.09/
|
||||
);
|
||||
});
|
||||
|
||||
test('phone verification helper completes add-phone flow, clears current activation, and stores reusable number state', async () => {
|
||||
@@ -499,6 +378,7 @@ test('phone verification helper completes add-phone flow, clears current activat
|
||||
const stateUpdates = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsMaxPrice: '0.08',
|
||||
verificationResendCount: 1,
|
||||
currentPhoneActivation: null,
|
||||
reusablePhoneActivation: null,
|
||||
@@ -511,12 +391,6 @@ test('phone verification helper completes add-phone flow, clears current activat
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (action === 'getPrices') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload(),
|
||||
};
|
||||
}
|
||||
if (action === 'getNumber') {
|
||||
return {
|
||||
ok: true,
|
||||
@@ -593,7 +467,18 @@ test('phone verification helper completes add-phone flow, clears current activat
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 1,
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
pendingPhoneActivationConfirmation: {
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
},
|
||||
},
|
||||
@@ -603,7 +488,87 @@ test('phone verification helper completes add-phone flow, clears current activat
|
||||
]);
|
||||
|
||||
const actions = requests.map((url) => url.searchParams.get('action'));
|
||||
assert.deepStrictEqual(actions, ['getPrices', 'getNumber', 'getStatus', 'setStatus']);
|
||||
assert.deepStrictEqual(actions, ['getNumber', 'getStatus', 'setStatus']);
|
||||
});
|
||||
|
||||
test('phone verification helper still succeeds when HeroSMS setStatus(3) fails after a successful submit', async () => {
|
||||
const requests = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsMaxPrice: '0.08',
|
||||
verificationResendCount: 1,
|
||||
currentPhoneActivation: null,
|
||||
reusablePhoneActivation: null,
|
||||
};
|
||||
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (action === 'getNumber') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => 'ACCESS_NUMBER:123456:66959916439',
|
||||
};
|
||||
}
|
||||
if (action === 'getStatus') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => 'STATUS_OK:654321',
|
||||
};
|
||||
}
|
||||
if (action === 'setStatus') {
|
||||
return {
|
||||
ok: false,
|
||||
text: async () => 'TEMPORARY_ERROR',
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getState: async () => ({ ...currentState }),
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
if (message.type === 'SUBMIT_PHONE_NUMBER') {
|
||||
return {
|
||||
phoneVerificationPage: true,
|
||||
url: 'https://auth.openai.com/phone-verification',
|
||||
};
|
||||
}
|
||||
if (message.type === 'SUBMIT_PHONE_VERIFICATION_CODE') {
|
||||
return {
|
||||
success: true,
|
||||
consentReady: true,
|
||||
url: 'https://auth.openai.com/authorize',
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected content-script message: ${message.type}`);
|
||||
},
|
||||
setState: async (updates) => {
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const result = await helpers.completePhoneVerificationFlow(1, {
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(result, {
|
||||
success: true,
|
||||
consentReady: true,
|
||||
url: 'https://auth.openai.com/authorize',
|
||||
});
|
||||
assert.deepStrictEqual(currentState.currentPhoneActivation, null);
|
||||
assert.deepStrictEqual(currentState.reusablePhoneActivation, null);
|
||||
assert.deepStrictEqual(currentState.pendingPhoneActivationConfirmation, null);
|
||||
const actions = requests.map((url) => url.searchParams.get('action'));
|
||||
assert.deepStrictEqual(actions, ['getNumber', 'getStatus', 'setStatus']);
|
||||
});
|
||||
|
||||
test('phone verification helper uses the configured HeroSMS country for both number acquisition and add-phone submission', async () => {
|
||||
@@ -611,6 +576,7 @@ test('phone verification helper uses the configured HeroSMS country for both num
|
||||
const submittedPayloads = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsMaxPrice: '0.08',
|
||||
heroSmsCountryId: 16,
|
||||
heroSmsCountryLabel: 'United Kingdom',
|
||||
verificationResendCount: 0,
|
||||
@@ -625,12 +591,6 @@ test('phone verification helper uses the configured HeroSMS country for both num
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (action === 'getPrices') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload({ country: '16' }),
|
||||
};
|
||||
}
|
||||
if (action === 'getNumber') {
|
||||
return {
|
||||
ok: true,
|
||||
@@ -688,12 +648,10 @@ test('phone verification helper uses the configured HeroSMS country for both num
|
||||
consentReady: true,
|
||||
url: 'https://auth.openai.com/authorize',
|
||||
});
|
||||
assert.equal(requests[0].searchParams.get('action'), 'getPrices');
|
||||
assert.equal(requests[0].searchParams.get('action'), 'getNumber');
|
||||
assert.equal(requests[0].searchParams.get('country'), '16');
|
||||
assert.equal(requests[1].searchParams.get('action'), 'getNumber');
|
||||
assert.equal(requests[1].searchParams.get('country'), '16');
|
||||
assert.equal(requests[1].searchParams.get('maxPrice'), '0.08');
|
||||
assert.equal(requests[1].searchParams.get('fixedPrice'), 'true');
|
||||
assert.equal(requests[0].searchParams.get('maxPrice'), '0.08');
|
||||
assert.equal(requests[0].searchParams.get('fixedPrice'), 'true');
|
||||
assert.deepStrictEqual(submittedPayloads, [{
|
||||
phoneNumber: '447911123456',
|
||||
countryId: 16,
|
||||
@@ -704,8 +662,10 @@ test('phone verification helper uses the configured HeroSMS country for both num
|
||||
test('phone verification helper throws a step-7 restart error after 60 seconds plus one resend window without SMS', async () => {
|
||||
const requests = [];
|
||||
const messages = [];
|
||||
const stateUpdates = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsMaxPrice: '0.08',
|
||||
verificationResendCount: 0,
|
||||
currentPhoneActivation: null,
|
||||
reusablePhoneActivation: null,
|
||||
@@ -725,13 +685,6 @@ test('phone verification helper throws a step-7 restart error after 60 seconds p
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
const id = parsedUrl.searchParams.get('id');
|
||||
|
||||
if (action === 'getPrices') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload(),
|
||||
};
|
||||
}
|
||||
|
||||
if (action === 'getNumber') {
|
||||
return {
|
||||
ok: true,
|
||||
@@ -775,6 +728,7 @@ test('phone verification helper throws a step-7 restart error after 60 seconds p
|
||||
throw new Error(`Unexpected content-script message: ${message.type}`);
|
||||
},
|
||||
setState: async (updates) => {
|
||||
stateUpdates.push(updates);
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {
|
||||
@@ -799,7 +753,6 @@ test('phone verification helper throws a step-7 restart error after 60 seconds p
|
||||
|
||||
const actions = requests.map((url) => `${url.searchParams.get('action')}:${url.searchParams.get('id') || ''}`);
|
||||
assert.deepStrictEqual(actions, [
|
||||
'getPrices:',
|
||||
'getNumber:',
|
||||
'getStatus:123456',
|
||||
'setStatus:123456',
|
||||
@@ -807,6 +760,11 @@ test('phone verification helper throws a step-7 restart error after 60 seconds p
|
||||
'setStatus:123456',
|
||||
]);
|
||||
assert.equal(currentState.currentPhoneActivation, null);
|
||||
assert.equal(
|
||||
stateUpdates.some((updates) => Number(updates.currentPhoneActivation?.successfulUses) > 0),
|
||||
false,
|
||||
'60 seconds without SMS should not increment reuse count'
|
||||
);
|
||||
} finally {
|
||||
Date.now = realDateNow;
|
||||
}
|
||||
@@ -817,6 +775,7 @@ test('phone verification helper replaces the number when code submission returns
|
||||
const messages = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsMaxPrice: '0.08',
|
||||
verificationResendCount: 1,
|
||||
currentPhoneActivation: null,
|
||||
reusablePhoneActivation: null,
|
||||
@@ -838,13 +797,6 @@ test('phone verification helper replaces the number when code submission returns
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
const id = parsedUrl.searchParams.get('id');
|
||||
|
||||
if (action === 'getPrices') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => buildHeroSmsPricesPayload(),
|
||||
};
|
||||
}
|
||||
|
||||
if (action === 'getNumber') {
|
||||
const nextNumber = numbers[numberIndex];
|
||||
numberIndex += 1;
|
||||
@@ -925,11 +877,9 @@ test('phone verification helper replaces the number when code submission returns
|
||||
|
||||
const actions = requests.map((url) => `${url.searchParams.get('action')}:${url.searchParams.get('id') || ''}`);
|
||||
assert.deepStrictEqual(actions, [
|
||||
'getPrices:',
|
||||
'getNumber:',
|
||||
'getStatus:111111',
|
||||
'setStatus:111111',
|
||||
'getPrices:',
|
||||
'getNumber:',
|
||||
'getStatus:222222',
|
||||
'setStatus:222222',
|
||||
@@ -941,15 +891,25 @@ test('phone verification helper replaces the number when code submission returns
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 1,
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
});
|
||||
assert.deepStrictEqual(currentState.pendingPhoneActivationConfirmation, {
|
||||
activationId: '222222',
|
||||
phoneNumber: '66950000002',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
});
|
||||
});
|
||||
|
||||
test('phone verification helper reuses the same number up to three successful registrations', async () => {
|
||||
test('phone verification helper defers maxUses accounting for reused activations until the full flow succeeds', async () => {
|
||||
const requests = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsMaxPrice: '0.08',
|
||||
verificationResendCount: 0,
|
||||
currentPhoneActivation: null,
|
||||
reusablePhoneActivation: {
|
||||
@@ -1031,13 +991,31 @@ test('phone verification helper reuses the same number up to three successful re
|
||||
});
|
||||
assert.equal(requests[0].searchParams.get('action'), 'reactivate');
|
||||
assert.equal(requests[0].searchParams.get('id'), '123456');
|
||||
assert.deepStrictEqual(currentState.reusablePhoneActivation, null);
|
||||
assert.deepStrictEqual(currentState.reusablePhoneActivation, {
|
||||
activationId: '222333',
|
||||
phoneNumber: '66959916439',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 2,
|
||||
maxUses: 3,
|
||||
});
|
||||
assert.deepStrictEqual(currentState.pendingPhoneActivationConfirmation, {
|
||||
activationId: '222333',
|
||||
phoneNumber: '66959916439',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 2,
|
||||
maxUses: 3,
|
||||
});
|
||||
});
|
||||
|
||||
test('phone verification helper keeps maxUses behavior for reused V2 activations', async () => {
|
||||
test('phone verification helper defers maxUses accounting for reused V2 activations until the full flow succeeds', async () => {
|
||||
const requests = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsMaxPrice: '0.08',
|
||||
heroSmsCountryId: 16,
|
||||
heroSmsCountryLabel: 'United Kingdom',
|
||||
verificationResendCount: 0,
|
||||
@@ -1122,5 +1100,131 @@ test('phone verification helper keeps maxUses behavior for reused V2 activations
|
||||
});
|
||||
const actions = requests.map((url) => url.searchParams.get('action'));
|
||||
assert.deepStrictEqual(actions, ['reactivate', 'getStatusV2', 'setStatus']);
|
||||
assert.deepStrictEqual(currentState.reusablePhoneActivation, null);
|
||||
assert.deepStrictEqual(currentState.reusablePhoneActivation, {
|
||||
activationId: '222333',
|
||||
phoneNumber: '447911123456',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 16,
|
||||
successfulUses: 2,
|
||||
maxUses: 3,
|
||||
statusAction: 'getStatusV2',
|
||||
});
|
||||
assert.deepStrictEqual(currentState.pendingPhoneActivationConfirmation, {
|
||||
activationId: '222333',
|
||||
phoneNumber: '447911123456',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 16,
|
||||
successfulUses: 2,
|
||||
maxUses: 3,
|
||||
statusAction: 'getStatusV2',
|
||||
});
|
||||
});
|
||||
|
||||
test('phone verification helper finalizes pending phone activation confirmation after the full flow succeeds', async () => {
|
||||
let currentState = {
|
||||
reusablePhoneActivation: {
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
},
|
||||
pendingPhoneActivationConfirmation: {
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
},
|
||||
};
|
||||
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
getState: async () => ({ ...currentState }),
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setState: async (updates) => {
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const committedActivation = await helpers.finalizePendingPhoneActivationConfirmation();
|
||||
|
||||
assert.deepStrictEqual(committedActivation, {
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 1,
|
||||
maxUses: 3,
|
||||
});
|
||||
assert.deepStrictEqual(currentState.reusablePhoneActivation, {
|
||||
activationId: '123456',
|
||||
phoneNumber: '66959916439',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 1,
|
||||
maxUses: 3,
|
||||
});
|
||||
assert.equal(currentState.pendingPhoneActivationConfirmation, null);
|
||||
});
|
||||
|
||||
test('phone verification helper clears reusable activation when final success exhausts maxUses', async () => {
|
||||
let currentState = {
|
||||
reusablePhoneActivation: {
|
||||
activationId: '222333',
|
||||
phoneNumber: '447911123456',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 16,
|
||||
successfulUses: 2,
|
||||
maxUses: 3,
|
||||
statusAction: 'getStatusV2',
|
||||
},
|
||||
pendingPhoneActivationConfirmation: {
|
||||
activationId: '222333',
|
||||
phoneNumber: '447911123456',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 16,
|
||||
successfulUses: 2,
|
||||
maxUses: 3,
|
||||
statusAction: 'getStatusV2',
|
||||
},
|
||||
};
|
||||
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
getState: async () => ({ ...currentState }),
|
||||
sendToContentScriptResilient: async () => ({}),
|
||||
setState: async (updates) => {
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const committedActivation = await helpers.finalizePendingPhoneActivationConfirmation();
|
||||
|
||||
assert.deepStrictEqual(committedActivation, {
|
||||
activationId: '222333',
|
||||
phoneNumber: '447911123456',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 16,
|
||||
successfulUses: 3,
|
||||
maxUses: 3,
|
||||
statusAction: 'getStatusV2',
|
||||
});
|
||||
assert.equal(currentState.reusablePhoneActivation, null);
|
||||
assert.equal(currentState.pendingPhoneActivationConfirmation, null);
|
||||
});
|
||||
|
||||
@@ -321,6 +321,7 @@ const inputVerificationResendCount = { value: '' };
|
||||
const inputPhoneVerificationEnabled = { checked: false };
|
||||
const DEFAULT_PHONE_VERIFICATION_ENABLED = false;
|
||||
const inputHeroSmsApiKey = { value: '' };
|
||||
const inputHeroSmsMaxPrice = { value: '' };
|
||||
const selectHeroSmsCountry = { value: '52', options: [{ value: '52' }] };
|
||||
const inputRunCount = { value: '' };
|
||||
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
||||
@@ -347,6 +348,7 @@ function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) ||
|
||||
function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
|
||||
function formatAutoStepDelayInputValue(value) { return value == null ? '' : String(value); }
|
||||
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
|
||||
function normalizeHeroSmsMaxPriceValue(value) { return String(value ?? '').trim(); }
|
||||
function normalizeHeroSmsCountryId() { return 52; }
|
||||
function getSelectedHeroSmsCountryOption() { return { label: 'Thailand' }; }
|
||||
function updateHeroSmsPlatformDisplay() {}
|
||||
|
||||
@@ -63,6 +63,7 @@ test('sidepanel html exposes phone verification toggle and dedicated HeroSMS row
|
||||
assert.match(html, /id="input-ip-proxy-enabled"/);
|
||||
assert.match(html, /id="row-hero-sms-platform"/);
|
||||
assert.match(html, /id="row-hero-sms-country"/);
|
||||
assert.match(html, /id="row-hero-sms-max-price"/);
|
||||
assert.match(html, /id="row-hero-sms-api-key"/);
|
||||
assert.doesNotMatch(html, /id="input-account-run-history-text-enabled"/);
|
||||
});
|
||||
@@ -101,6 +102,7 @@ test('updatePhoneVerificationSettingsUI toggles HeroSMS rows from the sms switch
|
||||
const inputPhoneVerificationEnabled = { checked: false };
|
||||
const rowHeroSmsPlatform = { style: { display: 'none' } };
|
||||
const rowHeroSmsCountry = { style: { display: 'none' } };
|
||||
const rowHeroSmsMaxPrice = { style: { display: 'none' } };
|
||||
const rowHeroSmsApiKey = { style: { display: 'none' } };
|
||||
|
||||
${extractFunction('updatePhoneVerificationSettingsUI')}
|
||||
@@ -109,6 +111,7 @@ return {
|
||||
inputPhoneVerificationEnabled,
|
||||
rowHeroSmsPlatform,
|
||||
rowHeroSmsCountry,
|
||||
rowHeroSmsMaxPrice,
|
||||
rowHeroSmsApiKey,
|
||||
updatePhoneVerificationSettingsUI,
|
||||
};
|
||||
@@ -117,12 +120,14 @@ return {
|
||||
api.updatePhoneVerificationSettingsUI();
|
||||
assert.equal(api.rowHeroSmsPlatform.style.display, 'none');
|
||||
assert.equal(api.rowHeroSmsCountry.style.display, 'none');
|
||||
assert.equal(api.rowHeroSmsMaxPrice.style.display, 'none');
|
||||
assert.equal(api.rowHeroSmsApiKey.style.display, 'none');
|
||||
|
||||
api.inputPhoneVerificationEnabled.checked = true;
|
||||
api.updatePhoneVerificationSettingsUI();
|
||||
assert.equal(api.rowHeroSmsPlatform.style.display, '');
|
||||
assert.equal(api.rowHeroSmsCountry.style.display, '');
|
||||
assert.equal(api.rowHeroSmsMaxPrice.style.display, '');
|
||||
assert.equal(api.rowHeroSmsApiKey.style.display, '');
|
||||
});
|
||||
|
||||
@@ -174,6 +179,7 @@ const inputAutoStepDelaySeconds = { value: '' };
|
||||
const inputPhoneVerificationEnabled = { checked: true };
|
||||
const inputVerificationResendCount = { value: '4' };
|
||||
const inputHeroSmsApiKey = { value: 'demo-key' };
|
||||
const inputHeroSmsMaxPrice = { value: '0.08' };
|
||||
const inputAccountRunHistoryHelperBaseUrl = { value: 'http://127.0.0.1:17373' };
|
||||
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
||||
const DEFAULT_HERO_SMS_COUNTRY_ID = 52;
|
||||
@@ -202,6 +208,7 @@ function normalizeAutoStepDelaySeconds(value) { return value === '' ? null : Num
|
||||
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
|
||||
${extractFunction('normalizeHeroSmsCountryId')}
|
||||
${extractFunction('normalizeHeroSmsCountryLabel')}
|
||||
${extractFunction('normalizeHeroSmsMaxPriceValue')}
|
||||
${extractFunction('getSelectedHeroSmsCountryOption')}
|
||||
${extractFunction('collectSettingsPayload')}
|
||||
return { collectSettingsPayload };
|
||||
@@ -213,6 +220,7 @@ return { collectSettingsPayload };
|
||||
assert.equal(payload.accountRunHistoryTextEnabled, true);
|
||||
assert.equal(payload.accountRunHistoryHelperBaseUrl, 'http://127.0.0.1:17373');
|
||||
assert.equal(payload.heroSmsApiKey, 'demo-key');
|
||||
assert.equal(payload.heroSmsMaxPrice, '0.08');
|
||||
assert.equal(payload.heroSmsCountryId, 52);
|
||||
assert.equal(payload.heroSmsCountryLabel, 'Thailand');
|
||||
});
|
||||
|
||||
@@ -114,6 +114,7 @@ function broadcastDataUpdate() {}
|
||||
async function addLog(message) {
|
||||
logMessages.push(message);
|
||||
}
|
||||
async function finalizePhoneActivationAfterSuccessfulFlow() {}
|
||||
async function finalizeIcloudAliasAfterSuccessfulFlow() {}
|
||||
function matchesSourceUrlFamily() {
|
||||
return false;
|
||||
|
||||
@@ -1030,6 +1030,77 @@ test('verification flow waits during resend cooldown instead of tight-looping',
|
||||
assert.ok(sleepCalls[0] >= 1000);
|
||||
});
|
||||
|
||||
test('verification flow notifies onResendRequestedAt when resend is triggered', async () => {
|
||||
const resendRequestedAtCalls = [];
|
||||
const stateUpdates = [];
|
||||
let pollCalls = 0;
|
||||
|
||||
const helpers = api.createVerificationFlowHelpers({
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
getState: async () => ({}),
|
||||
getTabId: async () => 1,
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isStopError: () => false,
|
||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||
MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
|
||||
MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
|
||||
pollCloudflareTempEmailVerificationCode: async () => ({}),
|
||||
pollHotmailVerificationCode: async () => ({}),
|
||||
pollLuckmailVerificationCode: async () => ({}),
|
||||
sendToContentScript: async (_source, message) => {
|
||||
if (message.type === 'RESEND_VERIFICATION_CODE') {
|
||||
return { resent: true };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
sendToMailContentScriptResilient: async (_mail, message) => {
|
||||
if (message.type !== 'POLL_EMAIL') {
|
||||
return {};
|
||||
}
|
||||
pollCalls += 1;
|
||||
return pollCalls === 1
|
||||
? {}
|
||||
: { code: '654321', emailTimestamp: 123 };
|
||||
},
|
||||
setState: async (payload) => {
|
||||
stateUpdates.push(payload);
|
||||
},
|
||||
setStepStatus: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
VERIFICATION_POLL_MAX_ROUNDS: 5,
|
||||
});
|
||||
|
||||
await helpers.resolveVerificationStep(
|
||||
8,
|
||||
{
|
||||
email: 'user@example.com',
|
||||
lastLoginCode: null,
|
||||
},
|
||||
{ provider: 'qq', label: 'QQ 邮箱' },
|
||||
{
|
||||
maxResendRequests: 1,
|
||||
resendIntervalMs: 25000,
|
||||
onResendRequestedAt: async (requestedAt) => {
|
||||
resendRequestedAtCalls.push(Number(requestedAt) || 0);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(resendRequestedAtCalls.length >= 1, true);
|
||||
assert.equal(resendRequestedAtCalls[0] > 0, true);
|
||||
assert.equal(
|
||||
stateUpdates.some((payload) => Number(payload?.loginVerificationRequestedAt) > 0),
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test('verification flow uses resilient signup-page transport when submitting verification code', async () => {
|
||||
const resilientCalls = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user