mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Added basic framework for webhook e2e tests
refs https://github.com/TryGhost/Toolbox/issues/320 - This is an **MVP** to be able to intercept and match webhook request snapshots. The concept is similar to the one used in API E2E tests using same "matchBodySnapshot" and other "match*" methods to test the webhook **request** data - Next up here would be: 1. Header matcher 2. Mocking more than one webhook (and doing something nicer with the way the fixture data is inserted, does this logic belong to the mock-receiver?
This commit is contained in:
parent
6a3f61f62a
commit
0f4aeaaa80
4 changed files with 268 additions and 0 deletions
113
test/e2e-webhooks/__snapshots__/posts.test.js.snap
Normal file
113
test/e2e-webhooks/__snapshots__/posts.test.js.snap
Normal file
|
@ -0,0 +1,113 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`post.* events post.published even is triggered 1: [body] 1`] = `
|
||||
Object {
|
||||
"post": Object {
|
||||
"current": Object {
|
||||
"canonical_url": null,
|
||||
"codeinjection_foot": null,
|
||||
"codeinjection_head": null,
|
||||
"comment_id": "62905373e751ff5d4a98db0f",
|
||||
"created_at": "2022-05-27T04:28:35.000Z",
|
||||
"custom_excerpt": null,
|
||||
"custom_template": null,
|
||||
"email_only": false,
|
||||
"email_segment": "all",
|
||||
"email_subject": null,
|
||||
"excerpt": null,
|
||||
"feature_image": null,
|
||||
"feature_image_alt": null,
|
||||
"feature_image_caption": null,
|
||||
"featured": false,
|
||||
"frontmatter": null,
|
||||
"html": null,
|
||||
"id": "62905373e751ff5d4a98db0f",
|
||||
"meta_description": null,
|
||||
"meta_title": null,
|
||||
"mobiledoc": "{\\"version\\":\\"0.3.1\\",\\"ghostVersion\\":\\"4.0\\",\\"markups\\":[],\\"atoms\\":[],\\"cards\\":[],\\"sections\\":[[1,\\"p\\",[[0,[],0,\\"\\"]]]]}",
|
||||
"og_description": null,
|
||||
"og_image": null,
|
||||
"og_title": null,
|
||||
"plaintext": null,
|
||||
"primary_tag": null,
|
||||
"published_at": "2022-05-27T04:28:40.000Z",
|
||||
"slug": "webhookz",
|
||||
"status": "published",
|
||||
"tags": Array [],
|
||||
"tiers": Array [
|
||||
Object {
|
||||
"active": true,
|
||||
"created_at": "2022-05-27T04:28:30.000Z",
|
||||
"description": null,
|
||||
"id": "6290536ee751ff5d4a98d92a",
|
||||
"monthly_price_id": null,
|
||||
"name": "Default Product",
|
||||
"slug": "default-product",
|
||||
"type": "paid",
|
||||
"updated_at": "2022-05-27T04:28:30.000Z",
|
||||
"visibility": "public",
|
||||
"welcome_page_url": null,
|
||||
"yearly_price_id": null,
|
||||
},
|
||||
Object {
|
||||
"active": true,
|
||||
"created_at": "2022-05-27T04:28:30.000Z",
|
||||
"description": null,
|
||||
"id": "6290536ee751ff5d4a98d92b",
|
||||
"monthly_price_id": null,
|
||||
"name": "Free",
|
||||
"slug": "free",
|
||||
"type": "free",
|
||||
"updated_at": "2022-05-27T04:28:30.000Z",
|
||||
"visibility": "public",
|
||||
"welcome_page_url": null,
|
||||
"yearly_price_id": null,
|
||||
},
|
||||
],
|
||||
"title": "webhookz",
|
||||
"twitter_description": null,
|
||||
"twitter_image": null,
|
||||
"twitter_title": null,
|
||||
"updated_at": "2022-05-27T04:28:40.000Z",
|
||||
"url": "http://127.0.0.1:2369/404/",
|
||||
"uuid": "e3e0900e-2d00-463f-b83f-c8fa44bea3ae",
|
||||
"visibility": "public",
|
||||
},
|
||||
"previous": Object {
|
||||
"published_at": null,
|
||||
"status": "draft",
|
||||
"tiers": Array [
|
||||
Object {
|
||||
"active": true,
|
||||
"created_at": "2022-05-27T04:28:30.000Z",
|
||||
"description": null,
|
||||
"id": "6290536ee751ff5d4a98d92a",
|
||||
"monthly_price_id": null,
|
||||
"name": "Default Product",
|
||||
"slug": "default-product",
|
||||
"type": "paid",
|
||||
"updated_at": "2022-05-27T04:28:30.000Z",
|
||||
"visibility": "public",
|
||||
"welcome_page_url": null,
|
||||
"yearly_price_id": null,
|
||||
},
|
||||
Object {
|
||||
"active": true,
|
||||
"created_at": "2022-05-27T04:28:30.000Z",
|
||||
"description": null,
|
||||
"id": "6290536ee751ff5d4a98d92b",
|
||||
"monthly_price_id": null,
|
||||
"name": "Free",
|
||||
"slug": "free",
|
||||
"type": "free",
|
||||
"updated_at": "2022-05-27T04:28:30.000Z",
|
||||
"visibility": "public",
|
||||
"welcome_page_url": null,
|
||||
"yearly_price_id": null,
|
||||
},
|
||||
],
|
||||
"updated_at": "2022-05-27T04:28:35.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
52
test/e2e-webhooks/posts.test.js
Normal file
52
test/e2e-webhooks/posts.test.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
const {agentProvider, mockManager, fixtureManager} = require('../utils/e2e-framework');
|
||||
|
||||
describe('post.* events', function () {
|
||||
let adminAPIAgent;
|
||||
let webhookMockReceiver;
|
||||
|
||||
before(async function () {
|
||||
adminAPIAgent = await agentProvider.getAdminAPIAgent();
|
||||
await fixtureManager.init('integrations');
|
||||
await adminAPIAgent.loginAsOwner();
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
webhookMockReceiver = mockManager.mockWebhookRequests();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
mockManager.restore();
|
||||
});
|
||||
|
||||
it('post.published even is triggered', async function () {
|
||||
await webhookMockReceiver.mock('post.published');
|
||||
await fixtureManager.insertWebhook({
|
||||
event: 'post.published',
|
||||
url: 'https://test-webhook-receiver.com/webhook'
|
||||
});
|
||||
|
||||
const res = await adminAPIAgent
|
||||
.post('posts/')
|
||||
.body({
|
||||
posts: [{
|
||||
title: 'webhookz',
|
||||
status: 'draft'
|
||||
}]
|
||||
})
|
||||
.expectStatus(201);
|
||||
|
||||
const id = res.body.posts[0].id;
|
||||
const updatedPost = res.body.posts[0];
|
||||
updatedPost.status = 'published';
|
||||
|
||||
await adminAPIAgent
|
||||
.put('posts/' + id)
|
||||
.body({
|
||||
posts: [updatedPost]
|
||||
})
|
||||
.expectStatus(200);
|
||||
|
||||
await webhookMockReceiver
|
||||
.matchBodySnapshot();
|
||||
});
|
||||
});
|
|
@ -5,6 +5,7 @@ const nock = require('nock');
|
|||
|
||||
// Helper services
|
||||
const configUtils = require('./configUtils');
|
||||
const WebhookMockReceiver = require('./webhook-mock-receiver');
|
||||
|
||||
let mocks = {};
|
||||
let emailCount = 0;
|
||||
|
@ -48,6 +49,12 @@ const mockMail = (response = 'Mail is disabled') => {
|
|||
return mocks.mail;
|
||||
};
|
||||
|
||||
const mockWebhookRequests = () => {
|
||||
mocks.webhookMockReceiver = new WebhookMockReceiver();
|
||||
|
||||
return mocks.webhookMockReceiver;
|
||||
};
|
||||
|
||||
const sentEmailCount = (count) => {
|
||||
if (!mocks.mail) {
|
||||
throw new errors.IncorrectUsageError({
|
||||
|
@ -139,6 +146,10 @@ const restore = () => {
|
|||
emailCount = 0;
|
||||
nock.cleanAll();
|
||||
nock.enableNetConnect();
|
||||
|
||||
if (mocks.webhookMockReceiver) {
|
||||
mocks.webhookMockReceiver.reset();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
@ -148,6 +159,7 @@ module.exports = {
|
|||
mockStripe,
|
||||
mockLabsEnabled,
|
||||
mockLabsDisabled,
|
||||
mockWebhookRequests,
|
||||
restore,
|
||||
assert: {
|
||||
sentEmailCount,
|
||||
|
|
91
test/utils/webhook-mock-receiver.js
Normal file
91
test/utils/webhook-mock-receiver.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
const nock = require('nock');
|
||||
const assert = require('assert');
|
||||
const {snapshotManager} = require('@tryghost/jest-snapshot');
|
||||
|
||||
// NOTE: this is a shameless copy-pasta from express-test utils.
|
||||
// needs to get refactored into reusable package, just like this whole module
|
||||
const makeMessageFromMatchMessage = (message, errorMessage) => {
|
||||
const messageLines = message.split('\n');
|
||||
messageLines.splice(0, 1, errorMessage);
|
||||
return messageLines.join('\n');
|
||||
};
|
||||
|
||||
class WebhookMockReceiver {
|
||||
constructor() {
|
||||
this.bodyResponse;
|
||||
this.receiver;
|
||||
this.recordBodyResponse = this.recordBodyResponse.bind(this);
|
||||
}
|
||||
|
||||
recordBodyResponse(body) {
|
||||
this.bodyResponse = {body};
|
||||
|
||||
// let the nock continue with the response
|
||||
return true;
|
||||
}
|
||||
|
||||
mock() {
|
||||
this.receiver = nock('https://test-webhook-receiver.com')
|
||||
.post('/webhook', this.recordBodyResponse)
|
||||
.reply(200, {status: 'OK'});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
reset() {
|
||||
nock.restore();
|
||||
this.bodyResponse = undefined;
|
||||
}
|
||||
|
||||
|
||||
_assertSnapshot(response, assertion) {
|
||||
const {properties, field, error} = assertion;
|
||||
|
||||
if (!response[field]) {
|
||||
error.message = `Unable to match snapshot on undefined field ${field} ${error.contextString}`;
|
||||
error.expected = field;
|
||||
error.actual = 'undefined';
|
||||
assert.notEqual(response[field], undefined, error);
|
||||
}
|
||||
|
||||
const hint = `[${field}]`;
|
||||
const match = snapshotManager.match(response[field], properties, hint);
|
||||
|
||||
Object.keys(properties).forEach((prop) => {
|
||||
const errorMessage = `"response.${field}" is missing the expected property "${prop}"`;
|
||||
error.message = makeMessageFromMatchMessage(match.message(), errorMessage);
|
||||
error.expected = prop;
|
||||
error.actual = 'undefined';
|
||||
error.showDiff = false; // Disable mocha's diff output as it's already present in match.message()
|
||||
|
||||
assert.notEqual(response[field][prop], undefined, error);
|
||||
});
|
||||
|
||||
if (match.pass !== true) {
|
||||
const errorMessage = `"response.${field}" does not match snapshot.`;
|
||||
error.message = makeMessageFromMatchMessage(match.message(), errorMessage);
|
||||
error.expected = match.expected;
|
||||
error.actual = match.actual;
|
||||
error.showDiff = false; // Disable mocha's diff output as it's already present in match.message()
|
||||
}
|
||||
|
||||
assert.equal(match.pass, true, error);
|
||||
}
|
||||
|
||||
async matchBodySnapshot(properties = {}) {
|
||||
while (!this.receiver.isDone()) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
}
|
||||
|
||||
let assertion = {
|
||||
fn: this._assertSnapshot,
|
||||
properties: properties,
|
||||
field: 'body',
|
||||
type: 'body'
|
||||
};
|
||||
|
||||
this._assertSnapshot(this.bodyResponse, assertion);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebhookMockReceiver;
|
Loading…
Add table
Reference in a new issue