Files
FlowPilot/tests/plus-hosted-checkout-success.test.js
T
2026-05-20 07:38:49 +08:00

136 lines
5.5 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('background/plus-hosted-checkout-success.js', 'utf8');
function loadApi() {
const scope = {};
return new Function('self', `${source}; return self.MultiPagePlusHostedCheckoutSuccess;`)(scope);
}
function createState(overrides = {}) {
return {
plusModeEnabled: true,
plusPaymentMethod: 'paypal',
plusHostedCheckoutCompletionPending: true,
plusCheckoutTabId: 77,
nodeStatuses: {
'plus-checkout-create': 'running',
},
...overrides,
};
}
test('hosted checkout success manager completes plus-checkout-create on the tracked success tab', async () => {
const api = loadApi();
const events = [];
let state = createState();
const manager = api.createPlusHostedCheckoutSuccessManager({
addLog: async (message, level, options) => events.push({ type: 'log', message, level, options }),
completeNodeFromBackground: async (nodeId, payload) => events.push({ type: 'complete', nodeId, payload }),
getState: async () => state,
setState: async (payload) => {
events.push({ type: 'set-state', payload });
state = { ...state, ...payload };
},
});
const successUrl = 'https://chatgpt.com/payments/success?session_id=cs_test';
const result = await manager.handleTabUpdated(77, { status: 'complete' }, { url: successUrl });
assert.deepEqual(result, {
completed: true,
plusReturnUrl: successUrl,
});
assert.deepEqual(events.find((event) => event.type === 'complete'), {
type: 'complete',
nodeId: 'plus-checkout-create',
payload: {
plusReturnUrl: successUrl,
plusHostedCheckoutCompleted: true,
},
});
assert.equal(state.plusHostedCheckoutCompletionPending, false);
assert.equal(state.plusHostedCheckoutCompleted, true);
assert.equal(state.plusReturnUrl, successUrl);
assert.equal(events.find((event) => event.type === 'log')?.options?.nodeId, 'plus-checkout-create');
});
test('hosted checkout success manager ignores unrelated updates and inactive states', async () => {
const api = loadApi();
const events = [];
let state = createState();
const manager = api.createPlusHostedCheckoutSuccessManager({
completeNodeFromBackground: async () => events.push('complete'),
getState: async () => state,
setState: async () => events.push('set-state'),
});
assert.equal(api.isPaymentSuccessUrl('https://chatgpt.com/payments/success'), true);
assert.equal(api.isPaymentSuccessUrl('https://example.com/payments/success'), false);
assert.equal(await manager.handleTabUpdated(77, { status: 'loading' }, { url: 'https://chatgpt.com/payments/success' }), null);
assert.equal(await manager.handleTabUpdated(77, { status: 'complete' }, { url: 'https://chatgpt.com/' }), null);
assert.equal(await manager.handleTabUpdated(88, { status: 'complete' }, { url: 'https://chatgpt.com/payments/success' }), null);
state = createState({ plusHostedCheckoutCompletionPending: false });
assert.equal(await manager.handleTabUpdated(77, { status: 'complete' }, { url: 'https://chatgpt.com/payments/success' }), null);
state = createState({ plusHostedCheckoutCompletionPending: true, nodeStatuses: { 'plus-checkout-create': 'completed' } });
assert.equal(await manager.handleTabUpdated(77, { status: 'complete' }, { url: 'https://chatgpt.com/payments/success' }), null);
assert.deepEqual(events, []);
});
test('hosted checkout success manager deduplicates concurrent success events for one tab', async () => {
const api = loadApi();
const events = [];
const state = createState();
let releaseComplete;
const completeGate = new Promise((resolve) => {
releaseComplete = resolve;
});
const manager = api.createPlusHostedCheckoutSuccessManager({
addLog: async () => {},
completeNodeFromBackground: async (nodeId) => {
events.push({ type: 'complete', nodeId });
await completeGate;
},
getState: async () => state,
setState: async (payload) => events.push({ type: 'set-state', payload }),
});
const first = manager.handleTabUpdated(77, { status: 'complete' }, { url: 'https://chatgpt.com/payments/success' });
const second = manager.handleTabUpdated(77, { status: 'complete' }, { url: 'https://chatgpt.com/payments/success' });
await Promise.resolve();
releaseComplete();
const results = await Promise.all([first, second]);
assert.equal(results.filter(Boolean).length, 1);
assert.equal(events.filter((event) => event.type === 'complete').length, 1);
});
test('hosted checkout success manager fails the checkout node when continuation throws', async () => {
const api = loadApi();
const events = [];
const manager = api.createPlusHostedCheckoutSuccessManager({
addLog: async (message, level) => events.push({ type: 'log', message, level }),
completeNodeFromBackground: async () => {
throw new Error('boom');
},
failNodeFromBackground: async (nodeId, message) => events.push({ type: 'fail', nodeId, message }),
getState: async () => createState(),
setState: async () => {},
});
const result = await manager.handleTabUpdated(77, { status: 'complete' }, { url: 'https://chatgpt.com/payments/success' });
assert.equal(result.failed, true);
assert.equal(result.message, 'boom');
assert.deepEqual(events.find((event) => event.type === 'fail'), {
type: 'fail',
nodeId: 'plus-checkout-create',
message: 'boom',
});
assert.equal(events.some((event) => event.type === 'log' && event.level === 'error'), true);
});