mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-23 22:27:34 -05:00
1928 lines
63 KiB
TypeScript
1928 lines
63 KiB
TypeScript
import { pseudoRandomBytes } from 'crypto';
|
|
import fs from 'fs';
|
|
import MockDate from 'mockdate';
|
|
import nock from 'nock';
|
|
import * as httpMocks from 'node-mocks-http';
|
|
import os from 'os';
|
|
import path from 'path';
|
|
|
|
import { Config, getDefaultConfig } from '@verdaccio/config';
|
|
import {
|
|
API_ERROR,
|
|
API_MESSAGE,
|
|
DIST_TAGS,
|
|
HEADERS,
|
|
HEADER_TYPE,
|
|
errorUtils,
|
|
fileUtils,
|
|
} from '@verdaccio/core';
|
|
import { setup } from '@verdaccio/logger';
|
|
import {
|
|
addNewVersion,
|
|
generateLocalPackageMetadata,
|
|
generatePackageMetadata,
|
|
generateRemotePackageMetadata,
|
|
getDeprecatedPackageMetadata,
|
|
} from '@verdaccio/test-helper';
|
|
import {
|
|
AbbreviatedManifest,
|
|
Author,
|
|
ConfigYaml,
|
|
Manifest,
|
|
PackageUsers,
|
|
Version,
|
|
} from '@verdaccio/types';
|
|
|
|
import { Storage } from '../src';
|
|
import manifestFooRemoteNpmjs from './fixtures/manifests/foo-npmjs.json';
|
|
import { configExample } from './helpers';
|
|
|
|
function generateRandomStorage() {
|
|
const tempStorage = pseudoRandomBytes(5).toString('hex');
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), '/verdaccio-test'));
|
|
|
|
return path.join(tempRoot, tempStorage);
|
|
}
|
|
|
|
setup({ type: 'stdout', format: 'pretty', level: 'trace' });
|
|
|
|
const domain = 'https://registry.npmjs.org';
|
|
const fakeHost = 'localhost:4873';
|
|
const fooManifest = generatePackageMetadata('foo', '1.0.0');
|
|
|
|
const getConfig = (file, override: Partial<ConfigYaml> = {}): Config => {
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
...override,
|
|
},
|
|
`./fixtures/config/${file}`,
|
|
__dirname
|
|
)
|
|
);
|
|
return config;
|
|
};
|
|
|
|
const defaultRequestOptions = {
|
|
host: 'localhost',
|
|
protocol: 'http',
|
|
headers: {},
|
|
};
|
|
|
|
const executeStarPackage = async (
|
|
storage,
|
|
options: {
|
|
users: PackageUsers;
|
|
username: string;
|
|
name: string;
|
|
_rev: string;
|
|
_id?: string;
|
|
}
|
|
) => {
|
|
const { name, _rev, _id, users, username } = options;
|
|
const starManifest = {
|
|
_rev,
|
|
_id,
|
|
users,
|
|
};
|
|
return storage.updateManifest(starManifest, {
|
|
signal: new AbortController().signal,
|
|
name,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: { ...defaultRequestOptions, username },
|
|
});
|
|
};
|
|
|
|
const executeChangeOwners = async (
|
|
storage,
|
|
options: {
|
|
maintainers: Author[];
|
|
username: string;
|
|
name: string;
|
|
_rev: string;
|
|
_id?: string;
|
|
}
|
|
) => {
|
|
const { name, _rev, _id, maintainers, username } = options;
|
|
const ownerManifest = {
|
|
_rev,
|
|
_id,
|
|
maintainers,
|
|
};
|
|
return storage.updateManifest(ownerManifest, {
|
|
signal: new AbortController().signal,
|
|
name,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: { ...defaultRequestOptions, username },
|
|
});
|
|
};
|
|
|
|
describe('storage', () => {
|
|
beforeEach(() => {
|
|
nock.cleanAll();
|
|
nock.abortPendingRequests();
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
describe('updateManifest', () => {
|
|
describe('publishing', () => {
|
|
test('create private package', async () => {
|
|
const mockDate = '2018-01-14T11:17:40.712Z';
|
|
MockDate.set(mockDate);
|
|
const pkgName = 'upstream';
|
|
const requestOptions = {
|
|
host: 'localhost',
|
|
protocol: 'http',
|
|
headers: {},
|
|
};
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/updateManifest-1.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions,
|
|
});
|
|
const manifest = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions,
|
|
})) as Manifest;
|
|
expect(manifest.name).toEqual(pkgName);
|
|
expect(manifest._id).toEqual(pkgName);
|
|
expect(Object.keys(manifest.versions)).toEqual(['1.0.0']);
|
|
expect(manifest.time).toEqual({
|
|
'1.0.0': mockDate,
|
|
created: mockDate,
|
|
modified: mockDate,
|
|
});
|
|
expect(manifest[DIST_TAGS]).toEqual({ latest: '1.0.0' });
|
|
// verdaccio keeps latest version of readme on manifest level but not by version
|
|
expect(manifest.versions['1.0.0'].readme).not.toBeDefined();
|
|
expect(manifest.readme).toEqual('# test');
|
|
expect(manifest._attachments).toEqual({});
|
|
expect(typeof manifest._rev).toBeTruthy();
|
|
});
|
|
|
|
// TODO: Review triggerUncaughtException exception on abort
|
|
test.skip('abort creating a private package', async () => {
|
|
const mockDate = '2018-01-14T11:17:40.712Z';
|
|
MockDate.set(mockDate);
|
|
const pkgName = 'upstream';
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/updateManifest-1.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const ac = new AbortController();
|
|
setTimeout(() => {
|
|
ac.abort();
|
|
}, 10);
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
await expect(
|
|
storage.updateManifest(bodyNewManifest, {
|
|
signal: ac.signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: {
|
|
host: 'localhost',
|
|
protocol: 'http',
|
|
headers: {},
|
|
},
|
|
})
|
|
).rejects.toThrow('should throw here');
|
|
});
|
|
|
|
test('create private package with multiple consecutive versions', async () => {
|
|
const mockDate = '2018-01-14T11:17:40.712Z';
|
|
MockDate.set(mockDate);
|
|
const settings = {
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: {
|
|
host: 'localhost',
|
|
protocol: 'http',
|
|
headers: {},
|
|
},
|
|
};
|
|
const pkgName = 'upstream';
|
|
// const storage = generateRandomStorage();
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: await fileUtils.createTempStorageFolder('storage-test'),
|
|
},
|
|
'./fixtures/config/updateManifest-1.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
// create a package
|
|
const bodyNewManifest1 = generatePackageMetadata(pkgName, '1.0.0');
|
|
await storage.updateManifest(bodyNewManifest1, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
...settings,
|
|
});
|
|
// publish second version
|
|
const bodyNewManifest2 = generatePackageMetadata(pkgName, '1.0.1');
|
|
await storage.updateManifest(bodyNewManifest2, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
...settings,
|
|
});
|
|
// retrieve package metadata
|
|
const manifest = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: {
|
|
host: 'localhost',
|
|
protocol: 'http',
|
|
headers: {},
|
|
},
|
|
})) as Manifest;
|
|
expect(manifest.name).toEqual(pkgName);
|
|
expect(manifest._id).toEqual(pkgName);
|
|
expect(Object.keys(manifest.versions)).toEqual(['1.0.0', '1.0.1']);
|
|
expect(manifest.time).toEqual({
|
|
'1.0.0': mockDate,
|
|
'1.0.1': mockDate,
|
|
created: mockDate,
|
|
modified: mockDate,
|
|
});
|
|
expect(manifest[DIST_TAGS]).toEqual({ latest: '1.0.1' });
|
|
expect(manifest.readme).toEqual('# test');
|
|
expect(manifest._attachments).toEqual({});
|
|
expect(typeof manifest._rev).toBeTruthy();
|
|
// verify the version structure is correct
|
|
const manifestVersion = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
version: '1.0.1',
|
|
uplinksLook: true,
|
|
requestOptions: {
|
|
host: 'localhost',
|
|
protocol: 'http',
|
|
headers: {},
|
|
},
|
|
})) as Version;
|
|
expect(manifestVersion.name).toEqual(pkgName);
|
|
expect(manifestVersion.version).toEqual('1.0.1');
|
|
expect(manifestVersion._id).toEqual(`${pkgName}@1.0.1`);
|
|
expect(manifestVersion.description).toEqual('package generated');
|
|
expect(manifestVersion.dist).toEqual({
|
|
fileCount: 4,
|
|
integrity:
|
|
'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
|
|
shasum: '2c03764f651a9f016ca0b7620421457b619151b9',
|
|
tarball: 'http://localhost:5555/upstream/-/upstream-1.0.1.tgz',
|
|
unpackedSize: 543,
|
|
});
|
|
|
|
expect(manifestVersion.contributors).toEqual([]);
|
|
expect(manifestVersion.main).toEqual('index.js');
|
|
expect(manifestVersion.author).toEqual({ name: 'User NPM', email: 'user@domain.com' });
|
|
expect(manifestVersion.dependencies).toEqual({ verdaccio: '^2.7.2' });
|
|
});
|
|
|
|
test('fails if version already exist', async () => {
|
|
const settings = {
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: {
|
|
host: 'localhost',
|
|
protocol: 'http',
|
|
headers: {},
|
|
},
|
|
};
|
|
const pkgName = 'upstream';
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/getTarball-getupstream.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const bodyNewManifest1 = generatePackageMetadata(pkgName, '1.0.0');
|
|
const bodyNewManifest2 = generatePackageMetadata(pkgName, '1.0.0');
|
|
await storage.updateManifest(bodyNewManifest1, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
...settings,
|
|
});
|
|
await expect(
|
|
storage.updateManifest(bodyNewManifest2, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
...settings,
|
|
})
|
|
).rejects.toThrow(API_ERROR.PACKAGE_EXIST);
|
|
});
|
|
|
|
test('create private package with readme only in manifest', async () => {
|
|
const mockDate = '2018-01-14T11:17:40.712Z';
|
|
MockDate.set(mockDate);
|
|
const pkgName = 'upstream';
|
|
const requestOptions = {
|
|
host: 'localhost',
|
|
protocol: 'http',
|
|
headers: {},
|
|
};
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/updateManifest-1.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
|
|
// Remove readme from version to simulate behaviour of older package managers like npm6
|
|
bodyNewManifest.versions['1.0.0'].readme = '';
|
|
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions,
|
|
});
|
|
const manifest = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions,
|
|
})) as Manifest;
|
|
|
|
// verdaccio keeps latest version of readme on manifest level but not by version
|
|
expect(manifest.versions['1.0.0'].readme).not.toBeDefined();
|
|
expect(manifest.readme).toEqual('# test');
|
|
});
|
|
});
|
|
describe('deprecate', () => {
|
|
test.each([['foo'], ['@scope/foo']])('deprecate package %s', async (pkgName) => {
|
|
const mockDate = '2018-01-14T11:17:40.712Z';
|
|
MockDate.set(mockDate);
|
|
const config = getConfig('deprecate.yaml');
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: defaultRequestOptions,
|
|
});
|
|
const manifest1 = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: defaultRequestOptions,
|
|
})) as Manifest;
|
|
expect(manifest1.versions['1.0.0'].deprecated).toBeUndefined();
|
|
|
|
const deprecatedManifest = getDeprecatedPackageMetadata(
|
|
pkgName,
|
|
'1.0.0',
|
|
{
|
|
['latest']: '1.0.0',
|
|
},
|
|
'some deprecation message',
|
|
manifest1._rev
|
|
);
|
|
await storage.updateManifest(deprecatedManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: defaultRequestOptions,
|
|
});
|
|
const manifest = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: defaultRequestOptions,
|
|
})) as Manifest;
|
|
expect(manifest.name).toEqual(pkgName);
|
|
expect(manifest.versions['1.0.0'].deprecated).toEqual('some deprecation message');
|
|
// important revision is updated
|
|
expect(manifest._rev !== deprecatedManifest._rev).toBeTruthy();
|
|
});
|
|
test.each([['foo'], ['@scope/foo']])('undeprecate package %s', async (pkgName) => {
|
|
const mockDate = '2018-01-14T11:17:40.712Z';
|
|
MockDate.set(mockDate);
|
|
const config = getConfig('deprecate.yaml');
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
// publish new package
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: defaultRequestOptions,
|
|
});
|
|
|
|
// verify not deprecated
|
|
const manifest1 = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: defaultRequestOptions,
|
|
})) as Manifest;
|
|
expect(manifest1.versions['1.0.0'].deprecated).toBeUndefined();
|
|
|
|
// deprecate version
|
|
const deprecatedManifest = getDeprecatedPackageMetadata(
|
|
pkgName,
|
|
'1.0.0',
|
|
{
|
|
['latest']: '1.0.0',
|
|
},
|
|
'some deprecation message',
|
|
manifest1._rev
|
|
);
|
|
await storage.updateManifest(deprecatedManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: defaultRequestOptions,
|
|
});
|
|
const manifest = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: defaultRequestOptions,
|
|
})) as Manifest;
|
|
expect(manifest.name).toEqual(pkgName);
|
|
expect(manifest.versions['1.0.0'].deprecated).toEqual('some deprecation message');
|
|
// important revision is updated
|
|
expect(manifest._rev !== deprecatedManifest._rev).toBeTruthy();
|
|
|
|
// un deprecated the previous deprecated
|
|
const undeprecatedManifest = {
|
|
...manifest,
|
|
};
|
|
undeprecatedManifest.versions['1.0.0'].deprecated = '';
|
|
await storage.updateManifest(undeprecatedManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: defaultRequestOptions,
|
|
});
|
|
|
|
const manifest3 = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: defaultRequestOptions,
|
|
})) as Manifest;
|
|
expect(manifest3.name).toEqual(pkgName);
|
|
expect(manifest3.versions['1.0.0'].deprecated).toBeUndefined();
|
|
// important revision is updated
|
|
expect(manifest3._rev !== deprecatedManifest._rev).toBeTruthy();
|
|
});
|
|
});
|
|
describe('star', () => {
|
|
test.each([['foo']])('star package %s', async (pkgName) => {
|
|
const config = getConfig('deprecate.yaml');
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: defaultRequestOptions,
|
|
});
|
|
const message = await executeStarPackage(storage, {
|
|
_rev: bodyNewManifest._rev,
|
|
_id: bodyNewManifest._id,
|
|
name: pkgName,
|
|
username: 'fooUser',
|
|
users: { fooUser: true },
|
|
});
|
|
expect(message).toEqual(API_MESSAGE.PKG_CHANGED);
|
|
const manifest1 = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: defaultRequestOptions,
|
|
})) as Manifest;
|
|
|
|
expect(manifest1?.users).toEqual({
|
|
fooUser: true,
|
|
});
|
|
});
|
|
|
|
test.each([['foo']])('should add multiple users to package %s', async (pkgName) => {
|
|
const mockDate = '2018-01-14T11:17:40.712Z';
|
|
MockDate.set(mockDate);
|
|
const config = getConfig('deprecate.yaml');
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: defaultRequestOptions,
|
|
});
|
|
const message = await executeStarPackage(storage, {
|
|
_rev: bodyNewManifest._rev,
|
|
_id: bodyNewManifest._id,
|
|
name: pkgName,
|
|
username: 'fooUser',
|
|
users: { fooUser: true },
|
|
});
|
|
expect(message).toEqual(API_MESSAGE.PKG_CHANGED);
|
|
|
|
await executeStarPackage(storage, {
|
|
_rev: bodyNewManifest._rev,
|
|
_id: bodyNewManifest._id,
|
|
name: pkgName,
|
|
username: 'owner',
|
|
users: { owner: true },
|
|
});
|
|
const manifest1 = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: defaultRequestOptions,
|
|
})) as Manifest;
|
|
|
|
expect(manifest1?.users).toEqual({
|
|
fooUser: true,
|
|
owner: true,
|
|
});
|
|
});
|
|
|
|
test.each([['foo']])('should ignore duplicate users to package %s', async (pkgName) => {
|
|
const mockDate = '2018-01-14T11:17:40.712Z';
|
|
MockDate.set(mockDate);
|
|
const config = getConfig('deprecate.yaml');
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: defaultRequestOptions,
|
|
});
|
|
const message = await executeStarPackage(storage, {
|
|
_rev: bodyNewManifest._rev,
|
|
_id: bodyNewManifest._id,
|
|
name: pkgName,
|
|
username: 'fooUser',
|
|
users: { fooUser: true },
|
|
});
|
|
expect(message).toEqual(API_MESSAGE.PKG_CHANGED);
|
|
|
|
await executeStarPackage(storage, {
|
|
_rev: bodyNewManifest._rev,
|
|
_id: bodyNewManifest._id,
|
|
name: pkgName,
|
|
username: 'fooUser',
|
|
users: { fooUser: true },
|
|
});
|
|
const manifest1 = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: defaultRequestOptions,
|
|
})) as Manifest;
|
|
|
|
expect(manifest1?.users).toEqual({
|
|
fooUser: true,
|
|
});
|
|
});
|
|
|
|
test.each([['foo']])('should unstar a package %s', async (pkgName) => {
|
|
const config = getConfig('deprecate.yaml');
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: defaultRequestOptions,
|
|
});
|
|
const message = await executeStarPackage(storage, {
|
|
_rev: bodyNewManifest._rev,
|
|
_id: bodyNewManifest._id,
|
|
name: pkgName,
|
|
username: 'fooUser',
|
|
users: { fooUser: true },
|
|
});
|
|
expect(message).toEqual(API_MESSAGE.PKG_CHANGED);
|
|
|
|
await executeStarPackage(storage, {
|
|
_rev: bodyNewManifest._rev,
|
|
_id: bodyNewManifest._id,
|
|
name: pkgName,
|
|
username: 'fooUser',
|
|
users: {},
|
|
});
|
|
const manifest1 = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: defaultRequestOptions,
|
|
})) as Manifest;
|
|
|
|
expect(manifest1?.users).toEqual({});
|
|
});
|
|
|
|
test.each([['foo']])('should handle missing username %s', async (pkgName) => {
|
|
const config = getConfig('deprecate.yaml');
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: defaultRequestOptions,
|
|
});
|
|
await expect(
|
|
executeStarPackage(storage, {
|
|
_rev: bodyNewManifest._rev,
|
|
_id: bodyNewManifest._id,
|
|
name: pkgName,
|
|
// @ts-expect-error
|
|
username: undefined,
|
|
users: { fooUser: true },
|
|
})
|
|
).rejects.toThrow();
|
|
});
|
|
});
|
|
describe('owner', () => {
|
|
test.each([
|
|
['foo', 'publishWithOwnerDefault.yaml'],
|
|
['foo', 'publishWithOwnerAndCheck.yaml'],
|
|
])('new package %s, %s (anonymous)', async (pkgName, configFile) => {
|
|
const config = getConfig(configFile);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: defaultRequestOptions,
|
|
});
|
|
const manifest = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: defaultRequestOptions,
|
|
})) as Manifest;
|
|
expect(manifest?.maintainers).toEqual([{ name: 'Anonymous', email: '' }]);
|
|
});
|
|
|
|
test.each([
|
|
['foo', 'publishWithOwnerDefault.yaml'],
|
|
['foo', 'publishWithOwnerAndCheck.yaml'],
|
|
])('new package %s, %s (logged in)', async (pkgName, configFile) => {
|
|
const config = getConfig(configFile);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const owner = { name: 'fooUser', email: '' };
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
const options = { ...defaultRequestOptions, username: owner.name };
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: options,
|
|
});
|
|
const manifest = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: defaultRequestOptions,
|
|
})) as Manifest;
|
|
expect(manifest?.maintainers).toEqual([owner]);
|
|
expect(manifest?.versions['1.0.0'].maintainers).toEqual([owner]);
|
|
});
|
|
|
|
test.each([
|
|
['foo', 'publishWithOwnerDefault.yaml'],
|
|
['foo', 'publishWithOwnerAndCheck.yaml'],
|
|
])('add/remove owner %s, %s', async (pkgName, configFile) => {
|
|
const config = getConfig(configFile);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const firstOwner = { name: 'fooUser', email: '' };
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
const options = { ...defaultRequestOptions, username: firstOwner.name };
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: false,
|
|
requestOptions: options,
|
|
});
|
|
|
|
// add owner
|
|
const secondOwner = { name: 'barUser', email: '' };
|
|
const maintainers = [firstOwner, secondOwner];
|
|
|
|
const message = await executeChangeOwners(storage, {
|
|
_rev: bodyNewManifest._rev,
|
|
_id: bodyNewManifest._id,
|
|
name: pkgName,
|
|
username: firstOwner.name,
|
|
maintainers: maintainers,
|
|
});
|
|
expect(message).toEqual(API_MESSAGE.PKG_CHANGED);
|
|
|
|
const manifest = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: false,
|
|
requestOptions: options,
|
|
})) as Manifest;
|
|
expect(manifest?.maintainers).toEqual(maintainers);
|
|
// published version should not be affected
|
|
expect(manifest?.versions['1.0.0'].maintainers).toEqual([firstOwner]);
|
|
|
|
// remove owner
|
|
const maintainers2 = [secondOwner];
|
|
const message2 = await executeChangeOwners(storage, {
|
|
_rev: bodyNewManifest._rev,
|
|
_id: bodyNewManifest._id,
|
|
name: pkgName,
|
|
username: firstOwner.name,
|
|
maintainers: maintainers2,
|
|
});
|
|
expect(message2).toEqual(API_MESSAGE.PKG_CHANGED);
|
|
|
|
const manifest2 = (await storage.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: false,
|
|
requestOptions: options,
|
|
})) as Manifest;
|
|
expect(manifest2?.maintainers).toEqual(maintainers2);
|
|
// published version should not be affected
|
|
expect(manifest2?.versions['1.0.0'].maintainers).toEqual([firstOwner]);
|
|
});
|
|
|
|
test.each([
|
|
['foo', 'publishWithOwnerDefault.yaml'],
|
|
['foo', 'publishWithOwnerAndCheck.yaml'],
|
|
])('should fail removing last owner %s, %s', async (pkgName, configFile) => {
|
|
const config = getConfig(configFile);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
const owner = 'fooUser';
|
|
const options = { ...defaultRequestOptions, username: owner };
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: false,
|
|
requestOptions: options,
|
|
});
|
|
|
|
// no owners
|
|
await expect(
|
|
executeChangeOwners(storage, {
|
|
_rev: bodyNewManifest._rev,
|
|
_id: bodyNewManifest._id,
|
|
name: pkgName,
|
|
username: owner,
|
|
maintainers: [],
|
|
})
|
|
).rejects.toThrow();
|
|
});
|
|
|
|
test.each([['foo', 'publishWithOwnerDefault.yaml']])(
|
|
'ok to publish as non-owner without check %s, %s',
|
|
async (pkgName, configFile) => {
|
|
const config = getConfig(configFile);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
const owner = 'fooUser';
|
|
const options = { ...defaultRequestOptions, username: owner };
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: false,
|
|
requestOptions: options,
|
|
});
|
|
|
|
// try to publish as user who's not an owner
|
|
const bodyNewManifest2 = generatePackageMetadata(pkgName, '1.0.1');
|
|
const nonOwner = 'barUser';
|
|
const options2 = { ...defaultRequestOptions, username: nonOwner };
|
|
const message2 = await storage.updateManifest(bodyNewManifest2, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: false,
|
|
requestOptions: options2,
|
|
});
|
|
expect(message2).toEqual(API_MESSAGE.PKG_CHANGED);
|
|
}
|
|
);
|
|
|
|
test.each([['foo', 'publishWithOwnerAndCheck.yaml']])(
|
|
'should fail publishing as non-owner with check %s, %s',
|
|
async (pkgName, configFile) => {
|
|
const config = getConfig(configFile);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
const owner = 'fooUser';
|
|
const options = { ...defaultRequestOptions, username: owner };
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: false,
|
|
requestOptions: options,
|
|
});
|
|
|
|
// try to publish as user who's not an owner
|
|
const bodyNewManifest2 = generatePackageMetadata(pkgName, '1.0.1');
|
|
const nonOwner = 'barUser';
|
|
const options2 = { ...defaultRequestOptions, username: nonOwner };
|
|
await expect(
|
|
storage.updateManifest(bodyNewManifest2, {
|
|
signal: new AbortController().signal,
|
|
name: pkgName,
|
|
uplinksLook: false,
|
|
requestOptions: options2,
|
|
})
|
|
).rejects.toThrow();
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getTarball', () => {
|
|
test('should get a package from local storage', (done) => {
|
|
const pkgName = 'foo';
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const storage = new Storage(config);
|
|
storage.init(config).then(() => {
|
|
const ac = new AbortController();
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
storage
|
|
.updateManifest(bodyNewManifest, {
|
|
signal: ac.signal,
|
|
name: pkgName,
|
|
uplinksLook: false,
|
|
requestOptions: defaultRequestOptions,
|
|
})
|
|
.then(() => {
|
|
const abort = new AbortController();
|
|
storage
|
|
.getTarball(pkgName, `${pkgName}-1.0.0.tgz`, {
|
|
signal: abort.signal,
|
|
})
|
|
.then((stream) => {
|
|
stream.on('data', (dat) => {
|
|
expect(dat).toBeDefined();
|
|
expect(dat.length).toEqual(512);
|
|
});
|
|
stream.on('end', () => {
|
|
done();
|
|
});
|
|
stream.on('error', () => {
|
|
done('this should not happen');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('should not found a package anywhere', (done) => {
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const storage = new Storage(config);
|
|
storage.init(config).then(() => {
|
|
const abort = new AbortController();
|
|
storage
|
|
.getTarball('some-tarball', 'some-tarball-1.0.0.tgz', {
|
|
signal: abort.signal,
|
|
})
|
|
.then((stream) => {
|
|
stream.on('error', (err) => {
|
|
expect(err).toEqual(errorUtils.getNotFound(API_ERROR.NO_PACKAGE));
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('should create a package if tarball is requested and does not exist locally', (done) => {
|
|
const pkgName = 'upstream';
|
|
const upstreamManifest = generateRemotePackageMetadata(
|
|
pkgName,
|
|
'1.0.0',
|
|
'https://registry.something.org'
|
|
);
|
|
nock('https://registry.verdaccio.org').get(`/${pkgName}`).reply(201, upstreamManifest);
|
|
nock('https://registry.something.org')
|
|
.get(`/${pkgName}/-/${pkgName}-1.0.0.tgz`)
|
|
// types does not match here with documentation
|
|
// @ts-expect-error
|
|
.replyWithFile(201, path.join(__dirname, 'fixtures/tarball.tgz'), {
|
|
[HEADER_TYPE.CONTENT_LENGTH]: 277,
|
|
});
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/getTarball-getupstream.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
storage.init(config).then(() => {
|
|
const abort = new AbortController();
|
|
storage
|
|
.getTarball(pkgName, `${pkgName}-1.0.0.tgz`, {
|
|
signal: abort.signal,
|
|
})
|
|
.then((stream) => {
|
|
stream.on('data', (dat) => {
|
|
expect(dat).toBeDefined();
|
|
});
|
|
stream.on('end', () => {
|
|
done();
|
|
});
|
|
stream.on('error', () => {
|
|
done('this should not happen');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('should serve fetch tarball from upstream without dist info local', (done) => {
|
|
const pkgName = 'upstream';
|
|
const upstreamManifest = addNewVersion(
|
|
generateRemotePackageMetadata(pkgName, '1.0.0') as Manifest,
|
|
'1.0.1'
|
|
);
|
|
nock('https://registry.verdaccio.org').get(`/${pkgName}`).reply(201, upstreamManifest);
|
|
nock('http://localhost:5555')
|
|
.get(`/${pkgName}/-/${pkgName}-1.0.1.tgz`)
|
|
// types does not match here with documentation
|
|
// @ts-expect-error
|
|
.replyWithFile(201, path.join(__dirname, 'fixtures/tarball.tgz'), {
|
|
[HEADER_TYPE.CONTENT_LENGTH]: 277,
|
|
});
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/getTarball-getupstream.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
storage.init(config).then(() => {
|
|
const ac = new AbortController();
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
storage
|
|
.updateManifest(bodyNewManifest, {
|
|
signal: ac.signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: {
|
|
host: 'localhost',
|
|
protocol: 'http',
|
|
headers: {},
|
|
},
|
|
})
|
|
.then(() => {
|
|
const abort = new AbortController();
|
|
storage
|
|
.getTarball(pkgName, `${pkgName}-1.0.1.tgz`, {
|
|
signal: abort.signal,
|
|
})
|
|
.then((stream) => {
|
|
stream.on('data', (dat) => {
|
|
expect(dat).toBeDefined();
|
|
});
|
|
stream.on('end', () => {
|
|
done();
|
|
});
|
|
stream.on('error', () => {
|
|
done('this should not happen');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('should serve fetch tarball from upstream without with info local', (done) => {
|
|
const pkgName = 'upstream';
|
|
const upstreamManifest = addNewVersion(
|
|
addNewVersion(generateRemotePackageMetadata(pkgName, '1.0.0') as Manifest, '1.0.1'),
|
|
'1.0.2'
|
|
);
|
|
nock('https://registry.verdaccio.org')
|
|
.get(`/${pkgName}`)
|
|
.times(10)
|
|
.reply(201, upstreamManifest);
|
|
nock('http://localhost:5555')
|
|
.get(`/${pkgName}/-/${pkgName}-1.0.0.tgz`)
|
|
// types does not match here with documentation
|
|
// @ts-expect-error
|
|
.replyWithFile(201, path.join(__dirname, 'fixtures/tarball.tgz'), {
|
|
[HEADER_TYPE.CONTENT_LENGTH]: 277,
|
|
});
|
|
const storagePath = generateRandomStorage();
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: storagePath,
|
|
},
|
|
'./fixtures/config/getTarball-getupstream.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
storage.init(config).then(() => {
|
|
const req = httpMocks.createRequest({
|
|
method: 'GET',
|
|
connection: { remoteAddress: fakeHost },
|
|
headers: {
|
|
host: fakeHost,
|
|
[HEADERS.FORWARDED_PROTO]: 'http',
|
|
},
|
|
url: '/',
|
|
});
|
|
return storage
|
|
.getPackageByOptions({
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
requestOptions: {
|
|
headers: req.headers as any,
|
|
protocol: req.protocol,
|
|
host: req.get('host') as string,
|
|
},
|
|
})
|
|
.then(() => {
|
|
const abort = new AbortController();
|
|
storage
|
|
.getTarball(pkgName, `${pkgName}-1.0.0.tgz`, {
|
|
signal: abort.signal,
|
|
})
|
|
.then((stream) => {
|
|
stream.on('data', (dat) => {
|
|
expect(dat).toBeDefined();
|
|
});
|
|
stream.on('end', () => {
|
|
done();
|
|
});
|
|
stream.once('error', () => {
|
|
done('this should not happen');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('should serve local cache', (done) => {
|
|
const pkgName = 'upstream';
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/getTarball-getupstream.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
storage.init(config).then(() => {
|
|
const ac = new AbortController();
|
|
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
|
storage
|
|
.updateManifest(bodyNewManifest, {
|
|
signal: ac.signal,
|
|
name: pkgName,
|
|
uplinksLook: true,
|
|
revision: '1',
|
|
requestOptions: {
|
|
host: 'localhost',
|
|
protocol: 'http',
|
|
headers: {},
|
|
},
|
|
})
|
|
.then(() => {
|
|
const abort = new AbortController();
|
|
storage
|
|
.getTarball(pkgName, `${pkgName}-1.0.0.tgz`, {
|
|
signal: abort.signal,
|
|
})
|
|
.then((stream) => {
|
|
stream.on('data', (dat) => {
|
|
expect(dat).toBeDefined();
|
|
});
|
|
stream.on('end', () => {
|
|
done();
|
|
});
|
|
stream.on('error', () => {
|
|
done('this should not happen');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('syncUplinksMetadata()', () => {
|
|
describe('error handling', () => {
|
|
test('should handle double failure on uplinks with timeout', async () => {
|
|
const fooManifest = generatePackageMetadata('timeout', '8.0.0');
|
|
nock('https://registry.timeout.com')
|
|
.get(`/${fooManifest.name}`)
|
|
.delayConnection(8000)
|
|
.reply(201, manifestFooRemoteNpmjs);
|
|
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/syncDoubleUplinksMetadata.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
await expect(
|
|
storage.syncUplinksMetadata(fooManifest.name, null, {
|
|
retry: { limit: 3 },
|
|
timeout: {
|
|
request: 1000,
|
|
},
|
|
})
|
|
).rejects.toThrow(API_ERROR.NO_PACKAGE);
|
|
}, 18000);
|
|
|
|
test('should handle one proxy fails', async () => {
|
|
const fooManifest = generatePackageMetadata('foo', '8.0.0');
|
|
nock('https://registry.verdaccio.org').get('/foo').replyWithError('service in holidays');
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/syncSingleUplinksMetadata.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
await expect(
|
|
storage.syncUplinksMetadata(fooManifest.name, null, {
|
|
retry: { limit: 0 },
|
|
})
|
|
).rejects.toThrow(API_ERROR.NO_PACKAGE);
|
|
});
|
|
|
|
test('should handle one proxy reply 304', async () => {
|
|
const fooManifest = generatePackageMetadata('foo-no-data', '8.0.0');
|
|
nock('https://registry.verdaccio.org').get('/foo-no-data').reply(304);
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/syncSingleUplinksMetadata.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const [manifest] = await storage.syncUplinksMetadata(fooManifest.name, fooManifest, {
|
|
retry: { limit: 0 },
|
|
});
|
|
expect(manifest).toBe(fooManifest);
|
|
});
|
|
});
|
|
|
|
describe('success scenarios', () => {
|
|
test('should handle one proxy success', async () => {
|
|
const fooManifest = generateLocalPackageMetadata('foo', '8.0.0');
|
|
nock('https://registry.verdaccio.org').get('/foo').reply(201, manifestFooRemoteNpmjs);
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/syncSingleUplinksMetadata.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
|
|
const [response] = await storage.syncUplinksMetadata(fooManifest.name, fooManifest);
|
|
expect(response).not.toBeNull();
|
|
expect((response as Manifest).name).toEqual(fooManifest.name);
|
|
expect(Object.keys((response as Manifest).versions)).toEqual([
|
|
'8.0.0',
|
|
'1.0.0',
|
|
'0.0.3',
|
|
'0.0.4',
|
|
'0.0.5',
|
|
'0.0.6',
|
|
'0.0.7',
|
|
]);
|
|
expect(Object.keys((response as Manifest).time)).toEqual([
|
|
'modified',
|
|
'created',
|
|
'8.0.0',
|
|
'1.0.0',
|
|
'0.0.3',
|
|
'0.0.4',
|
|
'0.0.5',
|
|
'0.0.6',
|
|
'0.0.7',
|
|
]);
|
|
expect((response as Manifest)[DIST_TAGS].latest).toEqual('8.0.0');
|
|
expect((response as Manifest).time['8.0.0']).toBeDefined();
|
|
});
|
|
|
|
test('should handle one proxy success with no local cache manifest', async () => {
|
|
nock('https://registry.verdaccio.org').get('/foo').reply(201, manifestFooRemoteNpmjs);
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/syncSingleUplinksMetadata.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
|
|
const [response] = await storage.syncUplinksMetadata(fooManifest.name, null);
|
|
// the latest from the remote manifest
|
|
expect(response).not.toBeNull();
|
|
expect((response as Manifest).name).toEqual(fooManifest.name);
|
|
expect((response as Manifest)[DIST_TAGS].latest).toEqual('0.0.7');
|
|
});
|
|
|
|
test('should handle no proxy found with local cache manifest', async () => {
|
|
const fooManifest = generatePackageMetadata('foo', '8.0.0');
|
|
nock('https://registry.verdaccio.org').get('/foo').reply(201, manifestFooRemoteNpmjs);
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/syncNoUplinksMetadata.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
|
|
const [response] = await storage.syncUplinksMetadata(fooManifest.name, fooManifest);
|
|
expect(response).not.toBeNull();
|
|
expect((response as Manifest).name).toEqual(fooManifest.name);
|
|
expect((response as Manifest)[DIST_TAGS].latest).toEqual('8.0.0');
|
|
});
|
|
test.todo('should handle double proxy with last one success');
|
|
});
|
|
|
|
describe('options', () => {
|
|
test('should handle disable uplinks via options.uplinksLook=false with cache', async () => {
|
|
const fooManifest = generatePackageMetadata('foo', '8.0.0');
|
|
nock('https://registry.verdaccio.org').get('/foo').reply(201, manifestFooRemoteNpmjs);
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/syncSingleUplinksMetadata.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
|
|
const [response] = await storage.syncUplinksMetadata(fooManifest.name, fooManifest, {
|
|
uplinksLook: false,
|
|
});
|
|
|
|
expect((response as Manifest).name).toEqual(fooManifest.name);
|
|
expect((response as Manifest)[DIST_TAGS].latest).toEqual('8.0.0');
|
|
});
|
|
|
|
test('should handle disable uplinks via options.uplinksLook=false without cache', async () => {
|
|
const fooRemoteManifest = generateRemotePackageMetadata(
|
|
'foo',
|
|
'9.0.0',
|
|
'https://registry.verdaccio.org',
|
|
['9.0.0', '9.0.1', '9.0.2', '9.0.3']
|
|
);
|
|
nock('https://registry.verdaccio.org').get('/foo').reply(201, fooRemoteManifest);
|
|
const config = new Config(
|
|
configExample(
|
|
{
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
},
|
|
'./fixtures/config/syncSingleUplinksMetadata.yaml',
|
|
__dirname
|
|
)
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
|
|
const [response] = await storage.syncUplinksMetadata('foo', null, {
|
|
uplinksLook: true,
|
|
});
|
|
|
|
expect((response as Manifest).name).toEqual('foo');
|
|
expect((response as Manifest)[DIST_TAGS].latest).toEqual('9.0.0');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getLocalDatabase', () => {
|
|
test('should return no results', async () => {
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
await expect(storage.getLocalDatabase()).resolves.toHaveLength(0);
|
|
});
|
|
|
|
test('should return single result', async () => {
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const req = httpMocks.createRequest({
|
|
method: 'GET',
|
|
connection: { remoteAddress: fakeHost },
|
|
headers: {
|
|
host: 'host',
|
|
},
|
|
url: '/',
|
|
});
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const manifest = generatePackageMetadata('foo');
|
|
const ac = new AbortController();
|
|
await storage.updateManifest(manifest, {
|
|
signal: ac.signal,
|
|
name: 'foo',
|
|
uplinksLook: false,
|
|
requestOptions: {
|
|
headers: req.headers as any,
|
|
protocol: req.protocol,
|
|
host: req.get('host') as string,
|
|
},
|
|
});
|
|
const response = await storage.getLocalDatabase();
|
|
expect(response).toHaveLength(1);
|
|
expect(response[0]).toEqual(expect.objectContaining({ name: 'foo', version: '1.0.0' }));
|
|
});
|
|
});
|
|
|
|
describe('tokens', () => {
|
|
describe('saveToken', () => {
|
|
test('should retrieve tokens created', async () => {
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
await storage.saveToken({
|
|
user: 'foo',
|
|
token: 'secret',
|
|
key: 'key',
|
|
created: 'created',
|
|
readonly: true,
|
|
});
|
|
const tokens = await storage.readTokens({ user: 'foo' });
|
|
expect(tokens).toEqual([
|
|
{ user: 'foo', token: 'secret', key: 'key', readonly: true, created: 'created' },
|
|
]);
|
|
});
|
|
|
|
test('should delete a token created', async () => {
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
await storage.saveToken({
|
|
user: 'foo',
|
|
token: 'secret',
|
|
key: 'key',
|
|
created: 'created',
|
|
readonly: true,
|
|
});
|
|
const tokens = await storage.readTokens({ user: 'foo' });
|
|
expect(tokens).toHaveLength(1);
|
|
await storage.deleteToken('foo', 'key');
|
|
const tokens2 = await storage.readTokens({ user: 'foo' });
|
|
expect(tokens2).toHaveLength(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('removeTarball', () => {
|
|
test('should fail on remove tarball of package does not exist', async () => {
|
|
const username = 'foouser';
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
await expect(storage.removeTarball('foo', 'foo-1.0.0.tgz', 'rev', username)).rejects.toThrow(
|
|
API_ERROR.NO_PACKAGE
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('removePackage', () => {
|
|
test('should remove entirely a package', async () => {
|
|
const username = 'foouser';
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const req = httpMocks.createRequest({
|
|
method: 'GET',
|
|
connection: { remoteAddress: fakeHost },
|
|
headers: {
|
|
host: fakeHost,
|
|
[HEADERS.FORWARDED_PROTO]: 'http',
|
|
},
|
|
url: '/',
|
|
});
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
|
|
const manifest = generatePackageMetadata('foo');
|
|
const ac = new AbortController();
|
|
// 1. publish a package
|
|
await storage.updateManifest(manifest, {
|
|
signal: ac.signal,
|
|
name: 'foo',
|
|
uplinksLook: false,
|
|
requestOptions: {
|
|
headers: req.headers as any,
|
|
protocol: req.protocol,
|
|
host: req.get('host') as string,
|
|
},
|
|
});
|
|
// 2. request package (should be available in the local cache)
|
|
const manifest1 = (await storage.getPackageByOptions({
|
|
name: 'foo',
|
|
uplinksLook: false,
|
|
requestOptions: {
|
|
headers: req.headers as any,
|
|
protocol: req.protocol,
|
|
host: req.get('host') as string,
|
|
},
|
|
})) as Manifest;
|
|
const _rev = manifest1._rev;
|
|
// 3. remove the tarball
|
|
await expect(
|
|
storage.removeTarball(manifest1.name, 'foo-1.0.0.tgz', _rev, username)
|
|
).resolves.toBeDefined();
|
|
// 4. remove the package
|
|
await storage.removePackage(manifest1.name, _rev, username);
|
|
// 5. fails if package does not exist anymore in storage
|
|
await expect(
|
|
storage.getPackageByOptions({
|
|
name: 'foo',
|
|
uplinksLook: false,
|
|
requestOptions: {
|
|
headers: req.headers as any,
|
|
protocol: req.protocol,
|
|
host: req.get('host') as string,
|
|
},
|
|
})
|
|
).rejects.toThrow('package does not exist on uplink: foo');
|
|
});
|
|
|
|
test('ok to remove package as non-owner without check', async () => {
|
|
const config = getConfig('publishWithOwnerDefault.yaml');
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const owner = 'fooUser';
|
|
const options = { ...defaultRequestOptions, username: owner };
|
|
|
|
// 1. publish a package
|
|
const bodyNewManifest = generatePackageMetadata('foo', '1.0.0');
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: 'foo',
|
|
uplinksLook: true,
|
|
requestOptions: options,
|
|
});
|
|
// 2. request package (should be available in the local cache)
|
|
const manifest1 = (await storage.getPackageByOptions({
|
|
name: 'foo',
|
|
uplinksLook: false,
|
|
requestOptions: options,
|
|
})) as Manifest;
|
|
const _rev = manifest1._rev;
|
|
// 3. remove the tarball as other user
|
|
const nonOwner = 'barUser';
|
|
await expect(
|
|
storage.removeTarball(manifest1.name, 'foo-1.0.0.tgz', _rev, nonOwner)
|
|
).resolves.toBeDefined();
|
|
// 4. remove the package as other user
|
|
await storage.removePackage(manifest1.name, _rev, nonOwner);
|
|
// 5. fails if package does not exist anymore in storage
|
|
await expect(
|
|
storage.getPackageByOptions({
|
|
name: 'foo',
|
|
uplinksLook: false,
|
|
requestOptions: options,
|
|
})
|
|
).rejects.toThrow('package does not exist on uplink: foo');
|
|
});
|
|
|
|
test('should fail as non-owner with check', async () => {
|
|
const config = getConfig('publishWithOwnerAndCheck.yaml');
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
const owner = 'fooUser';
|
|
const options = { ...defaultRequestOptions, username: owner };
|
|
|
|
// 1. publish a package
|
|
const bodyNewManifest = generatePackageMetadata('foo', '1.0.0');
|
|
await storage.updateManifest(bodyNewManifest, {
|
|
signal: new AbortController().signal,
|
|
name: 'foo',
|
|
uplinksLook: true,
|
|
requestOptions: options,
|
|
});
|
|
// 2. request package (should be available in the local cache)
|
|
const manifest1 = (await storage.getPackageByOptions({
|
|
name: 'foo',
|
|
uplinksLook: false,
|
|
requestOptions: options,
|
|
})) as Manifest;
|
|
const _rev = manifest1._rev;
|
|
// 3. try removing the tarball
|
|
const nonOwner = 'barUser';
|
|
await expect(
|
|
storage.removeTarball(manifest1.name, 'foo-1.0.0.tgz', _rev, nonOwner)
|
|
).rejects.toThrow();
|
|
// 4. try removing the package
|
|
await expect(storage.removePackage(manifest1.name, _rev, nonOwner)).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('getPackageByOptions()', () => {
|
|
describe('with uplinks', () => {
|
|
test('should get 201 and merge from uplink', async () => {
|
|
nock(domain).get('/foo').reply(201, fooManifest);
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const req = httpMocks.createRequest({
|
|
method: 'GET',
|
|
connection: { remoteAddress: fakeHost },
|
|
headers: {
|
|
host: fakeHost,
|
|
[HEADERS.FORWARDED_PROTO]: 'http',
|
|
},
|
|
url: '/',
|
|
});
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
await expect(
|
|
storage.getPackageByOptions({
|
|
name: 'foo',
|
|
uplinksLook: true,
|
|
requestOptions: {
|
|
headers: req.headers as any,
|
|
protocol: req.protocol,
|
|
host: req.get('host') as string,
|
|
},
|
|
})
|
|
).resolves.toEqual(expect.objectContaining({ name: 'foo' }));
|
|
});
|
|
|
|
test('should get 201 and merge from uplink with version', async () => {
|
|
nock(domain).get('/foo').reply(201, fooManifest);
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const req = httpMocks.createRequest({
|
|
method: 'GET',
|
|
connection: { remoteAddress: fakeHost },
|
|
headers: {
|
|
host: fakeHost,
|
|
[HEADERS.FORWARDED_PROTO]: 'http',
|
|
},
|
|
url: '/',
|
|
});
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
await expect(
|
|
storage.getPackageByOptions({
|
|
name: 'foo',
|
|
version: '1.0.0',
|
|
uplinksLook: true,
|
|
requestOptions: {
|
|
headers: req.headers as any,
|
|
protocol: req.protocol,
|
|
host: req.get('host') as string,
|
|
},
|
|
})
|
|
).resolves.toEqual(expect.objectContaining({ name: 'foo' }));
|
|
});
|
|
|
|
test('should get 201 and merge from uplink with dist-tag', async () => {
|
|
nock(domain).get('/foo').reply(201, fooManifest);
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const req = httpMocks.createRequest({
|
|
method: 'GET',
|
|
connection: { remoteAddress: fakeHost },
|
|
headers: {
|
|
host: fakeHost,
|
|
[HEADERS.FORWARDED_PROTO]: 'http',
|
|
},
|
|
url: '/',
|
|
});
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
await expect(
|
|
storage.getPackageByOptions({
|
|
name: 'foo',
|
|
version: 'latest',
|
|
uplinksLook: true,
|
|
requestOptions: {
|
|
headers: req.headers as any,
|
|
protocol: req.protocol,
|
|
host: req.get('host') as string,
|
|
},
|
|
})
|
|
).resolves.toEqual(expect.objectContaining({ name: 'foo' }));
|
|
});
|
|
|
|
test('should get 404 for version does not exist', async () => {
|
|
nock(domain).get('/foo').reply(201, fooManifest);
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const req = httpMocks.createRequest({
|
|
method: 'GET',
|
|
connection: { remoteAddress: fakeHost },
|
|
headers: {
|
|
host: fakeHost,
|
|
[HEADERS.FORWARDED_PROTO]: 'http',
|
|
},
|
|
url: '/',
|
|
});
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
await expect(
|
|
storage.getPackageByOptions({
|
|
name: 'foo',
|
|
version: '1.0.0-does-not-exist',
|
|
uplinksLook: true,
|
|
requestOptions: {
|
|
headers: req.headers as any,
|
|
protocol: req.protocol,
|
|
host: req.get('host') as string,
|
|
},
|
|
})
|
|
).rejects.toThrow(
|
|
errorUtils.getNotFound("this version doesn't exist: 1.0.0-does-not-exist")
|
|
);
|
|
});
|
|
|
|
test('should get 404', async () => {
|
|
nock(domain).get('/foo2').reply(404);
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
uplinks: {
|
|
npmjs: {
|
|
url: domain,
|
|
},
|
|
},
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const req = httpMocks.createRequest({
|
|
method: 'GET',
|
|
connection: { remoteAddress: fakeHost },
|
|
headers: {
|
|
host: fakeHost,
|
|
[HEADERS.FORWARDED_PROTO]: 'http',
|
|
},
|
|
url: '/',
|
|
});
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
await expect(
|
|
storage.getPackageByOptions({
|
|
name: 'foo2',
|
|
uplinksLook: true,
|
|
requestOptions: {
|
|
headers: req.headers as any,
|
|
protocol: req.protocol,
|
|
host: req.get('host') as string,
|
|
},
|
|
})
|
|
).rejects.toThrow(errorUtils.getNotFound());
|
|
});
|
|
|
|
test('should get ETIMEDOUT with uplink', async () => {
|
|
nock(domain).get('/foo2').replyWithError({
|
|
code: 'ETIMEDOUT',
|
|
errno: 'ETIMEDOUT',
|
|
});
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
uplinks: {
|
|
npmjs: {
|
|
url: domain,
|
|
},
|
|
},
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const req = httpMocks.createRequest({
|
|
method: 'GET',
|
|
connection: { remoteAddress: fakeHost },
|
|
headers: {
|
|
host: fakeHost,
|
|
[HEADERS.FORWARDED_PROTO]: 'http',
|
|
},
|
|
url: '/',
|
|
});
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
await expect(
|
|
storage.getPackageByOptions({
|
|
name: 'foo2',
|
|
uplinksLook: true,
|
|
retry: { limit: 0 },
|
|
requestOptions: {
|
|
headers: req.headers as any,
|
|
protocol: req.protocol,
|
|
host: req.get('host') as string,
|
|
},
|
|
})
|
|
).rejects.toThrow(errorUtils.getServiceUnavailable(API_ERROR.NO_PACKAGE));
|
|
});
|
|
|
|
test('should fetch abbreviated version of manifest ', async () => {
|
|
const fooManifest = generateLocalPackageMetadata('foo', '1.0.0');
|
|
nock(domain).get('/foo').reply(201, fooManifest);
|
|
const config = new Config(
|
|
configExample({
|
|
...getDefaultConfig(),
|
|
storage: generateRandomStorage(),
|
|
})
|
|
);
|
|
const req = httpMocks.createRequest({
|
|
method: 'GET',
|
|
connection: { remoteAddress: fakeHost },
|
|
headers: {
|
|
host: fakeHost,
|
|
[HEADERS.FORWARDED_PROTO]: 'http',
|
|
},
|
|
url: '/',
|
|
});
|
|
const storage = new Storage(config);
|
|
await storage.init(config);
|
|
|
|
const manifest = (await storage.getPackageByOptions({
|
|
name: 'foo',
|
|
uplinksLook: true,
|
|
requestOptions: {
|
|
headers: req.headers as any,
|
|
protocol: req.protocol,
|
|
host: req.get('host') as string,
|
|
},
|
|
abbreviated: true,
|
|
})) as AbbreviatedManifest;
|
|
const { versions, name } = manifest;
|
|
expect(name).toEqual('foo');
|
|
expect(Object.keys(versions)).toEqual(['1.0.0']);
|
|
expect(manifest[DIST_TAGS]).toEqual({ latest: '1.0.0' });
|
|
const version = versions['1.0.0'];
|
|
expect(Object.keys(version)).toEqual([
|
|
'name',
|
|
'version',
|
|
'deprecated',
|
|
'bin',
|
|
'dist',
|
|
'engines',
|
|
'funding',
|
|
'directories',
|
|
'dependencies',
|
|
'devDependencies',
|
|
'peerDependencies',
|
|
'optionalDependencies',
|
|
'bundleDependencies',
|
|
'cpu',
|
|
'os',
|
|
'peerDependenciesMeta',
|
|
'acceptDependencies',
|
|
'_hasShrinkwrap',
|
|
'hasInstallScript',
|
|
]);
|
|
expect(manifest.modified).toBeDefined();
|
|
// special case for pnpm/rfcs/pull/2
|
|
expect(manifest.time).toBeDefined();
|
|
// fields must not have
|
|
// @ts-expect-error
|
|
expect(manifest.readme).not.toBeDefined();
|
|
// @ts-expect-error
|
|
expect(manifest._attachments).not.toBeDefined();
|
|
// @ts-expect-error
|
|
expect(manifest._rev).not.toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
});
|