aboutsummaryrefslogtreecommitdiffstats
path: root/tools/public-feed-cache-test.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/public-feed-cache-test.js')
-rw-r--r--tools/public-feed-cache-test.js50
1 files changed, 50 insertions, 0 deletions
diff --git a/tools/public-feed-cache-test.js b/tools/public-feed-cache-test.js
new file mode 100644
index 0000000..77f833a
--- /dev/null
+++ b/tools/public-feed-cache-test.js
@@ -0,0 +1,50 @@
+import assert from 'node:assert/strict';
+import { buildSnapshot, createSnapshotCache, snapshotResponse } from '../feed/cache.js';
+import { popularCandidates } from '../src/popular-feed.js';
+const id = 'a'.repeat(64), author = 'b'.repeat(64);
+const reaction = (pk, kind = 7, tags = []) => ({ kind, pubkey: pk, created_at: 100, tags: [['e', id], ...tags] });
+const post = { kind: 1, id, pubkey: author, created_at: 90, content: 'This is a post about the things we build together.', tags: [] };
+assert.equal(popularCandidates([reaction('c'), reaction('c')]).ids.length, 0);
+assert.equal(popularCandidates([reaction(author), reaction('c')]).qualifies(post), false);
+assert.equal(popularCandidates([reaction('c'), reaction('d')]).qualifies(post), true);
+assert.equal(popularCandidates([reaction('zap-service', 9735, [['P', 'c']]), reaction('c')]).ids.length, 0);
+const snapshot = await buildSnapshot(async (filter) => {
+ if (filter.ids) return [post, post];
+ if (filter.kinds[0] === 0) return [{ kind: 0, pubkey: author, created_at: 1, content: '{}' }, { kind: 0, pubkey: author, created_at: 2, content: '{}' }];
+ return [reaction('c'), reaction('d')];
+}, () => 1000);
+assert.equal(snapshot.notes.length, 1);
+assert.equal(snapshot.profiles[0].created_at, 2);
+assert.equal(snapshot.popUntil, 100);
+await assert.rejects(buildSnapshot(async () => []), /No public posts/);
+let time = 1000, builds = 0, finish;
+const cache = createSnapshotCache({ now: () => time, build: () => { builds++; return new Promise((resolve) => { finish = resolve; }); } });
+const request = new Request('http://localhost/api/feed');
+assert.equal(snapshotResponse(cache, request).status, 503);
+for (let i = 0; i < 100; i++) assert.equal(cache.read(), null);
+await Promise.resolve();
+assert.equal(builds, 1, 'cold visitors share one build');
+finish(snapshot);
+await cache.refresh();
+assert.equal(cache.read(), snapshot);
+assert.equal(snapshotResponse(cache, request).status, 200);
+assert.equal(snapshotResponse(cache, new Request(request, { method: 'POST' })).status, 405);
+assert.equal(await snapshotResponse(cache, new Request(request, { method: 'HEAD' })).text(), '');
+time += 61_000;
+for (let i = 0; i < 100; i++) assert.equal(cache.read(), snapshot);
+await Promise.resolve();
+assert.equal(builds, 2, 'stale visitors share one background build');
+finish({ ...snapshot, generatedAt: time });
+await cache.refresh();
+assert.equal(cache.read().generatedAt, time);
+let failures = 0;
+const failing = createSnapshotCache({ initial: snapshot, now: () => time, build: async () => { failures++; throw Error('offline'); }, onError: () => {} });
+assert.equal(failing.read(), snapshot);
+await failing.refresh();
+assert.equal(failing.read(), snapshot);
+await failing.refresh();
+assert.equal(failures, 1, 'failed refreshes are throttled and preserve stale data');
+time += 86400_000;
+assert.equal(failing.read(), null, 'expired snapshots are not served');
+await failing.refresh();
+console.log('✓ public feed selection, profiles, single flight, stale refresh, failure recovery, expiry and HTTP behavior');