Files
habitica/test/api/v3/integration/webhook/PUT-user_update_webhook.test.js
Robert Whitaker af1d13d3a2 Fix bug where updated webhook options failed to save (fixes #12336) (#12342)
* Fix bug where updated webhook options failed to save

This bug was caused by Mongoose not creating getters/setters for array
elements (https://mongoosejs.com/docs/faq.html#array-changes-not-saved).
So, although the webhook was being updated properly, Mongoose was not
actually committing it to the database. Telling Mongoose that the array
of webhooks has changed via `markModified` fixes the issue.

Additionally, the relevant API test case was only checking whether or
not the webhook returned from the PUT endpoint matched the expected
update. Since the endpoint was returning the updated webhook without
querying the database again, this test case would pass. It has been
updated to check both the returned webhook as well as the version of the
webhook that is saved to the database against the expected. In other
words:

`assert returned === saved === expected`

Fixes #12336

* Call markModified on webhook.options instead of user.webhooks

This tells Mongoose that only the modified webhook's options changed
instead of telling it that the entire user.webhooks array changed,
saving a costly DB update.
2020-07-03 16:48:45 +02:00

180 lines
5.1 KiB
JavaScript

import { v4 as generateUUID } from 'uuid';
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import apiError from '../../../../../website/server/libs/apiError';
describe('PUT /user/webhook/:id', () => {
let user; let
webhookToUpdate;
beforeEach(async () => {
user = await generateUser();
webhookToUpdate = await user.post('/user/webhook', {
url: 'http://some-url.com',
label: 'Original Label',
enabled: true,
type: 'taskActivity',
options: { created: true, scored: true },
});
await user.post('/user/webhook', {
url: 'http://some-other-url.com',
enabled: false,
});
await user.sync();
});
it('returns an error if webhook with id does not exist', async () => {
await expect(user.put('/user/webhook/id-that-does-not-exist')).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('noWebhookWithId', { id: 'id-that-does-not-exist' }),
});
});
it('returns an error if validation fails', async () => {
await expect(user.put(`/user/webhook/${webhookToUpdate.id}`, { url: 'foo_invalid', enabled: true })).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'User validation failed',
});
});
it('updates a webhook', async () => {
const url = 'http://a-new-url.com';
const type = 'groupChatReceived';
const label = 'New Label';
const options = { groupId: generateUUID() };
await user.put(`/user/webhook/${webhookToUpdate.id}`, {
url, type, options, label,
});
await user.sync();
const webhook = user.webhooks.find(hook => webhookToUpdate.id === hook.id);
expect(webhook.url).to.equal(url);
expect(webhook.label).to.equal(label);
expect(webhook.type).to.equal(type);
expect(webhook.options).to.eql(options);
});
it('ignores protected fields', async () => {
const failures = 3;
const lastFailureAt = new Date();
await user.put(`/user/webhook/${webhookToUpdate.id}`, {
failures, lastFailureAt,
});
await user.sync();
const webhook = user.webhooks.find(hook => webhookToUpdate.id === hook.id);
expect(webhook.failures).to.eql(0);
expect(webhook.lastFailureAt).to.eql(undefined);
});
it('updates a webhook with empty label', async () => {
const url = 'http://a-new-url.com';
const type = 'groupChatReceived';
const label = '';
const options = { groupId: generateUUID() };
await user.put(`/user/webhook/${webhookToUpdate.id}`, {
url, type, options, label,
});
await user.sync();
const webhook = user.webhooks.find(hook => webhookToUpdate.id === hook.id);
expect(webhook.url).to.equal(url);
expect(webhook.label).to.equal(label);
expect(webhook.type).to.equal(type);
expect(webhook.options).to.eql(options);
});
it('returns the updated webhook', async () => {
const url = 'http://a-new-url.com';
const type = 'groupChatReceived';
const options = { groupId: generateUUID() };
const response = await user.put(`/user/webhook/${webhookToUpdate.id}`, { url, type, options });
expect(response.url).to.eql(url);
expect(response.type).to.eql(type);
expect(response.options).to.eql(options);
});
it('cannot update the id', async () => {
const id = generateUUID();
const url = 'http://a-new-url.com';
await user.put(`/user/webhook/${webhookToUpdate.id}`, { url, id });
await user.sync();
const webhook = user.webhooks.find(hook => webhookToUpdate.id === hook.id);
expect(webhook.id).to.eql(webhookToUpdate.id);
expect(webhook.url).to.eql(url);
});
it('can update taskActivity options', async () => {
const type = 'taskActivity';
const options = {
checklistScored: true,
updated: false,
scored: false,
};
const expected = {
checklistScored: true,
created: true, // starting value
updated: false,
deleted: false, // starting value
scored: false,
};
const returnedWebhook = await user.put(`/user/webhook/${webhookToUpdate.id}`, { type, options });
await user.sync();
const savedWebhook = user.webhooks.find(hook => webhookToUpdate.id === hook.id);
expect(returnedWebhook.options).to.eql(expected);
expect(savedWebhook.options).to.eql(expected);
});
it('errors if taskActivity option is not a boolean', async () => {
const type = 'taskActivity';
const options = {
created: 'not a boolean',
updated: false,
deleted: true,
};
await expect(user.put(`/user/webhook/${webhookToUpdate.id}`, { type, options })).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('webhookBooleanOption', { option: 'created' }),
});
});
it('errors if groupChatRecieved groupId option is not a uuid', async () => {
const type = 'groupChatReceived';
const options = {
groupId: 'not-a-uuid',
};
await expect(user.put(`/user/webhook/${webhookToUpdate.id}`, { type, options })).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: apiError('groupIdRequired'),
});
});
});