Files
habitica/test/api/v3/unit/libs/webhooks.test.js
Blade Barringer 35b92f13a3 Webhook improvements (#7879)
* refactor: Move translate test utility to helpers directory

* Add kind property to webhooks

* feat: Add options to create webhook route

* refactor: Move webhook ops into single file

* refactor: Create webhook objects for specific webhook behavior

* chore(tests): Add default sleep helper value of 1 second

* feat(api): Add method for groups to send out webhook

* feat(api): Add taskCreated webhook task creation

* feat(api): Send chat webhooks after a chat is sent

* refactor: Move webhook routes to own controller

* lint: Correct linting errors

* fix(api): Correct taskCreated webhook method

* fix(api): Fix webhook logging to only log when there is an error

* fix: Update groupChatRecieved webhook creation

* chore: Add integration tests for webhooks

* fix: Set webhook creation response to 201

* fix: Correct how task scored webhook data is sent

* Revert group chat recieved webhook to only support one group

* Remove quest activity option for webhooks

* feat: Send webhook for each task created

* feat: Allow webhooks without a type to default to taskScored

* feat: Add logic for adding ids to webhook

* feat: optimize webhook url check by shortcircuiting if no url is passed

* refactor: Use full name for webhook variable

* feat: Add missing params to client webhook

* lint: Add missing semicolon

* chore(tests): Fix inccorect webhook tests

* chore: Add migration to update task scored webhooks

* feat: Allow default value of webhook add route to be enabled

* chore: Update webhook documentation

* chore: Remove special handling for v2

* refactor: adjust addComputedStatsToJSONObject to work for webhooks

* refactor: combine taskScored and taskActivity webhooks

* feat(api): Add task activity to task update and delete routes

* chore: Change references to taskScored to taskActivity

* fix: Correct stats object being passed in for transform

* chore: Remove extra line break

* fix: Pass in the language to use for the translations

* refactor(api): Move webhooks from user.preferences.webhooks to user.webhooks

* chore: Update migration to set webhook array

* lint: Correct brace spacing

* chore: convert webhook lib to use user.webhooks

* refactor(api): Consolidate filters

* chore: clarify migration instructions

* fix(test): Correct user creation in user anonymized tests

* chore: add test that webhooks cannot be updated via PUT /user

* refactor: Simplify default webhook id value

* refactor(client): Push newly created webhook instead of doing a sync

* chore(test): Add test file for webhook model

* refactor: Remove webhook validation

* refactor: Remove need for watch on webhooks

* refactor(client): Update webhooks object without syncing

* chore: update webhook documentation

* Fix migrations issues

* chore: remove v2 test helper

* fix(api): Provide webhook type in task scored webhook

* fix(client): Fix webhook deletion appearing to delete all webhooks

* feat(api): add optional label field for webhooks

* feat: provide empty string as default for webhook label

* chore: Update webhook migration

* chore: update webhook migration name
2016-10-02 09:16:22 -05:00

377 lines
9.4 KiB
JavaScript

import request from 'request';
import {
WebhookSender,
taskScoredWebhook,
groupChatReceivedWebhook,
taskActivityWebhook,
} from '../../../../../website/server/libs/webhook';
describe('webhooks', () => {
let webhooks;
beforeEach(() => {
sandbox.stub(request, 'post');
webhooks = [{
id: 'taskActivity',
url: 'http://task-scored.com',
enabled: true,
type: 'taskActivity',
options: {
created: true,
updated: true,
deleted: true,
scored: true,
},
}, {
id: 'groupChatReceived',
url: 'http://group-chat-received.com',
enabled: true,
type: 'groupChatReceived',
options: {
groupId: 'group-id',
},
}];
});
afterEach(() => {
sandbox.restore();
});
describe('WebhookSender', () => {
it('creates a new WebhookSender object', () => {
let sendWebhook = new WebhookSender({
type: 'custom',
});
expect(sendWebhook.type).to.equal('custom');
expect(sendWebhook).to.respondTo('send');
});
it('provides default function for data transformation', () => {
sandbox.spy(WebhookSender, 'defaultTransformData');
let sendWebhook = new WebhookSender({
type: 'custom',
});
let body = { foo: 'bar' };
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
expect(WebhookSender.defaultTransformData).to.be.calledOnce;
expect(request.post).to.be.calledOnce;
expect(request.post).to.be.calledWithMatch({
body,
});
});
it('can pass in a data transformation function', () => {
sandbox.spy(WebhookSender, 'defaultTransformData');
let sendWebhook = new WebhookSender({
type: 'custom',
transformData (data) {
let dataToSend = Object.assign({baz: 'biz'}, data);
return dataToSend;
},
});
let body = { foo: 'bar' };
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
expect(WebhookSender.defaultTransformData).to.not.be.called;
expect(request.post).to.be.calledOnce;
expect(request.post).to.be.calledWithMatch({
body: {
foo: 'bar',
baz: 'biz',
},
});
});
it('provieds a default filter function', () => {
sandbox.spy(WebhookSender, 'defaultWebhookFilter');
let sendWebhook = new WebhookSender({
type: 'custom',
});
let body = { foo: 'bar' };
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
expect(WebhookSender.defaultWebhookFilter).to.be.calledOnce;
});
it('can pass in a webhook filter function', () => {
sandbox.spy(WebhookSender, 'defaultWebhookFilter');
let sendWebhook = new WebhookSender({
type: 'custom',
webhookFilter (hook) {
return hook.url !== 'http://custom-url.com';
},
});
let body = { foo: 'bar' };
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
expect(WebhookSender.defaultWebhookFilter).to.not.be.called;
expect(request.post).to.not.be.called;
});
it('can pass in a webhook filter function that filters on data', () => {
sandbox.spy(WebhookSender, 'defaultWebhookFilter');
let sendWebhook = new WebhookSender({
type: 'custom',
webhookFilter (hook, data) {
return hook.options.foo === data.foo;
},
});
let body = { foo: 'bar' };
sendWebhook.send([
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom', options: { foo: 'bar' }},
{ id: 'other-custom-webhook', url: 'http://other-custom-url.com', enabled: true, type: 'custom', options: { foo: 'foo' }},
], body);
expect(request.post).to.be.calledOnce;
expect(request.post).to.be.calledWithMatch({
url: 'http://custom-url.com',
});
});
it('ignores disabled webhooks', () => {
let sendWebhook = new WebhookSender({
type: 'custom',
});
let body = { foo: 'bar' };
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: false, type: 'custom'}], body);
expect(request.post).to.not.be.called;
});
it('ignores webhooks with invalid urls', () => {
let sendWebhook = new WebhookSender({
type: 'custom',
});
let body = { foo: 'bar' };
sendWebhook.send([{id: 'custom-webhook', url: 'httxp://custom-url!!', enabled: true, type: 'custom'}], body);
expect(request.post).to.not.be.called;
});
it('ignores webhooks of other types', () => {
let sendWebhook = new WebhookSender({
type: 'custom',
});
let body = { foo: 'bar' };
sendWebhook.send([
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'},
{ id: 'other-webhook', url: 'http://other-url.com', enabled: true, type: 'other'},
], body);
expect(request.post).to.be.calledOnce;
expect(request.post).to.be.calledWithMatch({
url: 'http://custom-url.com',
body,
json: true,
});
});
it('sends multiple webhooks of the same type', () => {
let sendWebhook = new WebhookSender({
type: 'custom',
});
let body = { foo: 'bar' };
sendWebhook.send([
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'},
{ id: 'other-custom-webhook', url: 'http://other-url.com', enabled: true, type: 'custom'},
], body);
expect(request.post).to.be.calledTwice;
expect(request.post).to.be.calledWithMatch({
url: 'http://custom-url.com',
body,
json: true,
});
expect(request.post).to.be.calledWithMatch({
url: 'http://other-url.com',
body,
json: true,
});
});
});
describe('taskScoredWebhook', () => {
let data;
beforeEach(() => {
data = {
user: {
_id: 'user-id',
_tmp: {foo: 'bar'},
stats: {
lvl: 5,
int: 10,
str: 5,
exp: 423,
toJSON () {
return this;
},
},
addComputedStatsToJSONObj () {
let mockStats = Object.assign({
maxHealth: 50,
maxMP: 103,
toNextLevel: 40,
}, this.stats);
delete mockStats.toJSON;
return mockStats;
},
},
task: {
text: 'text',
},
direction: 'up',
delta: 176,
};
});
it('sends task and stats data', () => {
taskScoredWebhook.send(webhooks, data);
expect(request.post).to.be.calledOnce;
expect(request.post).to.be.calledWithMatch({
body: {
type: 'scored',
user: {
_id: 'user-id',
_tmp: {foo: 'bar'},
stats: {
lvl: 5,
int: 10,
str: 5,
exp: 423,
toNextLevel: 40,
maxHealth: 50,
maxMP: 103,
},
},
task: {
text: 'text',
},
direction: 'up',
delta: 176,
},
});
});
it('does not send task scored data if scored option is not true', () => {
webhooks[0].options.scored = false;
taskScoredWebhook.send(webhooks, data);
expect(request.post).to.not.be.called;
});
});
describe('taskActivityWebhook', () => {
let data;
beforeEach(() => {
data = {
task: {
text: 'text',
},
};
});
['created', 'updated', 'deleted'].forEach((type) => {
it(`sends ${type} tasks`, () => {
data.type = type;
taskActivityWebhook.send(webhooks, data);
expect(request.post).to.be.calledOnce;
expect(request.post).to.be.calledWithMatch({
body: {
type,
task: data.task,
},
});
});
it(`does not send task ${type} data if ${type} option is not true`, () => {
data.type = type;
webhooks[0].options[type] = false;
taskActivityWebhook.send(webhooks, data);
expect(request.post).to.not.be.called;
});
});
});
describe('groupChatReceivedWebhook', () => {
it('sends chat data', () => {
let data = {
group: {
id: 'group-id',
name: 'some group',
otherData: 'foo',
},
chat: {
id: 'some-id',
text: 'message',
},
};
groupChatReceivedWebhook.send(webhooks, data);
expect(request.post).to.be.calledOnce;
expect(request.post).to.be.calledWithMatch({
body: {
group: {
id: 'group-id',
name: 'some group',
},
chat: {
id: 'some-id',
text: 'message',
},
},
});
});
it('does not send chat data for group if not selected', () => {
let data = {
group: {
id: 'not-group-id',
name: 'some group',
otherData: 'foo',
},
chat: {
id: 'some-id',
text: 'message',
},
};
groupChatReceivedWebhook.send(webhooks, data);
expect(request.post).to.not.be.called;
});
});
});