1import { vol } from 'memfs';
2import nock from 'nock';
3
4import { getExpoApiBaseUrl } from '../../api/endpoint';
5import { downloadAppAsync } from '../downloadAppAsync';
6import { downloadExpoGoAsync, getExpoGoVersionEntryAsync } from '../downloadExpoGoAsync';
7import { extractAsync } from '../tar';
8
9const asMock = (fn: any): jest.Mock => fn;
10
11jest.mock('../../log');
12
13jest.mock(`../downloadAppAsync`, () => ({
14  downloadAppAsync: jest.fn(),
15}));
16
17jest.mock(`../tar`, () => ({
18  extractAsync: jest.fn(),
19}));
20
21describe(getExpoGoVersionEntryAsync, () => {
22  beforeEach(() => {
23    vol.fromJSON({ tmp: '' }, '/tmp');
24  });
25  afterEach(() => {
26    vol.reset();
27  });
28
29  it(`returns the entry for a version`, async () => {
30    const scope = nock(getExpoApiBaseUrl())
31      .get('/v2/versions/latest')
32      .reply(200, require('../../api/__tests__/fixtures/versions-latest.json'));
33
34    expect(await getExpoGoVersionEntryAsync('42.0.0')).toEqual(
35      expect.objectContaining({
36        expoReactNativeTag: 'sdk-42.0.0',
37      })
38    );
39
40    expect(scope.isDone()).toBe(true);
41  });
42
43  it(`returns the entry for an UNVERSIONED project`, async () => {
44    const scope = nock(getExpoApiBaseUrl())
45      .get('/v2/versions/latest')
46      .reply(200, require('../../api/__tests__/fixtures/versions-latest.json'));
47
48    expect(await getExpoGoVersionEntryAsync('UNVERSIONED')).toEqual(
49      // Latest in the fixture
50      expect.objectContaining({
51        expoReactNativeTag: 'sdk-44.0.0',
52      })
53    );
54
55    expect(scope.isDone()).toBe(true);
56  });
57});
58
59describe(downloadExpoGoAsync, () => {
60  beforeEach(() => {
61    asMock(extractAsync).mockImplementationOnce(jest.fn());
62    asMock(downloadAppAsync).mockImplementationOnce(
63      jest.fn(jest.requireActual('../downloadAppAsync').downloadAppAsync)
64    );
65    vol.reset();
66  });
67  it('downloads the Expo Go app on iOS', async () => {
68    vol.fromJSON({ tmp: '' }, '/tmp');
69    const scope = nock(getExpoApiBaseUrl())
70      .get('/v2/versions/latest')
71      .reply(200, require('../../api/__tests__/fixtures/versions-latest.json'));
72    const scopeClient = nock('https://dpq5q02fu5f55.cloudfront.net')
73      .get('/Exponent-2.23.2.tar.gz')
74      .reply(200, '...');
75
76    await downloadExpoGoAsync('ios', { sdkVersion: '44.0.0' });
77
78    const generatedOutput = '/home/.expo/ios-simulator-app-cache/Exponent-2.23.2.tar.app';
79
80    // Endpoints were called
81    expect(scope.isDone()).toBe(true);
82    expect(scopeClient.isDone()).toBe(true);
83
84    // Platform options were parsed
85    expect(downloadAppAsync).toHaveBeenCalledWith({
86      cacheDirectory: 'expo-go',
87      extract: true,
88      onProgress: expect.anything(),
89      outputPath: generatedOutput,
90      url: 'https://dpq5q02fu5f55.cloudfront.net/Exponent-2.23.2.tar.gz',
91    });
92
93    // File won't be written because we mock the tar extraction.
94    // expect(vol.toJSON()[generatedOutput]).toBeDefined();
95
96    // Did extract tar
97    expect(extractAsync).toHaveBeenCalledWith('/tmp/Exponent-2.23.2.tar.app', generatedOutput);
98  });
99
100  it('downloads the Expo Go app on Android', async () => {
101    vol.fromJSON({}, '');
102
103    const scope = nock(getExpoApiBaseUrl())
104      .get('/v2/versions/latest')
105      .reply(200, require('../../api/__tests__/fixtures/versions-latest.json'));
106    const scopeClient = nock('https://d1ahtucjixef4r.cloudfront.net')
107      .get('/Exponent-2.23.2.apk')
108      .reply(200, '...');
109
110    await downloadExpoGoAsync('android', { sdkVersion: '44.0.0' });
111
112    const generatedOutput = '/home/.expo/android-apk-cache/Exponent-2.23.2.apk';
113
114    // Endpoints were called
115    expect(scope.isDone()).toBe(true);
116    expect(scopeClient.isDone()).toBe(true);
117
118    // Platform options were parsed
119    expect(downloadAppAsync).toHaveBeenCalledWith({
120      cacheDirectory: 'expo-go',
121      extract: false,
122      onProgress: expect.anything(),
123      outputPath: generatedOutput,
124      url: 'https://d1ahtucjixef4r.cloudfront.net/Exponent-2.23.2.apk',
125    });
126
127    expect(vol.toJSON()[generatedOutput]).toBeDefined();
128    // Did not extract Android APK
129    expect(extractAsync).toHaveBeenCalledTimes(0);
130  });
131
132  it('fails when the servers are down', async () => {
133    vol.fromJSON({}, '');
134
135    const scope = nock(getExpoApiBaseUrl())
136      .get('/v2/versions/latest')
137      .reply(200, require('../../api/__tests__/fixtures/versions-latest.json'));
138
139    // Mock a server failure when fetching from cloudfront.
140    const scopeClient = nock('https://d1ahtucjixef4r.cloudfront.net')
141      .get('/Exponent-2.23.2.apk')
142      .reply(500, 'something went wrong');
143
144    await expect(downloadExpoGoAsync('android', { sdkVersion: '44.0.0' })).rejects.toThrow(
145      /Unexpected response: Internal Server Error\. From url: https:\/\/d1ahtucjixef4r\.cloudfront\.net\/Exponent-2\.23\.2\.apk/
146    );
147    const generatedOutput = '/home/.expo/android-apk-cache/Exponent-2.23.2.apk';
148
149    // Endpoints were called
150    expect(scope.isDone()).toBe(true);
151    expect(scopeClient.isDone()).toBe(true);
152
153    // Platform options were parsed
154    expect(downloadAppAsync).toHaveBeenCalled();
155
156    // FS was not modified
157    expect(vol.toJSON()[generatedOutput]).not.toBeDefined();
158  });
159});
160