refactor: migrate request to node-fetch at hooks package (#1946)

* refactor(hooks): new structure for notifications

* chore: fix build

* chore: add debug

* chore: add changeset
This commit is contained in:
Juan Picado 2020-09-22 23:43:39 +02:00
parent 8c730c0694
commit 65cb26cf31
19 changed files with 278 additions and 456 deletions

View File

@ -2,6 +2,10 @@
"presets": [ [
"@babel/env",
{
"useBuiltIns": "usage",
"corejs": {
"version": 3, "proposals": true
},
"targets": {
"node": 10
}

View File

@ -0,0 +1,6 @@
---
'@verdaccio/hooks': patch
'@verdaccio/proxy': patch
---
refactor: migrate request to node-fetch at hooks package

View File

@ -1,4 +1,4 @@
FROM node:12.18.3-alpine as builder
FROM node:12.18.4-alpine as builder
ENV NODE_ENV=development \
VERDACCIO_BUILD_REGISTRY=https://registry.verdaccio.org
@ -12,14 +12,14 @@ RUN apk --no-cache add openssl ca-certificates wget && \
WORKDIR /opt/verdaccio-build
COPY . .
RUN npm -g i pnpm@latest && \
RUN npm -g i pnpm@5.5.12 && \
pnpm config set registry $VERDACCIO_BUILD_REGISTRY && \
pnpm recursive install --frozen-lockfile --ignore-scripts && \
pnpm run build && \
pnpm run lint && \
pnpm install --prod --ignore-scripts
FROM node:12.18.3-alpine
FROM node:12.18.4-alpine
LABEL maintainer="https://github.com/verdaccio/verdaccio"
ENV VERDACCIO_APPDIR=/opt/verdaccio \

View File

@ -28,6 +28,7 @@
"@verdaccio/loaders": "workspace:5.0.0-alpha.0",
"@verdaccio/logger": "workspace:5.0.0-alpha.0",
"@verdaccio/utils": "workspace:5.0.0-alpha.0",
"@verdaccio/auth": "workspace:5.0.0-alpha.0",
"debug": "^4.1.1",
"express": "4.17.1",
"lodash": "4.17.15"

View File

@ -15,7 +15,7 @@
"homepage": "https://verdaccio.org",
"license": "MIT",
"devDependencies": {
"@verdaccio/types": "workspace:10.0.0-beta"
"@verdaccio/types": "workspace:*"
},
"scripts": {
"clean": "rimraf ./build",

View File

@ -23,7 +23,7 @@
"access": "public"
},
"devDependencies": {
"@verdaccio/types": "^9.7.2"
"@verdaccio/types": "workspace:*"
},
"scripts": {
"clean": "rimraf ./build",

View File

@ -218,6 +218,7 @@ declare module '@verdaccio/types' {
max_users: number;
}
// FUTURE: rename to Notification
interface Notifications {
method: string;
packagePattern: RegExp;
@ -227,6 +228,8 @@ declare module '@verdaccio/types' {
headers: Headers;
}
type Notification = Notifications;
interface ConfigFile {
storage: string;
plugins: string;
@ -364,7 +367,9 @@ declare module '@verdaccio/types' {
https_proxy?: string;
no_proxy?: string;
max_body_size?: string;
// deprecated
notifications?: Notifications;
notify?: Notifications | Notifications[];
middlewares?: any;
filters?: any;
checkSecretKey(token: string): string;

View File

@ -15,18 +15,20 @@
"license": "MIT",
"homepage": "https://verdaccio.org",
"dependencies": {
"debug": "^4.2.0",
"@verdaccio/commons-api": "workspace:*",
"@verdaccio/logger": "workspace:5.0.0-alpha.0",
"handlebars": "4.5.3",
"lodash": "^4.17.20",
"request": "2.87.0"
"request": "2.87.0",
"node-fetch": "^2.6.1"
},
"devDependencies": {
"@verdaccio/auth": "workspace:5.0.0-alpha.0",
"@verdaccio/config": "workspace:5.0.0-alpha.0",
"@verdaccio/dev-commons": "workspace:5.0.0-alpha.0",
"@verdaccio/types": "workspace:*",
"@verdaccio/utils": "workspace:5.0.0-alpha.0"
"@verdaccio/utils": "workspace:5.0.0-alpha.0",
"nock": "^13.0.4"
},
"scripts": {
"clean": "rimraf ./build",

View File

@ -1,23 +1,38 @@
import isNil from 'lodash/isNil';
import request, { RequiredUriUrl } from 'request';
import fetch, { RequestInit } from 'node-fetch';
import buildDebug from 'debug';
import { logger } from '@verdaccio/logger';
import { HTTP_STATUS } from '@verdaccio/commons-api';
export function notifyRequest(options: RequiredUriUrl, content): Promise<any | Error> {
return new Promise((resolve, reject): void => {
request(options, function (err, response, body): void {
if (err || response.statusCode >= HTTP_STATUS.BAD_REQUEST) {
const errorMessage = isNil(err) ? response.body : err.message;
logger.error({ errorMessage }, 'notify service has thrown an error: @{errorMessage}');
reject(errorMessage);
}
logger.info({ content }, 'A notification has been shipped: @{content}');
if (isNil(body) === false) {
logger.debug({ body }, ' body: @{body}');
resolve(body);
}
reject(Error('body is missing'));
const debug = buildDebug('verdaccio:hooks:request');
export type NotifyRequestOptions = RequestInit;
export async function notifyRequest(url: string, options: NotifyRequestOptions): Promise<boolean> {
let response;
try {
debug('uri %o', url);
response = await fetch(url, {
body: JSON.stringify(options.body),
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
});
debug('response.status %o', response.status);
const body = await response.json();
if (response.status >= HTTP_STATUS.BAD_REQUEST) {
throw new Error(body);
}
logger.info(
{ content: options.body },
'The notification @{content} has been successfully dispatched'
);
return true;
} catch (err) {
debug('request error %o', err);
logger.error(
{ errorMessage: err?.message },
'notify service has thrown an error: @{errorMessage}'
);
return false;
}
}

View File

@ -1,46 +1,62 @@
import Handlebars from 'handlebars';
import _ from 'lodash';
import { OptionsWithUrl } from 'request';
import { Config, Package, RemoteUser } from '@verdaccio/types';
import { notifyRequest } from './notify-request';
import buildDebug from 'debug';
import { Config, Package, RemoteUser, Notification } from '@verdaccio/types';
import { logger } from '@verdaccio/logger';
import { notifyRequest, NotifyRequestOptions } from './notify-request';
const debug = buildDebug('verdaccio:hooks');
type TemplateMetadata = Package & { publishedPackage: string };
export function handleNotify(
metadata: Package,
export function compileTemplate(content, metadata) {
// FUTURE: multiple handlers
return new Promise((resolve, reject) => {
let handler;
try {
if (!handler) {
debug('compile default template handler %o', content);
const template: HandlebarsTemplateDelegate = Handlebars.compile(content);
return resolve(template(metadata));
}
} catch (error) {
debug('error template handler %o', error);
reject(error);
}
});
}
export async function handleNotify(
metadata: Partial<Package>,
notifyEntry,
remoteUser: RemoteUser,
remoteUser: Partial<RemoteUser>,
publishedPackage: string
): Promise<any> | void {
): Promise<boolean> {
let regex;
if (metadata.name && notifyEntry.packagePattern) {
regex = new RegExp(notifyEntry.packagePattern, notifyEntry.packagePatternFlags || '');
if (!regex.test(metadata.name)) {
return;
return false;
}
}
const template: HandlebarsTemplateDelegate = Handlebars.compile(notifyEntry.content);
// don't override 'publisher' if package.json already has that
/* eslint no-unused-vars: 0 */
/* eslint @typescript-eslint/no-unused-vars: 0 */
let content;
// FIXME: publisher is not part of the expected types metadata
// @ts-ignore
if (_.isNil(metadata.publisher)) {
if (typeof metadata?.publisher === 'undefined' || metadata?.publisher === null) {
// @ts-ignore
metadata = { ...metadata, publishedPackage, publisher: { name: remoteUser.name as string } };
debug('template metadata %o', metadata);
content = await compileTemplate(notifyEntry.content, metadata);
}
const content: string = template(metadata);
const options: OptionsWithUrl = {
body: content,
url: '',
const options: NotifyRequestOptions = {
body: JSON.stringify(content),
};
// provides fallback support, it's accept an Object {} and Array of {}
if (notifyEntry.headers && _.isArray(notifyEntry.headers)) {
if (notifyEntry.headers && Array.isArray(notifyEntry.headers)) {
const header = {};
// FIXME: we can simplify this
notifyEntry.headers.map(function (item): void {
if (Object.is(item, item)) {
for (const key in item) {
@ -56,44 +72,67 @@ export function handleNotify(
options.headers = notifyEntry.headers;
}
options.method = notifyEntry.method;
if (notifyEntry.endpoint) {
options.url = notifyEntry.endpoint;
if (!notifyEntry.endpoint) {
debug('error due endpoint is missing');
throw new Error('missing parameter');
}
return notifyRequest(options, content);
return notifyRequest(notifyEntry.endpoint, {
method: notifyEntry.method,
...options,
});
}
export function sendNotification(
metadata: Package,
metadata: Partial<Package>,
notify: Notification,
remoteUser: RemoteUser,
remoteUser: Partial<RemoteUser>,
publishedPackage: string
): Promise<any> {
): Promise<boolean> {
return handleNotify(metadata, notify, remoteUser, publishedPackage) as Promise<any>;
}
export function notify(
metadata: Package,
config: Config,
remoteUser: RemoteUser,
export async function notify(
metadata: Partial<Package>,
config: Partial<Config>,
remoteUser: Partial<RemoteUser>,
publishedPackage: string
): Promise<any> | void {
): Promise<boolean[]> {
debug('init send notification');
if (config.notify) {
if (config.notify.content) {
return sendNotification(
metadata,
(config.notify as unknown) as Notification,
remoteUser,
publishedPackage
);
}
// multiple notifications endpoints PR #108
return Promise.all(
_.map(config.notify, (key) => sendNotification(metadata, key, remoteUser, publishedPackage))
);
}
const isSingle = Object.keys(config.notify).includes('method');
if (isSingle) {
debug('send single notification');
try {
const response = await sendNotification(
metadata,
config.notify as Notification,
remoteUser,
publishedPackage
);
return [response];
} catch {
debug('error on sending single notification');
return [false];
}
} else {
debug('send multiples notification');
const results = await Promise.allSettled(
Object.keys(config.notify).map((keyId: string) => {
// @ts-ignore
const item = config.notify[keyId];
debug('send item %o', item);
return sendNotification(metadata, item, remoteUser, publishedPackage);
})
).catch((error) => {
logger.error({ error }, 'notify request has failed: @error');
});
return Promise.resolve();
// @ts-ignore
return Object.keys(results).map((promiseValue) => results[promiseValue].value);
}
} else {
debug('no notifications configuration detected');
return [false];
}
}

View File

@ -0,0 +1,85 @@
import nock from 'nock';
import { createRemoteUser, parseConfigFile } from '@verdaccio/utils';
import { Config } from '@verdaccio/types';
import { notify } from '../src/notify';
import { parseConfigurationFile } from './__helper';
const parseConfigurationNotifyFile = (name) => {
return parseConfigurationFile(`notify/${name}`);
};
const singleNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('single.notify'));
const singleHeaderNotificationConfig = parseConfigFile(
parseConfigurationNotifyFile('single.header.notify')
);
const packagePatternNotificationConfig = parseConfigFile(
parseConfigurationNotifyFile('single.packagePattern.notify')
);
const multiNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('multiple.notify'));
const mockInfo = jest.fn();
jest.mock('@verdaccio/logger', () => ({
setup: jest.fn(),
logger: {
child: jest.fn(),
debug: jest.fn(),
trace: jest.fn(),
warn: jest.fn(),
info: () => mockInfo(),
error: jest.fn(),
fatal: jest.fn(),
},
}));
const domain = 'http://slack-service';
describe('Notifications:: notifyRequest', () => {
beforeEach(() => {
nock.cleanAll();
});
test('when sending a empty notification', async () => {
nock(domain).post('/foo?auth_token=mySecretToken').reply(200, { body: 'test' });
const notificationResponse = await notify({}, {}, createRemoteUser('foo', []), 'bar');
expect(notificationResponse).toEqual([false]);
});
test('when sending a single notification', async () => {
nock(domain).post('/foo?auth_token=mySecretToken').reply(200, { body: 'test' });
const notificationResponse = await notify(
{},
singleHeaderNotificationConfig,
createRemoteUser('foo', []),
'bar'
);
expect(notificationResponse).toEqual([true]);
});
test('when notification endpoint is missing', async () => {
nock(domain).post('/foo?auth_token=mySecretToken').reply(200, { body: 'test' });
const name = 'package';
const config: Partial<Config> = {
// @ts-ignore
notify: {
method: 'POST',
endpoint: undefined,
content: '',
},
};
const notificationResponse = await notify({ name }, config, createRemoteUser('foo', []), 'bar');
expect(notificationResponse).toEqual([false]);
});
test('when multiple notifications', async () => {
nock(domain).post('/foo?auth_token=mySecretToken').reply(200, { body: 'test' });
nock(domain).post('/foo?auth_token=mySecretToken').reply(400, {});
nock(domain)
.post('/foo?auth_token=mySecretToken')
.reply(500, { message: 'Something bad happened' });
const name = 'package';
const responses = await notify({ name }, multiNotificationConfig, { name: 'foo' }, 'bar');
expect(responses).toEqual([true, false, false]);
});
});

View File

@ -1,87 +0,0 @@
import { parseConfigFile } from '@verdaccio/utils';
import { setup } from '@verdaccio/logger';
import { notify } from '../src';
import { notifyRequest } from '../src/notify-request';
import { parseConfigurationFile } from './__helper';
setup([]);
jest.mock('../src/notify-request', () => ({
notifyRequest: jest.fn((options, content) => Promise.resolve([options, content])),
}));
const parseConfigurationNotifyFile = (name) => {
return parseConfigurationFile(`notify/${name}`);
};
const singleNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('single.notify'));
const singleHeaderNotificationConfig = parseConfigFile(
parseConfigurationNotifyFile('single.header.notify')
);
const packagePatternNotificationConfig = parseConfigFile(
parseConfigurationNotifyFile('single.packagePattern.notify')
);
const multiNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('multiple.notify'));
describe('Notifications:: Notify', () => {
beforeEach(() => {
jest.clearAllMocks();
});
// FUTURE: we should add some sort of health check of all props, (not implemented yet)
test('should not fails if config is not provided', async () => {
// @ts-ignore
await notify({}, {});
expect(notifyRequest).toHaveBeenCalledTimes(0);
});
test('should send notification', async () => {
const name = 'package';
// @ts-ignore
const response = await notify({ name }, singleNotificationConfig, { name: 'foo' }, 'bar');
const [options, content] = response;
expect(options.headers).toBeDefined();
expect(options.url).toBeDefined();
expect(options.body).toBeDefined();
expect(content).toMatch(name);
expect(response).toBeTruthy();
expect(notifyRequest).toHaveBeenCalledTimes(1);
});
test('should send single header notification', async () => {
// @ts-ignore
await notify({}, singleHeaderNotificationConfig, { name: 'foo' }, 'bar');
expect(notifyRequest).toHaveBeenCalledTimes(1);
});
test('should send multiple notification', async () => {
const name = 'package';
// @ts-ignore
await notify({ name }, multiNotificationConfig, { name: 'foo' }, 'bar');
expect(notifyRequest).toHaveBeenCalled();
expect(notifyRequest).toHaveBeenCalledTimes(3);
});
describe('packagePatternFlags', () => {
test('should send single notification with packagePatternFlags', async () => {
const name = 'package';
// @ts-ignore
await notify({ name }, packagePatternNotificationConfig, { name: 'foo' }, 'bar');
expect(notifyRequest).toHaveBeenCalledTimes(1);
});
test('should not match on send single notification with packagePatternFlags', async () => {
const name = 'no-mach-name';
// @ts-ignore
await notify({ name }, packagePatternNotificationConfig, { name: 'foo' }, 'bar');
expect(notifyRequest).toHaveBeenCalledTimes(0);
});
});
});

View File

@ -2,15 +2,15 @@ notify:
'example-google-chat':
method: POST
headers: [{ 'Content-Type': 'application/json' }]
endpoint: https://chat.googleapis.com/v1/spaces/AAAAB_TcJYs/messages?key=myKey&token=myToken
endpoint: http://slack-service/foo?auth_token=mySecretToken
content: '{"text":"New package published: `{{ name }}{{#each versions}} v{{version}}{{/each}}`"}'
'example-hipchat':
method: POST
headers: [{ 'Content-Type': 'application/json' }]
endpoint: https://usagge.hipchat.com/v2/room/3729485/notification?auth_token=mySecretToken
endpoint: http://slack-service/foo?auth_token=mySecretToken
content: '{"color":"green","message":"New package published: * {{ name }}*","notify":true,"message_format":"text"}'
'example-stride':
method: POST
headers: [{ 'Content-Type': 'application/json' }, { 'authorization': 'Bearer secretToken' }]
endpoint: https://api.atlassian.com/site/{cloudId}/conversation/{conversationId}/message
endpoint: http://slack-service/foo?auth_token=mySecretToken
content: '{"body": {"version": 1,"type": "doc","content": [{"type": "paragraph","content": [{"type": "text","text": "New package published: * {{ name }}* Publisher name: * {{ publisher.name }}"}]}]}}'

View File

@ -1,5 +1,5 @@
notify:
method: POST
headers: { 'Content-Type': 'application/json' }
endpoint: https://usagge.hipchat.com/v2/room/3729485/notification?auth_token=mySecretToken
endpoint: http://slack-service/foo?auth_token=mySecretToken
content: '{"color":"green","message":"New package published: * {{ name }}*","notify":true,"message_format":"text"}'

View File

@ -1,106 +0,0 @@
import { HTTP_STATUS, API_ERROR } from '@verdaccio/dev-commons';
/* eslint-disable @typescript-eslint/no-var-requires */
/**
* Mocks Logger Service
*/
const logger = {
logger: {
error: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
},
};
jest.doMock('@verdaccio/logger', () => logger);
/**
* Test Data
*/
const options = {
url: 'http://slack-service',
};
const content = 'Verdaccio@x.x.x successfully published';
describe('Notifications:: notifyRequest', () => {
beforeEach(() => {
jest.resetModules();
});
test('when notification service throws error', async () => {
jest.doMock('request', () => (options, resolver) => {
const response = {
statusCode: HTTP_STATUS.BAD_REQUEST,
};
const error = {
message: API_ERROR.BAD_DATA,
};
resolver(error, response);
});
const notification = require('../src/notify-request');
const args = [
{ errorMessage: 'bad data' },
'notify service has thrown an error: @{errorMessage}',
];
await expect(notification.notifyRequest(options, content)).rejects.toEqual(API_ERROR.BAD_DATA);
expect(logger.logger.error).toHaveBeenCalledWith(...args);
});
test('when notification service throws error with null error value', async () => {
jest.doMock('request', () => (options, resolver) => {
const response = {
statusCode: HTTP_STATUS.BAD_REQUEST,
body: API_ERROR.BAD_DATA,
};
resolver(null, response);
});
const notification = require('../src/notify-request');
const args = [
{ errorMessage: 'bad data' },
'notify service has thrown an error: @{errorMessage}',
];
await expect(notification.notifyRequest(options, content)).rejects.toEqual(API_ERROR.BAD_DATA);
expect(logger.logger.error).toHaveBeenCalledWith(...args);
});
test('when notification is successfully delivered', async () => {
jest.doMock('request', () => (options, resolver) => {
const response = {
statusCode: HTTP_STATUS.OK,
body: 'Successfully delivered',
};
resolver(null, response, response.body);
});
const notification = require('../src/notify-request');
const infoArgs = [{ content }, 'A notification has been shipped: @{content}'];
const debugArgs = [{ body: 'Successfully delivered' }, ' body: @{body}'];
await expect(notification.notifyRequest(options, content)).resolves.toEqual(
'Successfully delivered'
);
expect(logger.logger.info).toHaveBeenCalledWith(...infoArgs);
expect(logger.logger.debug).toHaveBeenCalledWith(...debugArgs);
});
test('when notification is successfully delivered but body is undefined/null', async () => {
jest.doMock('request', () => (options, resolver) => {
const response = {
statusCode: HTTP_STATUS.OK,
};
resolver(null, response);
});
const notification = require('../src/notify-request');
const infoArgs = [{ content }, 'A notification has been shipped: @{content}'];
await expect(notification.notifyRequest(options, content)).rejects.toThrow('body is missing');
expect(logger.logger.info).toHaveBeenCalledWith(...infoArgs);
});
});

View File

@ -20,7 +20,6 @@
"license": "MIT",
"scripts": {
"clean": "rimraf ./build",
"build": "tsc --emitDeclarationOnly -p tsconfig.build.json",
"test": "echo \"Error: no test specified\" && exit 0"
}
}

View File

@ -12,7 +12,6 @@ import distTagsMerge from './tags/dist-tags-merge';
import addtag from './tags/addtag';
import adduser from './adduser/adduser';
import logout from './adduser/logout';
import notify from './notifications/notify';
import incomplete from './sanity/incomplete';
import mirror from './sanity/mirror';
import readme from './readme/readme';
@ -56,7 +55,6 @@ describe('functional test verdaccio', function () {
security(server1);
addtag(server1);
pluginsAuth(server2);
notify(app);
uplinkTimeout(server1, server2, server3);
// requires packages published to server1/server2
upLinkCache(server1, server2, server3);

View File

@ -1,175 +0,0 @@
import _ from 'lodash';
import { HEADERS } from '@verdaccio/dev-commons';
import { notify } from '@verdaccio/hooks';
import { DOMAIN_SERVERS, PORT_SERVER_APP } from '../config.functional';
import { RemoteUser } from '@verdaccio/types';
export default function (express) {
const config = {
notify: {
method: 'POST',
headers: [
{
'Content-Type': HEADERS.JSON,
},
],
endpoint: `http://${DOMAIN_SERVERS}:${PORT_SERVER_APP}/api/notify`,
content: `{"color":"green","message":"New package published: * {{ name }}*. Publisher name: * {{ publisher.name }} *.","notify":true,"message_format":"text"}`,
},
};
const publisherInfo: RemoteUser = {
name: 'publisher-name-test',
real_groups: [],
groups: [],
};
describe('notifications', () => {
function parseBody(notification) {
const jsonBody = JSON.parse(notification);
return jsonBody;
}
beforeAll(function () {
express.post('/api/notify', function (req, res) {
res.send(req.body);
});
express.post('/api/notify/bad', function (req, res) {
res.status(400);
res.send('bad response');
});
});
test('notification should be send', (done) => {
const metadata = {
name: 'pkg-test',
};
// @ts-ignore
notify(metadata, config, publisherInfo, 'foo').then(
function (body) {
const jsonBody = parseBody(body);
expect(
`New package published: * ${metadata.name}*. Publisher name: * ${publisherInfo.name} *.`
).toBe(jsonBody.message);
done();
},
function (err) {
expect(err).toBeDefined();
done();
}
);
});
test('notification should be send single header', (done) => {
const metadata = {
name: 'pkg-test',
};
const configMultipleHeader = _.cloneDeep(config);
configMultipleHeader.notify.headers = {
// @ts-ignore
'Content-Type': HEADERS.JSON,
};
// @ts-ignore
notify(metadata, configMultipleHeader, publisherInfo).then(
function (body) {
const jsonBody = parseBody(body);
expect(
`New package published: * ${metadata.name}*. Publisher name: * ${publisherInfo.name} *.`
).toBe(jsonBody.message);
done();
},
function (err) {
expect(err).toBeDefined();
done();
}
);
});
test('notification should be send multiple notifications endpoints', (done) => {
const metadata = {
name: 'pkg-test',
};
// let notificationsCounter = 0;
const multipleNotificationsEndpoint = {
notify: [],
};
for (let i = 0; i < 10; i++) {
const notificationSettings = _.cloneDeep(config.notify);
// basically we allow al notifications
// @ts-ignore
notificationSettings.packagePattern = /^pkg-test$/;
// notificationSettings.packagePatternFlags = 'i';
// @ts-ignore
multipleNotificationsEndpoint.notify.push(notificationSettings);
}
// @ts-ignore
notify(metadata, multipleNotificationsEndpoint, publisherInfo).then(
function (body) {
body.forEach(function (notification) {
const jsonBody = parseBody(notification);
expect(
`New package published: * ${metadata.name}*. Publisher name: * ${publisherInfo.name} *.`
).toBe(jsonBody.message);
});
done();
},
function (err) {
expect(err).toBeDefined();
done();
}
);
});
test('notification should fails', (done) => {
const metadata = {
name: 'pkg-test',
};
const configFail = _.cloneDeep(config);
configFail.notify.endpoint = `http://${DOMAIN_SERVERS}:${PORT_SERVER_APP}/api/notify/bad`;
// @ts-ignore
notify(metadata, configFail, publisherInfo).then(
function () {
expect(false).toBe('This service should fails with status code 400');
done();
},
function (err) {
expect(err).toEqual('bad response');
done();
}
);
});
test('publisher property should not be overridden if it exists in metadata', (done) => {
const metadata = {
name: 'pkg-test',
publisher: {
name: 'existing-publisher-name',
},
};
// @ts-ignore
notify(metadata, config, publisherInfo).then(
function (body) {
const jsonBody = parseBody(body);
expect(
`New package published: * ${metadata.name}*. Publisher name: * ${metadata.publisher.name} *.`
).toBe(jsonBody.message);
done();
},
function (err) {
expect(err).toBeDefined();
done();
}
);
});
});
}

View File

@ -216,6 +216,7 @@ importers:
supertest: next
packages/auth:
dependencies:
'@verdaccio/auth': 'link:'
'@verdaccio/commons-api': 'link:../core/commons-api'
'@verdaccio/dev-commons': 'link:../commons'
'@verdaccio/loaders': 'link:../loaders'
@ -229,6 +230,7 @@ importers:
'@verdaccio/mock': 'link:../mock'
'@verdaccio/types': 'link:../core/types'
specifiers:
'@verdaccio/auth': 'workspace:5.0.0-alpha.0'
'@verdaccio/commons-api': 'workspace:*'
'@verdaccio/config': 'workspace:5.0.0-alpha.0'
'@verdaccio/dev-commons': 'workspace:5.0.0-alpha.0'
@ -265,7 +267,7 @@ importers:
devDependencies:
'@verdaccio/types': 'link:../core/types'
specifiers:
'@verdaccio/types': 'workspace:10.0.0-beta'
'@verdaccio/types': 'workspace:*'
packages/config:
dependencies:
'@verdaccio/dev-commons': 'link:../commons'
@ -354,9 +356,9 @@ importers:
marked: 1.1.1
packages/core/streams:
devDependencies:
'@verdaccio/types': 9.7.2
'@verdaccio/types': 'link:../types'
specifiers:
'@verdaccio/types': ^9.7.2
'@verdaccio/types': 'workspace:*'
packages/core/types:
devDependencies:
'@types/node': 14.6.0
@ -366,8 +368,9 @@ importers:
dependencies:
'@verdaccio/commons-api': 'link:../core/commons-api'
'@verdaccio/logger': 'link:../logger'
debug: 4.2.0
handlebars: 4.5.3
lodash: 4.17.20
node-fetch: 2.6.1
request: 2.87.0
devDependencies:
'@verdaccio/auth': 'link:../auth'
@ -375,6 +378,7 @@ importers:
'@verdaccio/dev-commons': 'link:../commons'
'@verdaccio/types': 'link:../core/types'
'@verdaccio/utils': 'link:../utils'
nock: 13.0.4
specifiers:
'@verdaccio/auth': 'workspace:5.0.0-alpha.0'
'@verdaccio/commons-api': 'workspace:*'
@ -383,8 +387,10 @@ importers:
'@verdaccio/logger': 'workspace:5.0.0-alpha.0'
'@verdaccio/types': 'workspace:*'
'@verdaccio/utils': 'workspace:5.0.0-alpha.0'
debug: ^4.2.0
handlebars: 4.5.3
lodash: ^4.17.20
nock: ^13.0.4
node-fetch: ^2.6.1
request: 2.87.0
packages/loaders:
dependencies:
@ -5239,10 +5245,6 @@ packages:
npm: '>=5'
resolution:
integrity: sha512-SoCG1btVFPxOcrs8w9wLJCfe8nfE6EaEXCXyRwGbh+Sr3NLEG0R8JOugGJbuSE+zIRuUs5JaUKjzSec+JKLvZw==
/@verdaccio/types/9.7.2:
dev: true
resolution:
integrity: sha512-zv8sMrghtrzkxfo+IOojZYk/j4D5kmF/DMwXS9GnmObTM2nAOTsBzlOxHHBdHaiOK+cntw2YRYQJAebMG5J5sA==
/@verdaccio/ui-theme/0.3.13:
dev: true
engines:
@ -8406,6 +8408,19 @@ packages:
ms: 2.1.2
resolution:
integrity: sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
/debug/4.2.0:
dependencies:
ms: 2.1.2
dev: false
engines:
node: '>=6.0'
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
resolution:
integrity: sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
/decamelize-keys/1.1.0:
dependencies:
decamelize: 1.2.0
@ -14925,6 +14940,10 @@ packages:
dev: false
resolution:
integrity: sha1-XkKRsMdT+hq+sKq4+ynfG2bwf20=
/lodash.set/4.3.2:
dev: true
resolution:
integrity: sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=
/lodash.sortby/4.7.0:
resolution:
integrity: sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
@ -15875,6 +15894,17 @@ packages:
node: '>= 10.13'
resolution:
integrity: sha512-QNb/j8kbFnKCiyqi9C5DD0jH/FubFGj5rt9NQFONXwQm3IPB0CULECg/eS3AU1KgZb/6SwUa4/DTRKhVxkGABw==
/nock/13.0.4:
dependencies:
debug: 4.1.1
json-stringify-safe: 5.0.1
lodash.set: 4.3.2
propagate: 2.0.1
dev: true
engines:
node: '>= 10.13'
resolution:
integrity: sha512-alqTV8Qt7TUbc74x1pKRLSENzfjp4nywovcJgi/1aXDiUxXdt7TkruSTF5MDWPP7UoPVgea4F9ghVdmX0xxnSA==
/node-abi/2.19.1:
dependencies:
semver: 5.7.1
@ -15920,6 +15950,12 @@ packages:
node: 4.x || >=6.0.0
resolution:
integrity: sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
/node-fetch/2.6.1:
dev: false
engines:
node: 4.x || >=6.0.0
resolution:
integrity: sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
/node-forge/0.9.0:
engines:
node: '>= 4.5.0'