mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-16 21:56:25 -05:00
e7ebccb61d
* update major dependencies, remove old nodejs support * Update ci.yml * restore dep
466 lines
15 KiB
TypeScript
466 lines
15 KiB
TypeScript
import nock from 'nock';
|
|
import path from 'path';
|
|
import { setTimeout } from 'timers/promises';
|
|
|
|
import { Config, parseConfigFile } from '@verdaccio/config';
|
|
import { API_ERROR, errorUtils } from '@verdaccio/core';
|
|
import { Logger } from '@verdaccio/types';
|
|
|
|
import { ProxyStorage } from '../src';
|
|
|
|
const getConf = (name) => path.join(__dirname, '/conf', name);
|
|
|
|
const mockDebug = jest.fn();
|
|
const mockInfo = jest.fn();
|
|
const mockHttp = jest.fn();
|
|
const mockError = jest.fn();
|
|
const mockWarn = jest.fn();
|
|
|
|
const logger = {
|
|
debug: mockDebug,
|
|
info: mockInfo,
|
|
http: mockHttp,
|
|
error: mockError,
|
|
warn: mockWarn,
|
|
} as unknown as Logger;
|
|
|
|
// mock to get the headers fixed value
|
|
jest.mock('crypto', () => {
|
|
return {
|
|
randomBytes: (): { toString: () => string } => {
|
|
return {
|
|
toString: (): string => 'foo-random-bytes',
|
|
};
|
|
},
|
|
pseudoRandomBytes: (): { toString: () => string } => {
|
|
return {
|
|
toString: (): string => 'foo-phseudo-bytes',
|
|
};
|
|
},
|
|
};
|
|
});
|
|
|
|
const domain = 'https://registry.npmjs.org';
|
|
|
|
describe('proxy', () => {
|
|
beforeEach(() => {
|
|
nock.cleanAll();
|
|
});
|
|
const defaultRequestOptions = {
|
|
url: 'https://registry.npmjs.org',
|
|
};
|
|
const proxyPath = getConf('proxy1.yaml');
|
|
const conf = new Config(parseConfigFile(proxyPath));
|
|
|
|
describe('getRemoteMetadata', () => {
|
|
beforeEach(() => {
|
|
nock.cleanAll();
|
|
nock.abortPendingRequests();
|
|
jest.clearAllMocks();
|
|
});
|
|
describe('basic requests', () => {
|
|
test('success call to remote', async () => {
|
|
nock(domain, {
|
|
reqheaders: {
|
|
accept: 'application/json;',
|
|
'accept-encoding': 'gzip',
|
|
'x-forwarded-for': '127.0.0.1',
|
|
via: '1.1 foo-phseudo-bytes (Verdaccio)',
|
|
},
|
|
})
|
|
.get('/jquery')
|
|
.reply(200, { body: 'test' });
|
|
const prox1 = new ProxyStorage(defaultRequestOptions, conf, logger);
|
|
const [manifest] = await prox1.getRemoteMetadata('jquery', {
|
|
remoteAddress: '127.0.0.1',
|
|
});
|
|
expect(manifest).toEqual({ body: 'test' });
|
|
});
|
|
});
|
|
|
|
describe('etag header', () => {
|
|
test('proxy call with etag', async () => {
|
|
nock(domain, {
|
|
reqheaders: {
|
|
accept: 'application/json;',
|
|
'accept-encoding': 'gzip',
|
|
'x-forwarded-for': '127.0.0.1',
|
|
via: '1.1 foo-phseudo-bytes (Verdaccio)',
|
|
},
|
|
})
|
|
.get('/jquery')
|
|
.reply(
|
|
200,
|
|
{ body: 'test' },
|
|
{
|
|
etag: () => `_ref_4444`,
|
|
}
|
|
);
|
|
const prox1 = new ProxyStorage(defaultRequestOptions, conf, logger);
|
|
const [manifest, etag] = await prox1.getRemoteMetadata('jquery', {
|
|
remoteAddress: '127.0.0.1',
|
|
});
|
|
expect(etag).toEqual('_ref_4444');
|
|
expect(manifest).toEqual({ body: 'test' });
|
|
});
|
|
|
|
test('proxy call with etag as option', async () => {
|
|
nock(domain, {
|
|
reqheaders: {
|
|
accept: 'application/json;',
|
|
'accept-encoding': 'gzip',
|
|
'x-forwarded-for': '127.0.0.1',
|
|
via: '1.1 foo-phseudo-bytes (Verdaccio)',
|
|
// match only if etag is set as option
|
|
'if-none-match': 'foo',
|
|
},
|
|
})
|
|
.get('/jquery')
|
|
.reply(
|
|
200,
|
|
{ body: 'test' },
|
|
{
|
|
etag: () => `_ref_4444`,
|
|
}
|
|
);
|
|
const prox1 = new ProxyStorage(defaultRequestOptions, conf, logger);
|
|
const [manifest, etag] = await prox1.getRemoteMetadata('jquery', {
|
|
etag: 'foo',
|
|
remoteAddress: '127.0.0.1',
|
|
});
|
|
expect(etag).toEqual('_ref_4444');
|
|
expect(manifest).toEqual({ body: 'test' });
|
|
});
|
|
});
|
|
|
|
describe('log activity', () => {
|
|
test('proxy call with etag', async () => {
|
|
nock(domain)
|
|
.get('/jquery')
|
|
.reply(200, { body: { name: 'foo', version: '1.0.0' } }, {});
|
|
const prox1 = new ProxyStorage(defaultRequestOptions, conf, logger);
|
|
await prox1.getRemoteMetadata('jquery', {
|
|
remoteAddress: '127.0.0.1',
|
|
});
|
|
expect(mockHttp).toHaveBeenCalledTimes(2);
|
|
expect(mockHttp).toHaveBeenCalledWith(
|
|
{
|
|
request: { method: 'GET', url: `${domain}/jquery` },
|
|
status: 200,
|
|
},
|
|
"@{!status}, req: '@{request.method} @{request.url}' (streaming)"
|
|
);
|
|
expect(mockHttp).toHaveBeenLastCalledWith(
|
|
{
|
|
request: { method: 'GET', url: `${domain}/jquery` },
|
|
status: 200,
|
|
bytes: {
|
|
in: 0,
|
|
out: 41,
|
|
},
|
|
},
|
|
"@{!status}, req: '@{request.method} @{request.url}'"
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('error handling', () => {
|
|
test('proxy call with 304', async () => {
|
|
nock(domain).get('/jquery').reply(304);
|
|
const prox1 = new ProxyStorage(defaultRequestOptions, conf, logger);
|
|
await expect(prox1.getRemoteMetadata('jquery', { etag: 'rev_3333' })).rejects.toThrow(
|
|
'no data'
|
|
);
|
|
});
|
|
|
|
test('reply with error', async () => {
|
|
nock(domain).get('/jquery').replyWithError('something awful happened');
|
|
const prox1 = new ProxyStorage(defaultRequestOptions, conf, logger);
|
|
await expect(
|
|
prox1.getRemoteMetadata('jquery', {
|
|
remoteAddress: '127.0.0.1',
|
|
})
|
|
).rejects.toThrow(new Error('something awful happened'));
|
|
});
|
|
|
|
test('reply with 409 error', async () => {
|
|
nock(domain).get('/jquery').reply(409);
|
|
const prox1 = new ProxyStorage(defaultRequestOptions, conf, logger);
|
|
await expect(prox1.getRemoteMetadata('jquery', { retry: { limit: 0 } })).rejects.toThrow(
|
|
new Error('bad status code: 409')
|
|
);
|
|
});
|
|
|
|
test('reply with bad body json format', async () => {
|
|
nock(domain).get('/jquery').reply(200, 'some-text');
|
|
const prox1 = new ProxyStorage(defaultRequestOptions, conf, logger);
|
|
await expect(
|
|
prox1.getRemoteMetadata('jquery', {
|
|
remoteAddress: '127.0.0.1',
|
|
})
|
|
).rejects.toThrow();
|
|
});
|
|
|
|
test('400 error proxy call', async () => {
|
|
nock(domain).get('/jquery').reply(409);
|
|
const prox1 = new ProxyStorage(defaultRequestOptions, conf, logger);
|
|
await expect(
|
|
prox1.getRemoteMetadata('jquery', {
|
|
remoteAddress: '127.0.0.1',
|
|
})
|
|
).rejects.toThrow(
|
|
errorUtils.getInternalError(`${errorUtils.API_ERROR.BAD_STATUS_CODE}: 409`)
|
|
);
|
|
});
|
|
|
|
test('proxy not found', async () => {
|
|
nock(domain).get('/jquery').reply(404);
|
|
const prox1 = new ProxyStorage(defaultRequestOptions, conf, logger);
|
|
await expect(
|
|
prox1.getRemoteMetadata('jquery', {
|
|
remoteAddress: '127.0.0.1',
|
|
})
|
|
).rejects.toThrow(errorUtils.getNotFound(API_ERROR.NOT_PACKAGE_UPLINK));
|
|
expect(mockHttp).toHaveBeenCalledTimes(1);
|
|
expect(mockHttp).toHaveBeenLastCalledWith(
|
|
{
|
|
request: { method: 'GET', url: `${domain}/jquery` },
|
|
status: 404,
|
|
},
|
|
"@{!status}, req: '@{request.method} @{request.url}' (streaming)"
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('retry', () => {
|
|
test('retry twice on 500 and return 200 logging offline activity', async () => {
|
|
nock(domain)
|
|
.get('/jquery')
|
|
.twice()
|
|
.reply(500, 'some-text')
|
|
.get('/jquery')
|
|
.once()
|
|
.reply(200, { body: { name: 'foo', version: '1.0.0' } });
|
|
|
|
const prox1 = new ProxyStorage(defaultRequestOptions, conf, logger);
|
|
const [manifest] = await prox1.getRemoteMetadata('jquery', {
|
|
retry: { limit: 2 },
|
|
});
|
|
expect(manifest).toEqual({ body: { name: 'foo', version: '1.0.0' } });
|
|
expect(mockInfo).toHaveBeenCalledTimes(2);
|
|
expect(mockInfo).toHaveBeenLastCalledWith(
|
|
{
|
|
error: 'Response code 500 (Internal Server Error)',
|
|
request: { method: 'GET', url: `${domain}/jquery` },
|
|
retryCount: 2,
|
|
},
|
|
"retry @{retryCount} req: '@{request.method} @{request.url}'"
|
|
);
|
|
});
|
|
|
|
test('retry count is exceded and uplink goes offline with logging activity', async () => {
|
|
nock(domain).get('/jquery').times(10).reply(500);
|
|
|
|
const prox1 = new ProxyStorage(defaultRequestOptions, conf, logger);
|
|
await expect(
|
|
prox1.getRemoteMetadata('jquery', {
|
|
remoteAddress: '127.0.0.1',
|
|
retry: { limit: 2 },
|
|
})
|
|
).rejects.toThrow();
|
|
await expect(
|
|
prox1.getRemoteMetadata('jquery', {
|
|
remoteAddress: '127.0.0.1',
|
|
retry: { limit: 2 },
|
|
})
|
|
).rejects.toThrow(errorUtils.getInternalError(errorUtils.API_ERROR.UPLINK_OFFLINE));
|
|
expect(mockWarn).toHaveBeenCalledTimes(1);
|
|
expect(mockWarn).toHaveBeenLastCalledWith(
|
|
{
|
|
host: 'registry.npmjs.org',
|
|
},
|
|
'host @{host} is now offline'
|
|
);
|
|
});
|
|
|
|
test('fails calls and recover with 200 with log online activity', async () => {
|
|
// This unit test is designed to verify if the uplink goes to offline
|
|
// and recover after the fail_timeout has expired.
|
|
nock(domain)
|
|
.get('/jquery')
|
|
.thrice()
|
|
.reply(500, 'some-text')
|
|
.get('/jquery')
|
|
.once()
|
|
.reply(200, { body: { name: 'foo', version: '1.0.0' } });
|
|
|
|
const prox1 = new ProxyStorage(
|
|
{ ...defaultRequestOptions, fail_timeout: '1s', max_fails: 1 },
|
|
conf,
|
|
logger
|
|
);
|
|
// force retry
|
|
await expect(
|
|
prox1.getRemoteMetadata('jquery', {
|
|
remoteAddress: '127.0.0.1',
|
|
retry: { limit: 2 },
|
|
})
|
|
).rejects.toThrow();
|
|
// display offline error on exausted retry
|
|
await expect(
|
|
prox1.getRemoteMetadata('jquery', {
|
|
remoteAddress: '127.0.0.1',
|
|
retry: { limit: 2 },
|
|
})
|
|
).rejects.toThrow(errorUtils.getInternalError(errorUtils.API_ERROR.UPLINK_OFFLINE));
|
|
expect(mockWarn).toHaveBeenCalledTimes(2);
|
|
expect(mockWarn).toHaveBeenLastCalledWith(
|
|
{
|
|
host: 'registry.npmjs.org',
|
|
},
|
|
'host @{host} is now offline'
|
|
);
|
|
expect(mockWarn).toHaveBeenLastCalledWith(
|
|
{
|
|
host: 'registry.npmjs.org',
|
|
},
|
|
'host @{host} is now offline'
|
|
);
|
|
// this is based on max_fails, if change that also change here acordingly
|
|
await setTimeout(3000);
|
|
const [manifest] = await prox1.getRemoteMetadata('jquery', {
|
|
retry: { limit: 2 },
|
|
});
|
|
expect(manifest).toEqual({ body: { name: 'foo', version: '1.0.0' } });
|
|
expect(mockWarn).toHaveBeenLastCalledWith(
|
|
{
|
|
host: 'registry.npmjs.org',
|
|
},
|
|
'host @{host} is now online'
|
|
);
|
|
}, 10000);
|
|
});
|
|
|
|
describe('timeout', () => {
|
|
test('fail for timeout (2 seconds)', async () => {
|
|
nock(domain)
|
|
.get('/jquery')
|
|
.times(10)
|
|
.delayConnection(6000)
|
|
.reply(200, { body: { name: 'foo', version: '1.0.0' } });
|
|
|
|
const confTimeout = { ...defaultRequestOptions };
|
|
// @ts-expect-error
|
|
confTimeout.timeout = '2s';
|
|
const prox1 = new ProxyStorage(confTimeout, conf, logger);
|
|
await expect(
|
|
prox1.getRemoteMetadata('jquery', {
|
|
retry: { limit: 0 },
|
|
})
|
|
).rejects.toThrow('ETIMEDOUT');
|
|
}, 10000);
|
|
|
|
test('fail for one failure and timeout (2 seconds)', async () => {
|
|
nock(domain)
|
|
.get('/jquery')
|
|
.times(1)
|
|
.reply(500)
|
|
.get('/jquery')
|
|
.delayConnection(4000)
|
|
.reply(200, { body: { name: 'foo', version: '1.0.0' } });
|
|
|
|
const confTimeout = { ...defaultRequestOptions };
|
|
// @ts-expect-error
|
|
confTimeout.timeout = '2s';
|
|
const prox1 = new ProxyStorage(confTimeout, conf, logger);
|
|
await expect(
|
|
prox1.getRemoteMetadata('jquery', {
|
|
retry: { limit: 1 },
|
|
})
|
|
).rejects.toThrow('ETIMEDOUT');
|
|
}, 10000);
|
|
|
|
// test('retry count is exceded and uplink goes offline with logging activity', async () => {
|
|
// nock(domain).get('/jquery').times(10).reply(500);
|
|
|
|
// const prox1 = new ProxyStorage(defaultRequestOptions, conf, logger);
|
|
// await expect(
|
|
// prox1.getRemoteMetadata('jquery', {
|
|
// remoteAddress: '127.0.0.1',
|
|
// retry: { limit: 2 },
|
|
// })
|
|
// ).rejects.toThrow();
|
|
// await expect(
|
|
// prox1.getRemoteMetadata('jquery', {
|
|
// remoteAddress: '127.0.0.1',
|
|
// retry: { limit: 2 },
|
|
// })
|
|
// ).rejects.toThrow(errorUtils.getInternalError(errorUtils.API_ERROR.UPLINK_OFFLINE));
|
|
// expect(mockWarn).toHaveBeenCalledTimes(1);
|
|
// expect(mockWarn).toHaveBeenLastCalledWith(
|
|
// {
|
|
// host: 'registry.npmjs.org',
|
|
// },
|
|
// 'host @{host} is now offline'
|
|
// );
|
|
// });
|
|
|
|
// test('fails calls and recover with 200 with log online activity', async () => {
|
|
// // This unit test is designed to verify if the uplink goes to offline
|
|
// // and recover after the fail_timeout has expired.
|
|
// nock(domain)
|
|
// .get('/jquery')
|
|
// .thrice()
|
|
// .reply(500, 'some-text')
|
|
// .get('/jquery')
|
|
// .once()
|
|
// .reply(200, { body: { name: 'foo', version: '1.0.0' } });
|
|
|
|
// const prox1 = new ProxyStorage(
|
|
// { ...defaultRequestOptions, fail_timeout: '1s', max_fails: 1 },
|
|
// conf,
|
|
// logger
|
|
// );
|
|
// // force retry
|
|
// await expect(
|
|
// prox1.getRemoteMetadata('jquery', {
|
|
// remoteAddress: '127.0.0.1',
|
|
// retry: { limit: 2 },
|
|
// })
|
|
// ).rejects.toThrow();
|
|
// // display offline error on exausted retry
|
|
// await expect(
|
|
// prox1.getRemoteMetadata('jquery', {
|
|
// remoteAddress: '127.0.0.1',
|
|
// retry: { limit: 2 },
|
|
// })
|
|
// ).rejects.toThrow(errorUtils.getInternalError(errorUtils.API_ERROR.UPLINK_OFFLINE));
|
|
// expect(mockWarn).toHaveBeenCalledTimes(2);
|
|
// expect(mockWarn).toHaveBeenLastCalledWith(
|
|
// {
|
|
// host: 'registry.npmjs.org',
|
|
// },
|
|
// 'host @{host} is now offline'
|
|
// );
|
|
// expect(mockWarn).toHaveBeenLastCalledWith(
|
|
// {
|
|
// host: 'registry.npmjs.org',
|
|
// },
|
|
// 'host @{host} is now offline'
|
|
// );
|
|
// // this is based on max_fails, if change that also change here acordingly
|
|
// await setTimeout(3000);
|
|
// const [manifest] = await prox1.getRemoteMetadata('jquery', {
|
|
// retry: { limit: 2 },
|
|
// });
|
|
// expect(manifest).toEqual({ body: { name: 'foo', version: '1.0.0' } });
|
|
// expect(mockWarn).toHaveBeenLastCalledWith(
|
|
// {
|
|
// host: 'registry.npmjs.org',
|
|
// },
|
|
// 'host @{host} is now online'
|
|
// );
|
|
// }, 10000);
|
|
});
|
|
});
|
|
});
|