Refactor GPC plus flow to page recharge
This commit is contained in:
@@ -441,7 +441,7 @@ test('auto-run controller treats phone-number supply exhaustion as round-fatal a
|
||||
assert.equal(runtime.state.autoRunSessionId, 0);
|
||||
});
|
||||
|
||||
test('auto-run controller treats ended GPC task as round-fatal and skips same-round retries', async () => {
|
||||
test('auto-run controller treats ended GPC page flow as round-fatal and skips same-round retries', async () => {
|
||||
const events = {
|
||||
logs: [],
|
||||
broadcasts: [],
|
||||
@@ -528,7 +528,7 @@ test('auto-run controller treats ended GPC task as round-fatal and skips same-ro
|
||||
autoRunAttemptRun: payload.attemptRun ?? 0,
|
||||
autoRunSessionId: payload.sessionId ?? 0,
|
||||
}),
|
||||
getErrorMessage: (error) => String(error?.message || error || '').replace(/^GPC_TASK_ENDED::/i, ''),
|
||||
getErrorMessage: (error) => String(error?.message || error || '').replace(/^GPC_PAGE_FLOW_ENDED::/i, ''),
|
||||
getFirstUnfinishedStep: () => 1,
|
||||
getPendingAutoRunTimerPlan: () => null,
|
||||
getRunningSteps: () => [],
|
||||
@@ -541,7 +541,7 @@ test('auto-run controller treats ended GPC task as round-fatal and skips same-ro
|
||||
getStopRequested: () => false,
|
||||
hasSavedProgress: () => false,
|
||||
isAddPhoneAuthFailure: () => false,
|
||||
isGpcTaskEndedFailure: (error) => /GPC_TASK_ENDED::/i.test(error?.message || String(error || '')),
|
||||
isGpcPageFlowEndedFailure: (error) => /GPC_PAGE_FLOW_ENDED::/i.test(error?.message || String(error || '')),
|
||||
isRestartCurrentAttemptError: () => false,
|
||||
isStopError: (error) => (error?.message || String(error || '')) === '流程已被用户停止。',
|
||||
launchAutoRunTimerPlan: async () => false,
|
||||
@@ -558,7 +558,7 @@ test('auto-run controller treats ended GPC task as round-fatal and skips same-ro
|
||||
runAutoSequenceFromStep: async () => {
|
||||
events.runCalls += 1;
|
||||
if (events.runCalls === 1) {
|
||||
throw new Error('GPC_TASK_ENDED::等待 OTP 超过 60 秒,任务已超时');
|
||||
throw new Error('GPC_PAGE_FLOW_ENDED::步骤 7:GPC 页面等待超时,未检测到订阅完成。');
|
||||
}
|
||||
},
|
||||
runtime,
|
||||
@@ -592,12 +592,12 @@ test('auto-run controller treats ended GPC task as round-fatal and skips same-ro
|
||||
mode: 'restart',
|
||||
});
|
||||
|
||||
assert.equal(events.runCalls, 2, 'ended GPC task should fail current round and continue next round');
|
||||
assert.equal(events.runCalls, 2, 'ended GPC page flow should fail current round and continue next round');
|
||||
assert.equal(events.broadcasts.some(({ phase }) => phase === 'retrying'), false);
|
||||
assert.equal(events.accountRecords.length, 1);
|
||||
assert.equal(events.accountRecords[0].status, 'failed');
|
||||
assert.match(events.accountRecords[0].reason, /等待 OTP/);
|
||||
assert.ok(events.logs.some(({ message }) => /GPC 任务.*继续下一轮|继续下一轮/.test(message)));
|
||||
assert.match(events.accountRecords[0].reason, /GPC 页面等待超时/);
|
||||
assert.ok(events.logs.some(({ message }) => /GPC 页面流程.*继续下一轮|继续下一轮/.test(message)));
|
||||
});
|
||||
|
||||
test('auto-run controller keeps same-round retrying for step9 local replacement exhaustion errors', async () => {
|
||||
|
||||
@@ -152,7 +152,7 @@ test('auto-run controller preserves kiro flow across fresh reset and starts from
|
||||
getStopRequested: () => false,
|
||||
hasSavedNodeProgress: () => false,
|
||||
isAddPhoneAuthFailure: () => false,
|
||||
isGpcTaskEndedFailure: () => false,
|
||||
isGpcPageFlowEndedFailure: () => false,
|
||||
isPhoneSmsPlatformRateLimitFailure: () => false,
|
||||
isPlusCheckoutNonFreeTrialFailure: () => false,
|
||||
isRestartCurrentAttemptError: () => false,
|
||||
@@ -355,7 +355,7 @@ test('auto-run controller clears stale kiro completed statuses before a fresh re
|
||||
getStopRequested: () => false,
|
||||
hasSavedNodeProgress: () => false,
|
||||
isAddPhoneAuthFailure: () => false,
|
||||
isGpcTaskEndedFailure: () => false,
|
||||
isGpcPageFlowEndedFailure: () => false,
|
||||
isKiroProxyFailure: () => false,
|
||||
isPhoneSmsPlatformRateLimitFailure: () => false,
|
||||
isPlusCheckoutNonFreeTrialFailure: () => false,
|
||||
@@ -536,7 +536,7 @@ test('auto-run controller stops immediately on kiro proxy failures even when ski
|
||||
getStopRequested: () => false,
|
||||
hasSavedNodeProgress: () => false,
|
||||
isAddPhoneAuthFailure: () => false,
|
||||
isGpcTaskEndedFailure: () => false,
|
||||
isGpcPageFlowEndedFailure: () => false,
|
||||
isKiroProxyFailure: (error) => /Kiro 注册页出现 AWS 请求异常|Kiro 注册页返回 403|切换代理|更换代理/i.test(error?.message || String(error || '')),
|
||||
isPhoneSmsPlatformRateLimitFailure: () => false,
|
||||
isPlusCheckoutNonFreeTrialFailure: () => false,
|
||||
|
||||
@@ -752,7 +752,7 @@ test('auto-run restarts Plus checkout from step 6 when billing fails for non-fre
|
||||
assert.ok(events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新创建 Plus Checkout/.test(message)));
|
||||
});
|
||||
|
||||
test('auto-run restarts GPC checkout from step 6 when step 7 task polling stalls', async () => {
|
||||
test('auto-run restarts GPC checkout from step 6 when step 7 page flow stalls', async () => {
|
||||
const plusGpcSteps = {
|
||||
6: { key: 'plus-checkout-create' },
|
||||
7: { key: 'plus-checkout-billing' },
|
||||
@@ -765,7 +765,7 @@ test('auto-run restarts GPC checkout from step 6 when step 7 task polling stalls
|
||||
startStep: 6,
|
||||
failureStep: 7,
|
||||
failureBudget: 2,
|
||||
failureMessage: 'GPC API 请求超时(>30 秒):https://gpc.qlhazycoder.top/api/gp/tasks/task_stalled',
|
||||
failureMessage: 'GPC_PAGE_FLOW_ENDED::步骤 7:GPC 页面等待超时,未检测到订阅完成。',
|
||||
stepDefinitions: plusGpcSteps,
|
||||
finalOAuthChainStartStep: 10,
|
||||
customState: {
|
||||
@@ -785,7 +785,7 @@ test('auto-run restarts GPC checkout from step 6 when step 7 task polling stalls
|
||||
events.invalidations.map((entry) => entry.step),
|
||||
[5, 5]
|
||||
);
|
||||
assert.ok(events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新创建 GPC 任务/.test(message)));
|
||||
assert.ok(events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新准备 GPC 页面/.test(message)));
|
||||
});
|
||||
|
||||
test('auto-run treats GPC account binding as recoverable step 6 restart', async () => {
|
||||
@@ -801,7 +801,7 @@ test('auto-run treats GPC account binding as recoverable step 6 restart', async
|
||||
startStep: 6,
|
||||
failureStep: 7,
|
||||
failureBudget: 1,
|
||||
failureMessage: 'GPC_TASK_ENDED::GOPAY已经绑了订阅,需要手动解绑',
|
||||
failureMessage: 'GPC_PAGE_FLOW_ENDED::GOPAY已经绑了订阅,需要手动解绑',
|
||||
stepDefinitions: plusGpcSteps,
|
||||
finalOAuthChainStartStep: 10,
|
||||
customState: {
|
||||
@@ -817,7 +817,7 @@ test('auto-run treats GPC account binding as recoverable step 6 restart', async
|
||||
assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [5]);
|
||||
});
|
||||
|
||||
test('auto-run restarts GPC checkout from step 6 when accessToken cannot be read', async () => {
|
||||
test('auto-run restarts GPC checkout from step 6 when ChatGPT session cannot be read', async () => {
|
||||
const plusGpcSteps = {
|
||||
6: { key: 'plus-checkout-create' },
|
||||
7: { key: 'plus-checkout-billing' },
|
||||
@@ -830,7 +830,7 @@ test('auto-run restarts GPC checkout from step 6 when accessToken cannot be read
|
||||
startStep: 6,
|
||||
failureStep: 6,
|
||||
failureBudget: 1,
|
||||
failureMessage: '步骤 6:GPC 模式获取 accessToken 失败。',
|
||||
failureMessage: '步骤 6:GPC 模式获取 ChatGPT session 失败。',
|
||||
stepDefinitions: plusGpcSteps,
|
||||
finalOAuthChainStartStep: 10,
|
||||
customState: {
|
||||
@@ -844,10 +844,10 @@ test('auto-run restarts GPC checkout from step 6 when accessToken cannot be read
|
||||
|
||||
assert.deepStrictEqual(events.steps, [6, 6, 7, 10, 11, 12, 13]);
|
||||
assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [5]);
|
||||
assert.ok(events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新创建 GPC 任务/.test(message)));
|
||||
assert.ok(events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新准备 GPC 页面/.test(message)));
|
||||
});
|
||||
|
||||
test('auto-run restarts GPC checkout from step 6 when task status has no progress', async () => {
|
||||
test('auto-run restarts GPC checkout from step 6 when page returns without completion', async () => {
|
||||
const plusGpcSteps = {
|
||||
6: { key: 'plus-checkout-create' },
|
||||
7: { key: 'plus-checkout-billing' },
|
||||
@@ -860,7 +860,7 @@ test('auto-run restarts GPC checkout from step 6 when task status has no progres
|
||||
startStep: 6,
|
||||
failureStep: 7,
|
||||
failureBudget: 1,
|
||||
failureMessage: 'GPC_TASK_ENDED::GPC 任务状态超过 60 秒无进展(已创建),请重新创建任务。',
|
||||
failureMessage: 'GPC_PAGE_FLOW_ENDED::步骤 7:GPC 页面已尝试启动 10 次仍未显示订阅完成。',
|
||||
stepDefinitions: plusGpcSteps,
|
||||
finalOAuthChainStartStep: 10,
|
||||
customState: {
|
||||
@@ -874,7 +874,7 @@ test('auto-run restarts GPC checkout from step 6 when task status has no progres
|
||||
|
||||
assert.deepStrictEqual(events.steps, [6, 7, 6, 7, 10, 11, 12, 13]);
|
||||
assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [5]);
|
||||
assert.ok(events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新创建 GPC 任务/.test(message)));
|
||||
assert.ok(events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新准备 GPC 页面/.test(message)));
|
||||
});
|
||||
|
||||
test('auto-run keeps rebuilding GPC checkout beyond three failures', async () => {
|
||||
@@ -890,7 +890,7 @@ test('auto-run keeps rebuilding GPC checkout beyond three failures', async () =>
|
||||
startStep: 6,
|
||||
failureStep: 7,
|
||||
failureBudget: 4,
|
||||
failureMessage: 'GPC_TASK_ENDED::GPC task status stalled, recreate the task.',
|
||||
failureMessage: 'GPC_PAGE_FLOW_ENDED::步骤 7:GPC 页面等待超时,未检测到订阅完成。',
|
||||
stepDefinitions: plusGpcSteps,
|
||||
finalOAuthChainStartStep: 10,
|
||||
customState: {
|
||||
@@ -947,7 +947,7 @@ test('auto-run does not restart GPC checkout when account already has a ChatGPT
|
||||
startStep: 6,
|
||||
failureStep: 7,
|
||||
failureBudget: 1,
|
||||
failureMessage: 'GPC_TASK_ENDED::该账号已经开通过ChatGPT订阅套餐,不能重复订阅。(checkout_order)',
|
||||
failureMessage: 'GPC_PAGE_FLOW_ENDED::该账号已经开通过ChatGPT订阅套餐,不能重复订阅。(checkout_order)',
|
||||
stepDefinitions: plusGpcSteps,
|
||||
finalOAuthChainStartStep: 10,
|
||||
customState: {
|
||||
@@ -962,7 +962,7 @@ test('auto-run does not restart GPC checkout when account already has a ChatGPT
|
||||
assert.ok(result?.error);
|
||||
assert.deepStrictEqual(result.events.steps, [6, 7]);
|
||||
assert.equal(result.events.invalidations.length, 0);
|
||||
assert.ok(!result.events.logs.some(({ message }) => /回到步骤 6 重新创建 GPC 任务/.test(message)));
|
||||
assert.ok(!result.events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新准备 GPC 页面/.test(message)));
|
||||
});
|
||||
|
||||
test('auto-run does not reroute SUB2API session import failures into OAuth restarts', async () => {
|
||||
|
||||
@@ -58,7 +58,6 @@ test('background account history settings are normalized independently from hotm
|
||||
extractFunction('normalizeVerificationResendCount'),
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneMode'),
|
||||
extractFunction('normalizePhoneSmsProvider'),
|
||||
extractFunction('normalizePhoneSmsProviderOrder'),
|
||||
extractFunction('normalizeSignupMethod'),
|
||||
@@ -186,22 +185,23 @@ const self = {
|
||||
normalizeGoPayPin(value) {
|
||||
return String(value || '').trim().replace(/[^\\d]/g, '');
|
||||
},
|
||||
normalizeGpcHelperBaseUrl(value) {
|
||||
return String(value || '')
|
||||
normalizeGpcBaseUrl(value) {
|
||||
return String(value || 'https://gpc.qlhazycoder.top')
|
||||
.trim()
|
||||
.replace(/\\/+$/g, '')
|
||||
.replace(/\\/api\\/checkout\\/start$/i, '')
|
||||
.replace(/\\/api\\/gopay\\/(?:otp|pin)$/i, '')
|
||||
.replace(/\\/api\\/gp\\/tasks(?:\\/[^/?#]+)?(?:\\/(?:otp|pin|stop))?(?:\\?.*)?$/i, '')
|
||||
.replace(/\\/api\\/gp\\/balance(?:\\?.*)?$/i, '')
|
||||
.replace(/\\/api\\/web\\/card\\/balance(?:\\?.*)?$/i, '')
|
||||
.replace(/\\/api\\/card\\/balance(?:\\?.*)?$/i, '')
|
||||
.replace(/\\/api\\/card\\/redeem-api-key(?:\\?.*)?$/i, '');
|
||||
|| 'https://gpc.qlhazycoder.top';
|
||||
},
|
||||
normalizeGpcCardKey(value) {
|
||||
return String(value || '').trim().toUpperCase();
|
||||
},
|
||||
},
|
||||
};
|
||||
const PERSISTED_SETTING_DEFAULTS = {
|
||||
autoStepDelaySeconds: null,
|
||||
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top',
|
||||
gpcBaseUrl: 'https://gpc.qlhazycoder.top',
|
||||
mailProvider: '163',
|
||||
heroSmsMinPrice: '',
|
||||
fiveSimMinPrice: '',
|
||||
@@ -249,37 +249,19 @@ return {
|
||||
assert.equal(api.normalizePersistentSettingValue('plusAccountAccessStrategy', 'cpa_codex_session'), 'cpa_codex_session');
|
||||
assert.equal(api.normalizePersistentSettingValue('plusAccountAccessStrategy', 'unknown'), 'oauth');
|
||||
assert.equal(
|
||||
api.normalizePersistentSettingValue('gopayHelperApiUrl', ' https://gpc.qlhazycoder.top/api/checkout/start '),
|
||||
api.normalizePersistentSettingValue('gpcBaseUrl', ' https://gpc.qlhazycoder.top/api/checkout/start '),
|
||||
'https://gpc.qlhazycoder.top'
|
||||
);
|
||||
assert.equal(
|
||||
api.normalizePersistentSettingValue('gopayHelperApiUrl', ' https://gpc.qlhazycoder.top/api/gp/tasks/task_1/pin '),
|
||||
api.normalizePersistentSettingValue('gpcBaseUrl', ' https://gpc.qlhazycoder.top/api/web/card/balance?card_key=old '),
|
||||
'https://gpc.qlhazycoder.top'
|
||||
);
|
||||
assert.equal(
|
||||
api.normalizePersistentSettingValue('gopayHelperApiUrl', ' https://gpc.qlhazycoder.top/api/gp/balance '),
|
||||
'https://gpc.qlhazycoder.top'
|
||||
);
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperApiUrl', ''), 'https://gpc.qlhazycoder.top');
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperApiKey', ' gpc-123 '), 'gpc-123');
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperPhoneMode', 'auto'), 'auto');
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperPhoneMode', 'builtin'), 'auto');
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperPhoneMode', 'unknown'), 'manual');
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperRemainingUses', '998'), 998);
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperAutoModeEnabled', 1), true);
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperApiKeyStatus', ' active '), 'active');
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperCountryCode', ' 86 '), '+86');
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperPhoneNumber', ' +86 138-0013-8000 '), '+8613800138000');
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperPin', ' 12-34-56 '), '123456');
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperOtpChannel', 'SMS'), 'sms');
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperOtpChannel', 'unknown'), 'whatsapp');
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperLocalSmsHelperEnabled', 1), true);
|
||||
assert.equal(
|
||||
api.normalizePersistentSettingValue('gopayHelperLocalSmsHelperUrl', 'http://127.0.0.1:18767/otp?x=1'),
|
||||
'http://127.0.0.1:18767'
|
||||
);
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperLocalSmsTimeoutSeconds', '999'), 300);
|
||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperLocalSmsPollIntervalSeconds', '0'), 1);
|
||||
assert.equal(api.normalizePersistentSettingValue('gpcBaseUrl', ''), 'https://gpc.qlhazycoder.top');
|
||||
assert.equal(api.normalizePersistentSettingValue('gpcCardKey', ' gpc-6c9f1a32-45734795-914e6f00 '), 'GPC-6C9F1A32-45734795-914E6F00');
|
||||
assert.equal(api.normalizePersistentSettingValue('gpcRemainingUses', '998'), 998);
|
||||
assert.equal(api.normalizePersistentSettingValue('gpcCardStatus', ' active '), 'active');
|
||||
assert.equal(api.normalizePersistentSettingValue('gpcPageStatus', ' running '), 'running');
|
||||
assert.equal(api.normalizePersistentSettingValue('gpcPageStatusText', ' 页面启动 '), '页面启动');
|
||||
assert.equal(api.normalizePersistentSettingValue('verificationResendCount', '7'), 7);
|
||||
assert.equal(api.normalizePersistentSettingValue('verificationResendCount', '-1'), 0);
|
||||
assert.equal(api.normalizePersistentSettingValue('phoneVerificationReplacementLimit', '9'), 9);
|
||||
|
||||
@@ -48,8 +48,8 @@ test('logging/status add-phone detection ignores step 2 phone-entry switch failu
|
||||
assert.equal(loggingStatus.getLoginAuthStateLabel('oauth_consent_page'), 'OAuth 授权页');
|
||||
assert.equal(loggingStatus.getLoginAuthStateLabel('choose_account_page'), 'OpenAI choose account page');
|
||||
assert.equal(
|
||||
loggingStatus.getErrorMessage(new Error('GPC_TASK_ENDED::GPC OTP 超时,请重新创建任务')),
|
||||
'GPC OTP 超时,请重新创建任务'
|
||||
loggingStatus.getErrorMessage(new Error('GPC_PAGE_FLOW_ENDED::步骤 7:GPC 页面等待超时,未检测到订阅完成。')),
|
||||
'步骤 7:GPC 页面等待超时,未检测到订阅完成。'
|
||||
);
|
||||
assert.equal(
|
||||
loggingStatus.isKiroProxyFailure('Kiro 注册页出现 AWS 请求异常,通常是当前代理 IP 或出口区域异常,请先切换代理后再重试。'),
|
||||
|
||||
@@ -233,7 +233,7 @@ function createRouter(overrides = {}) {
|
||||
verifyHotmailAccount: async () => {},
|
||||
refreshGpcCardBalance: overrides.refreshGpcCardBalance || (async (state, options) => {
|
||||
events.balanceRefreshes.push({ state, options });
|
||||
return { balance: '余额 3', remainingUses: 3, autoModeEnabled: true, apiKeyStatus: 'active' };
|
||||
return { balance: '余额 3', remainingUses: 3, cardStatus: 'active' };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -868,39 +868,11 @@ test('message router delegates Kiro manual step 4 without OpenAI auth-tab prereq
|
||||
assert.deepStrictEqual(events.executedSteps, [4]);
|
||||
});
|
||||
|
||||
test('message router resolves GPC OTP manual confirmation without completing step early', async () => {
|
||||
const state = {
|
||||
plusManualConfirmationPending: true,
|
||||
plusManualConfirmationRequestId: 'otp-request-1',
|
||||
plusManualConfirmationStep: 7,
|
||||
plusManualConfirmationMethod: 'gopay-otp',
|
||||
};
|
||||
const { router, events } = createRouter({ state });
|
||||
|
||||
const response = await router.handleMessage({
|
||||
type: 'RESOLVE_PLUS_MANUAL_CONFIRMATION',
|
||||
source: 'sidepanel',
|
||||
payload: {
|
||||
step: 7,
|
||||
requestId: 'otp-request-1',
|
||||
confirmed: true,
|
||||
otp: ' 12-34 56 ',
|
||||
},
|
||||
}, {});
|
||||
|
||||
assert.deepStrictEqual(response, { ok: true });
|
||||
assert.equal(events.notifyCompletions.length, 0);
|
||||
assert.equal(events.stepStatuses.length, 0);
|
||||
assert.equal(events.stateUpdates[0].gopayHelperResolvedOtp, '123456');
|
||||
assert.equal(events.stateUpdates[0].plusManualConfirmationPending, false);
|
||||
assert.deepStrictEqual(events.broadcasts[0], events.stateUpdates[0]);
|
||||
});
|
||||
|
||||
test('message router refreshes GPC balance through explicit sidepanel message', async () => {
|
||||
const state = {
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
gopayHelperApiUrl: 'http://localhost:18473/',
|
||||
gopayHelperApiKey: 'state_api_key',
|
||||
gpcBaseUrl: 'http://localhost:18473/',
|
||||
gpcCardKey: 'GPC-11111111-22222222-33333333',
|
||||
};
|
||||
const { router, events } = createRouter({ state });
|
||||
|
||||
@@ -908,15 +880,15 @@ test('message router refreshes GPC balance through explicit sidepanel message',
|
||||
type: 'REFRESH_GPC_CARD_BALANCE',
|
||||
source: 'sidepanel',
|
||||
payload: {
|
||||
gopayHelperApiKey: 'payload_api_key',
|
||||
gpcCardKey: 'GPC-6C9F1A32-45734795-914E6F00',
|
||||
reason: 'manual',
|
||||
},
|
||||
}, {});
|
||||
|
||||
assert.deepStrictEqual(response, { ok: true, balance: '余额 3', remainingUses: 3, autoModeEnabled: true, apiKeyStatus: 'active' });
|
||||
assert.deepStrictEqual(response, { ok: true, balance: '余额 3', remainingUses: 3, cardStatus: 'active' });
|
||||
assert.equal(events.balanceRefreshes.length, 1);
|
||||
assert.equal(events.balanceRefreshes[0].state.gopayHelperApiUrl, 'http://localhost:18473/');
|
||||
assert.equal(events.balanceRefreshes[0].state.gopayHelperApiKey, 'payload_api_key');
|
||||
assert.equal(events.balanceRefreshes[0].state.gpcBaseUrl, 'http://localhost:18473/');
|
||||
assert.equal(events.balanceRefreshes[0].state.gpcCardKey, 'GPC-6C9F1A32-45734795-914E6F00');
|
||||
assert.deepStrictEqual(events.balanceRefreshes[0].options, { reason: 'manual' });
|
||||
});
|
||||
|
||||
|
||||
@@ -410,7 +410,7 @@ return {
|
||||
cloudflareTempEmailBaseUrl: 'https://mail.example.com',
|
||||
cloudflareTempEmailAdminAuth: 'admin-secret',
|
||||
cloudMailToken: 'cloud-token',
|
||||
gopayHelperApiKey: 'gpc-key',
|
||||
gpcCardKey: 'GPC-6C9F1A32-45734795-914E6F00',
|
||||
heroSmsApiKey: 'hero-key',
|
||||
phoneSmsProvider: 'madao',
|
||||
madaoBaseUrl: 'http://127.0.0.1:7822',
|
||||
@@ -443,7 +443,7 @@ return {
|
||||
assert.equal(api.getPayloadInput().cloudflareTempEmailBaseUrl, 'https://mail.example.com');
|
||||
assert.equal(api.getPayloadInput().cloudflareTempEmailAdminAuth, 'admin-secret');
|
||||
assert.equal(api.getPayloadInput().cloudMailToken, 'cloud-token');
|
||||
assert.equal(api.getPayloadInput().gopayHelperApiKey, 'gpc-key');
|
||||
assert.equal(api.getPayloadInput().gpcCardKey, 'GPC-6C9F1A32-45734795-914E6F00');
|
||||
assert.equal(api.getPayloadInput().heroSmsApiKey, 'hero-key');
|
||||
assert.equal(api.getPayloadInput().phoneSmsProvider, 'madao');
|
||||
assert.equal(api.getPayloadInput().madaoBaseUrl, 'http://127.0.0.1:7822');
|
||||
|
||||
+18
-51
@@ -12,16 +12,9 @@ test('GoPay utils normalize manual OTP input', () => {
|
||||
const api = loadGoPayUtils();
|
||||
assert.equal(api.normalizeGoPayOtp(' 12-34 56 '), '123456');
|
||||
assert.equal(api.normalizeGoPayOtp('abc'), '');
|
||||
assert.equal(api.normalizeGpcOtpChannel('sms'), 'sms');
|
||||
assert.equal(api.normalizeGpcOtpChannel('wa'), 'whatsapp');
|
||||
assert.equal(api.normalizeGpcOtpChannel('unknown'), 'whatsapp');
|
||||
assert.equal(api.normalizeGpcHelperPhoneMode('auto'), 'auto');
|
||||
assert.equal(api.normalizeGpcHelperPhoneMode('builtin'), 'auto');
|
||||
assert.equal(api.normalizeGpcHelperPhoneMode('manual'), 'manual');
|
||||
assert.equal(api.normalizeGpcHelperPhoneMode('unknown'), 'manual');
|
||||
});
|
||||
|
||||
test('GoPay utils keeps GPC helper payment method distinct', () => {
|
||||
test('GoPay utils keeps GPC payment method distinct', () => {
|
||||
const api = loadGoPayUtils();
|
||||
assert.equal(api.normalizePlusPaymentMethod('paypal-hosted'), 'paypal-hosted');
|
||||
assert.equal(api.normalizePlusPaymentMethod('paypal_direct'), 'paypal-hosted');
|
||||
@@ -32,51 +25,26 @@ test('GoPay utils keeps GPC helper payment method distinct', () => {
|
||||
assert.equal(api.normalizePlusPaymentMethod('unknown'), 'paypal');
|
||||
});
|
||||
|
||||
test('GoPay utils builds GPC queue task and balance URLs from helper endpoints', () => {
|
||||
test('GoPay utils builds GPC card balance URLs from portal endpoints', () => {
|
||||
const api = loadGoPayUtils();
|
||||
assert.equal(api.DEFAULT_GPC_HELPER_API_URL, 'https://gpc.qlhazycoder.top');
|
||||
assert.equal(api.normalizeGpcHelperBaseUrl(''), 'https://gpc.qlhazycoder.top');
|
||||
assert.equal(api.normalizeGpcHelperBaseUrl('https://example.com/api/gp/tasks'), 'https://gpc.qlhazycoder.top');
|
||||
assert.equal(api.DEFAULT_GPC_BASE_URL, 'https://gpc.qlhazycoder.top');
|
||||
assert.equal(api.normalizeGpcBaseUrl(''), 'https://gpc.qlhazycoder.top');
|
||||
assert.equal(api.normalizeGpcBaseUrl('https://example.com/api/web/card/balance'), 'https://gpc.qlhazycoder.top');
|
||||
assert.equal(
|
||||
api.buildGpcHelperApiUrl('', '/api/checkout/start'),
|
||||
api.buildGpcApiUrl('', '/api/checkout/start'),
|
||||
'https://gpc.qlhazycoder.top/api/checkout/start'
|
||||
);
|
||||
assert.equal(
|
||||
api.buildGpcApiKeyBalanceUrl('http://localhost:18473/'),
|
||||
'http://localhost:18473/api/gp/balance'
|
||||
api.buildGpcCardBalanceUrl('https://gpc.qlhazycoder.top/api/web/card/balance'),
|
||||
'https://gpc.qlhazycoder.top/api/web/card/balance'
|
||||
);
|
||||
assert.equal(
|
||||
api.buildGpcCardBalanceUrl('https://gpc.qlhazycoder.top/api/gp/balance'),
|
||||
'https://gpc.qlhazycoder.top/api/gp/balance'
|
||||
);
|
||||
assert.deepEqual(
|
||||
api.buildGpcApiKeyHeaders(' gpc-123 ', { Accept: 'application/json' }),
|
||||
{ Accept: 'application/json', 'X-API-Key': 'gpc-123' }
|
||||
);
|
||||
assert.equal(
|
||||
api.buildGpcTaskCreateUrl('https://gpc.qlhazycoder.top/api/checkout/start'),
|
||||
'https://gpc.qlhazycoder.top/api/gp/tasks'
|
||||
);
|
||||
assert.equal(
|
||||
api.buildGpcTaskQueryUrl('https://gpc.qlhazycoder.top/api/gp/tasks/task_old?card_key=old', 'task/1'),
|
||||
'https://gpc.qlhazycoder.top/api/gp/tasks/task%2F1'
|
||||
);
|
||||
assert.equal(
|
||||
api.buildGpcTaskActionUrl('https://gpc.qlhazycoder.top/api/gp/tasks/task_old/stop', 'task_1', 'pin'),
|
||||
'https://gpc.qlhazycoder.top/api/gp/tasks/task_1/pin'
|
||||
);
|
||||
});
|
||||
|
||||
test('GoPay utils builds GPC queue OTP/PIN payloads without card_key', () => {
|
||||
const api = loadGoPayUtils();
|
||||
assert.deepEqual(
|
||||
api.buildGpcTaskOtpPayload({ otp: ' 12-34 56 ', card_key: ' card_1 ', reference_id: 'ref_1' }),
|
||||
{ otp: '123456' }
|
||||
);
|
||||
assert.deepEqual(
|
||||
api.buildGpcTaskPinPayload({ pin: '65-43-21', cardKey: 'card_1', challengeId: 'challenge_1' }),
|
||||
{ pin: '654321' }
|
||||
api.buildGpcCardBalanceUrl('https://gpc.qlhazycoder.top/api/web/card/balance?card_key=old', ' gpc-6c9f1a32-45734795-914e6f00 '),
|
||||
'https://gpc.qlhazycoder.top/api/web/card/balance?card_key=GPC-6C9F1A32-45734795-914E6F00'
|
||||
);
|
||||
assert.equal(api.normalizeGpcCardKey(' gpc-6c9f1a32-45734795-914e6f00 '), 'GPC-6C9F1A32-45734795-914E6F00');
|
||||
assert.equal(api.isGpcCardKeyFormat('GPC-6C9F1A32-45734795-914E6F00'), true);
|
||||
assert.equal(api.isGpcCardKeyFormat('card-key-1'), false);
|
||||
});
|
||||
|
||||
test('GoPay utils formats balance and maps linked-account errors', () => {
|
||||
@@ -102,15 +70,14 @@ test('GoPay utils formats balance and maps linked-account errors', () => {
|
||||
'余额 998/1000,已用 2,状态 active'
|
||||
);
|
||||
assert.equal(api.getGpcBalanceRemainingUses({ data: { remaining_uses: 998 } }), 998);
|
||||
assert.equal(api.isGpcAutoModeEnabled({ data: { auto_mode_enabled: true } }), true);
|
||||
assert.equal(api.isGpcAutoModeEnabled({ data: { auto_mode_enabled: false } }), false);
|
||||
assert.equal(api.getGpcCardStatus({ data: { status: 'active' } }), 'active');
|
||||
assert.deepEqual(
|
||||
api.unwrapGpcResponse({ code: 200, message: 'ok', data: { task_id: 'task_1' } }),
|
||||
{ task_id: 'task_1' }
|
||||
api.unwrapGpcResponse({ code: 200, message: 'ok', data: { remaining_uses: 1 } }),
|
||||
{ remaining_uses: 1 }
|
||||
);
|
||||
assert.equal(
|
||||
api.extractGpcResponseErrorDetail({ errors: [{ loc: ['body', 'otp'], msg: 'Field required' }] }, 422),
|
||||
'body.otp: Field required'
|
||||
api.extractGpcResponseErrorDetail({ errors: [{ loc: ['query', 'card_key'], msg: 'Field required' }] }, 422),
|
||||
'query.card_key: Field required'
|
||||
);
|
||||
assert.equal(
|
||||
api.extractGpcResponseErrorDetail({
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const path = require('node:path');
|
||||
|
||||
const scriptPath = path.join(process.cwd(), 'scripts/gpc_sms_helper_macos.py');
|
||||
|
||||
function runPython(args, options = {}) {
|
||||
for (const command of ['python3', 'python']) {
|
||||
const result = spawnSync(command, args, {
|
||||
encoding: 'utf8',
|
||||
...options,
|
||||
env: {
|
||||
...process.env,
|
||||
PYTHONIOENCODING: 'utf-8',
|
||||
...(options.env || {}),
|
||||
},
|
||||
});
|
||||
if (result.error && result.error.code === 'ENOENT') {
|
||||
continue;
|
||||
}
|
||||
if (process.platform === 'win32' && result.status === 9009) {
|
||||
continue;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
test('GPC SMS helper shows macOS and iPhone forwarding guidance on non-macOS', () => {
|
||||
const help = runPython([scriptPath, '--help']);
|
||||
if (!help) {
|
||||
return;
|
||||
}
|
||||
assert.equal(help.status, 0);
|
||||
if (process.platform === 'darwin') {
|
||||
return;
|
||||
}
|
||||
const run = runPython([scriptPath, '--db', '/tmp/nonexistent-gpc-chat.db'], {
|
||||
timeout: 3000,
|
||||
});
|
||||
assert.ok(run);
|
||||
assert.notEqual(run.status, 0);
|
||||
assert.match(run.stderr, /仅支持 macOS/);
|
||||
assert.match(run.stderr, /iPhone 短信已转发|短信转发/);
|
||||
});
|
||||
|
||||
test('GPC SMS helper filters cached OTP records by order timestamp', () => {
|
||||
const code = `
|
||||
import importlib.util
|
||||
import json
|
||||
|
||||
script_path = ${JSON.stringify(scriptPath)}
|
||||
spec = importlib.util.spec_from_file_location("gpc_sms_helper_macos", script_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
old = {"otp": "111111", "code": "111111", "message_id": "old", "received_at": "2026-05-05T00:00:00+00:00"}
|
||||
fresh = {"otp": "222222", "code": "222222", "message_id": "fresh", "received_at": "2026-05-05T00:00:10+00:00"}
|
||||
state = {"last_otp": fresh, "otps": [fresh, old]}
|
||||
payload = {
|
||||
"without_filter": module.select_otp_record(state, 0)["otp"],
|
||||
"fresh_only": module.select_otp_record(state, module.parse_timestamp_ms("2026-05-05T00:00:05+00:00"))["otp"],
|
||||
"none_after_fresh": module.select_otp_record(state, module.parse_timestamp_ms("2026-05-05T00:00:11+00:00")) is None,
|
||||
}
|
||||
print(json.dumps(payload))
|
||||
`;
|
||||
const run = runPython(['-c', code], {
|
||||
timeout: 3000,
|
||||
env: { GPC_SMS_HELPER_ALLOW_NON_MAC: '1' },
|
||||
});
|
||||
if (!run) {
|
||||
return;
|
||||
}
|
||||
assert.equal(run.status, 0, run.stderr);
|
||||
assert.deepEqual(JSON.parse(run.stdout.trim()), {
|
||||
without_filter: '222222',
|
||||
fresh_only: '222222',
|
||||
none_after_fresh: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('GPC SMS helper selects and consumes cached OTP records by phone', () => {
|
||||
const code = `
|
||||
import importlib.util
|
||||
import json
|
||||
|
||||
script_path = ${JSON.stringify(scriptPath)}
|
||||
spec = importlib.util.spec_from_file_location("gpc_sms_helper_macos", script_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
record_a = {
|
||||
"otp": "111111",
|
||||
"code": "111111",
|
||||
"message_id": "a",
|
||||
"rowid": 1,
|
||||
"phone_e164": "+8615808505050",
|
||||
"account_phone": "+8615808505050",
|
||||
"received_at": "2026-05-05T00:00:00+00:00",
|
||||
}
|
||||
record_b = {
|
||||
"otp": "222222",
|
||||
"code": "222222",
|
||||
"message_id": "b",
|
||||
"rowid": 2,
|
||||
"phone_e164": "+8618984829950",
|
||||
"account_phone": "+8618984829950",
|
||||
"received_at": "2026-05-05T00:00:10+00:00",
|
||||
}
|
||||
module.STATE.update({"last_otp": record_b, "otps": [record_b, record_a]})
|
||||
selected_a = module.select_otp_record(module.get_state(), phone="+8615808505050")
|
||||
module.consume_otp_record(phone="+8615808505050", record=selected_a)
|
||||
state_after = module.get_state()
|
||||
payload = {
|
||||
"selected_a": selected_a["otp"],
|
||||
"selected_b_after": module.select_otp_record(state_after, phone="+8618984829950")["otp"],
|
||||
"selected_a_after": module.select_otp_record(state_after, phone="+8615808505050") is None,
|
||||
"global_after": module.select_otp_record(state_after)["otp"],
|
||||
}
|
||||
print(json.dumps(payload))
|
||||
`;
|
||||
const run = runPython(['-c', code], {
|
||||
timeout: 3000,
|
||||
env: { GPC_SMS_HELPER_ALLOW_NON_MAC: '1' },
|
||||
});
|
||||
if (!run) {
|
||||
return;
|
||||
}
|
||||
assert.equal(run.status, 0, run.stderr);
|
||||
assert.deepEqual(JSON.parse(run.stdout.trim()), {
|
||||
selected_a: '111111',
|
||||
selected_b_after: '222222',
|
||||
selected_a_after: true,
|
||||
global_after: '222222',
|
||||
});
|
||||
});
|
||||
|
||||
test('GPC SMS helper consume keeps newer same-phone OTP when consuming an older record', () => {
|
||||
const code = `
|
||||
import importlib.util
|
||||
import json
|
||||
|
||||
script_path = ${JSON.stringify(scriptPath)}
|
||||
spec = importlib.util.spec_from_file_location("gpc_sms_helper_macos", script_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
record_old = {
|
||||
"otp": "111111",
|
||||
"code": "111111",
|
||||
"message_id": "old",
|
||||
"rowid": 1,
|
||||
"phone_e164": "+8613800138000",
|
||||
"account_phone": "+8613800138000",
|
||||
"received_at": "2026-05-05T00:00:00+00:00",
|
||||
}
|
||||
record_new = {
|
||||
"otp": "222222",
|
||||
"code": "222222",
|
||||
"message_id": "new",
|
||||
"rowid": 2,
|
||||
"phone_e164": "+8613800138000",
|
||||
"account_phone": "+8613800138000",
|
||||
"received_at": "2026-05-05T00:00:10+00:00",
|
||||
}
|
||||
record_other = {
|
||||
"otp": "333333",
|
||||
"code": "333333",
|
||||
"message_id": "other",
|
||||
"rowid": 3,
|
||||
"phone_e164": "+8618984829950",
|
||||
"account_phone": "+8618984829950",
|
||||
"received_at": "2026-05-05T00:00:20+00:00",
|
||||
}
|
||||
module.STATE.update({"last_otp": record_new, "otps": [record_new, record_old, record_other]})
|
||||
module.consume_otp_record(phone="+8613800138000", record=record_old)
|
||||
state_after = module.get_state()
|
||||
payload = {
|
||||
"same_phone_after": module.select_otp_record(state_after, phone="+8613800138000")["otp"],
|
||||
"other_phone_after": module.select_otp_record(state_after, phone="+8618984829950")["otp"],
|
||||
"records_after": [item["message_id"] for item in state_after.get("otps", [])],
|
||||
"global_after": module.select_otp_record(state_after)["otp"],
|
||||
}
|
||||
print(json.dumps(payload))
|
||||
`;
|
||||
const run = runPython(['-c', code], {
|
||||
timeout: 3000,
|
||||
env: { GPC_SMS_HELPER_ALLOW_NON_MAC: '1' },
|
||||
});
|
||||
if (!run) {
|
||||
return;
|
||||
}
|
||||
assert.equal(run.status, 0, run.stderr);
|
||||
assert.deepEqual(JSON.parse(run.stdout.trim()), {
|
||||
same_phone_after: '222222',
|
||||
other_phone_after: '333333',
|
||||
records_after: ['new', 'other'],
|
||||
global_after: '222222',
|
||||
});
|
||||
});
|
||||
@@ -60,8 +60,6 @@ const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = 'oauth';
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
${extractFunction('normalizePlusAccountAccessStrategy')}
|
||||
${extractFunction('getRequestedPlusAccountAccessStrategy')}
|
||||
${extractFunction('updatePlusModeUI')}
|
||||
@@ -72,15 +70,6 @@ function normalizePlusPaymentMethod(value = '') {
|
||||
function getSelectedPlusPaymentMethod() {
|
||||
return normalizePlusPaymentMethod(selectPlusPaymentMethod.value || latestState?.plusPaymentMethod || currentPlusPaymentMethod || 'paypal');
|
||||
}
|
||||
function normalizeGpcHelperPhoneModeValue(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === 'auto' ? 'auto' : 'manual';
|
||||
}
|
||||
function normalizeGpcOtpChannelValue(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === 'sms' ? 'sms' : 'whatsapp';
|
||||
}
|
||||
function isGpcAutoModePermissionDenied() {
|
||||
return false;
|
||||
}
|
||||
function getSelectedPanelMode() {
|
||||
return latestState?.targetId || 'cpa';
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -156,37 +156,6 @@ function createCheckoutContentHarness() {
|
||||
return { checkoutEvents, send };
|
||||
}
|
||||
|
||||
function createGpcBalanceResponse(overrides = {}) {
|
||||
return {
|
||||
code: 200,
|
||||
message: 'ok',
|
||||
data: {
|
||||
api_key: 'gpc_test',
|
||||
status: 'active',
|
||||
auto_mode_enabled: false,
|
||||
total_uses: 1000,
|
||||
remaining_uses: 998,
|
||||
used_uses: 2,
|
||||
...overrides,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createGpcTaskResponse(overrides = {}) {
|
||||
return {
|
||||
code: 200,
|
||||
message: 'ok',
|
||||
data: {
|
||||
task_id: 'task_123',
|
||||
status: 'active',
|
||||
status_text: '处理中',
|
||||
phone_mode: 'manual',
|
||||
remote_stage: 'checkout_start',
|
||||
...overrides,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('Plus checkout create does not wait 20 seconds after opening checkout page', async () => {
|
||||
const events = [];
|
||||
const executor = api.createPlusCheckoutCreateExecutor({
|
||||
@@ -878,435 +847,199 @@ test('Plus checkout content routes same-frame autocomplete query and suggestion
|
||||
assert.equal(checkoutEvents.some((event) => event.type === 'delay' && event.ms !== 2000), false);
|
||||
});
|
||||
|
||||
test('GPC manual checkout injects Plus script before reading ChatGPT session token and sends X-API-Key', async () => {
|
||||
function createFakeGpcPrepareDom() {
|
||||
const dispatches = [];
|
||||
function createElement({ tagName = 'DIV', text = '', placeholder = '', className = '', value = '', disabled = false } = {}) {
|
||||
return {
|
||||
tagName,
|
||||
textContent: text,
|
||||
innerText: text,
|
||||
placeholder,
|
||||
className,
|
||||
value,
|
||||
disabled,
|
||||
clicked: false,
|
||||
style: { display: 'block', visibility: 'visible', opacity: '1' },
|
||||
name: '',
|
||||
id: '',
|
||||
getAttribute(name) {
|
||||
if (name === 'class') return this.className;
|
||||
if (name === 'aria-disabled') return this.disabled ? 'true' : '';
|
||||
if (name === 'placeholder') return this.placeholder;
|
||||
return '';
|
||||
},
|
||||
focus() {},
|
||||
blur() {},
|
||||
scrollIntoView() {},
|
||||
click() {
|
||||
this.clicked = true;
|
||||
},
|
||||
dispatchEvent(event) {
|
||||
dispatches.push({ element: this, type: event?.type || '' });
|
||||
return true;
|
||||
},
|
||||
getBoundingClientRect() {
|
||||
return { width: 120, height: 32 };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const freeButton = createElement({ tagName: 'BUTTON', text: '免费充值' });
|
||||
const cardModeButton = createElement({ tagName: 'BUTTON', text: '卡密充值 使用付费卡密扣次充值' });
|
||||
const startButton = createElement({ tagName: 'BUTTON', text: '开始 Plus 充值' });
|
||||
const cardInputs = [
|
||||
createElement({ tagName: 'INPUT', placeholder: 'XXXXXXXX', className: 'card-key-seg' }),
|
||||
createElement({ tagName: 'INPUT', placeholder: 'XXXXXXXX', className: 'card-key-seg' }),
|
||||
createElement({ tagName: 'INPUT', placeholder: 'XXXXXXXX', className: 'card-key-seg' }),
|
||||
];
|
||||
const sessionTextarea = createElement({
|
||||
tagName: 'TEXTAREA',
|
||||
placeholder: '可直接粘贴完整 session JSON,会自动提取 accessToken',
|
||||
className: 'design-session-input',
|
||||
});
|
||||
|
||||
return {
|
||||
cardModeButton,
|
||||
cardInputs,
|
||||
dispatches,
|
||||
sessionTextarea,
|
||||
document: {
|
||||
querySelectorAll(selector) {
|
||||
const text = String(selector || '');
|
||||
if (text.includes('button') || text.includes('[role="button"]') || text.includes('.design-mode-card')) {
|
||||
return [freeButton, cardModeButton, startButton];
|
||||
}
|
||||
if (text.includes('input.card-key-seg') || text.includes('placeholder*="XXXXXXXX"') || text.includes('maxlength="8"')) {
|
||||
return cardInputs;
|
||||
}
|
||||
if (text === 'textarea') {
|
||||
return [sessionTextarea];
|
||||
}
|
||||
if (text === 'input') {
|
||||
return cardInputs;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
querySelector(selector) {
|
||||
return String(selector || '').includes('textarea') ? sessionTextarea : null;
|
||||
},
|
||||
body: { innerText: 'SYSTEM 页面已就绪' },
|
||||
documentElement: { innerText: 'SYSTEM 页面已就绪' },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('GPC checkout prepare opens page, fills segmented card key, and writes full ChatGPT session JSON', async () => {
|
||||
const events = [];
|
||||
const fetchCalls = [];
|
||||
const dom = createFakeGpcPrepareDom();
|
||||
const session = {
|
||||
user: { email: 'current@example.com' },
|
||||
accessToken: 'session-access-token',
|
||||
expires: '2026-06-01T00:00:00.000Z',
|
||||
};
|
||||
const executor = api.createPlusCheckoutCreateExecutor({
|
||||
addLog: async (message, level = 'info') => events.push({ type: 'log', message, level }),
|
||||
chrome: {
|
||||
tabs: {
|
||||
create: async (payload) => {
|
||||
events.push({ type: 'tab-create', payload });
|
||||
return { id: 77 };
|
||||
return { id: payload.url === 'https://gpc.qlhazycoder.top/' ? 77 : 88, url: payload.url };
|
||||
},
|
||||
get: async () => null,
|
||||
query: async () => [],
|
||||
remove: async (tabId) => events.push({ type: 'tab-remove', tabId }),
|
||||
update: async (tabId, payload) => events.push({ type: 'tab-update', tabId, payload }),
|
||||
},
|
||||
scripting: {
|
||||
executeScript: async (details) => {
|
||||
events.push({ type: 'execute-script', target: details.target, args: details.args });
|
||||
const previous = {
|
||||
document: global.document,
|
||||
window: global.window,
|
||||
location: global.location,
|
||||
Event: global.Event,
|
||||
};
|
||||
global.document = dom.document;
|
||||
global.window = { getComputedStyle: (element) => element?.style || {} };
|
||||
global.location = { href: 'https://gpc.qlhazycoder.top/' };
|
||||
global.Event = class TestEvent { constructor(type) { this.type = type; } };
|
||||
try {
|
||||
return [{ result: details.func(...(details.args || [])) }];
|
||||
} finally {
|
||||
global.document = previous.document;
|
||||
global.window = previous.window;
|
||||
global.location = previous.location;
|
||||
global.Event = previous.Event;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async (step, payload) => events.push({ type: 'complete', step, payload }),
|
||||
ensureContentScriptReadyOnTabUntilStopped: async (source, tabId, options) => events.push({ type: 'ready', source, tabId, options }),
|
||||
fetch: async (url, options = {}) => {
|
||||
fetchCalls.push({ url, options });
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => url.endsWith('/api/gp/balance')
|
||||
? createGpcBalanceResponse({ auto_mode_enabled: false, remaining_uses: 998 })
|
||||
: createGpcTaskResponse({ otp_channel: 'whatsapp' }),
|
||||
};
|
||||
createAutomationTab: async (payload) => {
|
||||
events.push({ type: 'automation-tab-create', payload });
|
||||
return { id: payload.url === 'https://gpc.qlhazycoder.top/' ? 77 : 88, url: payload.url };
|
||||
},
|
||||
ensureContentScriptReadyOnTabUntilStopped: async (source, tabId, options) => events.push({ type: 'ready', source, tabId, options }),
|
||||
queryTabsInAutomationWindow: async () => [],
|
||||
registerTab: async (source, tabId) => events.push({ type: 'register', source, tabId }),
|
||||
sendTabMessageUntilStopped: async (tabId, source, message) => {
|
||||
events.push({ type: 'tab-message', tabId, source, message });
|
||||
return { accessToken: 'session-access-token' };
|
||||
return { session, accessToken: session.accessToken };
|
||||
},
|
||||
setState: async (payload) => events.push({ type: 'set-state', payload }),
|
||||
sleepWithStop: async (ms) => events.push({ type: 'sleep', ms }),
|
||||
waitForTabCompleteUntilStopped: async () => events.push({ type: 'tab-complete' }),
|
||||
waitForTabCompleteUntilStopped: async (tabId) => events.push({ type: 'tab-complete', tabId }),
|
||||
});
|
||||
|
||||
await executor.executePlusCheckoutCreate({
|
||||
email: 'Current.Round+GPC@Example.COM',
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
gopayHelperPhoneMode: 'manual',
|
||||
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||
gopayHelperPhoneNumber: '+8613800138000',
|
||||
gopayPhone: '',
|
||||
gopayHelperCountryCode: '+86',
|
||||
gopayHelperPin: '123456',
|
||||
gopayHelperApiKey: 'gpc_test_123',
|
||||
gpcCardKey: 'AAAA1111BBBB2222CCCC3333',
|
||||
});
|
||||
|
||||
const readyIndex = events.findIndex((event) => event.type === 'ready');
|
||||
const messageIndex = events.findIndex((event) => event.type === 'tab-message');
|
||||
assert.ok(readyIndex >= 0);
|
||||
assert.ok(messageIndex > readyIndex);
|
||||
assert.equal(events[messageIndex].message.type, 'PLUS_CHECKOUT_GET_STATE');
|
||||
assert.deepEqual(events[messageIndex].message.payload, {
|
||||
assert.equal(dom.cardModeButton.clicked, true);
|
||||
assert.deepEqual(dom.cardInputs.map((input) => input.value), ['AAAA1111', 'BBBB2222', 'CCCC3333']);
|
||||
assert.equal(dom.sessionTextarea.value, JSON.stringify(session));
|
||||
const sessionMessage = events.find((event) => event.type === 'tab-message');
|
||||
assert.deepEqual(sessionMessage.message.payload, {
|
||||
includeSession: true,
|
||||
includeAccessToken: true,
|
||||
});
|
||||
assert.equal(fetchCalls.length, 2);
|
||||
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/balance');
|
||||
assert.equal(fetchCalls[0].options.headers['X-API-Key'], 'gpc_test_123');
|
||||
assert.equal(fetchCalls[1].url, 'https://gpc.qlhazycoder.top/api/gp/tasks');
|
||||
const helperPayload = JSON.parse(fetchCalls[1].options.body);
|
||||
assert.deepEqual(helperPayload, {
|
||||
access_token: 'session-access-token',
|
||||
phone_mode: 'manual',
|
||||
country_code: '86',
|
||||
phone_number: '13800138000',
|
||||
otp_channel: 'whatsapp',
|
||||
});
|
||||
assert.equal(fetchCalls[1].options.headers['X-API-Key'], 'gpc_test_123');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'card_key'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'customer_email'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'checkout_ui_mode'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'gopay_link'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'plan_name'), false);
|
||||
assert.equal(events.find((event) => event.type === 'set-state')?.payload?.plusCheckoutSource, 'gpc-helper');
|
||||
assert.equal(events.find((event) => event.type === 'set-state')?.payload?.gopayHelperTaskId, 'task_123');
|
||||
assert.equal(events.find((event) => event.type === 'set-state')?.payload?.gopayHelperTaskStatus, 'active');
|
||||
assert.equal(events.find((event) => event.type === 'set-state')?.payload?.gopayHelperStatusText, '处理中');
|
||||
assert.equal(events.find((event) => event.type === 'set-state')?.payload?.gopayHelperRemoteStage, 'checkout_start');
|
||||
assert.equal(events.find((event) => event.type === 'set-state')?.payload?.gopayHelperReferenceId, '');
|
||||
assert.ok(events.find((event) => event.type === 'set-state')?.payload?.gopayHelperOrderCreatedAt > 0);
|
||||
const executeEvent = events.find((event) => event.type === 'execute-script');
|
||||
assert.equal(executeEvent.target.tabId, 77);
|
||||
assert.equal(executeEvent.args[1], JSON.stringify(session));
|
||||
const statePayload = events.find((event) => event.type === 'set-state')?.payload || {};
|
||||
assert.equal(statePayload.plusCheckoutTabId, 77);
|
||||
assert.equal(statePayload.plusCheckoutSource, 'gpc-helper');
|
||||
assert.equal(statePayload.gpcPageStatus, 'prepared');
|
||||
assert.equal(statePayload.gpcPageStatusText, '页面已准备');
|
||||
assert.equal(events.find((event) => event.type === 'complete')?.step, 'plus-checkout-create');
|
||||
assert.equal(events.find((event) => event.type === 'complete')?.payload?.plusCheckoutSource, 'gpc-helper');
|
||||
});
|
||||
|
||||
|
||||
test('GPC auto checkout only sends access token and API Key', async () => {
|
||||
test('GPC checkout prepare rejects missing card key before opening pages', async () => {
|
||||
const events = [];
|
||||
const fetchCalls = [];
|
||||
const executor = api.createPlusCheckoutCreateExecutor({
|
||||
addLog: async (message, level = 'info') => events.push({ type: 'log', message, level }),
|
||||
chrome: {
|
||||
tabs: {
|
||||
create: async () => {
|
||||
throw new Error('should not open token tab when direct access token exists');
|
||||
},
|
||||
remove: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async (step, payload) => events.push({ type: 'complete', step, payload }),
|
||||
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||
fetch: async (url, options = {}) => {
|
||||
fetchCalls.push({ url, options });
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => url.endsWith('/api/gp/balance')
|
||||
? createGpcBalanceResponse({ auto_mode_enabled: true, remaining_uses: 998 })
|
||||
: createGpcTaskResponse({
|
||||
task_id: 'task_auto',
|
||||
status: 'queued',
|
||||
status_text: '排队中',
|
||||
phone_mode: 'auto',
|
||||
api_waiting_for: '',
|
||||
}),
|
||||
};
|
||||
},
|
||||
registerTab: async () => {},
|
||||
sendTabMessageUntilStopped: async () => ({}),
|
||||
setState: async (payload) => events.push({ type: 'set-state', payload }),
|
||||
sleepWithStop: async () => {},
|
||||
waitForTabCompleteUntilStopped: async () => {},
|
||||
});
|
||||
|
||||
await executor.executePlusCheckoutCreate({
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
gopayHelperPhoneMode: 'auto',
|
||||
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||
chatgptAccessToken: 'state-access-token',
|
||||
gopayHelperApiKey: 'gpc_auto_123',
|
||||
});
|
||||
|
||||
assert.equal(fetchCalls.length, 2);
|
||||
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/balance');
|
||||
assert.equal(fetchCalls[0].options.headers['X-API-Key'], 'gpc_auto_123');
|
||||
assert.equal(fetchCalls[1].url, 'https://gpc.qlhazycoder.top/api/gp/tasks');
|
||||
const helperPayload = JSON.parse(fetchCalls[1].options.body);
|
||||
assert.deepEqual(helperPayload, {
|
||||
access_token: 'state-access-token',
|
||||
phone_mode: 'auto',
|
||||
});
|
||||
assert.equal(fetchCalls[1].options.headers['X-API-Key'], 'gpc_auto_123');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'country_code'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'phone_number'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'otp_channel'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'pin'), false);
|
||||
const statePayload = events.find((event) => event.type === 'set-state')?.payload || {};
|
||||
assert.equal(statePayload.gopayHelperTaskId, 'task_auto');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(statePayload, 'gopayHelperPhoneMode'), false);
|
||||
assert.equal(statePayload.gopayHelperTaskStatus, 'queued');
|
||||
assert.equal(events.find((event) => event.type === 'complete')?.step, 'plus-checkout-create');
|
||||
});
|
||||
|
||||
test('GPC auto checkout keeps running when balance payload omits auto mode permission', async () => {
|
||||
const events = [];
|
||||
const fetchCalls = [];
|
||||
const executor = api.createPlusCheckoutCreateExecutor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
create: async () => {
|
||||
throw new Error('should not open token tab when direct access token exists');
|
||||
events.push({ type: 'tab-create' });
|
||||
throw new Error('should not open tab without card key');
|
||||
},
|
||||
remove: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async (step, payload) => events.push({ type: 'complete', step, payload }),
|
||||
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||
fetch: async (url, options = {}) => {
|
||||
fetchCalls.push({ url, options });
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => url.endsWith('/api/gp/balance')
|
||||
? createGpcBalanceResponse({ auto_mode_enabled: undefined, remaining_uses: 998 })
|
||||
: createGpcTaskResponse({
|
||||
task_id: 'task_auto_unknown_permission',
|
||||
status: 'queued',
|
||||
status_text: '排队中',
|
||||
phone_mode: 'auto',
|
||||
api_waiting_for: '',
|
||||
}),
|
||||
};
|
||||
},
|
||||
registerTab: async () => {},
|
||||
sendTabMessageUntilStopped: async () => ({}),
|
||||
setState: async (payload) => events.push({ type: 'set-state', payload }),
|
||||
sleepWithStop: async () => {},
|
||||
waitForTabCompleteUntilStopped: async () => {},
|
||||
});
|
||||
|
||||
await executor.executePlusCheckoutCreate({
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
gopayHelperPhoneMode: 'auto',
|
||||
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||
chatgptAccessToken: 'state-access-token',
|
||||
gopayHelperApiKey: 'gpc_auto_123',
|
||||
});
|
||||
|
||||
assert.equal(fetchCalls.length, 2);
|
||||
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/balance');
|
||||
assert.equal(fetchCalls[1].url, 'https://gpc.qlhazycoder.top/api/gp/tasks');
|
||||
const helperPayload = JSON.parse(fetchCalls[1].options.body);
|
||||
assert.equal(helperPayload.phone_mode, 'auto');
|
||||
const statePayload = events.find((event) => event.type === 'set-state')?.payload || {};
|
||||
assert.equal(statePayload.gopayHelperTaskId, 'task_auto_unknown_permission');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(statePayload, 'gopayHelperPhoneMode'), false);
|
||||
assert.equal(events.find((event) => event.type === 'complete')?.step, 'plus-checkout-create');
|
||||
});
|
||||
|
||||
test('GPC auto checkout blocks API Keys without auto mode permission', async () => {
|
||||
const fetchCalls = [];
|
||||
const executor = api.createPlusCheckoutCreateExecutor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
create: async () => {
|
||||
throw new Error('should not open token tab when direct access token exists');
|
||||
scripting: {
|
||||
executeScript: async () => {
|
||||
throw new Error('should not inject without card key');
|
||||
},
|
||||
remove: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async () => {},
|
||||
createAutomationTab: async () => {
|
||||
events.push({ type: 'automation-tab-create' });
|
||||
throw new Error('should not open tab without card key');
|
||||
},
|
||||
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||
fetch: async (url, options = {}) => {
|
||||
fetchCalls.push({ url, options });
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => createGpcBalanceResponse({ auto_mode_enabled: false, remaining_uses: 998 }),
|
||||
};
|
||||
},
|
||||
registerTab: async () => {},
|
||||
sendTabMessageUntilStopped: async () => ({}),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
waitForTabCompleteUntilStopped: async () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executePlusCheckoutCreate({
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
gopayHelperPhoneMode: 'auto',
|
||||
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||
chatgptAccessToken: 'state-access-token',
|
||||
gopayHelperApiKey: 'gpc_auto_disabled',
|
||||
}),
|
||||
/未开通自动模式/
|
||||
);
|
||||
|
||||
assert.equal(fetchCalls.length, 1);
|
||||
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/balance');
|
||||
});
|
||||
|
||||
test('GPC checkout blocks exhausted API Keys before creating task', async () => {
|
||||
const fetchCalls = [];
|
||||
const executor = api.createPlusCheckoutCreateExecutor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
create: async () => {
|
||||
throw new Error('should not open token tab when direct access token exists');
|
||||
},
|
||||
remove: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async () => {},
|
||||
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||
fetch: async (url, options = {}) => {
|
||||
fetchCalls.push({ url, options });
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => createGpcBalanceResponse({ auto_mode_enabled: false, remaining_uses: 0 }),
|
||||
};
|
||||
},
|
||||
registerTab: async () => {},
|
||||
sendTabMessageUntilStopped: async () => ({}),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
waitForTabCompleteUntilStopped: async () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executePlusCheckoutCreate({
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
gopayHelperPhoneMode: 'manual',
|
||||
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||
chatgptAccessToken: 'state-access-token',
|
||||
gopayHelperPhoneNumber: '+8613800138000',
|
||||
gopayHelperCountryCode: '+86',
|
||||
gopayHelperPin: '123456',
|
||||
gopayHelperApiKey: 'gpc_exhausted',
|
||||
}),
|
||||
/剩余次数不足/
|
||||
);
|
||||
|
||||
assert.equal(fetchCalls.length, 1);
|
||||
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/balance');
|
||||
});
|
||||
|
||||
test('GPC checkout forwards selected SMS OTP channel', async () => {
|
||||
const fetchCalls = [];
|
||||
const executor = api.createPlusCheckoutCreateExecutor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
create: async () => ({ id: 88 }),
|
||||
remove: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async () => {},
|
||||
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||
fetch: async (url, options = {}) => {
|
||||
fetchCalls.push({ url, options });
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => url.endsWith('/api/gp/balance')
|
||||
? createGpcBalanceResponse({ auto_mode_enabled: false, remaining_uses: 998 })
|
||||
: createGpcTaskResponse({ task_id: 'task_sms', status: 'active', phone_mode: 'manual', remote_stage: 'checkout_start' }),
|
||||
};
|
||||
},
|
||||
registerTab: async () => {},
|
||||
sendTabMessageUntilStopped: async () => ({ accessToken: 'session-access-token' }),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
waitForTabCompleteUntilStopped: async () => {},
|
||||
});
|
||||
|
||||
await executor.executePlusCheckoutCreate({
|
||||
email: 'sms@example.com',
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||
gopayHelperPhoneNumber: '+8613800138000',
|
||||
gopayHelperCountryCode: '+86',
|
||||
gopayHelperPin: '123456',
|
||||
gopayHelperApiKey: 'gpc_sms',
|
||||
gopayHelperOtpChannel: 'sms',
|
||||
});
|
||||
|
||||
assert.equal(fetchCalls.length, 2);
|
||||
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/balance');
|
||||
assert.equal(fetchCalls[0].options.headers['X-API-Key'], 'gpc_sms');
|
||||
const helperPayload = JSON.parse(fetchCalls[1].options.body);
|
||||
assert.equal(helperPayload.phone_mode, 'manual');
|
||||
assert.equal(helperPayload.otp_channel, 'sms');
|
||||
assert.equal(fetchCalls[1].options.headers['X-API-Key'], 'gpc_sms');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'card_key'), false);
|
||||
});
|
||||
|
||||
test('GPC checkout surfaces unified queue API errors', async () => {
|
||||
const fetchCalls = [];
|
||||
const executor = api.createPlusCheckoutCreateExecutor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
create: async () => {
|
||||
throw new Error('should not open token tab when direct access token exists');
|
||||
},
|
||||
remove: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async () => {},
|
||||
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||
fetch: async (url, options = {}) => {
|
||||
fetchCalls.push({ url, options });
|
||||
if (url.endsWith('/api/gp/balance')) {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => createGpcBalanceResponse({ auto_mode_enabled: false, remaining_uses: 998 }),
|
||||
};
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
status: 400,
|
||||
json: async () => ({
|
||||
code: 400,
|
||||
message: 'invalid_param',
|
||||
data: { detail: 'access_token 无效' },
|
||||
}),
|
||||
};
|
||||
},
|
||||
registerTab: async () => {},
|
||||
sendTabMessageUntilStopped: async () => {},
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
waitForTabCompleteUntilStopped: async () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executePlusCheckoutCreate({
|
||||
email: 'paid@example.com',
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||
chatgptAccessToken: 'state-access-token',
|
||||
gopayHelperPhoneNumber: '+8613800138000',
|
||||
gopayHelperCountryCode: '+86',
|
||||
gopayHelperPin: '123456',
|
||||
gopayHelperApiKey: 'gpc_paid_456',
|
||||
}),
|
||||
/创建 GPC 订单失败:access_token 无效/
|
||||
);
|
||||
|
||||
assert.equal(fetchCalls.length, 2);
|
||||
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/balance');
|
||||
assert.equal(fetchCalls[1].url, 'https://gpc.qlhazycoder.top/api/gp/tasks');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(JSON.parse(fetchCalls[1].options.body), 'card_key'), false);
|
||||
assert.equal(fetchCalls[1].options.headers['X-API-Key'], 'gpc_paid_456');
|
||||
});
|
||||
|
||||
test('GPC checkout does not fall back to browser GoPay phone fields', async () => {
|
||||
const executor = api.createPlusCheckoutCreateExecutor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
create: async () => {
|
||||
throw new Error('should not open token tab when direct access token exists');
|
||||
},
|
||||
remove: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async () => {},
|
||||
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||
fetch: async () => {
|
||||
throw new Error('should not call helper API without helper phone');
|
||||
},
|
||||
queryTabsInAutomationWindow: async () => [],
|
||||
registerTab: async () => {},
|
||||
sendTabMessageUntilStopped: async () => {},
|
||||
setState: async () => {},
|
||||
@@ -1317,55 +1050,9 @@ test('GPC checkout does not fall back to browser GoPay phone fields', async () =
|
||||
await assert.rejects(
|
||||
() => executor.executePlusCheckoutCreate({
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
gopayHelperPhoneMode: 'manual',
|
||||
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||
chatgptAccessToken: 'state-access-token',
|
||||
email: 'helper-phone-test@example.com',
|
||||
gopayPhone: '+8613800138000',
|
||||
gopayCountryCode: '+86',
|
||||
gopayPin: '123456',
|
||||
gopayHelperPhoneNumber: '',
|
||||
gopayHelperPin: '123456',
|
||||
gopayHelperApiKey: 'gpc_phone_test',
|
||||
gpcCardKey: '',
|
||||
}),
|
||||
/缺少手机号/
|
||||
);
|
||||
});
|
||||
|
||||
test('GPC checkout rejects missing API Key before calling helper API', async () => {
|
||||
const executor = api.createPlusCheckoutCreateExecutor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
create: async () => {
|
||||
throw new Error('should not open token tab when direct access token exists');
|
||||
},
|
||||
remove: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async () => {},
|
||||
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||
fetch: async () => {
|
||||
throw new Error('should not call helper API without API Key');
|
||||
},
|
||||
registerTab: async () => {},
|
||||
sendTabMessageUntilStopped: async () => {},
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
waitForTabCompleteUntilStopped: async () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executePlusCheckoutCreate({
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||
chatgptAccessToken: 'state-access-token',
|
||||
email: 'missing-card@example.com',
|
||||
gopayHelperPhoneNumber: '+8613800138000',
|
||||
gopayHelperCountryCode: '+86',
|
||||
gopayHelperPin: '123456',
|
||||
gopayHelperApiKey: '',
|
||||
}),
|
||||
/缺少 API Key/
|
||||
/缺少卡密/
|
||||
);
|
||||
assert.equal(events.length, 0);
|
||||
});
|
||||
|
||||
@@ -134,7 +134,7 @@ async function refreshContributionContentHint() {
|
||||
events.push({ type: 'refresh' });
|
||||
${refreshImpl ? 'return (' + refreshImpl + ')();' : 'return null;'}
|
||||
}
|
||||
async function ensureGpcApiKeyReadyForStart() {
|
||||
async function ensureGpcCardKeyReadyForStart() {
|
||||
return true;
|
||||
}
|
||||
${bundle}
|
||||
@@ -323,7 +323,7 @@ async function refreshContributionContentHint() {
|
||||
events.push({ type: 'refresh' });
|
||||
return null;
|
||||
}
|
||||
async function ensureGpcApiKeyReadyForStart() {
|
||||
async function ensureGpcCardKeyReadyForStart() {
|
||||
return true;
|
||||
}
|
||||
${bundle}
|
||||
|
||||
@@ -204,6 +204,7 @@ const selectCfDomain = { value: 'example.com' };
|
||||
const selectTempEmailDomain = { value: 'mail.example.com' };
|
||||
const selectPanelMode = { value: 'cpa' };
|
||||
function getSelectedPlusPaymentMethod() { return 'paypal'; }
|
||||
function normalizeGpcCardKeyInput(value = '') { return String(value || '').trim().toUpperCase(); }
|
||||
const inputVpsUrl = { value: 'https://panel.example.com' };
|
||||
const inputVpsPassword = { value: 'panel-secret' };
|
||||
const inputSub2ApiUrl = { value: 'https://sub.example.com' };
|
||||
|
||||
@@ -95,6 +95,7 @@ const selectCfDomain = { value: '' };
|
||||
const selectTempEmailDomain = { value: '' };
|
||||
const selectPanelMode = { value: 'cpa' };
|
||||
function getSelectedPlusPaymentMethod() { return 'paypal'; }
|
||||
function normalizeGpcCardKeyInput(value = '') { return String(value || '').trim().toUpperCase(); }
|
||||
const inputVpsUrl = { value: '' };
|
||||
const inputVpsPassword = { value: '' };
|
||||
const inputSub2ApiUrl = { value: '' };
|
||||
|
||||
@@ -168,6 +168,7 @@ const selectCfDomain = { value: 'example.com' };
|
||||
const selectTempEmailDomain = { value: 'mail.example.com' };
|
||||
const selectPanelMode = { value: 'cpa' };
|
||||
function getSelectedPlusPaymentMethod() { return 'paypal'; }
|
||||
function normalizeGpcCardKeyInput(value = '') { return String(value || '').trim().toUpperCase(); }
|
||||
const inputVpsUrl = { value: '' };
|
||||
const inputVpsPassword = { value: '' };
|
||||
const inputSub2ApiUrl = { value: '' };
|
||||
|
||||
@@ -1245,6 +1245,7 @@ function getCloudflareTempEmailDomainsFromState() { return { domains: [], active
|
||||
function normalizeCloudflareTempEmailDomainValue(value) { return String(value || '').trim(); }
|
||||
function getSelectedLocalCpaStep9Mode() { return 'submit'; }
|
||||
function getSelectedPlusPaymentMethod() { return 'paypal'; }
|
||||
function normalizeGpcCardKeyInput(value = '') { return String(value || '').trim().toUpperCase(); }
|
||||
function getSelectedMail2925Mode() { return 'provide'; }
|
||||
function getSelectedHotmailServiceMode() { return 'local'; }
|
||||
function buildManagedAliasBaseEmailPayload() { return { gmailBaseEmail: '', mail2925BaseEmail: '', emailPrefix: '' }; }
|
||||
|
||||
@@ -151,14 +151,6 @@ test('sidepanel Plus UI hides PayPal account selector while GoPay is selected',
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||
extractFunction('hasGpcAutoModePermissionField'),
|
||||
extractFunction('isGpcAutoModePermissionDenied'),
|
||||
extractFunction('normalizeGpcOtpChannelValue'),
|
||||
extractFunction('updatePlusModeUI'),
|
||||
].join('\n');
|
||||
|
||||
@@ -168,8 +160,6 @@ let currentPlusPaymentMethod = 'paypal';
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const selectPlusPaymentMethod = { value: 'gopay', style: { display: 'none' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||
@@ -195,14 +185,6 @@ test('sidepanel Plus UI separates PayPal account mode from PayPal no-card bindin
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||
extractFunction('hasGpcAutoModePermissionField'),
|
||||
extractFunction('isGpcAutoModePermissionDenied'),
|
||||
extractFunction('normalizeGpcOtpChannelValue'),
|
||||
extractFunction('updatePlusModeUI'),
|
||||
].join('\n');
|
||||
|
||||
@@ -212,8 +194,6 @@ let currentPlusPaymentMethod = 'paypal-hosted';
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const selectPlusPaymentMethod = { value: 'paypal-hosted', style: { display: 'none' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||
@@ -256,14 +236,6 @@ test('sidepanel Plus UI supports no-payment mode without payment-specific rows',
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||
extractFunction('hasGpcAutoModePermissionField'),
|
||||
extractFunction('isGpcAutoModePermissionDenied'),
|
||||
extractFunction('normalizeGpcOtpChannelValue'),
|
||||
extractFunction('updatePlusModeUI'),
|
||||
].join('\n');
|
||||
|
||||
@@ -273,8 +245,6 @@ let currentPlusPaymentMethod = 'none';
|
||||
let currentPlusAccountAccessStrategy = 'sub2api_codex_session';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const selectPlusPaymentMethod = { value: 'none', style: { display: 'none' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_PAYMENT_METHOD_NONE = 'none';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
@@ -286,7 +256,7 @@ const rowHostedCheckoutVerificationUrl = { style: { display: '' } };
|
||||
const rowHostedCheckoutPhone = { style: { display: '' } };
|
||||
const rowPlusHostedCheckoutOauthDelay = { style: { display: '' } };
|
||||
const rowGoPayPhone = { style: { display: '' } };
|
||||
const rowGpcHelperApi = { style: { display: '' } };
|
||||
const rowGpcCardKey = { style: { display: '' } };
|
||||
${bundle}
|
||||
return {
|
||||
updatePlusModeUI,
|
||||
@@ -297,7 +267,7 @@ return {
|
||||
rowHostedCheckoutPhone,
|
||||
rowPlusHostedCheckoutOauthDelay,
|
||||
rowGoPayPhone,
|
||||
rowGpcHelperApi,
|
||||
rowGpcCardKey,
|
||||
},
|
||||
};
|
||||
`)();
|
||||
@@ -316,14 +286,6 @@ test('sidepanel Plus UI can hide Plus controls when the shared flow capability r
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||
extractFunction('hasGpcAutoModePermissionField'),
|
||||
extractFunction('isGpcAutoModePermissionDenied'),
|
||||
extractFunction('normalizeGpcOtpChannelValue'),
|
||||
extractFunction('updatePlusModeUI'),
|
||||
].join('\n');
|
||||
|
||||
@@ -349,8 +311,6 @@ const rowPlusMode = { style: { display: '' } };
|
||||
const selectPlusPaymentMethod = { value: 'paypal', style: { display: '' } };
|
||||
const rowPlusPaymentMethod = { style: { display: '' } };
|
||||
const rowPayPalAccount = { style: { display: '' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||
@@ -373,7 +333,7 @@ return {
|
||||
assert.equal(api.selectPlusPaymentMethod.style.display, 'none');
|
||||
});
|
||||
|
||||
test('sidepanel step definitions keep GPC helper mode distinct', () => {
|
||||
test('sidepanel step definitions keep GPC payment mode distinct', () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizeSignupMethod'),
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
@@ -436,25 +396,15 @@ test('sidepanel Plus UI shows GPC fields and purchase button only for GPC', () =
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||
extractFunction('hasGpcAutoModePermissionField'),
|
||||
extractFunction('isGpcAutoModePermissionDenied'),
|
||||
extractFunction('normalizeGpcOtpChannelValue'),
|
||||
extractFunction('updatePlusModeUI'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
let latestState = { plusPaymentMethod: 'gpc-helper', gopayHelperAutoModeEnabled: true };
|
||||
let latestState = { plusPaymentMethod: 'gpc-helper' };
|
||||
let currentPlusPaymentMethod = 'paypal';
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const selectPlusPaymentMethod = { value: 'gpc-helper', style: { display: 'none' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||
@@ -463,18 +413,7 @@ const plusPaymentMethodCaption = { textContent: '' };
|
||||
const btnGpcCardKeyPurchase = { style: { display: 'none' } };
|
||||
const rowPayPalAccount = { style: { display: '' } };
|
||||
const rowPlusPaymentMethod = { style: { display: 'none' } };
|
||||
const rowGpcHelperApi = { style: { display: 'none' } };
|
||||
const rowGpcHelperCardKey = { style: { display: 'none' } };
|
||||
const rowGpcHelperPhoneMode = { style: { display: 'none' } };
|
||||
const selectGpcHelperPhoneMode = { value: 'manual' };
|
||||
const rowGpcHelperCountryCode = { style: { display: 'none' } };
|
||||
const rowGpcHelperPhone = { style: { display: 'none' } };
|
||||
const rowGpcHelperOtpChannel = { style: { display: 'none' } };
|
||||
const selectGpcHelperOtpChannel = { value: 'whatsapp' };
|
||||
const rowGpcHelperLocalSmsEnabled = { style: { display: 'none' } };
|
||||
const inputGpcHelperLocalSmsEnabled = { checked: false };
|
||||
const rowGpcHelperLocalSmsUrl = { style: { display: 'none' } };
|
||||
const rowGpcHelperPin = { style: { display: 'none' } };
|
||||
const rowGpcCardKey = { style: { display: 'none' } };
|
||||
const rowGoPayCountryCode = { style: { display: 'none' } };
|
||||
const rowGoPayPhone = { style: { display: 'none' } };
|
||||
const rowGoPayOtp = { style: { display: 'none' } };
|
||||
@@ -483,13 +422,10 @@ ${bundle}
|
||||
return {
|
||||
updatePlusModeUI,
|
||||
selectPlusPaymentMethod,
|
||||
selectGpcHelperPhoneMode,
|
||||
selectGpcHelperOtpChannel,
|
||||
inputGpcHelperLocalSmsEnabled,
|
||||
btnGpcCardKeyPurchase,
|
||||
rowPayPalAccount,
|
||||
plusPaymentMethodCaption,
|
||||
rows: { rowGpcHelperApi, rowGpcHelperCardKey, rowGpcHelperPhoneMode, rowGpcHelperCountryCode, rowGpcHelperPhone, rowGpcHelperOtpChannel, rowGpcHelperLocalSmsEnabled, rowGpcHelperLocalSmsUrl, rowGpcHelperPin },
|
||||
rows: { rowGpcCardKey },
|
||||
};
|
||||
`)();
|
||||
|
||||
@@ -497,374 +433,61 @@ return {
|
||||
|
||||
assert.equal(api.rowPayPalAccount.style.display, 'none');
|
||||
assert.equal(api.btnGpcCardKeyPurchase.style.display, '');
|
||||
assert.equal(api.rows.rowGpcHelperApi.style.display, '');
|
||||
assert.equal(api.rows.rowGpcHelperCardKey.style.display, '');
|
||||
assert.equal(api.rows.rowGpcHelperPhoneMode.style.display, '');
|
||||
assert.equal(api.rows.rowGpcHelperPhone.style.display, '');
|
||||
assert.equal(api.rows.rowGpcHelperOtpChannel.style.display, '');
|
||||
assert.equal(api.rows.rowGpcHelperLocalSmsEnabled.style.display, '');
|
||||
assert.equal(api.rows.rowGpcHelperLocalSmsUrl.style.display, 'none');
|
||||
assert.match(api.plusPaymentMethodCaption.textContent, /GPC/);
|
||||
|
||||
api.inputGpcHelperLocalSmsEnabled.checked = true;
|
||||
api.updatePlusModeUI();
|
||||
assert.equal(api.selectGpcHelperOtpChannel.value, 'whatsapp');
|
||||
assert.equal(api.rows.rowGpcHelperLocalSmsUrl.style.display, '');
|
||||
|
||||
api.selectGpcHelperOtpChannel.value = 'sms';
|
||||
api.updatePlusModeUI();
|
||||
assert.equal(api.inputGpcHelperLocalSmsEnabled.checked, true);
|
||||
assert.equal(api.rows.rowGpcHelperLocalSmsEnabled.style.display, '');
|
||||
assert.equal(api.rows.rowGpcHelperLocalSmsUrl.style.display, '');
|
||||
|
||||
api.selectGpcHelperPhoneMode.value = 'auto';
|
||||
api.updatePlusModeUI();
|
||||
assert.equal(api.rows.rowGpcHelperPhoneMode.style.display, '');
|
||||
assert.equal(api.rows.rowGpcHelperPhone.style.display, 'none');
|
||||
assert.equal(api.rows.rowGpcHelperOtpChannel.style.display, 'none');
|
||||
assert.equal(api.rows.rowGpcHelperLocalSmsEnabled.style.display, 'none');
|
||||
assert.equal(api.rows.rowGpcHelperLocalSmsUrl.style.display, 'none');
|
||||
assert.match(api.plusPaymentMethodCaption.textContent, /自动/);
|
||||
assert.equal(api.rows.rowGpcCardKey.style.display, '');
|
||||
assert.equal(api.plusPaymentMethodCaption.textContent, 'GPC 网页充值链路');
|
||||
|
||||
api.selectPlusPaymentMethod.value = 'gopay';
|
||||
api.updatePlusModeUI();
|
||||
assert.equal(api.btnGpcCardKeyPurchase.style.display, 'none');
|
||||
assert.equal(api.rows.rowGpcHelperApi.style.display, 'none');
|
||||
assert.equal(api.rows.rowGpcCardKey.style.display, 'none');
|
||||
assert.equal(api.rowPayPalAccount.style.display, 'none');
|
||||
});
|
||||
|
||||
test('sidepanel keeps selected GPC auto mode when API Key has no auto permission', () => {
|
||||
test('sidepanel start check only requires a GPC card key', async () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||
extractFunction('hasGpcAutoModePermissionField'),
|
||||
extractFunction('isGpcAutoModePermissionDenied'),
|
||||
extractFunction('normalizeGpcOtpChannelValue'),
|
||||
extractFunction('updatePlusModeUI'),
|
||||
extractFunction('normalizeGpcCardKeyInput'),
|
||||
extractFunction('isGpcCardKeyInputFormat'),
|
||||
extractFunction('setGpcCardKeyStatus'),
|
||||
extractFunction('ensureGpcCardKeyReadyForStart'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
let latestState = { plusPaymentMethod: 'gpc-helper', gopayHelperPhoneMode: 'auto', gopayHelperAutoModeEnabled: false, gopayHelperBalancePayload: { auto_mode_enabled: false } };
|
||||
let currentPlusPaymentMethod = 'gpc-helper';
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const selectPlusPaymentMethod = { value: 'gpc-helper', style: { display: 'none' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
const plusPaymentMethodCaption = { textContent: '' };
|
||||
const btnGpcCardKeyPurchase = { style: { display: 'none' } };
|
||||
const rowPayPalAccount = { style: { display: '' } };
|
||||
const rowPlusPaymentMethod = { style: { display: 'none' } };
|
||||
const rowGpcHelperApi = { style: { display: 'none' } };
|
||||
const rowGpcHelperCardKey = { style: { display: 'none' } };
|
||||
const rowGpcHelperPhoneMode = { style: { display: 'none' } };
|
||||
const selectGpcHelperPhoneMode = { value: 'auto' };
|
||||
const rowGpcHelperCountryCode = { style: { display: 'none' } };
|
||||
const rowGpcHelperPhone = { style: { display: 'none' } };
|
||||
const rowGpcHelperOtpChannel = { style: { display: 'none' } };
|
||||
const selectGpcHelperOtpChannel = { value: 'whatsapp' };
|
||||
const rowGpcHelperLocalSmsEnabled = { style: { display: 'none' } };
|
||||
const inputGpcHelperLocalSmsEnabled = { checked: false };
|
||||
const rowGpcHelperLocalSmsUrl = { style: { display: 'none' } };
|
||||
const rowGpcHelperPin = { style: { display: 'none' } };
|
||||
${bundle}
|
||||
return { updatePlusModeUI, selectGpcHelperPhoneMode, plusPaymentMethodCaption, rows: { rowGpcHelperPhoneMode, rowGpcHelperPhone, rowGpcHelperOtpChannel, rowGpcHelperPin } };
|
||||
`)();
|
||||
|
||||
api.updatePlusModeUI();
|
||||
|
||||
assert.equal(api.rows.rowGpcHelperPhoneMode.style.display, '');
|
||||
assert.equal(api.selectGpcHelperPhoneMode.value, 'auto');
|
||||
assert.equal(api.rows.rowGpcHelperPhone.style.display, 'none');
|
||||
assert.equal(api.rows.rowGpcHelperOtpChannel.style.display, 'none');
|
||||
assert.equal(api.rows.rowGpcHelperPin.style.display, 'none');
|
||||
assert.match(api.plusPaymentMethodCaption.textContent, /手动/);
|
||||
});
|
||||
|
||||
test('sidepanel keeps selected GPC auto mode when persisted permission survives stop refresh', () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||
extractFunction('hasGpcAutoModePermissionField'),
|
||||
extractFunction('isGpcAutoModePermissionDenied'),
|
||||
extractFunction('normalizeGpcOtpChannelValue'),
|
||||
extractFunction('updatePlusModeUI'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
let latestState = {
|
||||
plusPaymentMethod: 'gpc-helper',
|
||||
gopayHelperPhoneMode: 'auto',
|
||||
gopayHelperAutoModeEnabled: true,
|
||||
gopayHelperBalancePayload: { auto_mode_enabled: true },
|
||||
};
|
||||
let currentPlusPaymentMethod = 'gpc-helper';
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const selectPlusPaymentMethod = { value: 'gpc-helper', style: { display: 'none' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
const plusPaymentMethodCaption = { textContent: '' };
|
||||
const rowPayPalAccount = { style: { display: '' } };
|
||||
const rowPlusPaymentMethod = { style: { display: 'none' } };
|
||||
const rowGpcHelperApi = { style: { display: 'none' } };
|
||||
const rowGpcHelperCardKey = { style: { display: 'none' } };
|
||||
const rowGpcHelperPhoneMode = { style: { display: 'none' } };
|
||||
const selectGpcHelperPhoneMode = { value: 'auto' };
|
||||
const rowGpcHelperCountryCode = { style: { display: 'none' } };
|
||||
const rowGpcHelperPhone = { style: { display: 'none' } };
|
||||
const rowGpcHelperOtpChannel = { style: { display: 'none' } };
|
||||
const selectGpcHelperOtpChannel = { value: 'whatsapp' };
|
||||
const rowGpcHelperLocalSmsEnabled = { style: { display: 'none' } };
|
||||
const inputGpcHelperLocalSmsEnabled = { checked: false };
|
||||
const rowGpcHelperLocalSmsUrl = { style: { display: 'none' } };
|
||||
const rowGpcHelperPin = { style: { display: 'none' } };
|
||||
${bundle}
|
||||
function syncLatestState(nextState) { latestState = { ...latestState, ...nextState }; }
|
||||
return {
|
||||
updatePlusModeUI,
|
||||
selectGpcHelperPhoneMode,
|
||||
getSelectedPhoneMode() { return selectGpcHelperPhoneMode.value; },
|
||||
getPayloadPhoneMode() {
|
||||
return (() => {
|
||||
return normalizeGpcHelperPhoneModeValue(selectGpcHelperPhoneMode.value);
|
||||
})();
|
||||
},
|
||||
applyDataUpdated(payload) {
|
||||
syncLatestState(payload);
|
||||
if (payload.gopayHelperPhoneMode !== undefined) {
|
||||
selectGpcHelperPhoneMode.value = normalizeGpcHelperPhoneModeValue(payload.gopayHelperPhoneMode);
|
||||
}
|
||||
updatePlusModeUI();
|
||||
},
|
||||
rows: { rowGpcHelperPhoneMode, rowGpcHelperPhone, rowGpcHelperOtpChannel, rowGpcHelperPin },
|
||||
};
|
||||
`)();
|
||||
|
||||
api.updatePlusModeUI();
|
||||
assert.equal(api.getSelectedPhoneMode(), 'auto');
|
||||
assert.equal(api.getPayloadPhoneMode(), 'auto');
|
||||
assert.equal(api.rows.rowGpcHelperPhoneMode.style.display, '');
|
||||
assert.equal(api.rows.rowGpcHelperPhone.style.display, 'none');
|
||||
|
||||
api.applyDataUpdated({
|
||||
autoRunning: false,
|
||||
autoRunPhase: 'stopped',
|
||||
gopayHelperAutoModeEnabled: false,
|
||||
});
|
||||
|
||||
assert.equal(api.getSelectedPhoneMode(), 'auto');
|
||||
assert.equal(api.getPayloadPhoneMode(), 'auto');
|
||||
assert.equal(api.rows.rowGpcHelperPhone.style.display, 'none');
|
||||
});
|
||||
|
||||
test('sidepanel keeps selected GPC auto mode before permission has been queried', () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizePlusPaymentMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('getSelectedPlusPaymentMethod'),
|
||||
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||
extractFunction('hasGpcAutoModePermissionField'),
|
||||
extractFunction('isGpcAutoModePermissionDenied'),
|
||||
extractFunction('normalizeGpcOtpChannelValue'),
|
||||
extractFunction('updatePlusModeUI'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
let latestState = { plusPaymentMethod: 'gpc-helper', gopayHelperPhoneMode: 'auto', gopayHelperAutoModeEnabled: false, gopayHelperBalancePayload: null };
|
||||
let currentPlusPaymentMethod = 'gpc-helper';
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
const inputPlusModeEnabled = { checked: true };
|
||||
const selectPlusPaymentMethod = { value: 'gpc-helper', style: { display: 'none' } };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
const plusPaymentMethodCaption = { textContent: '' };
|
||||
const rowPayPalAccount = { style: { display: '' } };
|
||||
const rowPlusPaymentMethod = { style: { display: 'none' } };
|
||||
const rowGpcHelperApi = { style: { display: 'none' } };
|
||||
const rowGpcHelperCardKey = { style: { display: 'none' } };
|
||||
const rowGpcHelperPhoneMode = { style: { display: 'none' } };
|
||||
const selectGpcHelperPhoneMode = { value: 'auto' };
|
||||
const rowGpcHelperCountryCode = { style: { display: 'none' } };
|
||||
const rowGpcHelperPhone = { style: { display: 'none' } };
|
||||
const rowGpcHelperOtpChannel = { style: { display: 'none' } };
|
||||
const selectGpcHelperOtpChannel = { value: 'whatsapp' };
|
||||
const rowGpcHelperLocalSmsEnabled = { style: { display: 'none' } };
|
||||
const inputGpcHelperLocalSmsEnabled = { checked: false };
|
||||
const rowGpcHelperLocalSmsUrl = { style: { display: 'none' } };
|
||||
const rowGpcHelperPin = { style: { display: 'none' } };
|
||||
${bundle}
|
||||
return { updatePlusModeUI, selectGpcHelperPhoneMode, plusPaymentMethodCaption, rows: { rowGpcHelperPhoneMode, rowGpcHelperPhone, rowGpcHelperOtpChannel, rowGpcHelperPin } };
|
||||
`)();
|
||||
|
||||
api.updatePlusModeUI();
|
||||
|
||||
assert.equal(api.rows.rowGpcHelperPhoneMode.style.display, '');
|
||||
assert.equal(api.selectGpcHelperPhoneMode.value, 'auto');
|
||||
assert.equal(api.rows.rowGpcHelperPhone.style.display, 'none');
|
||||
assert.equal(api.rows.rowGpcHelperOtpChannel.style.display, 'none');
|
||||
assert.equal(api.rows.rowGpcHelperPin.style.display, 'none');
|
||||
assert.match(api.plusPaymentMethodCaption.textContent, /自动/);
|
||||
});
|
||||
|
||||
test('sidepanel start check keeps GPC auto mode when balance payload omits permission field', async () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||
extractFunction('isGpcAutoModePermissionDenied'),
|
||||
extractFunction('normalizeGpcRemainingUsesValue'),
|
||||
extractFunction('ensureGpcApiKeyReadyForStart'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
let latestState = { gopayHelperPhoneMode: 'auto' };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const selectGpcHelperPhoneMode = { value: 'auto' };
|
||||
let latestState = { gpcCardKey: '' };
|
||||
const inputGpcCardKey = { value: '' };
|
||||
const displayGpcCardKeyStatus = { textContent: '', dataset: {} };
|
||||
const dialogs = [];
|
||||
let saveCalls = 0;
|
||||
let updateCalls = 0;
|
||||
const toasts = [];
|
||||
const window = { GoPayUtils: null };
|
||||
${bundle}
|
||||
function isGpcHelperCheckoutSelected() { return true; }
|
||||
function getSelectedGpcHelperPhoneMode() { return selectGpcHelperPhoneMode.value; }
|
||||
async function refreshGpcBalanceForStart() {
|
||||
return {
|
||||
gopayHelperRemainingUses: 998,
|
||||
gopayHelperApiKeyStatus: 'active',
|
||||
gopayHelperAutoModeEnabled: false,
|
||||
gopayHelperBalancePayload: {
|
||||
status: 'active',
|
||||
remaining_uses: 998,
|
||||
},
|
||||
};
|
||||
}
|
||||
async function showGpcStartBlockedDialog(message) {
|
||||
dialogs.push(message);
|
||||
}
|
||||
function syncLatestState(nextState) {
|
||||
latestState = { ...latestState, ...nextState };
|
||||
function showToast(message, type, duration) {
|
||||
toasts.push({ message, type, duration });
|
||||
}
|
||||
function updatePlusModeUI() {
|
||||
updateCalls += 1;
|
||||
}
|
||||
async function saveSettings() {
|
||||
saveCalls += 1;
|
||||
}
|
||||
function showToast() {}
|
||||
return {
|
||||
ensureGpcApiKeyReadyForStart,
|
||||
selectGpcHelperPhoneMode,
|
||||
ensureGpcCardKeyReadyForStart,
|
||||
inputGpcCardKey,
|
||||
displayGpcCardKeyStatus,
|
||||
setLatestState(nextState) { latestState = { ...latestState, ...nextState }; },
|
||||
getDialogs: () => dialogs.slice(),
|
||||
getSaveCalls: () => saveCalls,
|
||||
getUpdateCalls: () => updateCalls,
|
||||
getPersistedPhoneMode: () => latestState.gopayHelperPhoneMode,
|
||||
getToasts: () => toasts.slice(),
|
||||
};
|
||||
`)();
|
||||
|
||||
const allowed = await api.ensureGpcApiKeyReadyForStart();
|
||||
assert.equal(await api.ensureGpcCardKeyReadyForStart(), false);
|
||||
assert.deepEqual(api.getDialogs(), ['请先填写 GPC 卡密。']);
|
||||
|
||||
assert.equal(allowed, true);
|
||||
assert.equal(api.selectGpcHelperPhoneMode.value, 'auto');
|
||||
assert.equal(api.getPersistedPhoneMode(), 'auto');
|
||||
assert.equal(api.getSaveCalls(), 0);
|
||||
assert.equal(api.getUpdateCalls(), 0);
|
||||
assert.deepEqual(api.getDialogs(), []);
|
||||
});
|
||||
api.inputGpcCardKey.value = ' card-key-1 ';
|
||||
assert.equal(await api.ensureGpcCardKeyReadyForStart(), false);
|
||||
assert.match(api.getDialogs()[1], /GPC/);
|
||||
assert.equal(api.displayGpcCardKeyStatus.dataset.tone, 'error');
|
||||
|
||||
test('sidepanel start check blocks unsupported GPC auto mode without rewriting selection', async () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||
extractFunction('isGpcAutoModePermissionDenied'),
|
||||
extractFunction('normalizeGpcRemainingUsesValue'),
|
||||
extractFunction('ensureGpcApiKeyReadyForStart'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
let latestState = { gopayHelperPhoneMode: 'auto' };
|
||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||
const selectGpcHelperPhoneMode = { value: 'auto' };
|
||||
const dialogs = [];
|
||||
let saveCalls = 0;
|
||||
let updateCalls = 0;
|
||||
${bundle}
|
||||
function isGpcHelperCheckoutSelected() { return true; }
|
||||
function getSelectedGpcHelperPhoneMode() { return selectGpcHelperPhoneMode.value; }
|
||||
async function refreshGpcBalanceForStart() {
|
||||
return {
|
||||
gopayHelperRemainingUses: 998,
|
||||
gopayHelperApiKeyStatus: 'active',
|
||||
gopayHelperAutoModeEnabled: false,
|
||||
gopayHelperBalancePayload: {
|
||||
status: 'active',
|
||||
remaining_uses: 998,
|
||||
auto_mode_enabled: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
async function showGpcStartBlockedDialog(message) {
|
||||
dialogs.push(message);
|
||||
}
|
||||
function syncLatestState(nextState) {
|
||||
latestState = { ...latestState, ...nextState };
|
||||
}
|
||||
function updatePlusModeUI() {
|
||||
updateCalls += 1;
|
||||
}
|
||||
async function saveSettings() {
|
||||
saveCalls += 1;
|
||||
}
|
||||
function showToast() {}
|
||||
return {
|
||||
ensureGpcApiKeyReadyForStart,
|
||||
selectGpcHelperPhoneMode,
|
||||
getDialogs: () => dialogs.slice(),
|
||||
getSaveCalls: () => saveCalls,
|
||||
getUpdateCalls: () => updateCalls,
|
||||
getPersistedPhoneMode: () => latestState.gopayHelperPhoneMode,
|
||||
};
|
||||
`)();
|
||||
|
||||
const allowed = await api.ensureGpcApiKeyReadyForStart();
|
||||
|
||||
assert.equal(allowed, false);
|
||||
assert.equal(api.selectGpcHelperPhoneMode.value, 'auto');
|
||||
assert.equal(api.getPersistedPhoneMode(), 'auto');
|
||||
assert.equal(api.getSaveCalls(), 0);
|
||||
assert.equal(api.getUpdateCalls(), 0);
|
||||
assert.equal(api.getDialogs().length, 1);
|
||||
api.inputGpcCardKey.value = ' gpc-6c9f1a32-45734795-914e6f00 ';
|
||||
assert.equal(await api.ensureGpcCardKeyReadyForStart({ notify: true }), true);
|
||||
assert.equal(api.inputGpcCardKey.value, 'GPC-6C9F1A32-45734795-914E6F00');
|
||||
assert.deepEqual(api.getToasts(), [{ message: 'GPC 卡密已填写。', type: 'success', duration: 1800 }]);
|
||||
});
|
||||
|
||||
test('sidepanel resolves pending GoPay manual confirmation from DATA_UPDATED state', async () => {
|
||||
@@ -928,75 +551,3 @@ return { events, syncPlusManualConfirmationDialog };
|
||||
assert.match(api.events[2].message, /GoPay/);
|
||||
assert.equal(api.events[2].tone, 'info');
|
||||
});
|
||||
|
||||
test('sidepanel resolves pending GPC OTP with typed code', async () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizeSignupMethod'),
|
||||
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||
extractFunction('normalizePlusStrategyTargetId'),
|
||||
extractFunction('getPlusAccountAccessStrategyContinuationLabel'),
|
||||
extractFunction('resolvePlusManualContinuationActionLabelFromState'),
|
||||
extractLastFunction('openPlusManualConfirmationDialog'),
|
||||
extractLastFunction('syncPlusManualConfirmationDialog'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
const events = [];
|
||||
let latestState = {
|
||||
plusManualConfirmationPending: true,
|
||||
plusManualConfirmationRequestId: 'otp-request-1',
|
||||
plusManualConfirmationStep: 7,
|
||||
plusManualConfirmationMethod: 'gopay-otp',
|
||||
plusManualConfirmationTitle: 'GPC OTP 验证',
|
||||
plusManualConfirmationMessage: '',
|
||||
};
|
||||
let activePlusManualConfirmationRequestId = '';
|
||||
let plusManualConfirmationDialogInFlight = false;
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const SIGNUP_METHOD_EMAIL = 'email';
|
||||
const SIGNUP_METHOD_PHONE = 'phone';
|
||||
const DEFAULT_SIGNUP_METHOD = 'email';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = 'oauth';
|
||||
const sharedFormDialog = {
|
||||
async open(options) {
|
||||
events.push({ type: 'form', options });
|
||||
return { otp: ' 12-34 56 ' };
|
||||
},
|
||||
};
|
||||
function openActionModal(options) {
|
||||
events.push({ type: 'modal', options });
|
||||
return Promise.resolve('confirm');
|
||||
}
|
||||
function showToast(message, tone) {
|
||||
events.push({ type: 'toast', message, tone });
|
||||
}
|
||||
const chrome = {
|
||||
runtime: {
|
||||
async sendMessage(message) {
|
||||
events.push({ type: 'send', message });
|
||||
latestState = { ...latestState, plusManualConfirmationPending: false };
|
||||
return { ok: true };
|
||||
},
|
||||
},
|
||||
};
|
||||
${bundle}
|
||||
return { events, syncPlusManualConfirmationDialog };
|
||||
`)();
|
||||
|
||||
await api.syncPlusManualConfirmationDialog();
|
||||
|
||||
assert.equal(api.events[0].type, 'form');
|
||||
assert.equal(api.events[0].options.message, '请在WhatsApp里面获取验证码(耐心等待三十秒左右)');
|
||||
assert.equal(api.events[0].options.confirmLabel, '提交 OTP');
|
||||
const sendEvent = api.events.find((event) => event.type === 'send');
|
||||
assert.deepEqual(sendEvent.message.payload, {
|
||||
step: 7,
|
||||
requestId: 'otp-request-1',
|
||||
confirmed: true,
|
||||
otp: '123456',
|
||||
});
|
||||
assert.equal(api.events.some((event) => event.type === 'modal'), false);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,16 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const { readStepDefinitionsBundle } = require('./helpers/script-bundles.js');
|
||||
|
||||
function stripHtmlComments(html) {
|
||||
return String(html || '').replace(/<!--[\s\S]*?-->/g, '');
|
||||
}
|
||||
|
||||
function getSelectMarkup(html, selectId) {
|
||||
const match = String(html || '').match(new RegExp(`<select\\b[^>]*id="${selectId}"[^>]*>[\\s\\S]*?<\\/select>`));
|
||||
assert.ok(match, `Expected select #${selectId} to exist`);
|
||||
return match[0];
|
||||
}
|
||||
|
||||
test('step definitions module exposes ordered normal and Plus step metadata', () => {
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${readStepDefinitionsBundle()}; return self.MultiPageStepDefinitions;`)(globalScope);
|
||||
@@ -319,8 +329,8 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
);
|
||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]);
|
||||
assert.equal(api.getLastStepId({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), 13);
|
||||
assert.equal(gpcSteps[6].title, '创建 GPC 订单');
|
||||
assert.equal(gpcSteps[7].title, '等待 GPC 任务完成');
|
||||
assert.equal(gpcSteps[6].title, '打开 GPC 页面并准备');
|
||||
assert.equal(gpcSteps[7].title, '启动并等待 GPC 完成');
|
||||
});
|
||||
|
||||
test('Plus no-payment mode removes only payment chain nodes', () => {
|
||||
@@ -639,8 +649,12 @@ test('sidepanel html loads shared step definitions before sidepanel bootstrap',
|
||||
|
||||
test('sidepanel html exposes Plus mode, PayPal, and GoPay settings', () => {
|
||||
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||
const visibleHtml = stripHtmlComments(html);
|
||||
const plusPaymentSelect = getSelectMarkup(visibleHtml, 'select-plus-payment-method');
|
||||
assert.match(html, /id="input-plus-mode-enabled"/);
|
||||
assert.match(html, /id="select-plus-payment-method"/);
|
||||
assert.match(plusPaymentSelect, /<option value="gpc-helper">GPC<\/option>/);
|
||||
assert.doesNotMatch(plusPaymentSelect, /<option value="gopay">GoPay<\/option>/);
|
||||
assert.match(html, /<option value="none">无需支付<\/option>/);
|
||||
assert.match(html, /id="select-paypal-account"/);
|
||||
assert.match(html, /id="btn-add-paypal-account"/);
|
||||
@@ -649,21 +663,10 @@ test('sidepanel html exposes Plus mode, PayPal, and GoPay settings', () => {
|
||||
assert.match(html, /id="input-gopay-pin"/);
|
||||
assert.match(html, /<option value="gpc-helper">GPC<\/option>/);
|
||||
assert.match(html, /id="btn-gpc-card-key-purchase"/);
|
||||
assert.match(html, /id="btn-gpc-card-key-query"/);
|
||||
assert.match(html, />购买卡密</);
|
||||
assert.match(html, /GPC API/);
|
||||
assert.match(html, /id="input-gpc-helper-api"/);
|
||||
assert.match(html, /id="btn-gpc-helper-convert-api-key"/);
|
||||
assert.match(html, />转换 API Key</);
|
||||
assert.match(html, /GPC API Key/);
|
||||
assert.match(html, /id="input-gpc-helper-card-key"/);
|
||||
assert.match(html, /GPC 模式/);
|
||||
assert.match(html, /id="select-gpc-helper-phone-mode"/);
|
||||
assert.match(html, /<option value="auto">自动模式<\/option>/);
|
||||
assert.match(html, /id="btn-gpc-helper-balance"/);
|
||||
assert.match(html, /id="input-gpc-helper-phone"/);
|
||||
assert.match(html, /id="select-gpc-helper-otp-channel"/);
|
||||
assert.match(html, /id="input-gpc-helper-local-sms-enabled"/);
|
||||
assert.match(html, /id="input-gpc-helper-local-sms-url"/);
|
||||
assert.match(html, /id="input-gpc-helper-pin"/);
|
||||
assert.match(html, />查询</);
|
||||
assert.match(html, /GPC 卡密/);
|
||||
assert.match(html, /id="input-gpc-card-key"/);
|
||||
assert.match(html, /id="shared-form-modal"/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user