/
utils-test.js
344 lines (310 loc) · 12.6 KB
/
utils-test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
const utils = require('../lib/utils');
const assert = require('assert-diff');
const sinon = require('sinon');
const nock = require('nock');
describe('utils.between()', () => {
it('`left` positioned at the start', () => {
const rs = utils.between('<b>hello there friend</b>', '<b>', '</b>');
assert.strictEqual(rs, 'hello there friend');
});
it('somewhere in the middle', () => {
const rs = utils.between('something everything nothing', ' ', ' ');
assert.strictEqual(rs, 'everything');
});
it('not found', () => {
const rs = utils.between('oh oh _where_ is it', '<b>', '</b>');
assert.strictEqual(rs, '');
});
it('`right` before `left`', () => {
const rs = utils.between('>>> a <this> and that', '<', '>');
assert.strictEqual(rs, 'this');
});
it('`right` not found', () => {
const rs = utils.between('something [around[ somewhere', '[', ']');
assert.strictEqual(rs, '');
});
});
describe('utils.cutAfterJSON()', () => {
it('Works with simple JSON', () => {
assert.strictEqual(utils.cutAfterJSON('{"a": 1, "b": 1}'), '{"a": 1, "b": 1}');
});
it('Cut extra characters after JSON', () => {
assert.strictEqual(utils.cutAfterJSON('{"a": 1, "b": 1}abcd'), '{"a": 1, "b": 1}');
});
it('Tolerant to string constants', () => {
assert.strictEqual(utils.cutAfterJSON('{"a": "}1", "b": 1}abcd'), '{"a": "}1", "b": 1}');
});
it('Tolerant to string with escaped quoting', () => {
assert.strictEqual(utils.cutAfterJSON('{"a": "\\"}1", "b": 1}abcd'), '{"a": "\\"}1", "b": 1}');
});
it('works with nested', () => {
assert.strictEqual(
utils.cutAfterJSON('{"a": "\\"1", "b": 1, "c": {"test": 1}}abcd'),
'{"a": "\\"1", "b": 1, "c": {"test": 1}}',
);
});
it('Works with utf', () => {
assert.strictEqual(
utils.cutAfterJSON('{"a": "\\"фыва", "b": 1, "c": {"test": 1}}abcd'),
'{"a": "\\"фыва", "b": 1, "c": {"test": 1}}',
);
});
it('Works with \\\\ in string', () => {
assert.strictEqual(
utils.cutAfterJSON('{"a": "\\\\фыва", "b": 1, "c": {"test": 1}}abcd'),
'{"a": "\\\\фыва", "b": 1, "c": {"test": 1}}',
);
});
it('Works with \\\\ towards the end of a string', () => {
assert.strictEqual(
utils.cutAfterJSON('{"text": "\\\\"};'),
'{"text": "\\\\"}',
);
});
it('Works with [ as start', () => {
assert.strictEqual(
utils.cutAfterJSON('[{"a": 1}, {"b": 2}]abcd'),
'[{"a": 1}, {"b": 2}]',
);
});
it('Returns an error when not beginning with [ or {', () => {
assert.throws(() => {
utils.cutAfterJSON('abcd]}');
}, /Can't cut unsupported JSON \(need to begin with \[ or { \) but got: ./);
});
it('Returns an error when missing closing bracket', () => {
assert.throws(() => {
utils.cutAfterJSON('{"a": 1,{ "b": 1}');
}, /Can't cut unsupported JSON \(no matching closing bracket found\)/);
});
});
describe('utils.parseAbbreviatedNumber', () => {
it('Parses abbreviated numbers', () => {
assert.strictEqual(utils.parseAbbreviatedNumber('41K'), 41000);
assert.strictEqual(utils.parseAbbreviatedNumber('1.5M'), 1500000);
assert.strictEqual(utils.parseAbbreviatedNumber('8.19K '), 8190);
});
it('Parses non-abbreviated numbers', () => {
assert.strictEqual(utils.parseAbbreviatedNumber('1234'), 1234);
assert.strictEqual(utils.parseAbbreviatedNumber('123.456'), 123);
});
it('Returns `null` when given non-number', () => {
assert.strictEqual(utils.parseAbbreviatedNumber('abc'), null);
});
});
describe('utils.checkForUpdates', () => {
beforeEach(() => delete process.env.YTDL_NO_UPDATE);
after(() => process.env.YTDL_NO_UPDATE = 'true');
beforeEach(() => utils.lastUpdateCheck = Date.now());
afterEach(() => sinon.restore());
describe('Already on latest', () => {
it('Does not warn the console', async() => {
const pkg = require('../package.json');
sinon.replace(pkg, 'version', 'v1.0.0');
const scope = nock('https://api.github.com')
.get('/repos/fent/node-ytdl-core/releases/latest')
.reply(200, { tag_name: `v${pkg.version}` });
const warnSpy = sinon.spy();
sinon.replace(console, 'warn', warnSpy);
sinon.replace(Date, 'now', sinon.stub().returns(Infinity));
await utils.checkForUpdates();
scope.done();
assert.ok(warnSpy.notCalled);
});
});
describe('When there is a new update', () => {
it('Warns the console about the update', async() => {
const pkg = require('../package.json');
sinon.replace(pkg, 'version', 'v1.0.0');
const scope = nock('https://api.github.com')
.get('/repos/fent/node-ytdl-core/releases/latest')
.reply(200, { tag_name: 'vInfinity.0.0' });
const warnSpy = sinon.spy();
sinon.replace(console, 'warn', warnSpy);
sinon.replace(Date, 'now', sinon.stub().returns(Infinity));
await utils.checkForUpdates();
scope.done();
assert.ok(warnSpy.called);
assert.ok(/is out of date!/.test(warnSpy.firstCall.args[0]));
});
});
describe('Already checked recently', () => {
it('Does not make request to check', async() => {
const warnSpy = sinon.spy();
sinon.replace(console, 'warn', warnSpy);
await utils.checkForUpdates();
assert.ok(warnSpy.notCalled);
});
});
describe('With `YTDL_NO_UPDATE` env variable set', () => {
it('Does not make request to check', async() => {
process.env.YTDL_NO_UPDATE = 'true';
const warnSpy = sinon.spy();
sinon.replace(console, 'warn', warnSpy);
await utils.checkForUpdates();
assert.ok(warnSpy.notCalled);
});
});
describe('When there is an error checking for updates', () => {
it('Warns the console', async() => {
const pkg = require('../package.json');
sinon.replace(pkg, 'version', 'v1.0.0');
const scope = nock('https://api.github.com')
.get('/repos/fent/node-ytdl-core/releases/latest')
.reply(403, 'slow down there');
const warnSpy = sinon.spy();
sinon.replace(console, 'warn', warnSpy);
sinon.replace(Date, 'now', sinon.stub().returns(Infinity));
await utils.checkForUpdates();
scope.done();
assert.ok(warnSpy.called);
assert.ok(/Error checking for updates/.test(warnSpy.firstCall.args[0]));
assert.ok(/Status code: 403/.test(warnSpy.firstCall.args[1]));
});
});
});
describe('utils.isIPv6', () => {
it('returns true for valid IPv6 net', () => {
assert.ok(utils.isIPv6('100::/128'));
assert.ok(utils.isIPv6('100::/119'));
assert.ok(utils.isIPv6('100::/13'));
assert.ok(utils.isIPv6('100::/1'));
assert.ok(utils.isIPv6('20a::/13'));
assert.ok(utils.isIPv6('0064:ff9b:0000:0000:0000:0000:1234:5678/13'));
assert.ok(utils.isIPv6('0064:ff9b:0001:1122:0033:4400:0000:0001/13'));
assert.ok(utils.isIPv6('fe80:4:6c:8c74:0000:5efe:afef:a89/13'));
assert.ok(utils.isIPv6('fe80:4:6c:8c74:0000:5efe::a89/13'));
assert.ok(utils.isIPv6('fe80:4:6c:8c74:0000::a89/13'));
assert.ok(utils.isIPv6('fe80:4:6c:8c74::a89/13'));
assert.ok(utils.isIPv6('fe80:4:6c::a89/13'));
assert.ok(utils.isIPv6('fe80:4::a89/13'));
assert.ok(utils.isIPv6('fe80::a89/13'));
assert.ok(utils.isIPv6('fe80::/13'));
assert.ok(utils.isIPv6('fea3:c65:43ee:54:e2a:2357:4ac4:732/13'));
assert.ok(utils.isIPv6('fe80:1234:abc/13'));
assert.ok(utils.isIPv6('20a:1234::1/13'));
});
it('returns false for valid but unwanted IPv6 net', () => {
assert.ok(!utils.isIPv6('::/1'));
assert.ok(!utils.isIPv6('::1/1'));
assert.ok(!utils.isIPv6('::ffff:10.0.0.3/1'));
assert.ok(!utils.isIPv6('::10.0.0.3/1'));
assert.ok(!utils.isIPv6('127.0.0.1/1'));
assert.ok(!utils.isIPv6('24a6:57:c:36cf:0000:5efe:109.205.140.116/64'));
});
it('returns false for invalid IPv6 net', () => {
assert.ok(!utils.isIPv6('100::/129'));
assert.ok(!utils.isIPv6('100::/130'));
assert.ok(!utils.isIPv6('100::/abc'));
assert.ok(!utils.isIPv6('100::'));
assert.ok(!utils.isIPv6('fe80:4::8c74::5efe:afef:a89/64'));
assert.ok(!utils.isIPv6('24a6:57:c:36cf:0000:5efe:ab:cd:ef/64'));
assert.ok(!utils.isIPv6('24a6:57:c:36cf:0000:5efe::ab:cd/64'));
});
});
describe('utils.getRandomIPv6', () => {
it('errors for completely invalid ipv6', () => {
assert.throws(() => {
utils.getRandomIPv6('some random string');
}, /Invalid IPv6 format/);
});
it('errors for invalid subnet sizes', () => {
assert.throws(() => {
utils.getRandomIPv6('fe80::/300');
}, /Invalid IPv6 format/);
assert.throws(() => {
utils.getRandomIPv6('127::1/1');
}, /Invalid IPv6 subnet/);
assert.throws(() => {
utils.getRandomIPv6('fe80::');
}, /Invalid IPv6 format/);
assert.throws(() => {
utils.getRandomIPv6('fe80::/ff');
}, /Invalid IPv6 format/);
});
it('keeps the upper bits of the subnet', () => {
for (let i = 24; i < 128; i++) {
const ip = utils.getRandomIPv6(`ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/${i}`);
const bits = ip.split(':').map(x => parseInt(x, 16).toString(2)).join('');
assert.equal(bits.substr(0, i), '1'.repeat(i));
}
});
it('rolls random bits for the lower bits', () => {
// Only testing to 64 and not 128
// The second part of the random IP is tested to not be only onces
// and rolling 8 full 0xff bytes should be unlikely enough
for (let i = 24; i < 64; i++) {
const ip = utils.getRandomIPv6(`ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/${i}`);
const bits = ip.split(':').map(x => parseInt(x, 16).toString(2)).join('');
assert.ok(bits.substr(i).split('').some(x => x === '0'));
}
});
});
describe('utils.normalizeIP', () => {
it('does work for already expanded ips', () => {
assert.deepEqual(utils.normalizeIP('1:2:3:4:5:6:7:8'), [1, 2, 3, 4, 5, 6, 7, 8]);
});
it('resolves bytes to integers', () => {
assert.deepEqual(utils.normalizeIP('ffff'), [65535, 0, 0, 0, 0, 0, 0, 0]);
});
it('expands ::', () => {
assert.deepEqual(utils.normalizeIP('ab::cd'), [171, 0, 0, 0, 0, 0, 0, 205]);
assert.deepEqual(utils.normalizeIP('ab:cd::ef'), [171, 205, 0, 0, 0, 0, 0, 239]);
assert.deepEqual(utils.normalizeIP('ab:cd::12:ef'), [171, 205, 0, 0, 0, 0, 18, 239]);
assert.deepEqual(utils.normalizeIP('ab:cd::'), [171, 205, 0, 0, 0, 0, 0, 0]);
assert.deepEqual(utils.normalizeIP('123::'), [291, 0, 0, 0, 0, 0, 0, 0]);
assert.deepEqual(utils.normalizeIP('0::'), [0, 0, 0, 0, 0, 0, 0, 0]);
assert.deepEqual(utils.normalizeIP('::'), [0, 0, 0, 0, 0, 0, 0, 0]);
assert.deepEqual(utils.normalizeIP('::ab:cd'), [0, 0, 0, 0, 0, 0, 171, 205]);
});
it('does handle invalid ips', () => {
assert.deepEqual(utils.normalizeIP('1:2:3:4:5::6:7:8::'), [1, 2, 3, 4, 5, 6, 7, 8]);
assert.deepEqual(utils.normalizeIP('::1:2:3:4:5:6:7:8'), [1, 2, 3, 4, 5, 6, 7, 8]);
assert.deepEqual(utils.normalizeIP('1:2:3:4:5::6:7:8:9:10'), [1, 2, 3, 6, 7, 8, 9, 16]);
});
});
describe('utils.exposedMiniget', () => {
it('does not error with undefined requestOptionsOverwrite', async() => {
const scope = nock('https://test.com').get('/').reply(200, 'nice');
const req = utils.exposedMiniget('https://test.com/', {});
await req.text();
scope.done();
});
it('does not error without options', async() => {
const scope = nock('https://test.com').get('/').reply(200, 'nice');
const req = utils.exposedMiniget('https://test.com/');
await req.text();
scope.done();
});
it('does not error without options', async() => {
const scope = nock('https://test.com').get('/').reply(200, 'nice');
const req = utils.exposedMiniget('https://test.com/');
assert.equal(await req.text(), 'nice');
scope.done();
});
it('calls a provided callback with the req object', async() => {
const scope = nock('https://test.com').get('/').reply(200, 'nice');
let cbReq;
const requestCallback = r => cbReq = r;
const req = utils.exposedMiniget('https://test.com/', { requestCallback });
await req.text();
assert.equal(cbReq, req);
scope.done();
});
it('it uses requestOptions', async() => {
const scope = nock('https://test.com', { reqheaders: { auth: 'a' } }).get('/').reply(200, 'nice');
const req = utils.exposedMiniget('https://test.com/', { requestOptions: { headers: { auth: 'a' } } });
await req.text();
scope.done();
});
it('it prefers requestOptionsOverwrite over requestOptions', async() => {
const scope = nock('https://test.com', { reqheaders: { auth: 'b' } }).get('/').reply(200, 'nice');
const req = utils.exposedMiniget(
'https://test.com/',
{ requestOptions: { headers: { auth: 'a' } } },
{ headers: { auth: 'b' } },
);
await req.text();
scope.done();
});
});