1 import { InactiveSessionError } from '../../lib/api/helpers/errors';
2 import withApiHandlers from '../../lib/api/helpers/withApiHandlers';
3 import { withUIDHeaders } from '../../lib/fetch/headers';
5 const getApiError = ({ message, response = { headers: { get: () => '' } }, data, status }) => {
6 const error = new Error(message);
9 error.response = response;
13 const getApiResult = (result) => {
23 describe('auth handlers', () => {
24 it('should unlock', async () => {
27 .and.returnValues(Promise.reject(getApiError({ status: 403 })), Promise.resolve(getApiResult('123')));
28 const handleMissingScopes = jasmine.createSpy('unlock').and.callFake(({ options }) => {
31 const api = withApiHandlers({ call, onMissingScopes: handleMissingScopes });
32 const result = await api({}).then((r) => r.json());
33 expect(result).toBe('123');
34 expect(handleMissingScopes).toHaveBeenCalledTimes(1);
35 expect(call).toHaveBeenCalledTimes(2);
38 it('should unlock and be cancellable', async () => {
39 const unlockError = getApiError({ status: 403 });
40 const call = jasmine.createSpy('call').and.returnValues(Promise.reject(unlockError));
41 const handleUnlock = jasmine.createSpy('unlock').and.returnValues(Promise.reject(unlockError));
42 const handleError = jasmine.createSpy('error').and.callFake((e) => {
45 const api = withApiHandlers({ call, onMissingScopes: handleUnlock });
46 const error = await api({}).catch(handleError);
47 expect(error).toBe(unlockError);
48 expect(handleError).toHaveBeenCalledTimes(1);
49 expect(handleUnlock).toHaveBeenCalledTimes(1);
50 expect(call).toHaveBeenCalledTimes(1);
53 it('should retry 429 status', async () => {
56 .and.returnValues(Promise.reject(getApiError({ status: 429 })), Promise.resolve(getApiResult('123')));
57 const handleError = jasmine.createSpy('error').and.callFake((e) => {
60 const api = withApiHandlers({ call });
61 const result = await api({})
62 .then((r) => r.json())
64 expect(result).toBe('123');
65 expect(call).toHaveBeenCalledTimes(2);
66 expect(handleError).toHaveBeenCalledTimes(0);
69 it('should not retry 429 status if disabled', async () => {
72 .and.returnValues(Promise.reject(getApiError({ status: 429 })), Promise.resolve(getApiResult('123')));
73 const handleError = jasmine.createSpy('error').and.callFake((e) => {
76 const api = withApiHandlers({ call });
77 const error = await api({ ignoreHandler: [429] }).catch(handleError);
78 expect(error.status).toBe(429);
79 expect(call).toHaveBeenCalledTimes(1);
80 expect(handleError).toHaveBeenCalledTimes(1);
83 it('should retry maximum 5 times', async () => {
85 () => Promise.reject(getApiError({ status: 429 })),
86 () => Promise.reject(getApiError({ status: 429 })),
87 () => Promise.reject(getApiError({ status: 429 })),
88 () => Promise.reject(getApiError({ status: 429 })),
89 () => Promise.reject(getApiError({ status: 429 })),
92 const call = jasmine.createSpy('call').and.callFake(() => returns[i++]());
93 const handleError = jasmine.createSpy('error').and.callFake((e) => {
96 const api = withApiHandlers({ call });
97 const error = await api({}).catch(handleError);
98 expect(error.status).toBe(429);
99 expect(call).toHaveBeenCalledTimes(5);
100 expect(handleError).toHaveBeenCalledTimes(1);
103 it('should not handle retry when its greater than 10', async () => {
106 .and.returnValues(Promise.reject(getApiError({ status: 429, response: { headers: { get: () => '10' } } })));
107 const handleError = jasmine.createSpy('error').and.callFake((e) => {
110 const api = withApiHandlers({ call });
111 const error = await api({}).catch(handleError);
112 expect(error.status).toBe(429);
113 expect(call).toHaveBeenCalledTimes(1);
114 expect(handleError).toHaveBeenCalledTimes(1);
117 it('should not refresh if has no session', async () => {
118 const call = jasmine.createSpy('call').and.returnValues(Promise.reject(getApiError({ status: 401 })));
119 const handleError = jasmine.createSpy('error').and.callFake((e) => {
122 const api = withApiHandlers({ call });
123 const error = await api({}).catch(handleError);
124 expect(error.status).toBe(401);
125 expect(call).toHaveBeenCalledTimes(1);
126 expect(handleError).toHaveBeenCalledTimes(1);
129 it('should refresh once (if has session)', async () => {
130 let refreshed = false;
131 let refreshCalls = 0;
132 const call = jasmine.createSpy('call').and.callFake(async (args) => {
133 if (args.url === 'auth/refresh') {
137 headers: { get: () => '1' },
141 throw getApiError({ status: 401 });
145 const handleError = jasmine.createSpy('error').and.callFake((e) => {
148 const apiWithHandlers = withApiHandlers({ call });
149 apiWithHandlers.UID = '123';
150 const api = (a) => apiWithHandlers(a).catch(handleError);
151 const result = await Promise.all([api(123), api(231), api(321)]);
152 expect(result).toEqual([123, 231, 321]);
153 expect(call).toHaveBeenCalledTimes(7);
154 expect(handleError).toHaveBeenCalledTimes(0);
155 expect(refreshCalls).toBe(1);
158 it('should refresh once and fail all active calls (if has session)', async () => {
159 const call = jasmine.createSpy('call').and.callFake(async (args) => {
160 if (args.url === 'auth/refresh') {
161 throw getApiError({ status: 422, data: args });
163 throw getApiError({ status: 401, data: args });
165 const handleError = jasmine.createSpy('error').and.callFake((e) => {
168 const apiWithHandlers = withApiHandlers({ call });
169 apiWithHandlers.UID = '123';
170 const api = (a) => apiWithHandlers(a).catch(handleError);
171 const [p1, p2, p3] = [api(123), api(231), api(321)];
172 await expectAsync(p1).toBeRejectedWith(InactiveSessionError());
173 await expectAsync(p2).toBeRejectedWith(InactiveSessionError());
174 await expectAsync(p3).toBeRejectedWith(InactiveSessionError());
175 expect(call).toHaveBeenCalledTimes(4);
176 expect(handleError).toHaveBeenCalledTimes(3);
179 it('should refresh once and only logout if it is a 4xx error', async () => {
181 () => Promise.reject(getApiError({ status: 401 })),
182 () => Promise.reject(getApiError({ status: 500 })),
183 () => Promise.reject(getApiError({ status: 401 })),
184 () => Promise.reject(getApiError({ status: 422 })),
187 const call = jasmine.createSpy('call').and.callFake(() => returns[i++]());
189 const handleError = jasmine.createSpy('error').and.callFake((e) => {
192 const api = withApiHandlers({ call });
195 const error = await api(123).catch(handleError);
196 expect(error.status).toBe(500);
197 expect(call).toHaveBeenCalledTimes(2);
198 expect(handleError).toHaveBeenCalledTimes(1);
201 await expectAsync(r2).toBeRejectedWith(InactiveSessionError());
202 expect(call).toHaveBeenCalledTimes(4);
205 it('should only error with InactiveSession if the initial UID is the same', async () => {
207 () => Promise.reject(getApiError({ status: 401 })),
208 () => Promise.reject(getApiError({ status: 400 })),
209 () => Promise.reject(getApiError({ status: 401 })),
210 () => Promise.reject(getApiError({ status: 400 })),
213 const call = jasmine.createSpy('call').and.callFake(() => returns[i++]());
214 const handleError = jasmine.createSpy('error').and.callFake((e) => {
217 const api = withApiHandlers({ call });
220 const error = await api(withUIDHeaders('321', {})).catch(handleError);
221 expect(error.status).toBe(401);
222 expect(call).toHaveBeenCalledTimes(2);
223 expect(handleError).toHaveBeenCalledTimes(1);
225 const error2 = await api({}).catch(handleError);
226 expect(error2.name).toBe('InactiveSession');
227 expect(call).toHaveBeenCalledTimes(4);
228 expect(handleError).toHaveBeenCalledTimes(2);
231 it('should refresh once and handle 429 max attempts', async () => {
233 () => Promise.reject(getApiError({ status: 401 })),
234 () => Promise.reject(getApiError({ status: 429 })),
235 () => Promise.reject(getApiError({ status: 429 })),
236 () => Promise.reject(getApiError({ status: 429 })),
237 () => Promise.reject(getApiError({ status: 429 })),
238 () => Promise.reject(getApiError({ status: 429 })),
239 () => Promise.reject(getApiError({ status: 429 })),
242 const call = jasmine.createSpy('call').and.callFake(() => returns[i++]());
244 const api = withApiHandlers({ call });
246 await expectAsync(api(123)).toBeRejectedWith(InactiveSessionError());
247 expect(call).toHaveBeenCalledTimes(6);
250 it('should refresh once and handle 429', async () => {
252 () => Promise.reject(getApiError({ status: 401 })), // need refresh
253 () => Promise.reject(getApiError({ status: 429 })), // retry
254 () => Promise.reject(getApiError({ status: 429 })), // retry
255 () => Promise.resolve(getApiResult('')), // refresh ok
256 () => Promise.resolve(getApiResult('123')), // actual result
259 const call = jasmine.createSpy('call').and.callFake(() => returns[i++]());
260 const api = withApiHandlers({ call });
262 const result = await api(123).then((result) => result.json());
263 expect(call).toHaveBeenCalledTimes(5);
264 expect(result).toBe('123');
267 it('should fail all calls after it has logged out', async () => {
268 const call = jasmine.createSpy('call').and.callFake(async (args) => {
269 throw getApiError({ status: 401, data: args });
271 const api = withApiHandlers({ call });
273 await expectAsync(api()).toBeRejectedWith(InactiveSessionError());
274 expect(call).toHaveBeenCalledTimes(2);
275 await expectAsync(api()).toBeRejectedWith(InactiveSessionError());
276 expect(call).toHaveBeenCalledTimes(2);