File size: 4,989 Bytes
2b64d42 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = join(__dirname, '..');
// Issue #97 — sub2api and similar middlebox aggregators priority-cache
// proxy responses unless they see an explicit Cache-Control: no-store.
// no-cache (the prior value) only meant "revalidate before serving from
// cache" — some aggregators ignore that and return stale chunks for
// fresh requests. This regression test asserts every per-request
// response surface ships no-store.
function readSrc(rel) {
return readFileSync(join(REPO_ROOT, rel), 'utf-8');
}
describe('HTTP Cache-Control: no-store on per-request responses (issue #97)', () => {
it('json() helper in server.js sets Cache-Control: no-store', () => {
const src = readSrc('src/server.js');
const helper = src.match(/function json\(res, status, body\) \{[\s\S]*?\n\}/);
assert.ok(helper, 'json() helper not found');
assert.match(helper[0], /'Cache-Control':\s*'no-store'/, 'json() must set Cache-Control: no-store');
assert.doesNotMatch(helper[0], /'Cache-Control':\s*'no-cache'/, 'json() must not use no-cache (cacheable by spec)');
});
it('chat.js stream headers set Cache-Control: no-store', () => {
const src = readSrc('src/handlers/chat.js');
// Find the stream-header block (Content-Type: text/event-stream)
const block = src.match(/'Content-Type':\s*'text\/event-stream',[\s\S]*?'X-Accel-Buffering':\s*'no'/);
assert.ok(block, 'chat stream header block not found');
assert.match(block[0], /'Cache-Control':\s*'no-store'/);
assert.doesNotMatch(block[0], /'Cache-Control':\s*'no-cache'/);
});
it('messages.js stream headers set Cache-Control: no-store', () => {
const src = readSrc('src/handlers/messages.js');
const block = src.match(/'Content-Type':\s*'text\/event-stream',[\s\S]*?'X-Accel-Buffering':\s*'no'/);
assert.ok(block, 'messages stream header block not found');
assert.match(block[0], /'Cache-Control':\s*'no-store'/);
assert.doesNotMatch(block[0], /'Cache-Control':\s*'no-cache'/);
});
it('responses.js stream headers set Cache-Control: no-store', () => {
const src = readSrc('src/handlers/responses.js');
const block = src.match(/'Content-Type':\s*'text\/event-stream',[\s\S]*?'X-Accel-Buffering':\s*'no'/);
assert.ok(block, 'responses stream header block not found');
assert.match(block[0], /'Cache-Control':\s*'no-store'/);
assert.doesNotMatch(block[0], /'Cache-Control':\s*'no-cache'/);
});
it('json() actually emits Cache-Control: no-store on the wire', async () => {
// Spin up the route handler with a fake req/res and capture writeHead.
// Use a path that returns immediately without any account/auth state:
// /v1/models hits handleModels() which is pure. We have to bypass the
// API-key gate, but the simplest unit-level check is to import json
// indirectly by exercising any code path that calls it.
//
// Easier: spin a mini http.createServer with the same json helper
// signature and assert. But we can also just verify the writeHead
// call shape from the source — already done above. Add a runtime
// smoke test that imports the server module and triggers a no-auth
// 401 against /v1/models, then inspects the response.
const http = await import('node:http');
const { startServer } = await import('../src/server.js');
const { config } = await import('../src/config.js');
// Bind to a random port and ensure auth is required so /v1/models 401s
// through json() — we just need to see the response headers.
const prevPort = config.port;
const prevApiKey = config.apiKey;
config.port = 0;
config.apiKey = 'test-key-no-store';
let server;
try {
server = startServer();
await new Promise((resolve) => {
const tries = setInterval(() => {
const addr = server.address?.();
if (addr && typeof addr === 'object') { clearInterval(tries); resolve(); }
}, 10);
});
const port = server.address().port;
const res = await new Promise((resolve, reject) => {
const req = http.request({ host: '127.0.0.1', port, path: '/v1/models', method: 'GET' }, resolve);
req.on('error', reject);
req.end();
});
assert.equal(res.statusCode, 401, 'expected 401 without auth');
assert.equal(res.headers['cache-control'], 'no-store',
`expected Cache-Control: no-store, got: ${res.headers['cache-control']}`);
// Drain
await new Promise((resolve) => { res.on('data', () => {}); res.on('end', resolve); });
} finally {
config.port = prevPort;
config.apiKey = prevApiKey;
if (server) await new Promise((resolve) => server.close(resolve));
}
});
});
|