fix(customer-routes): Cache-Control: no-store on customer endpoints (#470)
The trigger: PR #458 mounted requireCustomerPortalEnabled which 410'd every /api/customer/* + /api/admin/customers/* request when the master toggle was off. Some browsers cached that 410 (no Cache-Control header was set, so heuristic freshness applied — the wrong default for an authenticated/sensitive surface). PR #470 reverted the middleware, but a customer whose tab cached the 410 still saw 410s until they hard-refreshed. Add noStoreCache middleware and mount it in front of both route groups. Every response (200, 4xx, 5xx) now carries `Cache-Control: no-store, no-cache, must-revalidate, private` plus the HTTP/1.0 Pragma + Expires fallbacks. Any future transient error from these endpoints can no longer get pinned in browser or proxy caches and outlive its cause. Cost is one setHeader per request; applied per route group rather than globally so static assets + galleries keep their own caching strategy unchanged. Includes a dedicated unit test pinning the header set so a future cleanup pass can't quietly drop it and re-introduce the bug.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Unit test for the noStoreCache middleware (#470 follow-up).
|
||||
*
|
||||
* The middleware exists because of a real production-class bug: when
|
||||
* #458 mounted a 410-returning kill-switch in front of customer
|
||||
* endpoints, browsers cached the 410 (no Cache-Control was set) and
|
||||
* kept serving it after #470 reverted the middleware. This test pins
|
||||
* the contract so a future cleanup pass doesn't quietly drop the
|
||||
* header set and re-introduce the bug.
|
||||
*/
|
||||
|
||||
const { noStoreCache } = require('../middleware/noStoreCache');
|
||||
|
||||
function makeRes() {
|
||||
const headers = {};
|
||||
return {
|
||||
setHeader: (k, v) => { headers[k] = v; },
|
||||
headers,
|
||||
};
|
||||
}
|
||||
|
||||
describe('noStoreCache middleware', () => {
|
||||
it('sets Cache-Control: no-store + private and calls next()', () => {
|
||||
const res = makeRes();
|
||||
const next = jest.fn();
|
||||
|
||||
noStoreCache({}, res, next);
|
||||
|
||||
expect(res.headers['Cache-Control']).toBe(
|
||||
'no-store, no-cache, must-revalidate, private',
|
||||
);
|
||||
// HTTP/1.0 fallbacks — old proxies in front of customer-facing
|
||||
// surfaces (corporate VPN gateways, legacy CDNs) honour these.
|
||||
expect(res.headers.Pragma).toBe('no-cache');
|
||||
expect(res.headers.Expires).toBe('0');
|
||||
expect(next).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('runs as middleware regardless of response status', () => {
|
||||
// The header must land on EVERY response coming from the route
|
||||
// group — including 4xx/5xx — so a stale 410 from a
|
||||
// now-reverted middleware can't get pinned in browser cache like
|
||||
// it did in the #458 → #470 sequence.
|
||||
const res = makeRes();
|
||||
noStoreCache({}, res, () => {});
|
||||
expect(res.headers['Cache-Control']).toContain('no-store');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user