0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-12-16 21:56:25 -05:00
verdaccio/packages/proxy/test/proxy.metadata.spec.ts
Juan Picado e7ebccb61d
update major dependencies, remove old nodejs support (#4059)
* update major dependencies, remove old nodejs support

* Update ci.yml

* restore dep
2023-10-07 16:23:04 +02:00

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);
});
});
});