diff --git a/core/server/services/api-version-compatibility/index.js b/core/server/services/api-version-compatibility/index.js index a27f4abdde..46e7872aa3 100644 --- a/core/server/services/api-version-compatibility/index.js +++ b/core/server/services/api-version-compatibility/index.js @@ -1,10 +1,13 @@ const APIVersionCompatibilityService = require('@tryghost/api-version-compatibility-service'); const VersionNotificationsDataService = require('@tryghost/version-notifications-data-service'); +const versionMismatchHandler = require('@tryghost/mw-api-version-mismatch'); // const {GhostMailer} = require('../mail'); const settingsService = require('../../services/settings'); const models = require('../../models'); const logging = require('@tryghost/logging'); +let serviceInstance; + const init = () => { //const ghostMailer = new GhostMailer(); const versionNotificationsDataService = new VersionNotificationsDataService({ @@ -12,7 +15,7 @@ const init = () => { settingsService: settingsService.getSettingsBREADServiceInstance() }); - this.APIVersionCompatibilityServiceInstance = new APIVersionCompatibilityService({ + serviceInstance = new APIVersionCompatibilityService({ sendEmail: (options) => { // NOTE: not using bind here because mockMailer is having trouble mocking bound methods //return ghostMailer.send(options); @@ -25,5 +28,7 @@ const init = () => { }); }; -module.exports.APIVersionCompatibilityServiceInstance; +module.exports.errorHandler = (req, res, next) => { + return versionMismatchHandler(serviceInstance)(req, res, next); +}; module.exports.init = init; diff --git a/core/server/web/api/app.js b/core/server/web/api/app.js index 16cd0d6bf8..aefa175c81 100644 --- a/core/server/web/api/app.js +++ b/core/server/web/api/app.js @@ -3,8 +3,7 @@ const config = require('../../../shared/config'); const express = require('../../../shared/express'); const sentry = require('../../../shared/sentry'); const errorHandler = require('@tryghost/mw-error-handler'); -const versionMissmatchHandler = require('@tryghost/mw-api-version-mismatch'); -const {APIVersionCompatibilityServiceInstance} = require('../../services/api-version-compatibility'); +const APIVersionCompatibilityService = require('../../services/api-version-compatibility'); module.exports = function setupApiApp() { debug('Parent API setup start'); @@ -32,7 +31,7 @@ module.exports = function setupApiApp() { // Error handling for requests to non-existent API versions apiApp.use(errorHandler.resourceNotFound); - apiApp.use(versionMissmatchHandler(APIVersionCompatibilityServiceInstance)); + apiApp.use(APIVersionCompatibilityService.errorHandler); apiApp.use(errorHandler.handleJSONResponseV2(sentry)); debug('Parent API setup end'); diff --git a/core/server/web/api/canary/admin/app.js b/core/server/web/api/canary/admin/app.js index 6a87c1c026..7a8f77d2d6 100644 --- a/core/server/web/api/canary/admin/app.js +++ b/core/server/web/api/canary/admin/app.js @@ -5,10 +5,9 @@ const bodyParser = require('body-parser'); const shared = require('../../../shared'); const apiMw = require('../../middleware'); const errorHandler = require('@tryghost/mw-error-handler'); -const versionMissmatchHandler = require('@tryghost/mw-api-version-mismatch'); const sentry = require('../../../../../shared/sentry'); const routes = require('./routes'); -const {APIVersionCompatibilityServiceInstance} = require('../../../../services/api-version-compatibility'); +const APIVersionCompatibilityService = require('../../../../services/api-version-compatibility'); module.exports = function setupApiApp() { debug('Admin API canary setup start'); @@ -35,7 +34,7 @@ module.exports = function setupApiApp() { // API error handling apiApp.use(errorHandler.resourceNotFound); - apiApp.use(versionMissmatchHandler(APIVersionCompatibilityServiceInstance)); + apiApp.use(APIVersionCompatibilityService.errorHandler); apiApp.use(errorHandler.handleJSONResponseV2(sentry)); debug('Admin API canary setup end'); diff --git a/test/e2e-api/shared/__snapshots__/version.test.js.snap b/test/e2e-api/shared/__snapshots__/version.test.js.snap index 144e9f3ced..e6fc924fb9 100644 --- a/test/e2e-api/shared/__snapshots__/version.test.js.snap +++ b/test/e2e-api/shared/__snapshots__/version.test.js.snap @@ -62,6 +62,62 @@ Object { } `; +exports[`API Versioning Admin API responds with 406 for an unknown version with accept-version set ahead 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": "UPDATE_GHOST", + "context": StringMatching /Provided client version v99\\\\\\.0 is ahead of current Ghost instance version v\\\\d\\+\\\\\\.\\\\d\\+/, + "details": null, + "help": "Upgrade your Ghost instance.", + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Request not acceptable for provided Accept-Version header.", + "property": null, + "type": "RequestNotAcceptableError", + }, + ], +} +`; + +exports[`API Versioning Admin API responds with 406 for an unknown version with accept-version set ahead 2: [headers] 1`] = ` +Object { + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "347", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`API Versioning Admin API responds with 406 for an unknown version with accept-version set behind 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": "UPDATE_CLIENT", + "context": StringMatching /Provided client version v1\\\\\\.0 is outdated and is behind current Ghost version v\\\\d\\+\\\\\\.\\\\d\\+/, + "details": null, + "help": "Upgrade your Ghost API client.", + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Request not acceptable for provided Accept-Version header.", + "property": null, + "type": "RequestNotAcceptableError", + }, + ], +} +`; + +exports[`API Versioning Admin API responds with 406 for an unknown version with accept-version set behind 2: [headers] 1`] = ` +Object { + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "354", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + exports[`API Versioning Admin API responds with current content version header when requested version is AHEAD and CAN respond 1: [body] 1`] = ` Object { "site": Object { diff --git a/test/e2e-api/shared/version.test.js b/test/e2e-api/shared/version.test.js index 3944c6fbda..cc0669ce5e 100644 --- a/test/e2e-api/shared/version.test.js +++ b/test/e2e-api/shared/version.test.js @@ -183,6 +183,34 @@ describe('API Versioning', function () { }) .expectEmptyBody(); }); + + it('responds with 406 for an unknown version with accept-version set ahead', async function () { + await agentAdminAPI + .get('/site/', {baseUrl: '/ghost/api/v99/admin/'}) + .header('Accept-Version', 'v99.0') + .expectStatus(406) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({errors: [{ + context: stringMatching(/Provided client version v99\.0 is ahead of current Ghost instance version v\d+\.\d+/), + id: anyErrorId + }]}); + }); + + it('responds with 406 for an unknown version with accept-version set behind', async function () { + await agentAdminAPI + .get('/site/', {baseUrl: '/ghost/api/v1/admin/'}) + .header('Accept-Version', 'v1.0') + .expectStatus(406) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({errors: [{ + context: stringMatching(/Provided client version v1\.0 is outdated and is behind current Ghost version v\d+\.\d+/), + id: anyErrorId + }]}); + }); }); describe('Content API', function () {