1import 'abort-controller/polyfill'; 2 3import { DevicePushToken } from '../../Tokens.types'; 4import { updateDevicePushTokenAsync } from '../updateDevicePushTokenAsync'; 5 6const TOKEN: DevicePushToken = { type: 'ios', data: 'i-am-token' }; 7 8jest.mock('../../ServerRegistrationModule', () => ({ 9 getInstallationIdAsync: () => 'abcdefg', 10})); 11 12declare const global: any; 13 14const expoEndpointUrl = 'https://exp.host/--/api/v2/push/updateDeviceToken'; 15 16describe('given valid registration info', () => { 17 const successResponse = { 18 status: 200, 19 ok: true, 20 } as Response; 21 22 const failureResponse = { 23 status: 500, 24 ok: false, 25 text: async () => 'Server error', 26 } as Response; 27 28 let originalFetch: typeof fetch | undefined; 29 30 beforeAll(() => { 31 originalFetch = global.fetch; 32 global.fetch = jest.fn(); 33 }); 34 35 afterAll(() => { 36 global.fetch = originalFetch; 37 }); 38 39 it('submits the request to proper URL', async () => { 40 global.fetch.mockResolvedValue(successResponse); 41 const warnSpy = jest.spyOn(console, 'warn').mockImplementation(); 42 const abortController = new AbortController(); 43 await updateDevicePushTokenAsync(abortController.signal, TOKEN); 44 warnSpy.mockRestore(); 45 expect(global.fetch).toHaveBeenCalledWith(expoEndpointUrl, expect.anything()); 46 }); 47 48 describe('when server responds with an ok status', () => { 49 beforeAll(() => { 50 global.fetch.mockResolvedValue(successResponse); 51 }); 52 53 it('submits the request only once', async () => { 54 const abortController = new AbortController(); 55 await updateDevicePushTokenAsync(abortController.signal, TOKEN); 56 expect(global.fetch).toHaveBeenCalledTimes(1); 57 }); 58 }); 59 60 it('retries until it succeeds whilst server responds with an error status', async () => { 61 const spy = jest.spyOn(console, 'debug').mockImplementation(); 62 global.fetch 63 .mockResolvedValueOnce(failureResponse) 64 .mockResolvedValueOnce(failureResponse) 65 .mockResolvedValueOnce(successResponse); 66 const abortController = new AbortController(); 67 await updateDevicePushTokenAsync(abortController.signal, TOKEN); 68 expect(global.fetch).toHaveBeenCalledTimes(3); 69 spy.mockRestore(); 70 }); 71 72 it('retries until it succeeds if fetch throws', async () => { 73 const debugSpy = jest.spyOn(console, 'debug').mockImplementation(); 74 const warnSpy = jest.spyOn(console, 'warn').mockImplementation(); 75 global.fetch.mockRejectedValueOnce(new TypeError()).mockResolvedValueOnce(successResponse); 76 const abortController = new AbortController(); 77 await updateDevicePushTokenAsync(abortController.signal, TOKEN); 78 expect(global.fetch).toHaveBeenCalledTimes(2); 79 warnSpy.mockRestore(); 80 debugSpy.mockRestore(); 81 }); 82 83 it('does not retry if signal has been aborted', async () => { 84 const debugSpy = jest.spyOn(console, 'debug').mockImplementation(); 85 const warnSpy = jest.spyOn(console, 'warn').mockImplementation(); 86 global.fetch.mockRejectedValue(new TypeError()); 87 const abortController = new AbortController(); 88 setTimeout(() => abortController.abort(), 1000); 89 await updateDevicePushTokenAsync(abortController.signal, TOKEN); 90 expect(global.fetch).toHaveBeenCalledTimes(2); 91 warnSpy.mockRestore(); 92 debugSpy.mockRestore(); 93 }); 94}); 95