From d3ea145c1980a8f7f5a81b30d1e44a9ba6158676 Mon Sep 17 00:00:00 2001 From: Hannah Wolfe Date: Fri, 6 May 2022 12:17:32 +0100 Subject: [PATCH] Refactored webhook tests to use e2e framework - webhooks are one of the remaining places where we need some sort of api version handling - in order to fixup the tests for this, I wanted to first change them to use the e2e framework --- .../admin/__snapshots__/webhooks.test.js.snap | 137 +++++++++++ test/e2e-api/admin/webhooks.test.js | 212 +++++++----------- 2 files changed, 218 insertions(+), 131 deletions(-) create mode 100644 test/e2e-api/admin/__snapshots__/webhooks.test.js.snap diff --git a/test/e2e-api/admin/__snapshots__/webhooks.test.js.snap b/test/e2e-api/admin/__snapshots__/webhooks.test.js.snap new file mode 100644 index 0000000000..c7ec2bfd7c --- /dev/null +++ b/test/e2e-api/admin/__snapshots__/webhooks.test.js.snap @@ -0,0 +1,137 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Webhooks API Can create a webhook 1: [body] 1`] = ` +Object { + "webhooks": Array [ + Object { + "api_version": "canary", + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/, + "event": "test.create", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "integration_id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "last_triggered_at": null, + "last_triggered_error": null, + "last_triggered_status": null, + "name": "test", + "secret": "thisissecret", + "status": "available", + "target_url": "http://example.com/webhooks/test/extra/1", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/, + }, + ], +} +`; + +exports[`Webhooks API Can create a webhook 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "414", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Webhooks API Can delete a webhook 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin", + "x-powered-by": "Express", +} +`; + +exports[`Webhooks API Can edit a webhook 1: [body] 1`] = ` +Object { + "webhooks": Array [ + Object { + "api_version": "canary", + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/, + "event": "subscriber.added", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "integration_id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "last_triggered_at": null, + "last_triggered_error": null, + "last_triggered_status": null, + "name": "Edit Test", + "secret": "thisissecret", + "status": "available", + "target_url": "https://example.com/new-subscriber", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/, + }, + ], +} +`; + +exports[`Webhooks API Can edit a webhook 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "418", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Webhooks API Fails nicely when adding a duplicate webhook 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "Target URL has already been used for this event.", + "details": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Validation error, cannot save webhook.", + "property": null, + "type": "ValidationError", + }, + ], +} +`; + +exports[`Webhooks API Fails nicely when adding a duplicate webhook 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "250", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Webhooks API Fails nicely when creating an orphaned webhook 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "Validation failed for 'integration_id'. 'integration_id' value does not match any existing integration.", + "details": null, + "help": "Provide the 'integration_id' of an existing integration.", + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Validation error, cannot save webhook.", + "property": null, + "type": "ValidationError", + }, + ], +} +`; + +exports[`Webhooks API Fails nicely when creating an orphaned webhook 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "359", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; diff --git a/test/e2e-api/admin/webhooks.test.js b/test/e2e-api/admin/webhooks.test.js index 42ab701b6e..56358dc008 100644 --- a/test/e2e-api/admin/webhooks.test.js +++ b/test/e2e-api/admin/webhooks.test.js @@ -1,116 +1,91 @@ -const should = require('should'); -const supertest = require('supertest'); -const testUtils = require('../../utils'); -const config = require('../../../core/shared/config'); -const localUtils = require('./utils'); +const {agentProvider, fixtureManager, matchers} = require('../../utils/e2e-framework'); +const {anyEtag, anyErrorId, anyObjectId, anyISODate} = matchers; +const API_VERSION = 'canary'; + +const webhookMatcher = { + id: anyObjectId, + integration_id: anyObjectId, + created_at: anyISODate, + updated_at: anyISODate +}; describe('Webhooks API', function () { - let request; - const API_VERSION = 'canary'; + let agent; + let createdWebhookId; + let webhookData; before(async function () { - await localUtils.startGhost(); - request = supertest.agent(config.get('url')); - await localUtils.doAuth(request, 'integrations'); - }); + agent = await agentProvider.getAdminAPIAgent(); + await fixtureManager.init('integrations'); + await agent.loginAsOwner(); - it('Can create a webhook', async function () { - const webhookData = { + // create a webhook linked to a real integration + webhookData = { event: 'test.create', target_url: 'http://example.com/webhooks/test/extra/1', name: 'test', secret: 'thisissecret', api_version: API_VERSION, - integration_id: testUtils.DataGenerator.Content.integrations[0].id + integration_id: fixtureManager.get('integrations', 0).id }; + }); - const res = await request.post(localUtils.API.getApiQuery('webhooks/')) - .set('Origin', config.get('url')) - .send({webhooks: [webhookData]}) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(201); + it('Can create a webhook', async function () { + const {body} = await agent.post('/webhooks/') + .body({webhooks: [webhookData]}) + .expectStatus(201) + .matchHeaderSnapshot({ + // Note: No location header as there is no read method for webhooks + etag: anyEtag - const jsonResponse = res.body; + }) + .matchBodySnapshot({ + webhooks: [webhookMatcher] + }); - should.exist(jsonResponse.webhooks); + // Store an id for use in future tests. Not the best pattern but does keep the tests readable. + createdWebhookId = body.webhooks[0].id; + }); - localUtils.API.checkResponse(jsonResponse.webhooks[0], 'webhook'); - - jsonResponse.webhooks[0].event.should.equal(webhookData.event); - jsonResponse.webhooks[0].target_url.should.equal(webhookData.target_url); - jsonResponse.webhooks[0].secret.should.equal(webhookData.secret); - jsonResponse.webhooks[0].name.should.equal(webhookData.name); - jsonResponse.webhooks[0].api_version.should.equal(webhookData.api_version); - jsonResponse.webhooks[0].integration_id.should.equal(webhookData.integration_id); - - should.not.exist(res.headers.location); - - await request.post(localUtils.API.getApiQuery('webhooks/')) - .set('Origin', config.get('url')) - .send({webhooks: [webhookData]}) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(422); + it('Fails nicely when adding a duplicate webhook', async function () { + await agent.post('/webhooks/') + .body({webhooks: [webhookData]}) + .expectStatus(422) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + errors: [{ + id: anyErrorId + }] + }); }); it('Fails nicely when creating an orphaned webhook', async function () { - const webhookData = { - event: 'test.create', - target_url: 'http://example.com/webhooks/test/extra/10', - name: 'test', - secret: 'thisissecret', - api_version: API_VERSION, - integration_id: `fake-integration` - }; - - const res = await request.post(localUtils.API.getApiQuery('webhooks/')) - .set('Origin', config.get('url')) - .send({webhooks: [webhookData]}) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(422); - - const jsonResponse = res.body; - - should.exist(jsonResponse.errors); - - jsonResponse.errors[0].type.should.equal('ValidationError'); - jsonResponse.errors[0].context.should.equal(`Validation failed for 'integration_id'. 'integration_id' value does not match any existing integration.`); + await agent + .post('/webhooks/') + .body({webhooks: [{ + event: 'test.create', + target_url: 'http://example.com/webhooks/test/extra/10', + name: 'test', + secret: 'thisissecret', + api_version: API_VERSION, + integration_id: `fake-integration` + }]}) + .expectStatus(422) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + errors: [{ + id: anyErrorId + }] + }); }); it('Can edit a webhook', async function () { - let createdIntegration; - let createdWebhook; - - const res = await request.post(localUtils.API.getApiQuery('integrations/')) - .set('Origin', config.get('url')) - .send({ - integrations: [{ - name: 'Rubbish Integration Name' - }] - }) - .expect(201); - - [createdIntegration] = res.body.integrations; - - const res2 = await request.post(localUtils.API.getApiQuery('webhooks/')) - .set('Origin', config.get('url')) - .send({ - webhooks: [{ - name: 'Testing', - event: 'site.changed', - target_url: 'https://example.com/rebuild', - integration_id: createdIntegration.id - }] - }) - .expect(201); - - [createdWebhook] = res2.body.webhooks; - - const res3 = await request.put(localUtils.API.getApiQuery(`webhooks/${createdWebhook.id}/`)) - .set('Origin', config.get('url')) - .send({ + await agent.put(`/webhooks/${createdWebhookId}/`) + .body({ webhooks: [{ name: 'Edit Test', event: 'subscriber.added', @@ -118,47 +93,22 @@ describe('Webhooks API', function () { integration_id: 'ignore_me' }] }) - .expect(200) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private); - - const [updatedWebhook] = res3.body.webhooks; - - should.equal(updatedWebhook.id, createdWebhook.id); - should.equal(updatedWebhook.name, 'Edit Test'); - should.equal(updatedWebhook.event, 'subscriber.added'); - should.equal(updatedWebhook.target_url, 'https://example.com/new-subscriber'); - should.equal(updatedWebhook.integration_id, createdIntegration.id); + .expectStatus(200) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + webhooks: [webhookMatcher] + }); }); it('Can delete a webhook', async function () { - const newWebhook = { - event: 'test.create', - // a different target_url from above is needed to avoid an "already exists" error - target_url: 'http://example.com/webhooks/test/2', - integration_id: testUtils.DataGenerator.Content.integrations[0].id - }; - - // create the webhook that is to be deleted - const res = await request.post(localUtils.API.getApiQuery('webhooks/')) - .set('Origin', config.get('url')) - .send({webhooks: [newWebhook]}) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(201); - - const jsonResponse = res.body; - - should.exist(jsonResponse.webhooks); - localUtils.API.checkResponse(jsonResponse.webhooks[0], 'webhook'); - jsonResponse.webhooks[0].event.should.equal(newWebhook.event); - jsonResponse.webhooks[0].target_url.should.equal(newWebhook.target_url); - - // begin delete test - const res2 = await request.del(localUtils.API.getApiQuery('webhooks/' + jsonResponse.webhooks[0].id + '/')) - .set('Origin', config.get('url')) - .expect(204); - - res2.body.should.be.empty(); + await agent + .delete(`/webhooks/${createdWebhookId}/`) + .expectStatus(204) + .expectEmptyBody() + .matchHeaderSnapshot({ + etag: anyEtag + }); }); });