diff --git a/ghost/api-version-compatibility-service/lib/api-version-compatibility-service.js b/ghost/api-version-compatibility-service/lib/api-version-compatibility-service.js index 7e1fafc62c..d470c2f6ab 100644 --- a/ghost/api-version-compatibility-service/lib/api-version-compatibility-service.js +++ b/ghost/api-version-compatibility-service/lib/api-version-compatibility-service.js @@ -1,15 +1,27 @@ class APIVersionCompatibilityService { - constructor({sendEmail}) { + /** + * + * @param {Object} options + * @param {Function} options.sendEmail - email sending function + * @param {(acceptVersion: String) => Promise} options.fetchHandled - retrives already handled compatibility notifications + * @param {(acceptVersion: String) => Promise} options.saveHandled - persists already handled compatibility notifications + */ + constructor({sendEmail, fetchHandled, saveHandled}) { this.sendEmail = sendEmail; + this.fetchHandled = fetchHandled; + this.saveHandled = saveHandled; } - handleMismatch({acceptVersion, contentVersion, userAgent}) { - const emailTemplate = ` - ${userAgent} integration expected Ghost version: ${acceptVersion} - Current Ghost version: ${contentVersion} - `; + async handleMismatch({acceptVersion, contentVersion, userAgent}) { + if (!await this.fetchHandled(acceptVersion)) { + const emailTemplate = ` + ${userAgent} integration expected Ghost version: ${acceptVersion} + Current Ghost version: ${contentVersion} + `; - this.sendEmail(emailTemplate); + await this.sendEmail(emailTemplate); + await this.saveHandled(acceptVersion); + } } } diff --git a/ghost/api-version-compatibility-service/test/api-version-compatibility-service.test.js b/ghost/api-version-compatibility-service/test/api-version-compatibility-service.test.js index b5c7e0b62c..1694f2d3d2 100644 --- a/ghost/api-version-compatibility-service/test/api-version-compatibility-service.test.js +++ b/ghost/api-version-compatibility-service/test/api-version-compatibility-service.test.js @@ -3,10 +3,19 @@ const sinon = require('sinon'); const APIVersionCompatibilityService = require('../index'); describe('APIVersionCompatibilityService', function () { + afterEach(function () { + sinon.reset(); + }); + it('Sends an email to the instance owner when fresh accept-version header mismatch detected', async function () { const sendEmail = sinon.spy(); + const fetchHandled = sinon.spy(); + const saveHandled = sinon.spy(); + const compatibilityService = new APIVersionCompatibilityService({ - sendEmail + sendEmail, + fetchHandled, + saveHandled }); await compatibilityService.handleMismatch({ @@ -19,4 +28,72 @@ describe('APIVersionCompatibilityService', function () { assert.match(sendEmail.args[0][0], /Elaborate Fox integration expected Ghost version: v4.5/); assert.match(sendEmail.args[0][0], /Current Ghost version: v5.1/); }); + + it('Does NOT send an email to the instance owner when previously handled accept-version header mismatch is detected', async function () { + const sendEmail = sinon.spy(); + const fetchHandled = sinon.stub() + .onFirstCall().resolves(null) + .onSecondCall().resolves({}); + + const saveHandled = sinon.stub().resolves({}); + + const compatibilityService = new APIVersionCompatibilityService({ + sendEmail, + fetchHandled, + saveHandled + }); + + await compatibilityService.handleMismatch({ + acceptVersion: 'v4.5', + contentVersion: 'v5.1', + userAgent: 'Elaborate Fox' + }); + + assert.equal(sendEmail.calledOnce, true); + assert.match(sendEmail.args[0][0], /Elaborate Fox integration expected Ghost version: v4.5/); + assert.match(sendEmail.args[0][0], /Current Ghost version: v5.1/); + + await compatibilityService.handleMismatch({ + acceptVersion: 'v4.5', + contentVersion: 'v5.1', + userAgent: 'Elaborate Fox' + }); + + assert.equal(sendEmail.calledTwice, false); + }); + + it('Does send multiple emails to the instance owner when previously unhandled accept-version header mismatch is detected', async function () { + const sendEmail = sinon.spy(); + const fetchHandled = sinon.stub() + .onFirstCall().resolves(null) + .onSecondCall().resolves(null); + + const saveHandled = sinon.stub().resolves({}); + + const compatibilityService = new APIVersionCompatibilityService({ + sendEmail, + fetchHandled, + saveHandled + }); + + await compatibilityService.handleMismatch({ + acceptVersion: 'v4.5', + contentVersion: 'v5.1', + userAgent: 'Elaborate Fox' + }); + + assert.equal(sendEmail.calledOnce, true); + assert.match(sendEmail.args[0][0], /Elaborate Fox integration expected Ghost version: v4.5/); + assert.match(sendEmail.args[0][0], /Current Ghost version: v5.1/); + + await compatibilityService.handleMismatch({ + acceptVersion: 'v4.8', + contentVersion: 'v5.1', + userAgent: 'Elaborate Fox' + }); + + assert.equal(sendEmail.calledTwice, true); + assert.match(sendEmail.args[1][0], /Elaborate Fox integration expected Ghost version: v4.8/); + assert.match(sendEmail.args[1][0], /Current Ghost version: v5.1/); + }); });