const assert = require('assert/strict'); const sinon = require('sinon'); const APIVersionCompatibilityService = require('../index'); describe('APIVersionCompatibilityService', function () { const getSiteUrl = () => 'https://amazeballsghostsite.com'; const getSiteTitle = () => 'Tahini and chickpeas'; let UserModel; let ApiKeyModel; let settingsService; beforeEach(function () { UserModel = { findAll: sinon .stub() .withArgs({ withRelated: ['roles'], filter: 'status:active' }, { internal: true }) .resolves({ toJSON: () => [{ email: 'simon@example.com', roles: [{ name: 'Administrator' }] }] }) }; ApiKeyModel = { findOne: sinon .stub() .withArgs({ secret: 'super_secret' }, { withRelated: ['integration'] }) .resolves({ relations: { integration: { toJSON: () => ({ name: 'Elaborate Fox', type: 'custom' }) } } }) }; settingsService = { read: sinon.stub().resolves({ version_notifications: { value: JSON.stringify([ 'v3.4', 'v4.1' ]) } }), edit: sinon.stub().resolves() }; }); afterEach(function () { sinon.reset(); }); it('Sends an email to the instance owners when fresh accept-version header mismatch detected', async function () { const sendEmail = sinon.spy(); const compatibilityService = new APIVersionCompatibilityService({ UserModel, ApiKeyModel, settingsService, sendEmail, getSiteUrl, getSiteTitle }); await compatibilityService.handleMismatch({ acceptVersion: 'v4.5', contentVersion: 'v5.1', userAgent: 'GhostAdminSDK/2.4.0', requestURL: '/ghost/api/admin/posts/dew023d9203se4', apiKeyValue: 'secret', apiKeyType: 'content' }); assert.equal(sendEmail.called, true); assert.equal(sendEmail.args[0][0].to, 'simon@example.com'); assert.equal(sendEmail.args[0][0].subject, `Attention required: Your Elaborate Fox integration has failed`); assert.match(sendEmail.args[0][0].html, /Your Elaborate Fox<\/strong> integration is no longer working as expected\./); assert.match(sendEmail.args[0][0].html, /Integration expected Ghost version:<\/strong>  v4.5/); assert.match(sendEmail.args[0][0].html, /Current Ghost version:<\/strong>  v5.1/); assert.match(sendEmail.args[0][0].html, /Failed request URL:<\/strong>  \/ghost\/api\/admin\/posts\/dew023d9203se4/); assert.match(sendEmail.args[0][0].html, /This email was sent from Elaborate Fox<\/strong> integration is no longer working as expected\./); assert.match(sendEmail.args[0][0].html, /Integration expected Ghost version:<\/strong>  v4.5/); assert.match(sendEmail.args[0][0].html, /Current Ghost version:<\/strong>  v5.1/); assert.match(sendEmail.args[0][0].html, /Failed request URL:<\/strong>  https:\/\/amazeballsghostsite.com\/ghost\/api\/admin\/posts\/dew023d9203se4/); assert.match(sendEmail.args[0][0].html, /This email was sent from ({ name: 'Core Integration', type: 'core' }) } } }); findOneStub .withArgs({ id: 'secrete_internal' }, { withRelated: ['integration'] }) .resolves({ relations: { integration: { toJSON: () => ({ name: 'Internal Integration', type: 'internal' }) } } }); findOneStub .withArgs({ secret: 'custom_key_id' }, { withRelated: ['integration'] }) .resolves({ relations: { integration: { toJSON: () => ({ name: 'Custom Integration', type: 'custom' }) } } }); ApiKeyModel = { findOne: findOneStub }; settingsService = { read: sinon.stub().resolves({ version_notifications: { value: JSON.stringify([]) } }), edit: sinon.stub().resolves() }; const compatibilityService = new APIVersionCompatibilityService({ sendEmail, ApiKeyModel, UserModel, settingsService, getSiteUrl, getSiteTitle }); await compatibilityService.handleMismatch({ acceptVersion: 'v4.5', contentVersion: 'v5.1', userAgent: 'GhostAdminSDK/2.4.0', requestURL: '', apiKeyValue: 'secret_core', apiKeyType: 'admin' }); assert.equal(sendEmail.called, false); await compatibilityService.handleMismatch({ acceptVersion: 'v4.5', contentVersion: 'v5.1', userAgent: 'GhostAdminSDK/2.4.0', requestURL: 'does not matter', apiKeyValue: 'secrete_internal', apiKeyType: 'admin' }); assert.equal(sendEmail.called, false); await compatibilityService.handleMismatch({ acceptVersion: 'v4.5', contentVersion: 'v5.1', userAgent: 'GhostContentSDK/2.4.0', requestURL: 'https://amazeballsghostsite.com/ghost/api/admin/posts/dew023d9203se4', apiKeyValue: 'custom_key_id', apiKeyType: 'content' }); assert.equal(sendEmail.called, true); }); it('Does send multiple emails to the instance owners when previously unhandled accept-version header mismatch is detected', async function () { const sendEmail = sinon.spy(); UserModel = { findAll: sinon .stub() .withArgs({ withRelated: ['roles'], filter: 'status:active' }, { internal: true }) .resolves({ toJSON: () => [{ email: 'simon@example.com', roles: [{ name: 'Administrator' }] }, { email: 'sam@example.com', roles: [{ name: 'Owner' }] }] }) }; settingsService = { read: sinon.stub() .onCall(0).resolves({ version_notifications: { value: JSON.stringify([]) } }) .onCall(1).resolves({ version_notifications: { value: JSON.stringify([]) } }) .onCall(2).resolves({ version_notifications: { value: JSON.stringify([ 'v4.5' ]) } }) .onCall(3).resolves({ version_notifications: { value: JSON.stringify([ 'v4.5' ]) } }), edit: sinon.stub().resolves() }; const compatibilityService = new APIVersionCompatibilityService({ sendEmail, UserModel, ApiKeyModel, settingsService, getSiteUrl, getSiteTitle }); await compatibilityService.handleMismatch({ acceptVersion: 'v4.5', contentVersion: 'v5.1', userAgent: 'GhostAdminSDK/2.4.0', requestURL: 'https://amazeballsghostsite.com/ghost/api/admin/posts/dew023d9203se4', apiKeyValue: 'secret', apiKeyType: 'content' }); assert.equal(sendEmail.calledTwice, true); assert.equal(sendEmail.args[0][0].to, 'simon@example.com'); assert.equal(sendEmail.args[0][0].subject, `Attention required: Your Elaborate Fox integration has failed`); assert.match(sendEmail.args[0][0].html, /Your Elaborate Fox<\/strong> integration is no longer working as expected\./); assert.match(sendEmail.args[0][0].html, /Integration expected Ghost version:<\/strong>  v4.5/); assert.match(sendEmail.args[0][0].html, /Current Ghost version:<\/strong>  v5.1/); assert.match(sendEmail.args[0][0].html, /Failed request URL:<\/strong>  https:\/\/amazeballsghostsite.com\/ghost\/api\/admin\/posts\/dew023d9203se4/); assert.match(sendEmail.args[0][0].html, /This email was sent from Elaborate Fox<\/strong> integration is no longer working as expected\./); assert.match(sendEmail.args[1][0].html, /Integration expected Ghost version:<\/strong>  v4.5/); assert.match(sendEmail.args[1][0].html, /Current Ghost version:<\/strong>  v5.1/); assert.match(sendEmail.args[1][0].html, /Failed request URL:<\/strong>  https:\/\/amazeballsghostsite.com\/ghost\/api\/admin\/posts\/dew023d9203se4/); assert.match(sendEmail.args[1][0].html, /This email was sent from Elaborate Fox<\/strong> integration is no longer working as expected\./); assert.match(sendEmail.args[2][0].html, /Integration expected Ghost version:<\/strong>  v4.8/); assert.match(sendEmail.args[2][0].html, /Current Ghost version:<\/strong>  v5.1/); assert.match(sendEmail.args[2][0].html, /Failed request URL:<\/strong>  https:\/\/amazeballsghostsite.com\/ghost\/api\/admin\/posts\/dew023d9203se4/); assert.match(sendEmail.args[2][0].text, /Your Elaborate Fox integration is no longer working/); assert.match(sendEmail.args[2][0].text, /Integration expected Ghost version:v4.8/); assert.match(sendEmail.args[2][0].text, /Current Ghost version:v5.1/); assert.match(sendEmail.args[2][0].text, /Failed request URL:/); assert.match(sendEmail.args[2][0].text, /https:\/\/amazeballsghostsite.com\/ghost\/api\/admin\/posts\/dew023d9203se4/); }); it('Sends Zapier-specific email when userAgent is a Zapier client', async function (){ const sendEmail = sinon.spy(); const compatibilityService = new APIVersionCompatibilityService({ sendEmail, UserModel, ApiKeyModel, settingsService, getSiteUrl, getSiteTitle }); await compatibilityService.handleMismatch({ acceptVersion: 'v4.5', contentVersion: 'v5.1', userAgent: 'Zapier/4.20 GhostAdminSDK/2.4.0', requestURL: 'https://amazeballsghostsite.com/ghost/api/admin/posts/dew023d9203se4', apiKeyValue: 'secret', apiKeyType: 'content' }); assert.equal(sendEmail.called, true); assert.equal(sendEmail.args[0][0].to, 'simon@example.com'); assert.equal(sendEmail.args[0][0].subject, `Attention required: One of your Zaps has failed`); assert.match(sendEmail.args[0][0].html, /One of the Zaps in your Zapier integration has stopped working<\/span>\./); assert.match(sendEmail.args[0][0].html, /To get this resolved as quickly as possible, please log in to your Zapier account to view any failing Zaps and recreate them using the most recent Ghost-supported versions. Zap errors can be found here<\/a> in your “Zap history”\./); assert.match(sendEmail.args[0][0].html, /This email was sent from