express-cache-ctrl
Version:
Express middleware to handle content expiration using Cache-Control header.
300 lines (269 loc) • 12 kB
JavaScript
const { expect } = require("chai");
const ms = require("ms");
const cache = require("../src/cache");
const Response = require("./mocks/response");
describe("Cache Middleware", function () {
const ttl = 3600;
it("cache disabled", function (done) {
const middleware = cache.disable();
expect(middleware).to.not.be.null;
expect(middleware).to.be.a("function");
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
expect(cacheControl).to.not.be.null;
const controls = parseCacheControl(cacheControl);
expect(controls).to.property("no-store");
expect(controls).to.property("no-cache");
expect(controls).to.property("must-revalidate");
expect(controls).to.property("proxy-revalidate");
const pragma = res.get("Pragma");
expect(pragma).to.not.be.null;
expect(pragma).to.equal("no-cache");
done();
});
});
it("cache secure", function (done) {
const middleware = cache.secure();
expect(middleware).to.not.be.null;
expect(middleware).to.be.a("function");
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
expect(cacheControl).to.not.be.null;
expect(cacheControl.indexOf("private")).to.equal(0);
const controls = parseCacheControl(cacheControl);
expect(controls).to.property("no-store");
expect(controls).to.property("no-cache");
expect(controls).to.property("must-revalidate");
expect(controls).to.property("no-transform");
const pragma = res.get("Pragma");
expect(pragma).to.not.be.null;
expect(pragma).to.equal("no-cache");
done();
});
});
it("cache with no TTL", function (done) {
const middleware = cache.private();
expect(middleware).to.not.be.null;
expect(middleware).to.be.a("function");
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
expect(cacheControl).to.not.be.null;
const controls = parseCacheControl(cacheControl);
expect(controls)
.to.property("max-age")
.and.to.equal(cache.toTimespan(cache.defaultTTL));
done();
});
});
it("cache private", function (done) {
const middleware = cache.private(ttl);
expect(middleware).to.not.be.null;
expect(middleware).to.be.a("function");
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
expect(cacheControl).to.not.be.null;
expect(cacheControl.indexOf("private")).to.equal(0);
const controls = parseCacheControl(cacheControl);
expect(controls).to.property("max-age").and.to.equal(ttl);
done();
});
});
it("cache private, with custom options", function (done) {
const middleware = cache.private(ttl, { mustRevalidate: true });
expect(middleware).to.not.be.null;
expect(middleware).to.be.a("function");
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
expect(cacheControl).to.not.be.null;
expect(cacheControl.indexOf("private")).to.equal(0);
const controls = parseCacheControl(cacheControl);
expect(controls).to.property("max-age").and.to.equal(ttl);
expect(controls).to.property("must-revalidate");
done();
});
});
it("cache public", function (done) {
const middleware = cache.public(ttl);
expect(middleware).to.not.be.null;
expect(middleware).to.be.a("function");
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
expect(cacheControl).to.not.be.null;
expect(cacheControl.indexOf("public")).to.equal(0);
const controls = parseCacheControl(cacheControl);
expect(controls).to.property("max-age").and.to.equal(ttl);
expect(controls).to.property("s-maxage").and.to.equal(ttl);
done();
});
});
it("cache public, with custom options", function (done) {
const middleware = cache.public(ttl, { mustRevalidate: true });
expect(middleware).to.not.be.null;
expect(middleware).to.be.a("function");
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
expect(cacheControl).to.not.be.null;
expect(cacheControl.indexOf("public")).to.equal(0);
const controls = parseCacheControl(cacheControl);
expect(controls).to.property("max-age").and.to.equal(ttl);
expect(controls).to.property("s-maxage").and.to.equal(ttl);
expect(controls).to.property("must-revalidate");
done();
});
});
it("cache custom", function (done) {
const middleware = cache.custom({
ttl: ttl,
sttl: ttl,
scope: "public",
mustRevalidate: true,
proxyRevalidate: true,
noTransform: true,
});
expect(middleware).to.not.be.null;
expect(middleware).to.be.a("function");
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
expect(cacheControl).to.not.be.null;
expect(cacheControl.indexOf("public")).to.equal(0);
const controls = parseCacheControl(cacheControl);
expect(controls).to.property("max-age").and.to.equal(ttl);
expect(controls).to.property("s-maxage").and.to.equal(ttl);
expect(controls).to.property("must-revalidate");
expect(controls).to.property("no-transform");
expect(controls).to.property("proxy-revalidate");
done();
});
});
describe("Edge Cases", function () {
it("cache private with TTL = 0 defaults to defaultTTL", function (done) {
const middleware = cache.private(0);
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
const controls = parseCacheControl(cacheControl);
const defaultTtlSeconds = cache.toTimespan(cache.defaultTTL);
// 0 is falsy, so it defaults to defaultTTL (1h = 3600)
expect(controls).to.property("max-age").and.to.equal(defaultTtlSeconds);
done();
});
});
it("cache public with string TTL '1d'", function (done) {
const middleware = cache.public("1d");
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
const controls = parseCacheControl(cacheControl);
expect(controls).to.property("max-age").and.to.equal(86400);
expect(controls).to.property("s-maxage").and.to.equal(86400);
done();
});
});
it("cache public with string TTL '2h'", function (done) {
const middleware = cache.public("2h");
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
const controls = parseCacheControl(cacheControl);
expect(controls).to.property("max-age").and.to.equal(7200);
expect(controls).to.property("s-maxage").and.to.equal(7200);
done();
});
});
it("cache public with string TTL '30m'", function (done) {
const middleware = cache.public("30m");
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
const controls = parseCacheControl(cacheControl);
expect(controls).to.property("max-age").and.to.equal(1800);
expect(controls).to.property("s-maxage").and.to.equal(1800);
done();
});
});
it("cache custom with no options", function (done) {
const middleware = cache.custom();
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
expect(cacheControl).to.not.be.null;
expect(cacheControl).to.be.a("string");
done();
});
});
it("cache custom with empty options", function (done) {
const middleware = cache.custom({});
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
expect(cacheControl).to.not.be.null;
const controls = parseCacheControl(cacheControl);
expect(controls).to.property("private");
done();
});
});
it("should remove Pragma header when noCache is false", function (done) {
const middleware = cache.private(3600);
const res = new Response();
// Set Pragma header first
res.setHeader("Pragma", "no-cache");
middleware.call(middleware, {}, res, function () {
const pragma = res.get("Pragma");
expect(pragma).to.be.undefined;
done();
});
});
it("should call next() callback", function (done) {
const middleware = cache.private(3600);
const res = new Response();
let nextCalled = false;
middleware.call(middleware, {}, res, function () {
nextCalled = true;
expect(nextCalled).to.be.true;
done();
});
});
it("cache public with 1 week TTL", function (done) {
const middleware = cache.public("1w");
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
const controls = parseCacheControl(cacheControl);
expect(controls).to.property("max-age").and.to.equal(604800);
expect(controls).to.property("s-maxage").and.to.equal(604800);
done();
});
});
it("cache custom with different max-age and s-maxage", function (done) {
const middleware = cache.custom({
scope: "public",
ttl: "2h",
sttl: "1h",
});
runMiddleware(middleware, function (res) {
const cacheControl = res.get("Cache-Control");
const controls = parseCacheControl(cacheControl);
expect(controls).to.property("max-age").and.to.equal(7200);
expect(controls).to.property("s-maxage").and.to.equal(3600);
done();
});
});
});
function runMiddleware(middleware, callback) {
const res = new Response();
middleware.call(middleware, {}, res, function () {
callback(res);
});
}
function parseCacheControl(input) {
//console.log('Cache-Control:', input);
const parts = input.split(",");
return parts.reduce(function (prev, current) {
let value = true;
let key = current.trim();
if (current.indexOf("=") !== -1) {
let keyAndValue = current.split("=");
key = keyAndValue[0] && keyAndValue[0].trim();
value = keyAndValue[1] && keyAndValue[1].trim();
}
if (!isNaN(value)) {
value = parseInt(value);
}
prev[key] = value;
return prev;
}, {});
}
});