@verdaccio/store 6.0.0-6-next.21 → 6.0.0-6-next.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/build/index.d.ts +5 -4
  3. package/build/index.js +31 -15
  4. package/build/index.js.map +1 -1
  5. package/build/lib/TransFormResults.d.ts +15 -0
  6. package/build/lib/TransFormResults.js +61 -0
  7. package/build/lib/TransFormResults.js.map +1 -0
  8. package/build/lib/search-utils.d.ts +4 -0
  9. package/build/lib/search-utils.js +59 -0
  10. package/build/lib/search-utils.js.map +1 -0
  11. package/build/lib/star-utils.d.ts +14 -0
  12. package/build/{star-utils.js → lib/star-utils.js} +16 -2
  13. package/build/lib/star-utils.js.map +1 -0
  14. package/build/lib/storage-utils.d.ts +82 -0
  15. package/build/{storage-utils.js → lib/storage-utils.js} +173 -60
  16. package/build/lib/storage-utils.js.map +1 -0
  17. package/build/lib/uplink-util.d.ts +10 -0
  18. package/build/lib/uplink-util.js +49 -0
  19. package/build/lib/uplink-util.js.map +1 -0
  20. package/build/lib/versions-utils.d.ts +28 -0
  21. package/build/lib/versions-utils.js +104 -0
  22. package/build/lib/versions-utils.js.map +1 -0
  23. package/build/local-storage.d.ts +3 -165
  24. package/build/local-storage.js +6 -1166
  25. package/build/local-storage.js.map +1 -1
  26. package/build/storage.d.ts +242 -86
  27. package/build/storage.js +1681 -548
  28. package/build/storage.js.map +1 -1
  29. package/build/type.d.ts +42 -19
  30. package/build/type.js.map +1 -1
  31. package/jest.config.js +8 -2
  32. package/package.json +71 -71
  33. package/src/index.ts +5 -4
  34. package/src/lib/TransFormResults.ts +42 -0
  35. package/src/lib/search-utils.ts +50 -0
  36. package/src/{star-utils.ts → lib/star-utils.ts} +16 -5
  37. package/src/lib/storage-utils.ts +362 -0
  38. package/src/lib/uplink-util.ts +41 -0
  39. package/src/lib/versions-utils.ts +87 -0
  40. package/src/local-storage.ts +7 -1217
  41. package/src/storage.ts +1683 -635
  42. package/src/type.ts +47 -21
  43. package/test/fixtures/config/getTarballNext-getupstream.yaml +17 -0
  44. package/test/fixtures/config/syncDoubleUplinksMetadata.yaml +25 -0
  45. package/test/fixtures/config/syncNoUplinksMetadata.yaml +17 -0
  46. package/test/fixtures/config/syncSingleUplinksMetadata.yaml +21 -0
  47. package/test/fixtures/config/updateManifest-1.yaml +16 -0
  48. package/test/fixtures/manifests/foo-npmjs.json +253 -0
  49. package/test/fixtures/manifests/foo-verdaccio.json +253 -0
  50. package/test/fixtures/tarball.tgz +0 -0
  51. package/test/helpers.ts +22 -0
  52. package/test/local-store.__disabled__.ts +553 -0
  53. package/test/search.spec.ts +4 -5
  54. package/test/star.spec.ts +31 -0
  55. package/test/storage-utils.spec.ts +129 -42
  56. package/test/storage.spec.ts +943 -33
  57. package/test/versions.spec.ts +109 -0
  58. package/tsconfig.json +6 -3
  59. package/build/search.d.ts +0 -35
  60. package/build/search.js +0 -198
  61. package/build/search.js.map +0 -1
  62. package/build/star-utils.d.ts +0 -9
  63. package/build/star-utils.js.map +0 -1
  64. package/build/storage-utils.d.ts +0 -39
  65. package/build/storage-utils.js.map +0 -1
  66. package/build/uplink-util.d.ts +0 -7
  67. package/build/uplink-util.js +0 -38
  68. package/build/uplink-util.js.map +0 -1
  69. package/src/search.ts +0 -175
  70. package/src/storage-utils.ts +0 -263
  71. package/src/uplink-util.ts +0 -32
  72. package/test/local-storage.spec.ts +0 -583
@@ -1,17 +1,36 @@
1
+ import { pseudoRandomBytes } from 'crypto';
2
+ import fs from 'fs';
3
+ import MockDate from 'mockdate';
1
4
  import nock from 'nock';
2
5
  import * as httpMocks from 'node-mocks-http';
6
+ import os from 'os';
7
+ import path from 'path';
3
8
 
4
- import { Config } from '@verdaccio/config';
5
- import { HEADERS, errorUtils } from '@verdaccio/core';
9
+ import { Config, getDefaultConfig } from '@verdaccio/config';
10
+ import { API_ERROR, DIST_TAGS, HEADERS, HEADER_TYPE, errorUtils, fileUtils } from '@verdaccio/core';
6
11
  import { setup } from '@verdaccio/logger';
7
- import { configExample, generateRamdonStorage } from '@verdaccio/mock';
8
- import { generatePackageMetadata } from '@verdaccio/test-helper';
12
+ import {
13
+ addNewVersion,
14
+ generateLocalPackageMetadata,
15
+ generatePackageMetadata,
16
+ generateRemotePackageMetadata,
17
+ } from '@verdaccio/test-helper';
18
+ import { AbbreviatedManifest, Manifest, Version } from '@verdaccio/types';
9
19
 
10
20
  import { Storage } from '../src';
21
+ import manifestFooRemoteNpmjs from './fixtures/manifests/foo-npmjs.json';
22
+ import { configExample } from './helpers';
11
23
 
12
- setup([]);
24
+ function generateRandomStorage() {
25
+ const tempStorage = pseudoRandomBytes(5).toString('hex');
26
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), '/verdaccio-test'));
13
27
 
14
- const domain = 'http://localhost:4873';
28
+ return path.join(tempRoot, tempStorage);
29
+ }
30
+
31
+ setup({ type: 'stdout', format: 'pretty', level: 'trace' });
32
+
33
+ const domain = 'https://registry.npmjs.org';
15
34
  const fakeHost = 'localhost:4873';
16
35
  const fooManifest = generatePackageMetadata('foo', '1.0.0');
17
36
 
@@ -21,31 +40,846 @@ describe('storage', () => {
21
40
  nock.abortPendingRequests();
22
41
  jest.clearAllMocks();
23
42
  });
24
- describe('add packages', () => {
25
- test('add package item', async () => {
26
- nock(domain).get('/foo').reply(404);
43
+
44
+ describe('updateManifest', () => {
45
+ test('create private package', async () => {
46
+ const mockDate = '2018-01-14T11:17:40.712Z';
47
+ MockDate.set(mockDate);
48
+ const pkgName = 'upstream';
49
+ const requestOptions = {
50
+ host: 'localhost',
51
+ protocol: 'http',
52
+ headers: {},
53
+ };
54
+ const config = new Config(
55
+ configExample(
56
+ {
57
+ ...getDefaultConfig(),
58
+ storage: generateRandomStorage(),
59
+ },
60
+ './fixtures/config/updateManifest-1.yaml',
61
+ __dirname
62
+ )
63
+ );
64
+ const storage = new Storage(config);
65
+ await storage.init(config);
66
+ const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
67
+ await storage.updateManifest(bodyNewManifest, {
68
+ signal: new AbortController().signal,
69
+ name: pkgName,
70
+ uplinksLook: true,
71
+ revision: '1',
72
+ requestOptions,
73
+ });
74
+ const manifest = (await storage.getPackageByOptions({
75
+ name: pkgName,
76
+ uplinksLook: true,
77
+ requestOptions,
78
+ })) as Manifest;
79
+ expect(manifest.name).toEqual(pkgName);
80
+ expect(manifest._id).toEqual(pkgName);
81
+ expect(Object.keys(manifest.versions)).toEqual(['1.0.0']);
82
+ expect(manifest.time).toEqual({
83
+ '1.0.0': mockDate,
84
+ created: mockDate,
85
+ modified: mockDate,
86
+ });
87
+ expect(manifest[DIST_TAGS]).toEqual({ latest: '1.0.0' });
88
+ expect(manifest.readme).toEqual('# test');
89
+ expect(manifest._attachments).toEqual({});
90
+ expect(typeof manifest._rev).toBeTruthy();
91
+ });
92
+
93
+ // TODO: Review triggerUncaughtException exception on abort
94
+ test.skip('abort creating a private package', async () => {
95
+ const mockDate = '2018-01-14T11:17:40.712Z';
96
+ MockDate.set(mockDate);
97
+ const pkgName = 'upstream';
98
+ const config = new Config(
99
+ configExample(
100
+ {
101
+ storage: generateRandomStorage(),
102
+ },
103
+ './fixtures/config/updateManifest-1.yaml',
104
+ __dirname
105
+ )
106
+ );
107
+ const storage = new Storage(config);
108
+ await storage.init(config);
109
+ const ac = new AbortController();
110
+ setTimeout(() => {
111
+ ac.abort();
112
+ }, 10);
113
+ const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
114
+ await expect(
115
+ storage.updateManifest(bodyNewManifest, {
116
+ signal: ac.signal,
117
+ name: pkgName,
118
+ uplinksLook: true,
119
+ revision: '1',
120
+ requestOptions: {
121
+ host: 'localhost',
122
+ protocol: 'http',
123
+ headers: {},
124
+ },
125
+ })
126
+ ).rejects.toThrow('should throw here');
127
+ });
128
+
129
+ test('create private package with multiple consecutive versions', async () => {
130
+ const mockDate = '2018-01-14T11:17:40.712Z';
131
+ MockDate.set(mockDate);
132
+ const settings = {
133
+ uplinksLook: true,
134
+ revision: '1',
135
+ requestOptions: {
136
+ host: 'localhost',
137
+ protocol: 'http',
138
+ headers: {},
139
+ },
140
+ };
141
+ const pkgName = 'upstream';
142
+ // const storage = generateRandomStorage();
143
+ const config = new Config(
144
+ configExample(
145
+ {
146
+ storage: await fileUtils.createTempStorageFolder('storage-test'),
147
+ },
148
+ './fixtures/config/updateManifest-1.yaml',
149
+ __dirname
150
+ )
151
+ );
152
+ const storage = new Storage(config);
153
+ await storage.init(config);
154
+ // create a package
155
+ const bodyNewManifest1 = generatePackageMetadata(pkgName, '1.0.0');
156
+ await storage.updateManifest(bodyNewManifest1, {
157
+ signal: new AbortController().signal,
158
+ name: pkgName,
159
+ ...settings,
160
+ });
161
+ // publish second version
162
+ const bodyNewManifest2 = generatePackageMetadata(pkgName, '1.0.1');
163
+ await storage.updateManifest(bodyNewManifest2, {
164
+ signal: new AbortController().signal,
165
+ name: pkgName,
166
+ ...settings,
167
+ });
168
+ // retrieve package metadata
169
+ const manifest = (await storage.getPackageByOptions({
170
+ name: pkgName,
171
+ uplinksLook: true,
172
+ requestOptions: {
173
+ host: 'localhost',
174
+ protocol: 'http',
175
+ headers: {},
176
+ },
177
+ })) as Manifest;
178
+ expect(manifest.name).toEqual(pkgName);
179
+ expect(manifest._id).toEqual(pkgName);
180
+ expect(Object.keys(manifest.versions)).toEqual(['1.0.0', '1.0.1']);
181
+ expect(manifest.time).toEqual({
182
+ '1.0.0': mockDate,
183
+ '1.0.1': mockDate,
184
+ created: mockDate,
185
+ modified: mockDate,
186
+ });
187
+ expect(manifest[DIST_TAGS]).toEqual({ latest: '1.0.1' });
188
+ expect(manifest.readme).toEqual('# test');
189
+ expect(manifest._attachments).toEqual({});
190
+ expect(typeof manifest._rev).toBeTruthy();
191
+ // verify the version structure is correct
192
+ const manifestVersion = (await storage.getPackageByOptions({
193
+ name: pkgName,
194
+ version: '1.0.1',
195
+ uplinksLook: true,
196
+ requestOptions: {
197
+ host: 'localhost',
198
+ protocol: 'http',
199
+ headers: {},
200
+ },
201
+ })) as Version;
202
+ expect(manifestVersion.name).toEqual(pkgName);
203
+ expect(manifestVersion.version).toEqual('1.0.1');
204
+ expect(manifestVersion._id).toEqual(`${pkgName}@1.0.1`);
205
+ expect(manifestVersion.description).toEqual('package generated');
206
+ expect(manifestVersion.dist).toEqual({
207
+ integrity:
208
+ 'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
209
+ shasum: '2c03764f651a9f016ca0b7620421457b619151b9',
210
+ tarball: 'http://localhost:5555/upstream/-/upstream-1.0.1.tgz',
211
+ });
212
+
213
+ expect(manifestVersion.contributors).toEqual([]);
214
+ expect(manifestVersion.main).toEqual('index.js');
215
+ expect(manifestVersion.author).toEqual({ name: 'User NPM', email: 'user@domain.com' });
216
+ expect(manifestVersion.dependencies).toEqual({ verdaccio: '^2.7.2' });
217
+ });
218
+
219
+ test('fails if version already exist', async () => {
220
+ const settings = {
221
+ uplinksLook: true,
222
+ revision: '1',
223
+ requestOptions: {
224
+ host: 'localhost',
225
+ protocol: 'http',
226
+ headers: {},
227
+ },
228
+ };
229
+ const pkgName = 'upstream';
230
+ const config = new Config(
231
+ configExample(
232
+ {
233
+ storage: generateRandomStorage(),
234
+ },
235
+ './fixtures/config/getTarballNext-getupstream.yaml',
236
+ __dirname
237
+ )
238
+ );
239
+ const storage = new Storage(config);
240
+ await storage.init(config);
241
+ const bodyNewManifest1 = generatePackageMetadata(pkgName, '1.0.0');
242
+ const bodyNewManifest2 = generatePackageMetadata(pkgName, '1.0.0');
243
+ await storage.updateManifest(bodyNewManifest1, {
244
+ signal: new AbortController().signal,
245
+ name: pkgName,
246
+ ...settings,
247
+ });
248
+ await expect(
249
+ storage.updateManifest(bodyNewManifest2, {
250
+ signal: new AbortController().signal,
251
+ name: pkgName,
252
+ ...settings,
253
+ })
254
+ ).rejects.toThrow(API_ERROR.PACKAGE_EXIST);
255
+ });
256
+ });
257
+
258
+ describe('getTarballNext', () => {
259
+ test('should not found a package anywhere', (done) => {
27
260
  const config = new Config(
28
261
  configExample({
29
- storage: generateRamdonStorage(),
262
+ ...getDefaultConfig(),
263
+ storage: generateRandomStorage(),
264
+ })
265
+ );
266
+ const storage = new Storage(config);
267
+ storage.init(config).then(() => {
268
+ const abort = new AbortController();
269
+ storage
270
+ .getTarballNext('some-tarball', 'some-tarball-1.0.0.tgz', {
271
+ signal: abort.signal,
272
+ })
273
+ .then((stream) => {
274
+ stream.on('error', (err) => {
275
+ expect(err).toEqual(errorUtils.getNotFound(API_ERROR.NO_PACKAGE));
276
+ done();
277
+ });
278
+ });
279
+ });
280
+ });
281
+
282
+ test('should create a package if tarball is requested and does not exist locally', (done) => {
283
+ const pkgName = 'upstream';
284
+ const upstreamManifest = generateRemotePackageMetadata(
285
+ pkgName,
286
+ '1.0.0',
287
+ 'https://registry.something.org'
288
+ );
289
+ nock('https://registry.verdaccio.org').get(`/${pkgName}`).reply(201, upstreamManifest);
290
+ nock('https://registry.something.org')
291
+ .get(`/${pkgName}/-/${pkgName}-1.0.0.tgz`)
292
+ // types does not match here with documentation
293
+ // @ts-expect-error
294
+ .replyWithFile(201, path.join(__dirname, 'fixtures/tarball.tgz'), {
295
+ [HEADER_TYPE.CONTENT_LENGTH]: 277,
296
+ });
297
+ const config = new Config(
298
+ configExample(
299
+ {
300
+ storage: generateRandomStorage(),
301
+ },
302
+ './fixtures/config/getTarballNext-getupstream.yaml',
303
+ __dirname
304
+ )
305
+ );
306
+ const storage = new Storage(config);
307
+ storage.init(config).then(() => {
308
+ const abort = new AbortController();
309
+ storage
310
+ .getTarballNext(pkgName, `${pkgName}-1.0.0.tgz`, {
311
+ signal: abort.signal,
312
+ })
313
+ .then((stream) => {
314
+ stream.on('data', (dat) => {
315
+ expect(dat).toBeDefined();
316
+ });
317
+ stream.on('end', () => {
318
+ done();
319
+ });
320
+ stream.on('error', () => {
321
+ done('this should not happen');
322
+ });
323
+ });
324
+ });
325
+ });
326
+
327
+ test('should serve fetch tarball from upstream without dist info local', (done) => {
328
+ const pkgName = 'upstream';
329
+ const upstreamManifest = addNewVersion(
330
+ generateRemotePackageMetadata(pkgName, '1.0.0'),
331
+ '1.0.1'
332
+ );
333
+ nock('https://registry.verdaccio.org').get(`/${pkgName}`).reply(201, upstreamManifest);
334
+ nock('http://localhost:5555')
335
+ .get(`/${pkgName}/-/${pkgName}-1.0.1.tgz`)
336
+ // types does not match here with documentation
337
+ // @ts-expect-error
338
+ .replyWithFile(201, path.join(__dirname, 'fixtures/tarball.tgz'), {
339
+ [HEADER_TYPE.CONTENT_LENGTH]: 277,
340
+ });
341
+ const config = new Config(
342
+ configExample(
343
+ {
344
+ storage: generateRandomStorage(),
345
+ },
346
+ './fixtures/config/getTarballNext-getupstream.yaml',
347
+ __dirname
348
+ )
349
+ );
350
+ const storage = new Storage(config);
351
+ storage.init(config).then(() => {
352
+ const ac = new AbortController();
353
+ const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
354
+ storage
355
+ .updateManifest(bodyNewManifest, {
356
+ signal: ac.signal,
357
+ name: pkgName,
358
+ uplinksLook: true,
359
+ revision: '1',
360
+ requestOptions: {
361
+ host: 'localhost',
362
+ protocol: 'http',
363
+ headers: {},
364
+ },
365
+ })
366
+ .then(() => {
367
+ const abort = new AbortController();
368
+ storage
369
+ .getTarballNext(pkgName, `${pkgName}-1.0.1.tgz`, {
370
+ signal: abort.signal,
371
+ })
372
+ .then((stream) => {
373
+ stream.on('data', (dat) => {
374
+ expect(dat).toBeDefined();
375
+ });
376
+ stream.on('end', () => {
377
+ done();
378
+ });
379
+ stream.on('error', () => {
380
+ done('this should not happen');
381
+ });
382
+ });
383
+ });
384
+ });
385
+ });
386
+
387
+ test('should serve fetch tarball from upstream without with info local', (done) => {
388
+ const pkgName = 'upstream';
389
+ const upstreamManifest = addNewVersion(
390
+ addNewVersion(generateRemotePackageMetadata(pkgName, '1.0.0'), '1.0.1'),
391
+ '1.0.2'
392
+ );
393
+ nock('https://registry.verdaccio.org')
394
+ .get(`/${pkgName}`)
395
+ .times(10)
396
+ .reply(201, upstreamManifest);
397
+ nock('http://localhost:5555')
398
+ .get(`/${pkgName}/-/${pkgName}-1.0.0.tgz`)
399
+ // types does not match here with documentation
400
+ // @ts-expect-error
401
+ .replyWithFile(201, path.join(__dirname, 'fixtures/tarball.tgz'), {
402
+ [HEADER_TYPE.CONTENT_LENGTH]: 277,
403
+ });
404
+ const storagePath = generateRandomStorage();
405
+ const config = new Config(
406
+ configExample(
407
+ {
408
+ storage: storagePath,
409
+ },
410
+ './fixtures/config/getTarballNext-getupstream.yaml',
411
+ __dirname
412
+ )
413
+ );
414
+ const storage = new Storage(config);
415
+ storage.init(config).then(() => {
416
+ const req = httpMocks.createRequest({
417
+ method: 'GET',
418
+ connection: { remoteAddress: fakeHost },
419
+ headers: {
420
+ host: fakeHost,
421
+ [HEADERS.FORWARDED_PROTO]: 'http',
422
+ },
423
+ url: '/',
424
+ });
425
+ return storage
426
+ .getPackageByOptions({
427
+ name: pkgName,
428
+ uplinksLook: true,
429
+ requestOptions: {
430
+ headers: req.headers as any,
431
+ protocol: req.protocol,
432
+ host: req.get('host') as string,
433
+ },
434
+ })
435
+ .then(() => {
436
+ const abort = new AbortController();
437
+ storage
438
+ .getTarballNext(pkgName, `${pkgName}-1.0.0.tgz`, {
439
+ signal: abort.signal,
440
+ })
441
+ .then((stream) => {
442
+ stream.on('data', (dat) => {
443
+ expect(dat).toBeDefined();
444
+ });
445
+ stream.on('end', () => {
446
+ done();
447
+ });
448
+ stream.once('error', () => {
449
+ done('this should not happen');
450
+ });
451
+ });
452
+ });
453
+ });
454
+ });
455
+
456
+ test('should serve local cache', (done) => {
457
+ const pkgName = 'upstream';
458
+ const config = new Config(
459
+ configExample(
460
+ {
461
+ storage: generateRandomStorage(),
462
+ },
463
+ './fixtures/config/getTarballNext-getupstream.yaml',
464
+ __dirname
465
+ )
466
+ );
467
+ const storage = new Storage(config);
468
+ storage.init(config).then(() => {
469
+ const ac = new AbortController();
470
+ const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
471
+ storage
472
+ .updateManifest(bodyNewManifest, {
473
+ signal: ac.signal,
474
+ name: pkgName,
475
+ uplinksLook: true,
476
+ revision: '1',
477
+ requestOptions: {
478
+ host: 'localhost',
479
+ protocol: 'http',
480
+ headers: {},
481
+ },
482
+ })
483
+ .then(() => {
484
+ const abort = new AbortController();
485
+ storage
486
+ .getTarballNext(pkgName, `${pkgName}-1.0.0.tgz`, {
487
+ signal: abort.signal,
488
+ })
489
+ .then((stream) => {
490
+ stream.on('data', (dat) => {
491
+ expect(dat).toBeDefined();
492
+ });
493
+ stream.on('end', () => {
494
+ done();
495
+ });
496
+ stream.on('error', () => {
497
+ done('this should not happen');
498
+ });
499
+ });
500
+ });
501
+ });
502
+ });
503
+ });
504
+
505
+ describe('syncUplinksMetadataNext()', () => {
506
+ describe('error handling', () => {
507
+ test('should handle double failure on uplinks with timeout', async () => {
508
+ const fooManifest = generatePackageMetadata('timeout', '8.0.0');
509
+
510
+ nock('https://registry.domain.com')
511
+ .get('/timeout')
512
+ .times(10)
513
+ .delayConnection(2000)
514
+ .reply(201, manifestFooRemoteNpmjs);
515
+
516
+ const config = new Config(
517
+ configExample(
518
+ {
519
+ storage: generateRandomStorage(),
520
+ },
521
+ './fixtures/config/syncDoubleUplinksMetadata.yaml',
522
+ __dirname
523
+ )
524
+ );
525
+
526
+ const storage = new Storage(config);
527
+ await storage.init(config);
528
+ await expect(
529
+ storage.syncUplinksMetadataNext(fooManifest.name, null, {
530
+ retry: { limit: 0 },
531
+ timeout: {
532
+ lookup: 100,
533
+ connect: 50,
534
+ secureConnect: 50,
535
+ socket: 500,
536
+ // send: 10000,
537
+ response: 1000,
538
+ },
539
+ })
540
+ ).rejects.toThrow('ETIMEDOUT');
541
+ }, 10000);
542
+
543
+ test('should handle one proxy fails', async () => {
544
+ const fooManifest = generatePackageMetadata('foo', '8.0.0');
545
+ nock('https://registry.verdaccio.org').get('/foo').replyWithError('service in holidays');
546
+ const config = new Config(
547
+ configExample(
548
+ {
549
+ storage: generateRandomStorage(),
550
+ },
551
+ './fixtures/config/syncSingleUplinksMetadata.yaml',
552
+ __dirname
553
+ )
554
+ );
555
+ const storage = new Storage(config);
556
+ await storage.init(config);
557
+ await expect(
558
+ storage.syncUplinksMetadataNext(fooManifest.name, null, {
559
+ retry: { limit: 0 },
560
+ })
561
+ ).rejects.toThrow(API_ERROR.NO_PACKAGE);
562
+ });
563
+
564
+ test('should handle one proxy reply 304', async () => {
565
+ const fooManifest = generatePackageMetadata('foo-no-data', '8.0.0');
566
+ nock('https://registry.verdaccio.org').get('/foo-no-data').reply(304);
567
+ const config = new Config(
568
+ configExample(
569
+ {
570
+ storage: generateRandomStorage(),
571
+ },
572
+ './fixtures/config/syncSingleUplinksMetadata.yaml',
573
+ __dirname
574
+ )
575
+ );
576
+ const storage = new Storage(config);
577
+ await storage.init(config);
578
+ const [manifest] = await storage.syncUplinksMetadataNext(fooManifest.name, fooManifest, {
579
+ retry: 0,
580
+ });
581
+ expect(manifest).toBe(fooManifest);
582
+ });
583
+ });
584
+
585
+ describe('success scenarios', () => {
586
+ test('should handle one proxy success', async () => {
587
+ const fooManifest = generateLocalPackageMetadata('foo', '8.0.0');
588
+ nock('https://registry.verdaccio.org').get('/foo').reply(201, manifestFooRemoteNpmjs);
589
+ const config = new Config(
590
+ configExample(
591
+ {
592
+ storage: generateRandomStorage(),
593
+ },
594
+ './fixtures/config/syncSingleUplinksMetadata.yaml',
595
+ __dirname
596
+ )
597
+ );
598
+ const storage = new Storage(config);
599
+ await storage.init(config);
600
+
601
+ const [response] = await storage.syncUplinksMetadataNext(fooManifest.name, fooManifest);
602
+ expect(response).not.toBeNull();
603
+ expect((response as Manifest).name).toEqual(fooManifest.name);
604
+ expect(Object.keys((response as Manifest).versions)).toEqual([
605
+ '8.0.0',
606
+ '1.0.0',
607
+ '0.0.3',
608
+ '0.0.4',
609
+ '0.0.5',
610
+ '0.0.6',
611
+ '0.0.7',
612
+ ]);
613
+ expect(Object.keys((response as Manifest).time)).toEqual([
614
+ 'modified',
615
+ 'created',
616
+ '8.0.0',
617
+ '1.0.0',
618
+ '0.0.3',
619
+ '0.0.4',
620
+ '0.0.5',
621
+ '0.0.6',
622
+ '0.0.7',
623
+ ]);
624
+ expect((response as Manifest)[DIST_TAGS].latest).toEqual('8.0.0');
625
+ expect((response as Manifest).time['8.0.0']).toBeDefined();
626
+ });
627
+
628
+ test('should handle one proxy success with no local cache manifest', async () => {
629
+ nock('https://registry.verdaccio.org').get('/foo').reply(201, manifestFooRemoteNpmjs);
630
+ const config = new Config(
631
+ configExample(
632
+ {
633
+ storage: generateRandomStorage(),
634
+ },
635
+ './fixtures/config/syncSingleUplinksMetadata.yaml',
636
+ __dirname
637
+ )
638
+ );
639
+ const storage = new Storage(config);
640
+ await storage.init(config);
641
+
642
+ const [response] = await storage.syncUplinksMetadataNext(fooManifest.name, null);
643
+ // the latest from the remote manifest
644
+ expect(response).not.toBeNull();
645
+ expect((response as Manifest).name).toEqual(fooManifest.name);
646
+ expect((response as Manifest)[DIST_TAGS].latest).toEqual('0.0.7');
647
+ });
648
+
649
+ test('should handle no proxy found with local cache manifest', async () => {
650
+ const fooManifest = generatePackageMetadata('foo', '8.0.0');
651
+ nock('https://registry.verdaccio.org').get('/foo').reply(201, manifestFooRemoteNpmjs);
652
+ const config = new Config(
653
+ configExample(
654
+ {
655
+ storage: generateRandomStorage(),
656
+ },
657
+ './fixtures/config/syncNoUplinksMetadata.yaml',
658
+ __dirname
659
+ )
660
+ );
661
+ const storage = new Storage(config);
662
+ await storage.init(config);
663
+
664
+ const [response] = await storage.syncUplinksMetadataNext(fooManifest.name, fooManifest);
665
+ expect(response).not.toBeNull();
666
+ expect((response as Manifest).name).toEqual(fooManifest.name);
667
+ expect((response as Manifest)[DIST_TAGS].latest).toEqual('8.0.0');
668
+ });
669
+ test.todo('should handle double proxy with last one success');
670
+ });
671
+ describe('options', () => {
672
+ test('should handle disable uplinks via options.uplinksLook=false', async () => {
673
+ const fooManifest = generatePackageMetadata('foo', '8.0.0');
674
+ nock('https://registry.verdaccio.org').get('/foo').reply(201, manifestFooRemoteNpmjs);
675
+ const config = new Config(
676
+ configExample(
677
+ {
678
+ storage: generateRandomStorage(),
679
+ },
680
+ './fixtures/config/syncSingleUplinksMetadata.yaml',
681
+ __dirname
682
+ )
683
+ );
684
+ const storage = new Storage(config);
685
+ await storage.init(config);
686
+
687
+ const [response] = await storage.syncUplinksMetadataNext(fooManifest.name, fooManifest, {
688
+ uplinksLook: false,
689
+ });
690
+
691
+ expect((response as Manifest).name).toEqual(fooManifest.name);
692
+ expect((response as Manifest)[DIST_TAGS].latest).toEqual('8.0.0');
693
+ });
694
+ });
695
+ });
696
+
697
+ describe('getLocalDatabaseNext', () => {
698
+ test('should return 0 local packages', async () => {
699
+ const config = new Config(
700
+ configExample({
701
+ ...getDefaultConfig(),
702
+ storage: generateRandomStorage(),
30
703
  })
31
704
  );
32
705
  const storage = new Storage(config);
33
706
  await storage.init(config);
707
+ await expect(storage.getLocalDatabaseNext()).resolves.toHaveLength(0);
708
+ });
34
709
 
35
- await storage.addPackage('foo', fooManifest, (err) => {
36
- expect(err).toBeNull();
710
+ test('should return 1 local packages', async () => {
711
+ const config = new Config(
712
+ configExample({
713
+ ...getDefaultConfig(),
714
+ storage: generateRandomStorage(),
715
+ })
716
+ );
717
+ const req = httpMocks.createRequest({
718
+ method: 'GET',
719
+ connection: { remoteAddress: fakeHost },
720
+ headers: {
721
+ host: 'host',
722
+ },
723
+ url: '/',
37
724
  });
725
+ const storage = new Storage(config);
726
+ await storage.init(config);
727
+ const manifest = generatePackageMetadata('foo');
728
+ const ac = new AbortController();
729
+ await storage.updateManifest(manifest, {
730
+ signal: ac.signal,
731
+ name: 'foo',
732
+ uplinksLook: false,
733
+ requestOptions: {
734
+ headers: req.headers as any,
735
+ protocol: req.protocol,
736
+ host: req.get('host') as string,
737
+ },
738
+ });
739
+ const response = await storage.getLocalDatabaseNext();
740
+ expect(response).toHaveLength(1);
741
+ expect(response[0]).toEqual(expect.objectContaining({ name: 'foo', version: '1.0.0' }));
38
742
  });
39
743
  });
40
744
 
41
- // TODO: getPackageNext should replace getPackage eventually
42
- describe('get packages getPackageNext()', () => {
745
+ describe('tokens', () => {
746
+ describe('saveToken', () => {
747
+ test('should retrieve tokens created', async () => {
748
+ const config = new Config(
749
+ configExample({
750
+ ...getDefaultConfig(),
751
+ storage: generateRandomStorage(),
752
+ })
753
+ );
754
+ const storage = new Storage(config);
755
+ await storage.init(config);
756
+ await storage.saveToken({
757
+ user: 'foo',
758
+ token: 'secret',
759
+ key: 'key',
760
+ created: 'created',
761
+ readonly: true,
762
+ });
763
+ const tokens = await storage.readTokens({ user: 'foo' });
764
+ expect(tokens).toEqual([
765
+ { user: 'foo', token: 'secret', key: 'key', readonly: true, created: 'created' },
766
+ ]);
767
+ });
768
+
769
+ test('should delete a token created', async () => {
770
+ const config = new Config(
771
+ configExample({
772
+ ...getDefaultConfig(),
773
+ storage: generateRandomStorage(),
774
+ })
775
+ );
776
+ const storage = new Storage(config);
777
+ await storage.init(config);
778
+ await storage.saveToken({
779
+ user: 'foo',
780
+ token: 'secret',
781
+ key: 'key',
782
+ created: 'created',
783
+ readonly: true,
784
+ });
785
+ const tokens = await storage.readTokens({ user: 'foo' });
786
+ expect(tokens).toHaveLength(1);
787
+ await storage.deleteToken('foo', 'key');
788
+ const tokens2 = await storage.readTokens({ user: 'foo' });
789
+ expect(tokens2).toHaveLength(0);
790
+ });
791
+ });
792
+ });
793
+
794
+ describe('removeTarball', () => {
795
+ test('should fail on remove tarball of package does not exist', async () => {
796
+ const config = new Config(
797
+ configExample({
798
+ ...getDefaultConfig(),
799
+ storage: generateRandomStorage(),
800
+ })
801
+ );
802
+ const storage = new Storage(config);
803
+ await storage.init(config);
804
+ await expect(storage.removeTarball('foo', 'foo-1.0.0.tgz', 'rev')).rejects.toThrow(
805
+ API_ERROR.NO_PACKAGE
806
+ );
807
+ });
808
+ });
809
+
810
+ describe('removePackage', () => {
811
+ test('should remove entirely a package', async () => {
812
+ const config = new Config(
813
+ configExample({
814
+ ...getDefaultConfig(),
815
+ storage: generateRandomStorage(),
816
+ })
817
+ );
818
+ const req = httpMocks.createRequest({
819
+ method: 'GET',
820
+ connection: { remoteAddress: fakeHost },
821
+ headers: {
822
+ host: fakeHost,
823
+ [HEADERS.FORWARDED_PROTO]: 'http',
824
+ },
825
+ url: '/',
826
+ });
827
+ const storage = new Storage(config);
828
+ await storage.init(config);
829
+
830
+ const manifest = generatePackageMetadata('foo');
831
+ const ac = new AbortController();
832
+ // 1. publish a package
833
+ await storage.updateManifest(manifest, {
834
+ signal: ac.signal,
835
+ name: 'foo',
836
+ uplinksLook: false,
837
+ requestOptions: {
838
+ headers: req.headers as any,
839
+ protocol: req.protocol,
840
+ host: req.get('host') as string,
841
+ },
842
+ });
843
+ // 2. request package (should be available in the local cache)
844
+ const manifest1 = (await storage.getPackageByOptions({
845
+ name: 'foo',
846
+ uplinksLook: false,
847
+ requestOptions: {
848
+ headers: req.headers as any,
849
+ protocol: req.protocol,
850
+ host: req.get('host') as string,
851
+ },
852
+ })) as Manifest;
853
+ const _rev = manifest1._rev;
854
+ // 3. remove the tarball
855
+ await expect(
856
+ storage.removeTarball(manifest1.name, 'foo-1.0.0.tgz', _rev)
857
+ ).resolves.toBeDefined();
858
+ // 4. remove the package
859
+ await storage.removePackage(manifest1.name, _rev);
860
+ // 5. fails if package does not exist anymore in storage
861
+ await expect(
862
+ storage.getPackageByOptions({
863
+ name: 'foo',
864
+ uplinksLook: false,
865
+ requestOptions: {
866
+ headers: req.headers as any,
867
+ protocol: req.protocol,
868
+ host: req.get('host') as string,
869
+ },
870
+ })
871
+ ).rejects.toThrowError('package does not exist on uplink: foo');
872
+ });
873
+ });
874
+
875
+ describe('get packages getPackageByOptions()', () => {
43
876
  describe('with uplinks', () => {
44
877
  test('should get 201 and merge from uplink', async () => {
45
878
  nock(domain).get('/foo').reply(201, fooManifest);
46
879
  const config = new Config(
47
880
  configExample({
48
- storage: generateRamdonStorage(),
881
+ ...getDefaultConfig(),
882
+ storage: generateRandomStorage(),
49
883
  })
50
884
  );
51
885
  const req = httpMocks.createRequest({
@@ -63,11 +897,10 @@ describe('storage', () => {
63
897
  storage.getPackageByOptions({
64
898
  name: 'foo',
65
899
  uplinksLook: true,
66
- req,
67
900
  requestOptions: {
68
901
  headers: req.headers as any,
69
902
  protocol: req.protocol,
70
- host: req.get('host'),
903
+ host: req.get('host') as string,
71
904
  },
72
905
  })
73
906
  ).resolves.toEqual(expect.objectContaining({ name: 'foo' }));
@@ -77,7 +910,8 @@ describe('storage', () => {
77
910
  nock(domain).get('/foo').reply(201, fooManifest);
78
911
  const config = new Config(
79
912
  configExample({
80
- storage: generateRamdonStorage(),
913
+ ...getDefaultConfig(),
914
+ storage: generateRandomStorage(),
81
915
  })
82
916
  );
83
917
  const req = httpMocks.createRequest({
@@ -96,11 +930,10 @@ describe('storage', () => {
96
930
  name: 'foo',
97
931
  version: '1.0.0',
98
932
  uplinksLook: true,
99
- req,
100
933
  requestOptions: {
101
934
  headers: req.headers as any,
102
935
  protocol: req.protocol,
103
- host: req.get('host'),
936
+ host: req.get('host') as string,
104
937
  },
105
938
  })
106
939
  ).resolves.toEqual(expect.objectContaining({ name: 'foo' }));
@@ -110,7 +943,8 @@ describe('storage', () => {
110
943
  nock(domain).get('/foo').reply(201, fooManifest);
111
944
  const config = new Config(
112
945
  configExample({
113
- storage: generateRamdonStorage(),
946
+ ...getDefaultConfig(),
947
+ storage: generateRandomStorage(),
114
948
  })
115
949
  );
116
950
  const req = httpMocks.createRequest({
@@ -129,11 +963,10 @@ describe('storage', () => {
129
963
  name: 'foo',
130
964
  version: 'latest',
131
965
  uplinksLook: true,
132
- req,
133
966
  requestOptions: {
134
967
  headers: req.headers as any,
135
968
  protocol: req.protocol,
136
- host: req.get('host'),
969
+ host: req.get('host') as string,
137
970
  },
138
971
  })
139
972
  ).resolves.toEqual(expect.objectContaining({ name: 'foo' }));
@@ -143,7 +976,8 @@ describe('storage', () => {
143
976
  nock(domain).get('/foo').reply(201, fooManifest);
144
977
  const config = new Config(
145
978
  configExample({
146
- storage: generateRamdonStorage(),
979
+ ...getDefaultConfig(),
980
+ storage: generateRandomStorage(),
147
981
  })
148
982
  );
149
983
  const req = httpMocks.createRequest({
@@ -162,11 +996,10 @@ describe('storage', () => {
162
996
  name: 'foo',
163
997
  version: '1.0.0-does-not-exist',
164
998
  uplinksLook: true,
165
- req,
166
999
  requestOptions: {
167
1000
  headers: req.headers as any,
168
1001
  protocol: req.protocol,
169
- host: req.get('host'),
1002
+ host: req.get('host') as string,
170
1003
  },
171
1004
  })
172
1005
  ).rejects.toThrow(
@@ -178,7 +1011,13 @@ describe('storage', () => {
178
1011
  nock(domain).get('/foo2').reply(404);
179
1012
  const config = new Config(
180
1013
  configExample({
181
- storage: generateRamdonStorage(),
1014
+ ...getDefaultConfig(),
1015
+ uplinks: {
1016
+ npmjs: {
1017
+ url: domain,
1018
+ },
1019
+ },
1020
+ storage: generateRandomStorage(),
182
1021
  })
183
1022
  );
184
1023
  const req = httpMocks.createRequest({
@@ -196,11 +1035,10 @@ describe('storage', () => {
196
1035
  storage.getPackageByOptions({
197
1036
  name: 'foo2',
198
1037
  uplinksLook: true,
199
- req,
200
1038
  requestOptions: {
201
1039
  headers: req.headers as any,
202
1040
  protocol: req.protocol,
203
- host: req.get('host'),
1041
+ host: req.get('host') as string,
204
1042
  },
205
1043
  })
206
1044
  ).rejects.toThrow(errorUtils.getNotFound());
@@ -213,7 +1051,13 @@ describe('storage', () => {
213
1051
  });
214
1052
  const config = new Config(
215
1053
  configExample({
216
- storage: generateRamdonStorage(),
1054
+ ...getDefaultConfig(),
1055
+ uplinks: {
1056
+ npmjs: {
1057
+ url: domain,
1058
+ },
1059
+ },
1060
+ storage: generateRandomStorage(),
217
1061
  })
218
1062
  );
219
1063
  const req = httpMocks.createRequest({
@@ -231,14 +1075,80 @@ describe('storage', () => {
231
1075
  storage.getPackageByOptions({
232
1076
  name: 'foo2',
233
1077
  uplinksLook: true,
234
- req,
1078
+ retry: { limit: 0 },
235
1079
  requestOptions: {
236
1080
  headers: req.headers as any,
237
1081
  protocol: req.protocol,
238
- host: req.get('host'),
1082
+ host: req.get('host') as string,
239
1083
  },
240
1084
  })
241
- ).rejects.toThrow(errorUtils.getServiceUnavailable());
1085
+ ).rejects.toThrow(errorUtils.getServiceUnavailable('ETIMEDOUT'));
1086
+ });
1087
+
1088
+ test('should fetch abbreviated version of manifest ', async () => {
1089
+ const fooManifest = generateLocalPackageMetadata('foo', '1.0.0');
1090
+ nock(domain).get('/foo').reply(201, fooManifest);
1091
+ const config = new Config(
1092
+ configExample({
1093
+ ...getDefaultConfig(),
1094
+ storage: generateRandomStorage(),
1095
+ })
1096
+ );
1097
+ const req = httpMocks.createRequest({
1098
+ method: 'GET',
1099
+ connection: { remoteAddress: fakeHost },
1100
+ headers: {
1101
+ host: fakeHost,
1102
+ [HEADERS.FORWARDED_PROTO]: 'http',
1103
+ },
1104
+ url: '/',
1105
+ });
1106
+ const storage = new Storage(config);
1107
+ await storage.init(config);
1108
+
1109
+ const manifest = (await storage.getPackageByOptions({
1110
+ name: 'foo',
1111
+ uplinksLook: true,
1112
+ requestOptions: {
1113
+ headers: req.headers as any,
1114
+ protocol: req.protocol,
1115
+ host: req.get('host') as string,
1116
+ },
1117
+ abbreviated: true,
1118
+ })) as AbbreviatedManifest;
1119
+ const { versions, name } = manifest;
1120
+ expect(name).toEqual('foo');
1121
+ expect(Object.keys(versions)).toEqual(['1.0.0']);
1122
+ expect(manifest[DIST_TAGS]).toEqual({ latest: '1.0.0' });
1123
+ const version = versions['1.0.0'];
1124
+ expect(Object.keys(version)).toEqual([
1125
+ 'name',
1126
+ 'version',
1127
+ 'description',
1128
+ 'deprecated',
1129
+ 'bin',
1130
+ 'dist',
1131
+ 'engines',
1132
+ 'funding',
1133
+ 'directories',
1134
+ 'dependencies',
1135
+ 'devDependencies',
1136
+ 'peerDependencies',
1137
+ 'optionalDependencies',
1138
+ 'bundleDependencies',
1139
+ '_hasShrinkwrap',
1140
+ 'hasInstallScript',
1141
+ ]);
1142
+ expect(manifest.modified).toBeDefined();
1143
+ // special case for pnpm/rfcs/pull/2
1144
+ expect(manifest.time).toBeDefined();
1145
+ // fields must not have
1146
+ // @ts-expect-error
1147
+ expect(manifest.readme).not.toBeDefined();
1148
+ // @ts-expect-error
1149
+ expect(manifest._attachments).not.toBeDefined();
1150
+ // @ts-expect-error
1151
+ expect(manifest._rev).not.toBeDefined();
242
1152
  });
243
1153
  });
244
1154
  });