1import nock from 'nock';
2import fetch from 'node-fetch';
3import path from 'path';
4import { Stream } from 'stream';
5import { promisify } from 'util';
6
7import * as Log from '../../../log';
8import { wrapFetchWithProgress } from '../wrapFetchWithProgress';
9
10const fs = jest.requireActual('fs') as typeof import('fs');
11const pipeline = promisify(Stream.pipeline);
12
13const asMock = <T extends (...args: any[]) => any>(fn: T): jest.MockedFunction<T> =>
14  fn as jest.MockedFunction<T>;
15
16jest.mock(`../../../log`);
17
18describe(wrapFetchWithProgress, () => {
19  beforeEach(() => {
20    asMock(Log.warn).mockClear();
21  });
22  it('should call the progress callback', async () => {
23    const url = 'https://example.com';
24
25    const scope = nock(url)
26      .get('/asset')
27      .reply(() => {
28        const fixturePath = path.join(__dirname, './fixtures/panda.png');
29        return [
30          // Status
31          200,
32          // Data
33          fs.createReadStream(fixturePath),
34          {
35            // Headers for progress
36            'Content-Length': fs.statSync(fixturePath).size,
37          },
38        ];
39      });
40
41    const onProgress = jest.fn();
42
43    await pipeline(
44      (
45        await wrapFetchWithProgress(fetch)(url + '/asset', {
46          onProgress,
47        })
48      ).body,
49      require('fs').createWriteStream('/foobar')
50    );
51    // Ensure this example is called more than once.
52    expect(onProgress).toHaveBeenCalledTimes(4);
53    expect(onProgress).toHaveBeenNthCalledWith(1, {
54      loaded: 65536,
55      progress: 0.43515444476906323,
56      total: 150604,
57    });
58
59    // Ensure progress ends on 1.0
60    expect(onProgress).toHaveBeenLastCalledWith({
61      loaded: 150604,
62      progress: 1,
63      total: 150604,
64    });
65    expect(scope.isDone()).toBe(true);
66  });
67
68  it('should warn that a request is missing the content length header', async () => {
69    const url = 'https://example.com';
70
71    // Return no Content-Length header.
72    const scope = nock(url).get('/asset').reply(200, '');
73
74    const onProgress = jest.fn();
75
76    await wrapFetchWithProgress(fetch)(url + '/asset', {
77      onProgress,
78    });
79    expect(Log.warn).toHaveBeenCalledWith(
80      'Progress callback not supported for network request because "Content-Length" header missing or invalid in response from URL:',
81      'https://example.com/asset'
82    );
83
84    expect(scope.isDone()).toBe(true);
85  });
86});
87