fix: 修正 GPC 自动模式在权限未知时的误回退
- 合并 PR #237 的核心改动:停止或状态刷新后保留 GPC 自动模式选择,只有余额回包明确拒绝时才回退手动模式 - 本地补充修复:补齐余额查询、启动前校验和 Step 6 创建前校验在权限字段缺失时的误判,并补充回归测试 - 影响范围:GPC sidepanel 模式切换、Plus Step 6 GPC 创建前校验、相关回归测试
This commit is contained in:
@@ -210,13 +210,42 @@
|
|||||||
return Number.isFinite(numeric) ? Math.max(0, Math.floor(numeric)) : null;
|
return Number.isFinite(numeric) ? Math.max(0, Math.floor(numeric)) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isGpcAutoModeEnabled(payload = {}) {
|
function normalizeGpcAutoModePermissionValue(value) {
|
||||||
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
if (typeof value === 'boolean') {
|
||||||
if (rootScope.GoPayUtils?.isGpcAutoModeEnabled) {
|
return value;
|
||||||
return rootScope.GoPayUtils.isGpcAutoModeEnabled(payload);
|
|
||||||
}
|
}
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
if (value === 1) return true;
|
||||||
|
if (value === 0) return false;
|
||||||
|
}
|
||||||
|
const normalized = String(value ?? '').trim().toLowerCase();
|
||||||
|
if (!normalized) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (['true', '1', 'yes', 'y', 'on', 'enabled', 'enable'].includes(normalized)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (['false', '0', 'no', 'n', 'off', 'disabled', 'disable'].includes(normalized)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGpcAutoModePermission(payload = {}) {
|
||||||
const data = unwrapGpcResponse(payload);
|
const data = unwrapGpcResponse(payload);
|
||||||
return data?.auto_mode_enabled === true || data?.autoModeEnabled === true;
|
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return normalizeGpcAutoModePermissionValue(
|
||||||
|
data.auto_mode_enabled
|
||||||
|
?? data.autoModeEnabled
|
||||||
|
?? data.auto_enabled
|
||||||
|
?? data.autoEnabled
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGpcAutoModePermissionDenied(payload = {}) {
|
||||||
|
return getGpcAutoModePermission(payload) === false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function assertGpcApiKeyReadyForCreate(state = {}, phoneMode = GPC_HELPER_PHONE_MODE_MANUAL, apiKey = '') {
|
async function assertGpcApiKeyReadyForCreate(state = {}, phoneMode = GPC_HELPER_PHONE_MODE_MANUAL, apiKey = '') {
|
||||||
@@ -244,7 +273,7 @@
|
|||||||
if (remainingUses !== null && remainingUses <= 0) {
|
if (remainingUses !== null && remainingUses <= 0) {
|
||||||
throw new Error('创建 GPC 订单失败:API Key 剩余次数不足。');
|
throw new Error('创建 GPC 订单失败:API Key 剩余次数不足。');
|
||||||
}
|
}
|
||||||
if (phoneMode === GPC_HELPER_PHONE_MODE_AUTO && !isGpcAutoModeEnabled(balanceData)) {
|
if (phoneMode === GPC_HELPER_PHONE_MODE_AUTO && isGpcAutoModePermissionDenied(balanceData)) {
|
||||||
throw new Error('创建 GPC 订单失败:当前 GPC API Key 未开通自动模式。');
|
throw new Error('创建 GPC 订单失败:当前 GPC API Key 未开通自动模式。');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-19
@@ -2167,22 +2167,55 @@ function getGpcHelperAutoModeEnabled(state = latestState) {
|
|||||||
return Boolean(state?.gopayHelperAutoModeEnabled);
|
return Boolean(state?.gopayHelperAutoModeEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasGpcAutoModePermissionField(payload = {}) {
|
function normalizeGpcAutoModePermissionValue(value) {
|
||||||
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
if (typeof value === 'boolean') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
if (value === 1) return true;
|
||||||
|
if (value === 0) return false;
|
||||||
|
}
|
||||||
|
const normalized = String(value ?? '').trim().toLowerCase();
|
||||||
|
if (!normalized) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (['true', '1', 'yes', 'y', 'on', 'enabled', 'enable'].includes(normalized)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (['false', '0', 'no', 'n', 'off', 'disabled', 'disable'].includes(normalized)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return payload.auto_mode_enabled !== undefined
|
return null;
|
||||||
|| payload.autoModeEnabled !== undefined
|
}
|
||||||
|| payload.auto_enabled !== undefined
|
|
||||||
|| payload.autoEnabled !== undefined
|
function getGpcAutoModePermissionFromPayload(payload = {}) {
|
||||||
|| (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data) && hasGpcAutoModePermissionField(payload.data));
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (const key of ['auto_mode_enabled', 'autoModeEnabled', 'auto_enabled', 'autoEnabled']) {
|
||||||
|
if (payload[key] !== undefined) {
|
||||||
|
return normalizeGpcAutoModePermissionValue(payload[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) {
|
||||||
|
return getGpcAutoModePermissionFromPayload(payload.data);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldPreserveSelectedGpcAutoMode(state = latestState) {
|
||||||
|
const payloadPermission = getGpcAutoModePermissionFromPayload(state?.gopayHelperBalancePayload);
|
||||||
|
return normalizeGpcHelperPhoneModeValue(state?.gopayHelperPhoneMode) === GPC_HELPER_PHONE_MODE_AUTO
|
||||||
|
&& (Boolean(state?.gopayHelperAutoModeEnabled) || payloadPermission === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasGpcAutoModePermissionField(payload = {}) {
|
||||||
|
return getGpcAutoModePermissionFromPayload(payload) !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isGpcAutoModePermissionDenied(state = latestState) {
|
function isGpcAutoModePermissionDenied(state = latestState) {
|
||||||
if (getGpcHelperAutoModeEnabled(state)) {
|
const payloadPermission = getGpcAutoModePermissionFromPayload(state?.gopayHelperBalancePayload);
|
||||||
return false;
|
return payloadPermission === false;
|
||||||
}
|
|
||||||
return hasGpcAutoModePermissionField(state?.gopayHelperBalancePayload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeGpcRemainingUsesValue(value) {
|
function normalizeGpcRemainingUsesValue(value) {
|
||||||
@@ -3389,7 +3422,10 @@ function collectSettingsPayload() {
|
|||||||
? selectGpcHelperPhoneMode.value
|
? selectGpcHelperPhoneMode.value
|
||||||
: (latestState?.gopayHelperPhoneMode || 'manual')
|
: (latestState?.gopayHelperPhoneMode || 'manual')
|
||||||
);
|
);
|
||||||
const effectiveGpcPhoneMode = (typeof isGpcAutoModePermissionDenied === 'function' && isGpcAutoModePermissionDenied(latestState))
|
const preserveSelectedGpcAutoMode = typeof shouldPreserveSelectedGpcAutoMode === 'function'
|
||||||
|
? shouldPreserveSelectedGpcAutoMode(latestState)
|
||||||
|
: false;
|
||||||
|
const effectiveGpcPhoneMode = (!preserveSelectedGpcAutoMode && typeof isGpcAutoModePermissionDenied === 'function' && isGpcAutoModePermissionDenied(latestState))
|
||||||
? 'manual'
|
? 'manual'
|
||||||
: selectedGpcPhoneMode;
|
: selectedGpcPhoneMode;
|
||||||
const selectedGpcOtpChannel = normalizeGpcOtpChannelSafe(
|
const selectedGpcOtpChannel = normalizeGpcOtpChannelSafe(
|
||||||
@@ -7370,7 +7406,11 @@ function updatePlusModeUI() {
|
|||||||
);
|
);
|
||||||
const gpcAutoModeDenied = isGpcAutoModePermissionDenied(latestState);
|
const gpcAutoModeDenied = isGpcAutoModePermissionDenied(latestState);
|
||||||
const gpcAutoModeEnabled = getGpcHelperAutoModeEnabled(latestState);
|
const gpcAutoModeEnabled = getGpcHelperAutoModeEnabled(latestState);
|
||||||
const isGpcAutoMode = !gpcAutoModeDenied && gpcPhoneMode === GPC_HELPER_PHONE_MODE_AUTO;
|
const preserveSelectedGpcAutoMode = typeof shouldPreserveSelectedGpcAutoMode === 'function'
|
||||||
|
? shouldPreserveSelectedGpcAutoMode(latestState)
|
||||||
|
: false;
|
||||||
|
const effectiveGpcAutoModeDenied = gpcAutoModeDenied && !preserveSelectedGpcAutoMode;
|
||||||
|
const isGpcAutoMode = !effectiveGpcAutoModeDenied && gpcPhoneMode === GPC_HELPER_PHONE_MODE_AUTO;
|
||||||
const gpcOtpChannel = normalizeGpcOtpChannelValue(
|
const gpcOtpChannel = normalizeGpcOtpChannelValue(
|
||||||
typeof selectGpcHelperOtpChannel !== 'undefined' && selectGpcHelperOtpChannel
|
typeof selectGpcHelperOtpChannel !== 'undefined' && selectGpcHelperOtpChannel
|
||||||
? selectGpcHelperOtpChannel.value
|
? selectGpcHelperOtpChannel.value
|
||||||
@@ -7385,7 +7425,7 @@ function updatePlusModeUI() {
|
|||||||
? normalizePlusPaymentMethod(selectPlusPaymentMethod.value)
|
? normalizePlusPaymentMethod(selectPlusPaymentMethod.value)
|
||||||
: method;
|
: method;
|
||||||
const gpcRowsVisible = enabled && selectedMethod === gpcValue;
|
const gpcRowsVisible = enabled && selectedMethod === gpcValue;
|
||||||
const canShowGpcModeSelector = gpcRowsVisible && (gpcAutoModeEnabled || !gpcAutoModeDenied);
|
const canShowGpcModeSelector = gpcRowsVisible && (gpcAutoModeEnabled || !effectiveGpcAutoModeDenied);
|
||||||
const localSmsControlsVisible = gpcRowsVisible && !isGpcAutoMode;
|
const localSmsControlsVisible = gpcRowsVisible && !isGpcAutoMode;
|
||||||
const effectiveLocalSmsEnabled = !isGpcAutoMode && localSmsEnabled;
|
const effectiveLocalSmsEnabled = !isGpcAutoMode && localSmsEnabled;
|
||||||
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
|
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
|
||||||
@@ -7430,7 +7470,7 @@ function updatePlusModeUI() {
|
|||||||
rowGpcHelperPhoneMode.style.display = canShowGpcModeSelector ? '' : 'none';
|
rowGpcHelperPhoneMode.style.display = canShowGpcModeSelector ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode) {
|
if (typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode) {
|
||||||
selectGpcHelperPhoneMode.value = gpcAutoModeDenied ? GPC_HELPER_PHONE_MODE_MANUAL : gpcPhoneMode;
|
selectGpcHelperPhoneMode.value = effectiveGpcAutoModeDenied ? GPC_HELPER_PHONE_MODE_MANUAL : gpcPhoneMode;
|
||||||
}
|
}
|
||||||
[
|
[
|
||||||
typeof rowGpcHelperCountryCode !== 'undefined' ? rowGpcHelperCountryCode : null,
|
typeof rowGpcHelperCountryCode !== 'undefined' ? rowGpcHelperCountryCode : null,
|
||||||
@@ -7735,7 +7775,7 @@ async function ensureGpcApiKeyReadyForStart(options = {}) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedMode === GPC_HELPER_PHONE_MODE_AUTO && !balanceState.gopayHelperAutoModeEnabled) {
|
if (selectedMode === GPC_HELPER_PHONE_MODE_AUTO && isGpcAutoModePermissionDenied(balanceState)) {
|
||||||
if (typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode) {
|
if (typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode) {
|
||||||
selectGpcHelperPhoneMode.value = GPC_HELPER_PHONE_MODE_MANUAL;
|
selectGpcHelperPhoneMode.value = GPC_HELPER_PHONE_MODE_MANUAL;
|
||||||
}
|
}
|
||||||
@@ -11765,14 +11805,22 @@ btnGpcHelperBalance?.addEventListener('click', async () => {
|
|||||||
gopayHelperAutoModeEnabled: getGpcAutoModeEnabledFromResponse(response),
|
gopayHelperAutoModeEnabled: getGpcAutoModeEnabledFromResponse(response),
|
||||||
gopayHelperApiKeyStatus: response?.apiKeyStatus || response?.data?.status || response?.payload?.data?.status || response?.payload?.status || '',
|
gopayHelperApiKeyStatus: response?.apiKeyStatus || response?.data?.status || response?.payload?.data?.status || response?.payload?.status || '',
|
||||||
};
|
};
|
||||||
|
const nextAutoModePermission = getGpcAutoModePermissionFromPayload(nextState.gopayHelperBalancePayload);
|
||||||
|
const nextAutoModeDenied = nextAutoModePermission === false;
|
||||||
|
const nextAutoModeConfirmed = nextAutoModePermission === true || nextState.gopayHelperAutoModeEnabled;
|
||||||
|
const selectedModeBeforeBalanceState = getSelectedGpcHelperPhoneMode();
|
||||||
syncLatestState(nextState);
|
syncLatestState(nextState);
|
||||||
if (!nextState.gopayHelperAutoModeEnabled && getSelectedGpcHelperPhoneMode() === GPC_HELPER_PHONE_MODE_AUTO) {
|
if (nextAutoModeDenied && selectedModeBeforeBalanceState === GPC_HELPER_PHONE_MODE_AUTO) {
|
||||||
selectGpcHelperPhoneMode.value = GPC_HELPER_PHONE_MODE_MANUAL;
|
selectGpcHelperPhoneMode.value = GPC_HELPER_PHONE_MODE_MANUAL;
|
||||||
syncLatestState({ gopayHelperPhoneMode: GPC_HELPER_PHONE_MODE_MANUAL });
|
syncLatestState({ gopayHelperPhoneMode: GPC_HELPER_PHONE_MODE_MANUAL });
|
||||||
await saveSettings({ silent: true, force: true }).catch(() => {});
|
await saveSettings({ silent: true, force: true }).catch(() => {});
|
||||||
showToast('当前 API Key 未开通自动模式,已切回手动模式。', 'warn');
|
showToast('当前 API Key 未开通自动模式,已切回手动模式。', 'warn');
|
||||||
|
} else if (nextAutoModeDenied) {
|
||||||
|
showToast('GPC 余额已更新,当前 API Key 只能使用手动模式。', 'success');
|
||||||
|
} else if (nextAutoModeConfirmed) {
|
||||||
|
showToast('GPC 余额已更新,自动模式可用。', 'success');
|
||||||
} else {
|
} else {
|
||||||
showToast(nextState.gopayHelperAutoModeEnabled ? 'GPC 余额已更新,自动模式可用。' : 'GPC 余额已更新,当前 API Key 只能使用手动模式。', 'success');
|
showToast('GPC 余额已更新,当前接口未返回自动模式权限,已保留所选模式。', 'success');
|
||||||
}
|
}
|
||||||
updatePlusModeUI();
|
updatePlusModeUI();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -13728,7 +13776,9 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
if (message.payload.gopayHelperPhoneMode !== undefined && selectGpcHelperPhoneMode) {
|
if (message.payload.gopayHelperPhoneMode !== undefined && selectGpcHelperPhoneMode) {
|
||||||
selectGpcHelperPhoneMode.value = normalizeGpcHelperPhoneModeValue(message.payload.gopayHelperPhoneMode);
|
selectGpcHelperPhoneMode.value = normalizeGpcHelperPhoneModeValue(message.payload.gopayHelperPhoneMode);
|
||||||
}
|
}
|
||||||
if (message.payload.gopayHelperAutoModeEnabled === false && selectGpcHelperPhoneMode?.value === GPC_HELPER_PHONE_MODE_AUTO) {
|
if (message.payload.gopayHelperAutoModeEnabled === false
|
||||||
|
&& selectGpcHelperPhoneMode?.value === GPC_HELPER_PHONE_MODE_AUTO
|
||||||
|
&& isGpcAutoModePermissionDenied(latestState)) {
|
||||||
selectGpcHelperPhoneMode.value = GPC_HELPER_PHONE_MODE_MANUAL;
|
selectGpcHelperPhoneMode.value = GPC_HELPER_PHONE_MODE_MANUAL;
|
||||||
syncLatestState({ gopayHelperPhoneMode: GPC_HELPER_PHONE_MODE_MANUAL });
|
syncLatestState({ gopayHelperPhoneMode: GPC_HELPER_PHONE_MODE_MANUAL });
|
||||||
showToast('当前 API Key 未开通自动模式,已切回手动模式。', 'warn', 2200);
|
showToast('当前 API Key 未开通自动模式,已切回手动模式。', 'warn', 2200);
|
||||||
|
|||||||
@@ -510,6 +510,63 @@ test('GPC auto checkout only sends access token and API Key', async () => {
|
|||||||
assert.equal(events.find((event) => event.type === 'complete')?.step, 6);
|
assert.equal(events.find((event) => event.type === 'complete')?.step, 6);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
},
|
||||||
|
remove: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeStepFromBackground: 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(statePayload.gopayHelperPhoneMode, 'auto');
|
||||||
|
assert.equal(events.find((event) => event.type === 'complete')?.step, 6);
|
||||||
|
});
|
||||||
|
|
||||||
test('GPC auto checkout blocks API Keys without auto mode permission', async () => {
|
test('GPC auto checkout blocks API Keys without auto mode permission', async () => {
|
||||||
const fetchCalls = [];
|
const fetchCalls = [];
|
||||||
const executor = api.createPlusCheckoutCreateExecutor({
|
const executor = api.createPlusCheckoutCreateExecutor({
|
||||||
|
|||||||
@@ -134,6 +134,9 @@ test('sidepanel Plus UI hides PayPal account selector while GoPay is selected',
|
|||||||
extractFunction('getSelectedPlusPaymentMethod'),
|
extractFunction('getSelectedPlusPaymentMethod'),
|
||||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||||
|
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||||
|
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||||
|
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||||
extractFunction('hasGpcAutoModePermissionField'),
|
extractFunction('hasGpcAutoModePermissionField'),
|
||||||
extractFunction('isGpcAutoModePermissionDenied'),
|
extractFunction('isGpcAutoModePermissionDenied'),
|
||||||
extractFunction('normalizeGpcOtpChannelValue'),
|
extractFunction('normalizeGpcOtpChannelValue'),
|
||||||
@@ -217,6 +220,9 @@ test('sidepanel Plus UI shows GPC fields and purchase button only for GPC', () =
|
|||||||
extractFunction('getSelectedPlusPaymentMethod'),
|
extractFunction('getSelectedPlusPaymentMethod'),
|
||||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||||
|
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||||
|
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||||
|
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||||
extractFunction('hasGpcAutoModePermissionField'),
|
extractFunction('hasGpcAutoModePermissionField'),
|
||||||
extractFunction('isGpcAutoModePermissionDenied'),
|
extractFunction('isGpcAutoModePermissionDenied'),
|
||||||
extractFunction('normalizeGpcOtpChannelValue'),
|
extractFunction('normalizeGpcOtpChannelValue'),
|
||||||
@@ -310,6 +316,9 @@ test('sidepanel hides GPC auto mode selector when API Key has no auto permission
|
|||||||
extractFunction('getSelectedPlusPaymentMethod'),
|
extractFunction('getSelectedPlusPaymentMethod'),
|
||||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||||
|
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||||
|
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||||
|
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||||
extractFunction('hasGpcAutoModePermissionField'),
|
extractFunction('hasGpcAutoModePermissionField'),
|
||||||
extractFunction('isGpcAutoModePermissionDenied'),
|
extractFunction('isGpcAutoModePermissionDenied'),
|
||||||
extractFunction('normalizeGpcOtpChannelValue'),
|
extractFunction('normalizeGpcOtpChannelValue'),
|
||||||
@@ -353,12 +362,104 @@ return { updatePlusModeUI, selectGpcHelperPhoneMode, plusPaymentMethodCaption, r
|
|||||||
assert.match(api.plusPaymentMethodCaption.textContent, /手动/);
|
assert.match(api.plusPaymentMethodCaption.textContent, /手动/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('sidepanel keeps selected GPC auto mode when persisted permission survives stop refresh', () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('normalizePlusPaymentMethod'),
|
||||||
|
extractFunction('getSelectedPlusPaymentMethod'),
|
||||||
|
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';
|
||||||
|
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 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 (() => {
|
||||||
|
const selectedGpcPhoneMode = normalizeGpcHelperPhoneModeValue(selectGpcHelperPhoneMode.value);
|
||||||
|
const preserveSelectedGpcAutoMode = shouldPreserveSelectedGpcAutoMode(latestState);
|
||||||
|
return (!preserveSelectedGpcAutoMode && isGpcAutoModePermissionDenied(latestState)) ? 'manual' : selectedGpcPhoneMode;
|
||||||
|
})();
|
||||||
|
},
|
||||||
|
applyDataUpdated(payload) {
|
||||||
|
syncLatestState(payload);
|
||||||
|
if (payload.gopayHelperPhoneMode !== undefined) {
|
||||||
|
selectGpcHelperPhoneMode.value = normalizeGpcHelperPhoneModeValue(payload.gopayHelperPhoneMode);
|
||||||
|
}
|
||||||
|
if (payload.gopayHelperAutoModeEnabled === false
|
||||||
|
&& selectGpcHelperPhoneMode?.value === GPC_HELPER_PHONE_MODE_AUTO
|
||||||
|
&& isGpcAutoModePermissionDenied(latestState)) {
|
||||||
|
selectGpcHelperPhoneMode.value = GPC_HELPER_PHONE_MODE_MANUAL;
|
||||||
|
syncLatestState({ gopayHelperPhoneMode: GPC_HELPER_PHONE_MODE_MANUAL });
|
||||||
|
}
|
||||||
|
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', () => {
|
test('sidepanel keeps selected GPC auto mode before permission has been queried', () => {
|
||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('normalizePlusPaymentMethod'),
|
extractFunction('normalizePlusPaymentMethod'),
|
||||||
extractFunction('getSelectedPlusPaymentMethod'),
|
extractFunction('getSelectedPlusPaymentMethod'),
|
||||||
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||||
extractFunction('getGpcHelperAutoModeEnabled'),
|
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||||
|
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||||
|
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||||
|
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||||
extractFunction('hasGpcAutoModePermissionField'),
|
extractFunction('hasGpcAutoModePermissionField'),
|
||||||
extractFunction('isGpcAutoModePermissionDenied'),
|
extractFunction('isGpcAutoModePermissionDenied'),
|
||||||
extractFunction('normalizeGpcOtpChannelValue'),
|
extractFunction('normalizeGpcOtpChannelValue'),
|
||||||
@@ -401,6 +502,70 @@ return { updatePlusModeUI, selectGpcHelperPhoneMode, plusPaymentMethodCaption, r
|
|||||||
assert.match(api.plusPaymentMethodCaption.textContent, /自动/);
|
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' };
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
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, 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(), []);
|
||||||
|
});
|
||||||
|
|
||||||
test('sidepanel resolves pending GoPay manual confirmation from DATA_UPDATED state', async () => {
|
test('sidepanel resolves pending GoPay manual confirmation from DATA_UPDATED state', async () => {
|
||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('openPlusManualConfirmationDialog'),
|
extractFunction('openPlusManualConfirmationDialog'),
|
||||||
|
|||||||
Reference in New Issue
Block a user