Skip to content

Commit

Permalink
feat(webhooks): added a test webhook endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
syncush committed Oct 30, 2020
1 parent 74d0ddd commit ada5370
Show file tree
Hide file tree
Showing 12 changed files with 662 additions and 424 deletions.
43 changes: 43 additions & 0 deletions docs/devguide/docs/swagger-docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1613,6 +1613,41 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/error_response'
/v1/webhooks/{webhook_id}/test:
get:
operationId: test-a-webhook
tags:
- Webhooks
summary: Tests that the webhook is configure correctly
description: Tests that the webhook is configure correctly by sending a message to the target webhook url.
parameters:
- in: path
name: webhook_id
description: The webhook id.
required: true
schema:
type: string
format: uuid
example: 4bf5d7ab-f310-4a64-8ec2-d65c06188ec1
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/test_webhook_response'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/error_response'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/error_response'

# Files
/v1/files:
Expand Down Expand Up @@ -2734,3 +2769,11 @@ components:
- slack
- json
- teams
test_webhook_response:
type: object
required:
- webhook_status_code
properties:
webhook_status_code:
type: number
example: 400
43 changes: 43 additions & 0 deletions docs/openapi3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1704,6 +1704,41 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/error_response'
/v1/webhooks/{webhook_id}/test:
get:
operationId: test-a-webhook
tags:
- Webhooks
summary: Tests that the webhook is configure correctly
description: Tests that the webhook is configure correctly by sending a message to the target webhook url.
parameters:
- in: path
name: webhook_id
description: The webhook id.
required: true
schema:
type: string
format: uuid
example: 4bf5d7ab-f310-4a64-8ec2-d65c06188ec1
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/test_webhook_response'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/error_response'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/error_response'

# Files
/v1/files:
Expand Down Expand Up @@ -2783,3 +2818,11 @@ components:
- slack
- json
- teams
test_webhook_response:
type: object
required:
- webhook_status_code
properties:
webhook_status_code:
type: number
example: 400
1 change: 1 addition & 0 deletions src/common/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ module.exports = {
KUBERNETES: 'KUBERNETES',
METRONOME: 'METRONOME',
DOCKER: 'DOCKER',
WEBHOOK_TEST_MESSAGE: 'Hello From Predator! Wuff! Wuff!',
CONFIG: {
GRFANA_URL: 'grafana_url',
DELAY_RUNNER_MS: 'delay_runner_ms',
Expand Down
2 changes: 1 addition & 1 deletion src/common/requestSender.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ module.exports.send = async (options) => {
logger.error({ method: options.method, url: options.url, error }, 'Error occurred sending request');
throw error;
}
};
};
13 changes: 12 additions & 1 deletion src/webhooks/controllers/webhooksController.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,15 @@ module.exports.updateWebhook = async function (req, res, next) {
} catch (err) {
return next(err);
}
};
};

module.exports.testWebhook = async function(req, res, next) {
const { params: { webhook_id: webhookId } } = req;
try {
const webhookStatusCode = await webhookManager.testWebhook(webhookId);
return res.status(200).json({ webhook_status_code: webhookStatusCode });
}
catch (err) {
return next(err);
}
};
23 changes: 21 additions & 2 deletions src/webhooks/models/webhookManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ async function getAllGlobalWebhooks() {

async function fireSingleWebhook(webhook, payload) {
try {
await requestSender.send({
const response = await requestSender.send({
method: 'POST',
url: webhook.url,
body: payload
body: payload,
resolveWithFullResponse: true
});
logger.info(`Webhook fired successfully, url = ${webhook.url}`);
return response;
} catch (requestError) {
logger.error(`Webhook failed, url = ${webhook.url}`);
throw requestError;
Expand All @@ -81,11 +83,28 @@ async function fireWebhookByEvent(job, eventType, report, additionalInfo = {}, o
await Promise.allSettled(webhooksPromises);
}

async function testWebhook(webhookId) {
const webhook = await databaseConnector.getWebhook(webhookId);
if (!webhook) {
throw generateError(404, ERROR_MESSAGES.NOT_FOUND);
}
const payload = webhooksFormatter.formatSimpleMessage(webhook.format_type);
let webhookStatusCode = null;
try {
const response = await fireSingleWebhook(webhook, payload);
webhookStatusCode = response.statusCode;
} catch (requestError) {
webhookStatusCode = requestError.statusCode;
}
return webhookStatusCode;
}

module.exports = {
getAllWebhooks,
getWebhook,
createWebhook,
deleteWebhook,
updateWebhook,
testWebhook,
fireWebhookByEvent
};
25 changes: 22 additions & 3 deletions src/webhooks/models/webhooksFormatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const {
WEBHOOK_SLACK_DEFAULT_MESSAGE_ICON,
WEBHOOK_SLACK_DEFAULT_REPORTER_NAME,
WEBHOOK_EVENT_TYPE_IN_PROGRESS,
WEBHOOK_TEAMS_DEFAULT_THEME_COLOR
WEBHOOK_TEAMS_DEFAULT_THEME_COLOR,
WEBHOOK_TEST_MESSAGE
} = require('../../common/consts');
const statsFormatter = require('./statsFormatter');

Expand All @@ -38,7 +39,7 @@ function getThresholdMessage(state, { isSlack, testName, benchmarkThreshold, las
`.*\n${statsFormatter.getStatsFormatted('aggregate', aggregatedReport, { score })}\n`;
}

function slackWebhookFormat(message, options) {
function slackWebhookFormat(message, options = {}) {
return {
text: message,
icon_emoji: options.icon || WEBHOOK_SLACK_DEFAULT_MESSAGE_ICON,
Expand Down Expand Up @@ -208,4 +209,22 @@ module.exports.format = function(format, eventType, jobId, testId, report, addit
throw new Error(`Unrecognized webhook format: ${format}, available options: ${EVENT_FORMAT_TYPES.join()}`);
}
}
};
};

module.exports.formatSimpleMessage = function(format) {
const simpleMessage = WEBHOOK_TEST_MESSAGE;
switch (format) {
case EVENT_FORMAT_TYPE_SLACK: {
return slackWebhookFormat(simpleMessage);
}
case EVENT_FORMAT_TYPE_JSON: {
return { greeting: simpleMessage };
}
case EVENT_FORMAT_TYPE_TEAMS: {
return teamsWebhookFormat(simpleMessage);
}
default: {
throw new Error(`Unrecognized webhook format: ${format}, available options: ${EVENT_FORMAT_TYPES.join()}`);
}
}
};
1 change: 1 addition & 0 deletions src/webhooks/routes/webhooksRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const webhooksController = require('../controllers/webhooksController');
router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks);
router.post('/', swaggerValidator.validate, webhooksController.createWebhook);
router.get('/:webhook_id', swaggerValidator.validate, webhooksController.getWebhook);
router.get('/:webhook_id/test', swaggerValidator.validate, webhooksController.testWebhook);
router.delete('/:webhook_id', swaggerValidator.validate, webhooksController.deleteWebhook);
router.put('/:webhook_id', swaggerValidator.validate, webhooksController.updateWebhook);

Expand Down
9 changes: 8 additions & 1 deletion tests/integration-tests/webhooks/helpers/requestCreator.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = {
getWebhooks,
getWebhook,
deleteWebhook,
testWebhook,
updateWebhook
};

Expand Down Expand Up @@ -68,4 +69,10 @@ function updateWebhook(webhookId, webhook) {
.expect(function (res) {
return res;
});
}
}

function testWebhook(webhookId) {
return request(app)
.get(`${resourceUri}/${webhookId}/test`)
.set(headers);
}
63 changes: 62 additions & 1 deletion tests/integration-tests/webhooks/webhooks-test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const { expect } = require('chai');
const uuid = require('uuid');
const nock = require('nock');

const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPES, WEBHOOK_EVENT_TYPE_API_FAILURE, WEBHOOK_EVENT_TYPE_FAILED } = require('../../../src/common/consts');
const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPES, WEBHOOK_EVENT_TYPE_API_FAILURE, WEBHOOK_EVENT_TYPE_FAILED, ERROR_MESSAGES: { NOT_FOUND } } = require('../../../src/common/consts');

const webhookRequestSender = require('./helpers/requestCreator');

Expand All @@ -12,6 +13,47 @@ describe('Webhooks api', function () {
});

describe('Good requests', async function () {
describe('GET /v1/webhooks/:webhook_id/test', function() {
it('should return 200 and webhook_status_code=200', async function() {
const webhook = generateWebhook();
nock(webhook.url).post('').reply(200);

const createWebhookResponse = await webhookRequestSender.createWebhook(webhook);
expect(createWebhookResponse.status).to.be.equal(201);

const webhookId = createWebhookResponse.body.id;

const testWebhookResponse = await webhookRequestSender.testWebhook(webhookId);
expect(testWebhookResponse.status).to.be.equal(200);
expect(testWebhookResponse.body).to.have.a.property('webhook_status_code').and.to.be.equal(200);
});
it('should return 400 and webhook_status_code=400', async function () {
const webhook = generateWebhook();
nock(webhook.url).post('').reply(400);

const createWebhookResponse = await webhookRequestSender.createWebhook(webhook);
expect(createWebhookResponse.status).to.be.equal(201);

const webhookId = createWebhookResponse.body.id;

const testWebhookResponse = await webhookRequestSender.testWebhook(webhookId);
expect(testWebhookResponse.status).to.be.equal(200);
expect(testWebhookResponse.body).to.have.a.property('webhook_status_code').and.to.be.equal(400);
});
it('should return 200 and webhook_status_code=422', async function () {
const webhook = generateWebhook();
nock(webhook.url).post('').reply(422);

const createWebhookResponse = await webhookRequestSender.createWebhook(webhook);
expect(createWebhookResponse.status).to.be.equal(201);

const webhookId = createWebhookResponse.body.id;

const testWebhookResponse = await webhookRequestSender.testWebhook(webhookId);
expect(testWebhookResponse.status).to.be.equal(200);
expect(testWebhookResponse.body).to.have.a.property('webhook_status_code').and.to.be.equal(422);
});
});
describe('GET /v1/webhooks', async function () {
before('clean webhooks', async function() {
const webhooksResponse = await webhookRequestSender.getWebhooks();
Expand Down Expand Up @@ -183,6 +225,18 @@ describe('Webhooks api', function () {
});

describe('Bad requests', function () {
describe('GET /v1/webhooks/:webhook_id/test', function() {
it('should get 400 for bad webhook_id format', async function() {
const testWebhookResponse = await webhookRequestSender.testWebhook('bad_id_format');
expect(testWebhookResponse.statusCode).to.equal(400);
expect(testWebhookResponse.body).to.be.deep.equal({
message: 'Input validation error',
validation_errors: [
'path/webhook_id should match format "uuid"'
]
});
});
});
describe('POST /v1/webhooks', function () {
describe('name validation', function() {
it('Create webhook with bad type of name', async function () {
Expand Down Expand Up @@ -271,6 +325,13 @@ describe('Webhooks api', function () {
});

describe('Sad requests', function() {
describe('GET /v1/webhooks/:webhook_id/test', function () {
it('should return 404 for unexist webhook', async function() {
const testWebhookResponse = await webhookRequestSender.testWebhook(uuid.v4());
expect(testWebhookResponse.status).to.be.equal(404);
expect(testWebhookResponse.body).to.have.a.property('message').and.to.be.equal(NOT_FOUND);
});
});
describe('GET /v1/webhooks/:webhook_id', function () {
it('should return 404 for no existing webhook', async function() {
const notExistingWebhookId = uuid.v4();
Expand Down
Loading

0 comments on commit ada5370

Please sign in to comment.