0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Added 'content-version' header response

refs https://github.com/TryGhost/Toolbox/issues/280

- In response to 'Accept-Version' header in the request headers, Ghost will always respond with a content-version header indicating the version of the Ghost install that is responding. This should signal to the client the content version that is bein g served
- This is a bare bones implementation and more logic with edge cases where `content-version` is served with a  version value of "best format API could respond with" will be added later.
This commit is contained in:
Naz 2022-04-06 16:12:20 +08:00 committed by naz
parent 132726fe20
commit 76aa2479f8
4 changed files with 84 additions and 2 deletions

View file

@ -64,7 +64,7 @@ const http = (apiImpl) => {
const result = await apiImpl(frame);
debug(`External API request to ${frame.docName}.${frame.method}`);
const headers = await shared.headers.get(result, apiImpl.headers, frame);
const headers = await shared.headers.get(result, apiImpl.headers, frame) || {};
// CASE: api ctrl wants to handle the express response (e.g. streams)
if (typeof result === 'function') {
@ -82,6 +82,9 @@ const http = (apiImpl) => {
res.status(statusCode);
// CASE: generate headers based on the api ctrl configuration
if (req && req.headers && req.headers['accept-version'] && res.locals) {
headers['content-version'] = `v${res.locals.safeVersion}`;
}
res.set(headers);
const send = (format) => {

View file

@ -0,0 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`API Versioning responds with current content version header when requested version is behind current version with no known changes 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",
"content-length": "167",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": Any<String>,
"vary": "Origin, Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`API Versioning responds with no content version header when accept version header is not present 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",
"content-length": "167",
"content-type": "application/json; charset=utf-8",
"etag": Any<String>,
"vary": "Origin, Accept-Encoding",
"x-powered-by": "Express",
}
`;

View file

@ -0,0 +1,28 @@
const {agentProvider, matchers} = require('../../utils/e2e-framework');
const {anyString, stringMatching} = matchers;
describe('API Versioning', function () {
let agent;
before(async function () {
agent = await agentProvider.getAdminAPIAgent();
});
it('responds with no content version header when accept version header is NOT PRESENT', async function () {
await agent
.get('site/')
.matchHeaderSnapshot({
etag: anyString
});
});
it('responds with current content version header when requested version is behind current version with no known changes', async function () {
await agent
.get('site/')
.header('Accept-Version', 'v3.0')
.matchHeaderSnapshot({
etag: anyString,
'content-version': stringMatching(/v\d+\.\d+/)
});
});
});

View file

@ -22,7 +22,9 @@ describe('Unit: api/shared/http', function () {
res.status = sinon.stub();
res.json = sinon.stub();
res.set = sinon.stub();
res.set = (headers) => {
res.headers = headers;
};
res.send = sinon.stub();
sinon.stub(shared.headers, 'get').resolves();
@ -85,4 +87,27 @@ describe('Unit: api/shared/http', function () {
shared.http(apiImpl)(req, res, next);
});
it('adds content-version header to the response when accept-version header is present in the request', function (done) {
const apiImpl = sinon.stub().resolves('data');
req.headers = {
'accept-version': 'v5.1'
};
apiImpl.headers = {
'Content-Type': 'application/json'
};
res.locals = {
safeVersion: '5.4'
};
next.callsFake(done);
res.json.callsFake(function () {
shared.headers.get.calledOnce.should.be.true();
res.status.calledOnce.should.be.true();
res.headers['content-version'].should.equal('v5.4');
done();
});
shared.http(apiImpl)(req, res, next);
});
});