Merge branch 'INDA-330-pii-update' into 'main'
[ProtonMail-WebClient.git] / packages / payments / core / ensureTokenChargeable.test.ts
blobe85c74c44ee32d7fff6003deb24ff907340ef8a0
1 import { PAYMENT_TOKEN_STATUS } from './constants';
2 import { type EnsureTokenChargeableTranslations, ensureTokenChargeable } from './ensureTokenChargeable';
4 let tab: { closed: boolean; close: () => any };
6 const translations: EnsureTokenChargeableTranslations = {
7     processAbortedError: 'Process aborted',
8     paymentProcessCanceledError: 'Payment process canceled',
9     paymentProcessFailedError: 'Payment process failed',
10     paymentProcessConsumedError: 'Payment process consumed',
11     paymentProcessNotSupportedError: 'Payment process not supported',
12     unknownPaymentTokenStatusError: 'Unknown payment token status',
13     tabClosedError: 'Tab closed',
16 describe('process', () => {
17     let ApprovalURL = 'https://example.proton.me';
18     let ReturnHost = 'https://return.proton.me';
19     let Token = 'some-payment-token-222';
20     let api: jest.Mock;
21     let signal: AbortSignal;
23     beforeEach(() => {
24         jest.clearAllMocks();
25         tab = { closed: false, close: jest.fn() };
26         jest.spyOn(window, 'open').mockReturnValue(tab as any);
27         jest.spyOn(window, 'removeEventListener');
28         jest.spyOn(window, 'addEventListener');
30         api = jest.fn();
31         signal = {
32             aborted: false,
33             reason: null,
34             any: jest.fn(),
35             onabort: jest.fn(),
36             throwIfAborted: jest.fn(),
37             addEventListener: jest.fn(),
38             removeEventListener: jest.fn(),
39             dispatchEvent: jest.fn(),
40         };
41     });
43     it('should open the ApprovalURL', () => {
44         tab.closed = true; // prevents the test from hanging because of an unhandled promise
45         ensureTokenChargeable({ api, ApprovalURL, ReturnHost, signal, Token }, translations).catch(() => {});
47         expect(window.open).toHaveBeenCalledWith(ApprovalURL);
48     });
50     it('should add abort listener to the signal', async () => {
51         const promise = ensureTokenChargeable({ api, ApprovalURL, ReturnHost, signal, Token }, translations);
53         expect(signal.addEventListener).toHaveBeenCalledTimes(1);
54         const signalAddEventListenerMock = signal.addEventListener as jest.Mock;
55         expect(signalAddEventListenerMock.mock.lastCall[0]).toEqual('abort');
56         expect(typeof signalAddEventListenerMock.mock.lastCall[1]).toBe('function');
58         expect(window.addEventListener).toHaveBeenCalledTimes(1);
59         const windowAddEventListenerMock = window.addEventListener as jest.Mock;
60         expect(windowAddEventListenerMock.mock.lastCall[0]).toEqual('message');
61         expect(typeof windowAddEventListenerMock.mock.lastCall[1]).toEqual('function');
62         const onMessage = windowAddEventListenerMock.mock.lastCall[1];
64         const abort = signalAddEventListenerMock.mock.lastCall[1];
66         abort();
68         expect(window.removeEventListener).toHaveBeenCalledWith('message', onMessage, false);
69         expect(signal.removeEventListener).toHaveBeenCalledWith('abort', abort);
70         expect(tab.close).toHaveBeenCalled();
71         await expect(promise).rejects.toEqual(new Error('Process aborted'));
72     });
74     it('should resolve if Status is STATUS_CHARGEABLE', async () => {
75         tab.closed = true;
76         api.mockResolvedValue({
77             Status: PAYMENT_TOKEN_STATUS.STATUS_CHARGEABLE,
78         });
80         const promise = ensureTokenChargeable({ api, ApprovalURL, ReturnHost, signal, Token }, translations);
82         await expect(promise).resolves.toEqual(undefined);
83     });
85     it.each([
86         PAYMENT_TOKEN_STATUS.STATUS_PENDING,
87         PAYMENT_TOKEN_STATUS.STATUS_FAILED,
88         PAYMENT_TOKEN_STATUS.STATUS_CONSUMED,
89         PAYMENT_TOKEN_STATUS.STATUS_NOT_SUPPORTED,
90     ])('should reject if Status is %s', async (Status) => {
91         tab.closed = true;
92         api.mockResolvedValue({
93             Status,
94         });
96         const promise = ensureTokenChargeable({ api, ApprovalURL, ReturnHost, signal, Token }, translations);
98         await expect(promise).rejects.toEqual({ tryAgain: true });
99     });
101     it('should re-try to confirm until the tab is closed', async () => {
102         api.mockResolvedValue({
103             Status: PAYMENT_TOKEN_STATUS.STATUS_CHARGEABLE,
104         });
106         const delayListening = 10;
107         const promise = ensureTokenChargeable(
108             { api, ApprovalURL, ReturnHost, signal, Token },
109             translations,
110             delayListening
111         );
112         jest.runAllTimers();
114         tab.closed = true;
116         await expect(promise).resolves.toEqual(undefined);
117     });