Paypal增加轻量级号池
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
test('background imports paypal account store module', () => {
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
assert.match(source, /background\/paypal-account-store\.js/);
|
||||
assert.match(source, /paypal-utils\.js/);
|
||||
});
|
||||
|
||||
test('paypal account store module exposes a factory', () => {
|
||||
const source = fs.readFileSync('background/paypal-account-store.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundPayPalAccountStore;`)(globalScope);
|
||||
|
||||
assert.equal(typeof api?.createPayPalAccountStore, 'function');
|
||||
});
|
||||
|
||||
test('paypal account store selects account and keeps legacy paypal credentials in sync', async () => {
|
||||
const source = fs.readFileSync('background/paypal-account-store.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundPayPalAccountStore;`)(globalScope);
|
||||
|
||||
let latestState = {
|
||||
paypalAccounts: [],
|
||||
currentPayPalAccountId: '',
|
||||
paypalEmail: '',
|
||||
paypalPassword: '',
|
||||
};
|
||||
const broadcasts = [];
|
||||
|
||||
const store = api.createPayPalAccountStore({
|
||||
broadcastDataUpdate(payload) {
|
||||
broadcasts.push(payload);
|
||||
},
|
||||
findPayPalAccount(accounts, accountId) {
|
||||
return (Array.isArray(accounts) ? accounts : []).find((account) => account.id === accountId) || null;
|
||||
},
|
||||
getState: async () => latestState,
|
||||
normalizePayPalAccount(account = {}) {
|
||||
return {
|
||||
id: String(account.id || 'generated'),
|
||||
email: String(account.email || '').trim().toLowerCase(),
|
||||
password: String(account.password || ''),
|
||||
createdAt: Number(account.createdAt) || 1,
|
||||
updatedAt: Number(account.updatedAt) || 1,
|
||||
lastUsedAt: Number(account.lastUsedAt) || 0,
|
||||
};
|
||||
},
|
||||
normalizePayPalAccounts(accounts) {
|
||||
return Array.isArray(accounts) ? accounts.slice() : [];
|
||||
},
|
||||
setPersistentSettings: async (updates) => {
|
||||
latestState = { ...latestState, ...updates };
|
||||
},
|
||||
setState: async (updates) => {
|
||||
latestState = { ...latestState, ...updates };
|
||||
},
|
||||
upsertPayPalAccountInList(accounts, nextAccount) {
|
||||
const list = Array.isArray(accounts) ? accounts.slice() : [];
|
||||
const existingIndex = list.findIndex((account) => account.id === nextAccount.id);
|
||||
if (existingIndex >= 0) {
|
||||
list[existingIndex] = nextAccount;
|
||||
return list;
|
||||
}
|
||||
list.push(nextAccount);
|
||||
return list;
|
||||
},
|
||||
});
|
||||
|
||||
const account = await store.upsertPayPalAccount({
|
||||
id: 'pp-1',
|
||||
email: 'User@Example.com',
|
||||
password: 'secret',
|
||||
});
|
||||
assert.equal(account.email, 'user@example.com');
|
||||
|
||||
const selected = await store.setCurrentPayPalAccount('pp-1');
|
||||
assert.equal(selected.id, 'pp-1');
|
||||
assert.equal(latestState.currentPayPalAccountId, 'pp-1');
|
||||
assert.equal(latestState.paypalEmail, 'user@example.com');
|
||||
assert.equal(latestState.paypalPassword, 'secret');
|
||||
assert.deepStrictEqual(
|
||||
broadcasts.at(-1),
|
||||
{
|
||||
currentPayPalAccountId: 'pp-1',
|
||||
paypalEmail: 'user@example.com',
|
||||
paypalPassword: 'secret',
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -107,6 +107,32 @@ test('PayPal approve keeps original combined email and password login path', asy
|
||||
assert.equal(events.messages.includes('PAYPAL_CLICK_APPROVE'), true);
|
||||
});
|
||||
|
||||
test('PayPal approve prefers the selected paypal pool account over legacy fields', async () => {
|
||||
const { executor, events } = createExecutor({
|
||||
pageStates: [
|
||||
{ needsLogin: true, hasEmailInput: true, hasPasswordInput: true, loginPhase: 'login_combined' },
|
||||
{ needsLogin: false, approveReady: true },
|
||||
{ needsLogin: false, approveReady: true },
|
||||
],
|
||||
submitResults: [
|
||||
{ submitted: true, phase: 'password_submitted', awaiting: 'redirect_or_approval' },
|
||||
],
|
||||
});
|
||||
|
||||
await executor.executePayPalApprove({
|
||||
paypalEmail: '',
|
||||
paypalPassword: '',
|
||||
currentPayPalAccountId: 'pp-1',
|
||||
paypalAccounts: [
|
||||
{ id: 'pp-1', email: 'pool@example.com', password: 'pool-secret' },
|
||||
],
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(events.submittedPayloads, [
|
||||
{ email: 'pool@example.com', password: 'pool-secret' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('PayPal approve discovers an already open unregistered PayPal tab', async () => {
|
||||
const { executor, events } = createExecutor({
|
||||
pageStates: [
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
test('sidepanel loads reusable form dialog and paypal manager before sidepanel bootstrap', () => {
|
||||
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||
const formDialogIndex = html.indexOf('<script src="form-dialog.js"></script>');
|
||||
const managerIndex = html.indexOf('<script src="paypal-manager.js"></script>');
|
||||
const sidepanelIndex = html.indexOf('<script src="sidepanel.js"></script>');
|
||||
|
||||
assert.notEqual(formDialogIndex, -1);
|
||||
assert.notEqual(managerIndex, -1);
|
||||
assert.notEqual(sidepanelIndex, -1);
|
||||
assert.ok(formDialogIndex < managerIndex);
|
||||
assert.ok(managerIndex < sidepanelIndex);
|
||||
});
|
||||
|
||||
test('sidepanel html contains paypal select and add button controls', () => {
|
||||
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||
|
||||
assert.match(html, /id="row-paypal-account"/);
|
||||
assert.match(html, /id="select-paypal-account"/);
|
||||
assert.match(html, /id="btn-add-paypal-account"/);
|
||||
assert.match(html, /id="shared-form-modal"/);
|
||||
});
|
||||
|
||||
test('paypal manager saves a paypal account and selects it immediately', async () => {
|
||||
const source = fs.readFileSync('sidepanel/paypal-manager.js', 'utf8');
|
||||
const windowObject = {};
|
||||
const api = new Function('window', `${source}; return window.SidepanelPayPalManager;`)(windowObject);
|
||||
|
||||
let latestState = {
|
||||
paypalAccounts: [],
|
||||
currentPayPalAccountId: null,
|
||||
paypalEmail: '',
|
||||
paypalPassword: '',
|
||||
};
|
||||
const events = [];
|
||||
const clickHandlers = {};
|
||||
const changeHandlers = {};
|
||||
const selectNode = {
|
||||
innerHTML: '',
|
||||
value: '',
|
||||
disabled: false,
|
||||
addEventListener(type, handler) {
|
||||
changeHandlers[type] = handler;
|
||||
},
|
||||
};
|
||||
const addButton = {
|
||||
disabled: false,
|
||||
addEventListener(type, handler) {
|
||||
clickHandlers[type] = handler;
|
||||
},
|
||||
};
|
||||
|
||||
const manager = api.createPayPalManager({
|
||||
state: {
|
||||
getLatestState: () => latestState,
|
||||
syncLatestState(updates) {
|
||||
latestState = { ...latestState, ...updates };
|
||||
},
|
||||
},
|
||||
dom: {
|
||||
btnAddPayPalAccount: addButton,
|
||||
selectPayPalAccount: selectNode,
|
||||
},
|
||||
helpers: {
|
||||
escapeHtml: (value) => String(value || ''),
|
||||
getPayPalAccounts: (state) => Array.isArray(state?.paypalAccounts) ? state.paypalAccounts : [],
|
||||
openFormDialog: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||
showToast(message, tone) {
|
||||
events.push({ type: 'toast', message, tone });
|
||||
},
|
||||
},
|
||||
runtime: {
|
||||
sendMessage: async (message) => {
|
||||
events.push({ type: 'message', message });
|
||||
if (message.type === 'UPSERT_PAYPAL_ACCOUNT') {
|
||||
return {
|
||||
ok: true,
|
||||
account: {
|
||||
id: 'pp-1',
|
||||
email: 'user@example.com',
|
||||
password: 'secret',
|
||||
},
|
||||
};
|
||||
}
|
||||
if (message.type === 'SELECT_PAYPAL_ACCOUNT') {
|
||||
return {
|
||||
ok: true,
|
||||
account: {
|
||||
id: 'pp-1',
|
||||
email: 'user@example.com',
|
||||
password: 'secret',
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected message ${message.type}`);
|
||||
},
|
||||
},
|
||||
paypalUtils: {
|
||||
upsertPayPalAccountInList(accounts, nextAccount) {
|
||||
const list = Array.isArray(accounts) ? accounts.slice() : [];
|
||||
const existingIndex = list.findIndex((account) => account.id === nextAccount.id);
|
||||
if (existingIndex >= 0) {
|
||||
list[existingIndex] = nextAccount;
|
||||
return list;
|
||||
}
|
||||
list.push(nextAccount);
|
||||
return list;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
manager.bindPayPalEvents();
|
||||
manager.renderPayPalAccounts();
|
||||
|
||||
assert.match(selectNode.innerHTML, /请先添加 PayPal 账号/);
|
||||
clickHandlers.click();
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
assert.deepStrictEqual(
|
||||
events.filter((event) => event.type === 'message').map((event) => event.message.type),
|
||||
['UPSERT_PAYPAL_ACCOUNT', 'SELECT_PAYPAL_ACCOUNT']
|
||||
);
|
||||
assert.equal(latestState.currentPayPalAccountId, 'pp-1');
|
||||
assert.equal(latestState.paypalEmail, 'user@example.com');
|
||||
assert.equal(latestState.paypalPassword, 'secret');
|
||||
assert.equal(selectNode.value, 'pp-1');
|
||||
assert.equal(selectNode.disabled, false);
|
||||
assert.match(events.at(-1)?.message || '', /已保存 PayPal 账号/);
|
||||
});
|
||||
@@ -68,6 +68,7 @@ test('sidepanel html loads shared step definitions before sidepanel bootstrap',
|
||||
test('sidepanel html exposes Plus mode and PayPal settings', () => {
|
||||
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||
assert.match(html, /id="input-plus-mode-enabled"/);
|
||||
assert.match(html, /id="input-paypal-email"/);
|
||||
assert.match(html, /id="input-paypal-password"/);
|
||||
assert.match(html, /id="select-paypal-account"/);
|
||||
assert.match(html, /id="btn-add-paypal-account"/);
|
||||
assert.match(html, /id="shared-form-modal"/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user