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