Discovery cache with per-kind TTL and size cap (BL-0051)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
12c54e5ffb
commit
aad2761dc9
2 files changed
+163
No files matched your search
@@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { DiscoveryCache } = require('../discovery/cache');
|
||||
|
||||
const DAY = 86400000;
|
||||
|
||||
function setup(t, options = {}) {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'circus-dcache-'));
|
||||
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
||||
const clock = { t: Date.UTC(2026, 0, 1) };
|
||||
const cache = new DiscoveryCache(dir, { ...options, now: () => clock.t });
|
||||
return { dir, clock, cache };
|
||||
}
|
||||
|
||||
test('cache round-trips an entry', (t) => {
|
||||
const { clock, cache } = setup(t);
|
||||
cache.put('title', 42, { a: 1 });
|
||||
const entry = cache.get('title', 42);
|
||||
assert.deepEqual(entry.data, { a: 1 });
|
||||
assert.equal(entry.fetchedAt, new Date(clock.t).toISOString());
|
||||
assert.equal(entry.schema, 1);
|
||||
assert.equal(entry.kind, 'title');
|
||||
});
|
||||
|
||||
test('freshness follows TTL per kind', (t) => {
|
||||
const { clock, cache } = setup(t);
|
||||
cache.put('fresh', 'global', []);
|
||||
cache.put('title', 1, {});
|
||||
clock.t += 8 * DAY;
|
||||
assert.equal(cache.isFresh('fresh', 'global'), false);
|
||||
assert.equal(cache.isFresh('title', 1), true);
|
||||
clock.t += 23 * DAY;
|
||||
assert.equal(cache.isFresh('title', 1), false);
|
||||
assert.notEqual(cache.get('title', 1), null);
|
||||
assert.equal(cache.list('title').length, 1);
|
||||
});
|
||||
|
||||
test('prune keeps the newest maxFiles', (t) => {
|
||||
const { clock, cache } = setup(t, { maxFiles: 3 });
|
||||
for (let i = 1; i <= 5; i++) { clock.t += 1000; cache.put('title', i, { i }); }
|
||||
assert.equal(cache.stats().files, 3);
|
||||
assert.equal(cache.get('title', 1), null);
|
||||
assert.equal(cache.get('title', 2), null);
|
||||
assert.notEqual(cache.get('title', 5), null);
|
||||
});
|
||||
|
||||
test('corrupt entry is deleted', (t) => {
|
||||
const { dir, cache } = setup(t);
|
||||
cache.put('recs', 7, {});
|
||||
const file = path.join(dir, 'recs', fs.readdirSync(path.join(dir, 'recs'))[0]);
|
||||
fs.writeFileSync(file, '{broken');
|
||||
assert.equal(cache.get('recs', 7), null);
|
||||
assert.equal(fs.existsSync(file), false);
|
||||
});
|
||||
|
||||
test('odd keys are file-safe', (t) => {
|
||||
const { dir, cache } = setup(t);
|
||||
cache.put('tagTop', 'genre:Slice of Life/../x', [1]);
|
||||
const files = fs.readdirSync(path.join(dir, 'tagTop'));
|
||||
assert.equal(files.length, 1);
|
||||
assert.deepEqual(fs.readdirSync(dir), ['tagTop']);
|
||||
assert.deepEqual(cache.get('tagTop', 'genre:Slice of Life/../x').data, [1]);
|
||||
});
|
||||
|
||||
test('unknown kind throws', (t) => {
|
||||
const { cache } = setup(t);
|
||||
assert.throws(() => cache.put('nope', 1, {}), TypeError);
|
||||
assert.throws(() => cache.get('nope', 1), TypeError);
|
||||
});
|
||||
Reference in new issue
Block a user