Ghost/ghost/core/test/regression/site/frontend.test.js
Naz 7b009bf1fe Enabled shared caching of 404 error responses
refs https://github.com/TryGhost/Toolbox/issues/410

- The 'private' value in 'Cache-Control' response header for all errors made it impossible for shared caches (e.g.: Fastly, Cloudflare) to cache 404 responses efficiently.
- The change substitutes 'max-age=0' which should not effect the browser cache behavior but would allow shared caches to process such requests efficiently.
- A more loose caching logic only applies to 404 responses from GET requests that are not user-specific (non-authenticated, non-cookie containing requests)
2022-09-26 14:54:50 +08:00

281 lines
12 KiB
JavaScript

// # Frontend Route tests
// As it stands, these tests depend on the database, and as such are integration tests.
// Mocking out the models to not touch the DB would turn these into unit tests, and should probably be done in future,
// But then again testing real code, rather than mock code, might be more useful...
const should = require('should');
const sinon = require('sinon');
const supertest = require('supertest');
const cheerio = require('cheerio');
const _ = require('lodash');
const testUtils = require('../../utils');
const configUtils = require('../../utils/configUtils');
const config = require('../../../core/shared/config');
const settingsCache = require('../../../core/shared/settings-cache');
let request;
describe('Frontend Routing', function () {
function doEnd(done) {
return function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
should.not.exist(res.headers['X-CSRF-Token']);
should.not.exist(res.headers['set-cookie']);
should.exist(res.headers.date);
done();
};
}
function assertCorrectFrontendHeaders(res) {
should.not.exist(res.headers['x-cache-invalidate']);
should.not.exist(res.headers['X-CSRF-Token']);
should.not.exist(res.headers['set-cookie']);
should.exist(res.headers.date);
}
function addPosts(done) {
testUtils.clearData().then(function () {
return testUtils.initData();
}).then(function () {
return testUtils.fixtures.insertPostsAndTags();
}).then(function () {
done();
});
}
afterEach(function () {
sinon.restore();
});
before(async function () {
await testUtils.startGhost({
copyThemes: true,
copySettings: true,
redirectsFile: true
});
request = supertest.agent(config.get('url'));
});
describe('Test with Initial Fixtures', function () {
describe('Error', function () {
it('should 404 for unknown post with invalid characters', function (done) {
request.get('/$pec+acular~/')
.expect('Cache-Control', testUtils.cacheRules.noCache)
.expect(404)
.expect(/Page not found/)
.end(doEnd(done));
});
it('should 404 for unknown frontend route', function (done) {
request.get('/spectacular/marvellous/')
.set('Accept', 'application/json')
.expect('Cache-Control', testUtils.cacheRules.noCache)
.expect(404)
.expect(/Page not found/)
.end(doEnd(done));
});
it('should 404 for encoded char not 301 from uncapitalise', function (done) {
request.get('/|/')
.expect('Cache-Control', testUtils.cacheRules.noCache)
.expect(404)
.expect(/Page not found/)
.end(doEnd(done));
});
});
describe('Default Redirects (clean URLS)', function () {
it('Single post should redirect without slash', function (done) {
request.get('/welcome')
.expect('Location', '/welcome/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEnd(done));
});
it('Single post should redirect uppercase', function (done) {
request.get('/Welcome/')
.expect('Location', '/welcome/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEnd(done));
});
it('Single post should sanitize double slashes when redirecting uppercase', function (done) {
request.get('///Google.com/')
.expect('Location', '/google.com/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEnd(done));
});
it('AMP post should redirect without slash', function (done) {
request.get('/welcome/amp')
.expect('Location', '/welcome/amp/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEnd(done));
});
it('AMP post should redirect uppercase', function (done) {
request.get('/Welcome/AMP/')
.expect('Location', '/welcome/amp/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEnd(done));
});
});
});
describe('Test with added posts', function () {
before(addPosts);
describe('Static page', function () {
it('should respond with html', function (done) {
request.get('/static-page-test/')
.expect('Content-Type', /html/)
.expect('Cache-Control', testUtils.cacheRules.public)
.expect(200)
.end(function (err, res) {
const $ = cheerio.load(res.text);
should.not.exist(res.headers['x-cache-invalidate']);
should.not.exist(res.headers['X-CSRF-Token']);
should.not.exist(res.headers['set-cookie']);
should.exist(res.headers.date);
$('title').text().should.equal('This is a static page');
$('body.page-template').length.should.equal(1);
$('article.post').length.should.equal(1);
doEnd(done)(err, res);
});
});
it('should redirect without slash', function (done) {
request.get('/static-page-test')
.expect('Location', '/static-page-test/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEnd(done));
});
describe('edit', function () {
it('should redirect without slash', async function () {
await request.get('/static-page-test/edit')
.expect('Location', '/static-page-test/edit/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.expect(assertCorrectFrontendHeaders);
});
it('should redirect to editor for post resource', async function () {
await request.get('//welcome/edit/')
.expect('Location', /ghost\/#\/editor\/post\/\w+/)
.expect('Cache-Control', testUtils.cacheRules.public)
.expect(302)
.expect(assertCorrectFrontendHeaders);
});
it('should redirect to editor for page resource', async function () {
await request.get('/static-page-test/edit/')
.expect('Location', /ghost\/#\/editor\/page\/\w+/)
.expect('Cache-Control', testUtils.cacheRules.public)
.expect(302)
.expect(assertCorrectFrontendHeaders);
});
it('should 404 for non-edit parameter', async function () {
await request.get('/static-page-test/notedit/')
.expect('Cache-Control', testUtils.cacheRules.noCache)
.expect(404)
.expect(/Page not found/)
.expect(assertCorrectFrontendHeaders);
});
});
describe('edit with admin redirects disabled', function () {
before(function (done) {
configUtils.set('admin:redirects', false);
testUtils.startGhost({forceStart: true})
.then(function () {
request = supertest.agent(config.get('url'));
addPosts(done);
});
});
after(function (done) {
configUtils.restore();
testUtils.startGhost({forceStart: true})
.then(function () {
request = supertest.agent(config.get('url'));
addPosts(done);
});
});
it('should redirect without slash', function (done) {
request.get('/static-page-test/edit')
.expect('Location', '/static-page-test/edit/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEnd(done));
});
it('should not redirect to editor', function (done) {
request.get('/static-page-test/edit/')
.expect(404)
.expect('Cache-Control', testUtils.cacheRules.noCache)
.end(doEnd(done));
});
});
describe('amp', function () {
describe('amp enabled', function (){
beforeEach(function () {
sinon.stub(settingsCache, 'get').withArgs('amp').returns(true);
});
it('should 404 for amp parameter', function (done) {
// NOTE: only post pages are supported so the router doesn't have a way to distinguish if
// the request was done after AMP 'Page' or 'Post'
request.get('/static-page-test/amp/')
.expect('Cache-Control', testUtils.cacheRules.noCache)
.expect(404)
.expect(/Post not found/)
.end(doEnd(done));
});
});
describe('amp disabled', function (){
beforeEach(function () {
sinon.stub(settingsCache, 'get').withArgs('amp').returns(false);
});
it('should 301 for amp parameter', function (done) {
// NOTE: only post pages are supported so the router doesn't have a way to distinguish if
// the request was done after AMP 'Page' or 'Post'
request.get('/static-page-test/amp/')
.expect(301)
.end(doEnd(done));
});
});
});
});
describe('Post with Ghost in the url', function () {
// All of Ghost's admin depends on the /ghost/ in the url to work properly
// Badly formed regexs can cause breakage if a post slug starts with the 5 letters ghost
it('should retrieve a blog post with ghost at the start of the url', function (done) {
request.get('/ghostly-kitchen-sink/')
.expect('Cache-Control', testUtils.cacheRules.public)
.expect(200)
.end(doEnd(done));
});
});
});
});