1import nock from 'nock';
2
3import { getExpoApiBaseUrl } from '../../../api/endpoint';
4import * as ProjectDevices from '../../project/devices';
5import { DevelopmentSession } from '../DevelopmentSession';
6
7const asMock = (fn: any): jest.Mock => fn as jest.Mock;
8
9jest.mock('../../project/devices', () => ({
10  getDevicesInfoAsync: jest.fn(),
11}));
12jest.mock('../../../api/user/user');
13
14describe(`startAsync`, () => {
15  beforeEach(() => {
16    delete process.env.EXPO_OFFLINE;
17  });
18  it(`starts a dev session`, async () => {
19    const err = jest.fn();
20    const session = new DevelopmentSession('/', 'http://localhost:19001/', err);
21
22    asMock(ProjectDevices.getDevicesInfoAsync).mockResolvedValue({
23      devices: [{ installationId: '123' }, { installationId: '456' }],
24    });
25
26    const exp = {
27      name: 'my-app',
28      slug: 'my-app',
29      description: 'my-foo-bar',
30      primaryColor: '#4630eb',
31    };
32    const runtime = 'native';
33    const startScope = nock(getExpoApiBaseUrl())
34      .post('/v2/development-sessions/notify-alive?deviceId=123&deviceId=456')
35      .reply(200, '');
36    const closeScope = nock(getExpoApiBaseUrl())
37      .post('/v2/development-sessions/notify-close?deviceId=123&deviceId=456')
38      .reply(200, '');
39
40    await session.startAsync({
41      exp,
42      runtime,
43    });
44
45    await session.closeAsync();
46
47    expect(ProjectDevices.getDevicesInfoAsync).toHaveBeenCalledTimes(2);
48    expect(startScope.isDone()).toBe(true);
49    expect(closeScope.isDone()).toBe(true);
50    expect(err).not.toBeCalled();
51  });
52
53  it(`surfaces exceptions that would otherwise be uncaught`, async () => {
54    const err = jest.fn();
55    const session = new DevelopmentSession('/', 'http://localhost:19001/', err);
56
57    asMock(ProjectDevices.getDevicesInfoAsync).mockRejectedValueOnce(new Error('predefined error'));
58
59    const exp = {
60      name: 'my-app',
61      slug: 'my-app',
62      description: 'my-foo-bar',
63      primaryColor: '#4630eb',
64    };
65    const runtime = 'native';
66
67    // Does not throw directly
68    await session.startAsync({
69      exp,
70      runtime,
71    });
72
73    expect(err).toBeCalled();
74
75    // Did not repeat the cycle
76    expect(session['timeout']).toBe(null);
77  });
78
79  it(`gracefully handles server outages`, async () => {
80    const err = jest.fn();
81    const session = new DevelopmentSession('/', 'http://localhost:19001/', err);
82
83    asMock(ProjectDevices.getDevicesInfoAsync).mockResolvedValue({
84      devices: [{ installationId: '123' }, { installationId: '456' }],
85    });
86
87    const exp = {
88      name: 'my-app',
89      slug: 'my-app',
90      description: 'my-foo-bar',
91      primaryColor: '#4630eb',
92    };
93    const runtime = 'native';
94
95    // Server is down
96    nock(getExpoApiBaseUrl())
97      .post('/v2/development-sessions/notify-alive?deviceId=123&deviceId=456')
98      .reply(500, '');
99
100    // Does not throw directly
101    await session.startAsync({
102      exp,
103      runtime,
104    });
105
106    expect(err).toBeCalled();
107
108    // Did not repeat the cycle
109    expect(session['timeout']).toBe(null);
110  });
111});
112