From a9cce0281d0028987c68a1dae9b085d5d0765918 Mon Sep 17 00:00:00 2001 From: ceecko Date: Mon, 2 May 2022 20:08:30 +0200 Subject: [PATCH] Added support for eu Mailgun domain (#73) closes: https://github.com/TryGhost/Ghost/issues/14640 - eu mailgun domains have a different structure. - we weren't accounting for this when fetching the next page of results, meaning that email stats didn't work on EU domains --- .../lib/provider-mailgun.js | 5 +- .../test/fixtures/all-1-eu.json | 64 +++++++++++++ .../test/fixtures/all-2-eu.json | 37 ++++++++ .../test/provider-mailgun.test.js | 93 +++++++++++++++++++ 4 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 ghost/email-analytics-provider-mailgun/test/fixtures/all-1-eu.json create mode 100644 ghost/email-analytics-provider-mailgun/test/fixtures/all-2-eu.json diff --git a/ghost/email-analytics-provider-mailgun/lib/provider-mailgun.js b/ghost/email-analytics-provider-mailgun/lib/provider-mailgun.js index 286466a036..a3524ee549 100644 --- a/ghost/email-analytics-provider-mailgun/lib/provider-mailgun.js +++ b/ghost/email-analytics-provider-mailgun/lib/provider-mailgun.js @@ -109,8 +109,9 @@ class EmailAnalyticsProviderMailgun { break pagesLoop; } - debug(`_fetchPages: starting fetching next page ${page.paging.next.replace('https://api.mailgun.net/v3', '')}`); - page = await mailgun.get(page.paging.next.replace('https://api.mailgun.net/v3', '')); + const nextPageUrl = page.paging.next.replace(/https:\/\/api\.(eu\.)?mailgun\.net\/v3/, ''); + debug(`_fetchPages: starting fetching next page ${nextPageUrl}`); + page = await mailgun.get(nextPageUrl); events = page && page.items && page.items.map(this.normalizeEvent) || []; debug(`_fetchPages: finished fetching next page with ${events.length} events`); } diff --git a/ghost/email-analytics-provider-mailgun/test/fixtures/all-1-eu.json b/ghost/email-analytics-provider-mailgun/test/fixtures/all-1-eu.json new file mode 100644 index 0000000000..c6b5f74fe8 --- /dev/null +++ b/ghost/email-analytics-provider-mailgun/test/fixtures/all-1-eu.json @@ -0,0 +1,64 @@ +{ + "items": [ + { + "event": "delivered", + "recipient": "recipient1@gmail.com", + "user-variables": { + "email-id": "5fbe5d9607bdfa3765dc3819" + }, + "message": { + "headers": { + "message-id": "0201125133533.1.C55897076DBD42F2@domain.com" + } + }, + "timestamp": 1606399301.266528 + }, + { + "event": "failed", + "severity": "temporary", + "recipient": "recipient2@gmail.com", + "user-variables": { + "email-id": "5fbe5d9607bdfa3765dc3819" + }, + "message": { + "headers": { + "message-id": "0201125133533.1.C55897076DBD42F2@domain.com" + } + }, + "timestamp": 1606399301.266528 + }, + { + "event": "failed", + "severity": "permanent", + "recipient": "recipient3@gmail.com", + "user-variables": { + "email-id": "5fbe5d9607bdfa3765dc3819" + }, + "message": { + "headers": { + "message-id": "0201125133533.1.C55897076DBD42F2@domain.com" + } + }, + "timestamp": 1606399301.266528 + }, + { + "event": "unsubscribed", + "recipient": "recipient4@gmail.com", + "user-variables": { + "email-id": "5fbe5d9607bdfa3765dc3819" + }, + "message": { + "headers": { + "message-id": "0201125133533.1.C55897076DBD42F2@domain.com" + } + }, + "timestamp": 1606399301.266528 + } + ], + "paging": { + "previous": "https://api.eu.mailgun.net/v3/domain.com/events/all-1-previous", + "first": "https://api.eu.mailgun.net/v3/domain.com/events/all-1-first", + "last": "https://api.eu.mailgun.net/v3/domain.com/events/all-1-last", + "next": "https://api.eu.mailgun.net/v3/domain.com/events/all-1-next" + } +} diff --git a/ghost/email-analytics-provider-mailgun/test/fixtures/all-2-eu.json b/ghost/email-analytics-provider-mailgun/test/fixtures/all-2-eu.json new file mode 100644 index 0000000000..5beee51671 --- /dev/null +++ b/ghost/email-analytics-provider-mailgun/test/fixtures/all-2-eu.json @@ -0,0 +1,37 @@ +{ + "items": [ + { + "event": "delivered", + "recipient": "recipient5@gmail.com", + "user-variables": { + "email-id": "5fbe5d9607bdfa3765dc3819" + }, + "message": { + "headers": { + "message-id": "0201125133533.1.C55897076DBD42F2@domain.com" + } + }, + "timestamp": 1606399301.266528 + }, + { + "event": "failed", + "severity": "temporary", + "recipient": "recipient6@gmail.com", + "user-variables": { + "email-id": "5fbe5d9607bdfa3765dc3819" + }, + "message": { + "headers": { + "message-id": "0201125133533.1.C55897076DBD42F2@domain.com" + } + }, + "timestamp": 1606399301.266528 + } + ], + "paging": { + "previous": "https://api.eu.mailgun.net/v3/domain.com/events/all-2-previous", + "first": "https://api.eu.mailgun.net/v3/domain.com/events/all-2-first", + "last": "https://api.eu.mailgun.net/v3/domain.com/events/all-2-last", + "next": "https://api.eu.mailgun.net/v3/domain.com/events/all-2-next" + } +} diff --git a/ghost/email-analytics-provider-mailgun/test/provider-mailgun.test.js b/ghost/email-analytics-provider-mailgun/test/provider-mailgun.test.js index 2fe51fe54f..129925ddae 100644 --- a/ghost/email-analytics-provider-mailgun/test/provider-mailgun.test.js +++ b/ghost/email-analytics-provider-mailgun/test/provider-mailgun.test.js @@ -158,6 +158,51 @@ describe('EmailAnalyticsProviderMailgun', function () { batchHandler.callCount.should.eql(2); // one per page }); + it('supports EU Mailgun domain', async function () { + const configStub = sinon.stub(config, 'get'); + configStub.withArgs('bulkEmail').returns({ + mailgun: { + apiKey: 'apiKey', + domain: 'domain.com', + baseUrl: 'https://api.eu.mailgun.net/v3' + } + }); + + const firstPageMock = nock('https://api.eu.mailgun.net') + .get('/v3/domain.com/events') + .query({ + event: 'delivered OR opened OR failed OR unsubscribed OR complained', + limit: 300, + tags: 'bulk-email' + }) + .replyWithFile(200, `${__dirname}/fixtures/all-1-eu.json`, { + 'Content-Type': 'application/json' + }); + + const secondPageMock = nock('https://api.eu.mailgun.net') + .get('/v3/domain.com/events/all-1-next') + .replyWithFile(200, `${__dirname}/fixtures/all-2-eu.json`, { + 'Content-Type': 'application/json' + }); + + // requests continue until an empty items set is returned + nock('https://api.eu.mailgun.net') + .get('/v3/domain.com/events/all-2-next') + .reply(200, {'Content-Type': 'application/json'}, { + items: [] + }); + + const mailgunProvider = new EmailAnalyticsProviderMailgun({config, settings}); + + const batchHandler = sinon.spy(); + + await mailgunProvider.fetchAll(batchHandler); + + firstPageMock.isDone().should.be.true(); + secondPageMock.isDone().should.be.true(); + batchHandler.callCount.should.eql(2); // one per page + }); + it('uses custom tags when supplied', async function () { const configStub = sinon.stub(config, 'get'); configStub.withArgs('bulkEmail').returns({ @@ -253,6 +298,54 @@ describe('EmailAnalyticsProviderMailgun', function () { batchHandler.callCount.should.eql(2); // one per page }); + it('supports EU Mailgun domain', async function () { + const configStub = sinon.stub(config, 'get'); + configStub.withArgs('bulkEmail').returns({ + mailgun: { + apiKey: 'apiKey', + domain: 'domain.com', + baseUrl: 'https://api.eu.mailgun.net/v3' + } + }); + + const firstPageMock = nock('https://api.eu.mailgun.net') + .get('/v3/domain.com/events') + .query({ + event: 'delivered OR opened OR failed OR unsubscribed OR complained', + limit: 300, + tags: 'bulk-email', + begin: 'Thu, 25 Feb 2021 11:30:00 GMT', // latest minus threshold + ascending: 'yes' + }) + .replyWithFile(200, `${__dirname}/fixtures/all-1-eu.json`, { + 'Content-Type': 'application/json' + }); + + const secondPageMock = nock('https://api.eu.mailgun.net') + .get('/v3/domain.com/events/all-1-next') + .replyWithFile(200, `${__dirname}/fixtures/all-2-eu.json`, { + 'Content-Type': 'application/json' + }); + + // requests continue until an empty items set is returned + nock('https://api.eu.mailgun.net') + .get('/v3/domain.com/events/all-2-next') + .reply(200, {'Content-Type': 'application/json'}, { + items: [] + }); + + const mailgunProvider = new EmailAnalyticsProviderMailgun({config, settings}); + + const batchHandler = sinon.spy(); + + const latestTimestamp = new Date('Thu Feb 25 2021 12:00:00 GMT+0000'); + await mailgunProvider.fetchLatest(latestTimestamp, batchHandler); + + firstPageMock.isDone().should.be.true(); + secondPageMock.isDone().should.be.true(); + batchHandler.callCount.should.eql(2); // one per page + }); + it('uses custom tags when supplied', async function () { const configStub = sinon.stub(config, 'get'); configStub.withArgs('bulkEmail').returns({