cursedbelt-server 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,908 @@
1
+ /**
2
+ * The suite `createBinaryStore` never had.
3
+ *
4
+ * ── 🔴 Why this file is new rather than ported ──────────────────────────────────────
5
+ * The task that moved this module here said "with the ported suite in the count".
6
+ * There was no suite to port. Measured 2026-09-17 across all five apps that carried a
7
+ * copy — `roms`, `family`, `music`, `vault`, `collections` — **no test anywhere
8
+ * constructed a real `createBinaryStore` and exercised it**. All 38 consumer sites
9
+ * import the TYPES (`import type { BinaryStore }`) and substitute the fake; the 1,036
10
+ * lines that mint the token, chunk the upload, memoise the absence and count the
11
+ * repeats were run only in production.
12
+ *
13
+ * That is worse than it sounds, because this module's own comments cite tests that do
14
+ * not exist — {@link BINARY_ABSENCE_TTL_MS} says both of its bypasses are *"proven by
15
+ * `forgetMisses`' tests"*, and nothing proved either. Publishing an untested 1,036-line
16
+ * module and then telling five apps to depend on it would have converted five private
17
+ * copies into one shared, still-unproven dependency, which is a worse arrangement than
18
+ * the fork it replaces: the same risk, now with one blast radius instead of five.
19
+ *
20
+ * So the deliverable is the module AND its first real coverage. Everything below is an
21
+ * assertion about a behaviour the file's own prose claims — the memo demotions, the
22
+ * 429-is-not-a-404 rule, the chunk fallback, the stable window, the two-barrier delete
23
+ * — each one a documented incident that nothing could have caught a regression of.
24
+ *
25
+ * `fetch` is stubbed at the global, so nothing here touches the network or a key
26
+ * server; the RSA keypair is generated in-process and the signature is verified with
27
+ * `node:crypto`, which is what makes the token assertions real rather than shape-deep.
28
+ */
29
+ import { afterAll, afterEach, beforeEach, describe, expect, it } from 'bun:test';
30
+ import { generateKeyPairSync, verify as cryptoVerify } from 'node:crypto';
31
+ import {
32
+ AUTO_CHUNK_THRESHOLD_BYTES,
33
+ BINARY_ABSENCE_TTL_MS,
34
+ BINARY_REPEAT_THRESHOLD,
35
+ BINARY_REPEAT_WINDOW_MS,
36
+ type BinaryStoreConfig,
37
+ binaryStoreTenant,
38
+ createBinaryStore,
39
+ normalizePrivateKeyPem,
40
+ readBinaryStoreEnv,
41
+ repeatedBinaryKeys,
42
+ resetRepeatedBinaryKeys,
43
+ resolvedMediaTenant,
44
+ wantsFresh,
45
+ } from './binaryStore';
46
+ import { binaryStoreFakeDefaults } from './binaryStoreFake';
47
+
48
+ // One 2048-bit keypair for the whole file — generation is the slow part, and every
49
+ // assertion below only needs *a* valid PKCS8 PEM and its public half.
50
+ const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
51
+ const PEM = privateKey.export({ type: 'pkcs8', format: 'pem' }).toString();
52
+
53
+ const BASE = 'https://binary-server.test';
54
+ const INTERNAL = 'http://127.0.0.1:3099';
55
+
56
+ // ── the fetch stub ────────────────────────────────────────────────────────────────
57
+ type Call = { url: string; init: RequestInit | undefined };
58
+ let calls: Call[] = [];
59
+ let reply: (url: string, init: RequestInit | undefined) => Response;
60
+ const realFetch = globalThis.fetch;
61
+ const realWarn = console.warn;
62
+ let warnings: string[] = [];
63
+
64
+ beforeEach(() => {
65
+ calls = [];
66
+ warnings = [];
67
+ reply = () => new Response(null, { status: 404 });
68
+ globalThis.fetch = ((input: RequestInfo | URL, init?: RequestInit) => {
69
+ const url = typeof input === 'string' ? input : String(input);
70
+ calls.push({ url, init });
71
+ return Promise.resolve(reply(url, init));
72
+ }) as unknown as typeof fetch;
73
+ console.warn = (...args: unknown[]) => {
74
+ warnings.push(args.map(String).join(' '));
75
+ };
76
+ // The repeat register is module-level by design (it measures the PROCESS), so a
77
+ // spec that did not clear it would inherit the previous one's counts.
78
+ resetRepeatedBinaryKeys();
79
+ });
80
+
81
+ afterEach(() => {
82
+ globalThis.fetch = realFetch;
83
+ console.warn = realWarn;
84
+ });
85
+
86
+ afterAll(() => {
87
+ globalThis.fetch = realFetch;
88
+ console.warn = realWarn;
89
+ });
90
+
91
+ /** A store on a controllable clock. `now` drives the memos; token times use the real one. */
92
+ let clock = 1_700_000_000_000;
93
+ const makeStore = (over: Partial<BinaryStoreConfig> = {}) => {
94
+ clock = 1_700_000_000_000;
95
+ return createBinaryStore({
96
+ baseUrl: BASE,
97
+ appId: 'roms',
98
+ issuer: 'roms-issuer',
99
+ privateKey: PEM,
100
+ now: () => clock,
101
+ ...over,
102
+ });
103
+ };
104
+
105
+ /**
106
+ * The `?token=` of a URL, decoded.
107
+ *
108
+ * Read with a regex rather than `new URL().searchParams`: happy-dom registers its own
109
+ * `URL` global from this repo's test preload, and it throws `Invalid URL` on these.
110
+ */
111
+ const tokenOf = (url: string): { header: any; payload: any; signed: string; sig: string } => {
112
+ const raw = decodeURIComponent(/[?&]token=([^&]*)/.exec(url)?.[1] ?? '');
113
+ const [h, p, s] = raw.split('.');
114
+ return {
115
+ header: JSON.parse(Buffer.from(h ?? '', 'base64url').toString('utf8')),
116
+ payload: JSON.parse(Buffer.from(p ?? '', 'base64url').toString('utf8')),
117
+ signed: `${h}.${p}`,
118
+ sig: s ?? '',
119
+ };
120
+ };
121
+
122
+ const lastUrl = () => calls[calls.length - 1]?.url ?? '';
123
+ const ok = (body: BodyInit | null = null, init: ResponseInit = {}) => new Response(body, init);
124
+ const json = (value: unknown) =>
125
+ new Response(JSON.stringify(value), { headers: { 'content-type': 'application/json' } });
126
+
127
+ // ═══════════════════════════════════════════════════════════════════════════════════
128
+
129
+ describe('the token it mints', () => {
130
+ it('is an RS256 JWT that verifies against the tenant public key', async () => {
131
+ const store = makeStore();
132
+ const { header, payload, signed, sig } = tokenOf(await store.mediaUrl('art/zelda.png'));
133
+
134
+ expect(header).toEqual({ alg: 'RS256', typ: 'JWT' });
135
+ expect(payload.iss).toBe('roms-issuer');
136
+ // 🔴 The assertion that makes this a real test and not a shape check: binary-server
137
+ // holds only the PUBLIC half, so a token it cannot verify is a 403 in production.
138
+ expect(
139
+ cryptoVerify('sha256', Buffer.from(signed), publicKey, Buffer.from(sig, 'base64url')),
140
+ 'binary-server would refuse this token',
141
+ ).toBe(true);
142
+ });
143
+
144
+ it('app-prefixes the key into the `k` claim and expires it', async () => {
145
+ const store = makeStore();
146
+ const { payload } = tokenOf(await store.mediaUrl('art/zelda.png'));
147
+ expect(payload.k).toBe('roms/art/zelda.png');
148
+ expect(payload.exp - payload.iat).toBe(60); // DOWNLOAD_TOKEN_TTL_SECONDS
149
+ });
150
+
151
+ it('carries filename and disposition as `fn`/`dl`', async () => {
152
+ const store = makeStore();
153
+ const { payload } = tokenOf(
154
+ await store.mediaUrl('rom/x', { filename: 'Zelda.nds', disposition: 'attachment' }),
155
+ );
156
+ expect(payload.fn).toBe('Zelda.nds');
157
+ expect(payload.dl).toBe('attachment');
158
+ });
159
+
160
+ it('percent-encodes each key SEGMENT and keeps `/` literal', async () => {
161
+ // binary-server decodes the whole path before matching it to the token, so a key
162
+ // whose segment contains a space or a `#` must survive as one segment.
163
+ const store = makeStore();
164
+ const url = await store.mediaUrl('box art/Zelda #1.png');
165
+ expect(url).toContain('/media/roms/box%20art/Zelda%20%231.png?');
166
+ expect(tokenOf(url).payload.k).toBe('roms/box art/Zelda #1.png');
167
+ });
168
+ });
169
+
170
+ describe('normalizePrivateKeyPem', () => {
171
+ it('accepts a raw PEM unchanged', () => {
172
+ expect(normalizePrivateKeyPem(` ${PEM} `)).toBe(PEM.trim());
173
+ });
174
+
175
+ it('accepts a PEM whose newlines arrived as literal backslash-n', () => {
176
+ // How a private key survives a round trip through an .env file. A literal `\n` is
177
+ // not whitespace, so the trim leaves it and the replace restores the real newline.
178
+ expect(normalizePrivateKeyPem(PEM.replaceAll('\n', '\\n'))).toBe(PEM);
179
+ });
180
+
181
+ it('accepts the base64-wrapped form `register-app` prints', () => {
182
+ expect(normalizePrivateKeyPem(Buffer.from(PEM).toString('base64'))).toBe(PEM);
183
+ });
184
+
185
+ it('refuses something that is neither, naming what it is not', () => {
186
+ // Buffer.from(…, 'base64') never throws — it yields garbage — so THIS is the
187
+ // branch that actually fires for a malformed key.
188
+ expect(() => normalizePrivateKeyPem('not-a-key')).toThrow(/not a PEM/);
189
+ });
190
+ });
191
+
192
+ describe('put', () => {
193
+ it('uploads whole bytes with the mime and the upload TTL', async () => {
194
+ const store = makeStore();
195
+ reply = () => ok(null, { status: 201 });
196
+ await store.put('rom/abc', new Uint8Array([1, 2, 3]), 'application/octet-stream');
197
+
198
+ expect(calls).toHaveLength(1);
199
+ const call = calls[0]!;
200
+ expect(call.url).toContain(`${BASE}/upload/roms/rom/abc?`);
201
+ expect(call.init?.method).toBe('PUT');
202
+ expect(call.init?.headers).toEqual({ 'content-type': 'application/octet-stream' });
203
+ expect(new Uint8Array(call.init?.body as Uint8Array)).toEqual(new Uint8Array([1, 2, 3]));
204
+ expect(tokenOf(call.url).payload.exp - tokenOf(call.url).payload.iat).toBe(600);
205
+ });
206
+
207
+ it('sends title and immutable as query parameters only when asked', async () => {
208
+ const store = makeStore();
209
+ reply = () => ok(null, { status: 201 });
210
+ await store.put('rom/a', new Uint8Array([1]), undefined, { title: ' My ROM ', immutable: true });
211
+ expect(lastUrl()).toContain('&title=My%20ROM');
212
+ expect(lastUrl()).toContain('&immutable=1');
213
+
214
+ calls = [];
215
+ await store.put('rom/b', new Uint8Array([1]));
216
+ expect(lastUrl()).not.toContain('title=');
217
+ expect(lastUrl()).not.toContain('immutable=');
218
+ expect(calls[0]?.init?.headers).toBeUndefined();
219
+ });
220
+
221
+ it('explains a 413 instead of re-raising the bare status', async () => {
222
+ // The 2026-08-08 incident: eight DS cartridges read as eight unrelated mysteries.
223
+ const store = makeStore({ autoChunkBytes: 1024 });
224
+ reply = () => ok(null, { status: 413 });
225
+ await expect(store.put('rom/big', new Uint8Array(10))).rejects.toThrow(
226
+ /413 — 10 bytes was refused before it reached binary-server.*lower autoChunkBytes \(currently 1024\)/s,
227
+ );
228
+ });
229
+
230
+ it('re-raises any other failure with its status', async () => {
231
+ const store = makeStore();
232
+ reply = () => ok(null, { status: 500 });
233
+ await expect(store.put('rom/a', new Uint8Array([1]))).rejects.toThrow(
234
+ '[binary-store] put rom/a: 500',
235
+ );
236
+ });
237
+
238
+ it('🔴 takes the CHUNKED path by itself above the threshold', async () => {
239
+ // The whole point of AUTO_CHUNK_THRESHOLD_BYTES: the Cloudflare Tunnel's limit is a
240
+ // property of this client's destination, so no call site should have to know it.
241
+ const store = makeStore({ autoChunkBytes: 8 });
242
+ reply = () => ok(null, { status: 201 });
243
+ await store.put('rom/big', new Uint8Array(20), 'application/octet-stream');
244
+
245
+ // Every request went to /upload-chunk, and none to /upload — the caller said `put`.
246
+ expect(calls.every((c) => c.url.includes('/upload-chunk/'))).toBe(true);
247
+ expect(tokenOf(calls[0]!.url).payload.sid).toBeString();
248
+
249
+ // …and `Infinity` restores the pre-2026-08-08 behaviour for a caller that has a
250
+ // measured reason to want one request.
251
+ calls = [];
252
+ const direct = makeStore({ autoChunkBytes: Number.POSITIVE_INFINITY });
253
+ await direct.put('rom/big', new Uint8Array(20));
254
+ expect(calls).toHaveLength(1);
255
+ expect(calls[0]?.url).toContain('/upload/roms/rom/big?');
256
+ });
257
+
258
+ it('forgets both memos after a write, so a name-addressed key self-heals', async () => {
259
+ // roms' box art hashes the game NAME, so bytes CAN change under a key. Asserting a
260
+ // presence here would hand out a stale size and mime for the life of the process.
261
+ const store = makeStore();
262
+ reply = () => json({ size: 10, mime: 'image/png' });
263
+ await store.meta('art/zelda');
264
+ expect(store.known('art/zelda')).toBe('present');
265
+
266
+ reply = () => ok(null, { status: 201 });
267
+ await store.put('art/zelda', new Uint8Array([1]));
268
+ expect(store.known('art/zelda')).toBe('unknown');
269
+ });
270
+ });
271
+
272
+ describe('putLarge', () => {
273
+ it('sends ceil(size/chunk) chunks, one session id, ascending ci, constant tc', async () => {
274
+ const store = makeStore();
275
+ reply = () => ok(null, { status: 201 });
276
+ await store.putLarge('video/x', new Uint8Array(10), 'video/mp4', { chunkBytes: 4 });
277
+
278
+ expect(calls).toHaveLength(3); // ceil(10/4)
279
+ const claims = calls.map((c) => tokenOf(c.url).payload);
280
+ expect(claims.map((p) => p.ci)).toEqual([0, 1, 2]);
281
+ expect(claims.every((p) => p.tc === 3)).toBe(true);
282
+ expect(new Set(claims.map((p) => p.sid)).size).toBe(1);
283
+ expect(claims.every((p) => p.k === 'roms/video/x')).toBe(true);
284
+ });
285
+
286
+ it('🔴 sends the real BYTES of each slice, not a lazy view', async () => {
287
+ // The 2026-08-13 hang: a Bun.file partial view handed to fetch never sends, and
288
+ // 42 files / 35 GB failed 42/42 for a day with nothing in binary-server's log.
289
+ const store = makeStore();
290
+ reply = () => ok(null, { status: 201 });
291
+ const source = new Uint8Array([0, 1, 2, 3, 4, 5, 6]);
292
+ await store.putLarge('video/x', source, undefined, { chunkBytes: 3 });
293
+
294
+ const sent = calls.map((c) => [...new Uint8Array(c.init?.body as Uint8Array)]);
295
+ expect(sent).toEqual([[0, 1, 2], [3, 4, 5], [6]]);
296
+ });
297
+
298
+ it('accepts a Blob source and names the failing chunk', async () => {
299
+ const store = makeStore();
300
+ reply = (url) => ok(null, { status: url.includes('token') && calls.length === 2 ? 500 : 201 });
301
+ await expect(
302
+ store.putLarge('video/x', new Blob([new Uint8Array(6)]), undefined, { chunkBytes: 3 }),
303
+ ).rejects.toThrow('[binary-store] putLarge video/x chunk 2/2: 500');
304
+ });
305
+
306
+ it('never divides by a zero chunk size', async () => {
307
+ const store = makeStore();
308
+ reply = () => ok(null, { status: 201 });
309
+ await store.putLarge('video/x', new Uint8Array(3), undefined, { chunkBytes: 0 });
310
+ expect(calls).toHaveLength(3); // clamped to 1 byte per chunk, not Infinity chunks
311
+ });
312
+ });
313
+
314
+ describe('the negative memo', () => {
315
+ it('answers a second read from memory instead of the wire', async () => {
316
+ const store = makeStore();
317
+ reply = () => ok(null, { status: 404 });
318
+ expect(await store.get('art/missing')).toBeNull();
319
+ expect(calls).toHaveLength(1);
320
+
321
+ expect(await store.get('art/missing')).toBeNull();
322
+ expect(calls, 'the memo did not short-circuit the second read').toHaveLength(1);
323
+ expect(store.known('art/missing')).toBe('absent');
324
+ });
325
+
326
+ it('🔴 does NOT record an absence for a transport failure', async () => {
327
+ // family met this twice in one day: a 429 from a spent Cloudflare allowance
328
+ // rendering as a photograph that does not exist, cached for an hour.
329
+ const store = makeStore();
330
+ reply = () => ok(null, { status: 429 });
331
+ await expect(store.get('art/there')).rejects.toThrow('[binary-store] get art/there: 429');
332
+ expect(store.known('art/there')).toBe('unknown');
333
+ expect(store.cacheStats()).toEqual({ present: 0, absent: 0 });
334
+ });
335
+
336
+ it('lapses after the TTL and asks again', async () => {
337
+ const store = makeStore();
338
+ reply = () => ok(null, { status: 404 });
339
+ await store.get('art/missing');
340
+ expect(calls).toHaveLength(1);
341
+
342
+ clock += BINARY_ABSENCE_TTL_MS + 1;
343
+ await store.get('art/missing');
344
+ expect(calls, 'a lapsed absence must re-ask').toHaveLength(2);
345
+ });
346
+
347
+ it('honours a shorter TTL when an app has a measured reason', async () => {
348
+ const store = makeStore({ absenceTtlMs: 1_000 });
349
+ reply = () => ok(null, { status: 404 });
350
+ await store.get('art/missing');
351
+ clock += 1_001;
352
+ await store.get('art/missing');
353
+ expect(calls).toHaveLength(2);
354
+ });
355
+
356
+ it('is bypassed by `{ fresh: true }` — the per-request escape hatch', async () => {
357
+ const store = makeStore();
358
+ reply = () => ok(null, { status: 404 });
359
+ await store.get('art/missing');
360
+ await store.get('art/missing', { fresh: true });
361
+ expect(calls).toHaveLength(2);
362
+ });
363
+ });
364
+
365
+ describe('the positive memo', () => {
366
+ it('remembers a meta for ever and stops asking', async () => {
367
+ const store = makeStore();
368
+ reply = () => json({ size: 42, mime: 'image/png', title: 'Zelda', checksum: 'abc' });
369
+ const first = await store.meta('art/zelda');
370
+ expect(first).toEqual({
371
+ size: 42,
372
+ mime: 'image/png',
373
+ title: 'Zelda',
374
+ checksum: 'abc',
375
+ createdAt: undefined,
376
+ updatedAt: undefined,
377
+ });
378
+
379
+ clock += BINARY_ABSENCE_TTL_MS * 10;
380
+ expect(await store.meta('art/zelda')).toEqual(first!);
381
+ expect(calls, 'a present key must never expire — the key shape makes it safe').toHaveLength(1);
382
+ });
383
+
384
+ it('fills the missing fields with null rather than undefined', async () => {
385
+ const store = makeStore();
386
+ reply = () => json({ size: '7' });
387
+ expect(await store.meta('art/x')).toMatchObject({ size: 7, mime: null, title: null, checksum: null });
388
+ });
389
+
390
+ it('is demoted when the object turns out to be gone', async () => {
391
+ const store = makeStore();
392
+ reply = () => json({ size: 1 });
393
+ await store.meta('art/zelda');
394
+ expect(store.known('art/zelda')).toBe('present');
395
+
396
+ reply = () => ok(null, { status: 404 });
397
+ expect(await store.get('art/zelda')).toBeNull();
398
+ expect(store.known('art/zelda'), 'a 404 must demote a believed-present key').toBe('absent');
399
+ });
400
+
401
+ it('leaves both memos untouched when meta cannot be asked', async () => {
402
+ const store = makeStore();
403
+ reply = () => ok(null, { status: 503 });
404
+ await expect(store.meta('art/x')).rejects.toThrow('[binary-store] meta art/x: 503');
405
+ expect(store.known('art/x')).toBe('unknown');
406
+ });
407
+ });
408
+
409
+ describe('has / known / cacheStats / forgetMisses', () => {
410
+ it('has() is meta() as a boolean', async () => {
411
+ const store = makeStore();
412
+ reply = () => json({ size: 1 });
413
+ expect(await store.has('a')).toBe(true);
414
+ reply = () => ok(null, { status: 404 });
415
+ expect(await store.has('b')).toBe(false);
416
+ });
417
+
418
+ it('🔴 known() reports "unknown" for a key nobody has asked about', () => {
419
+ // Collapsing this into "absent" turns a cold process into one that draws nothing.
420
+ expect(makeStore().known('never-asked')).toBe('unknown');
421
+ });
422
+
423
+ it('known() reports a lapsed absence as unknown, not absent', async () => {
424
+ const store = makeStore();
425
+ reply = () => ok(null, { status: 404 });
426
+ await store.get('a');
427
+ expect(store.known('a')).toBe('absent');
428
+ clock += BINARY_ABSENCE_TTL_MS + 1;
429
+ expect(store.known('a')).toBe('unknown');
430
+ });
431
+
432
+ it('cacheStats counts only LIVE absences', async () => {
433
+ const store = makeStore();
434
+ reply = () => ok(null, { status: 404 });
435
+ await store.get('a');
436
+ reply = () => json({ size: 1 });
437
+ await store.meta('b');
438
+ expect(store.cacheStats()).toEqual({ present: 1, absent: 1 });
439
+
440
+ clock += BINARY_ABSENCE_TTL_MS + 1;
441
+ expect(store.cacheStats(), 'an expired row reads as a memo that never releases').toEqual({
442
+ present: 1,
443
+ absent: 0,
444
+ });
445
+ });
446
+
447
+ it('forgetMisses() drops every absence and reports how many', async () => {
448
+ const store = makeStore();
449
+ reply = () => ok(null, { status: 404 });
450
+ await store.get('a');
451
+ await store.get('b');
452
+ expect(store.forgetMisses()).toBe(2);
453
+ expect(store.cacheStats().absent).toBe(0);
454
+ });
455
+
456
+ it('🔴 forgetMisses(prefix) is a REPAIR, not a cache flush', async () => {
457
+ // The positives survive, so proving a fix costs only the keys that were missing.
458
+ const store = makeStore();
459
+ reply = () => json({ size: 1 });
460
+ await store.meta('keep/me');
461
+ reply = () => ok(null, { status: 404 });
462
+ await store.get('art/one');
463
+ await store.get('art/two');
464
+ await store.get('other/three');
465
+
466
+ expect(store.forgetMisses('art/')).toBe(2);
467
+ expect(store.known('other/three')).toBe('absent');
468
+ expect(store.known('keep/me'), 'a positive memo must survive a repair').toBe('present');
469
+ });
470
+ });
471
+
472
+ describe('stat', () => {
473
+ it('prefers /meta and pays no Range probe when it answers', async () => {
474
+ const store = makeStore();
475
+ reply = () => json({ size: 99 });
476
+ expect(await store.stat('a')).toEqual({ size: 99 });
477
+ expect(calls).toHaveLength(1);
478
+ expect(calls[0]?.url).toContain('/meta/');
479
+ });
480
+
481
+ it('🔴 falls back to the Range probe, and UNDOES the wrong absence', async () => {
482
+ // A binary-server predating /meta answers 404 for an object it HAS — indistinguishable
483
+ // by status from "no such object", so the null has to be confirmed.
484
+ const store = makeStore();
485
+ reply = (url) =>
486
+ url.includes('/meta/')
487
+ ? ok(null, { status: 404 })
488
+ : ok(null, { status: 206, headers: { 'content-range': 'bytes 0-0/4096' } });
489
+
490
+ expect(await store.stat('a')).toEqual({ size: 4096 });
491
+ expect(
492
+ store.known('a'),
493
+ 'meta recorded an absence that was WRONG; leaving it blanks the key for an hour',
494
+ ).toBe('unknown');
495
+ });
496
+
497
+ it('records the absence when both agree the key is gone', async () => {
498
+ const store = makeStore();
499
+ reply = () => ok(null, { status: 404 });
500
+ expect(await store.stat('a')).toBeNull();
501
+ expect(store.known('a')).toBe('absent');
502
+ });
503
+
504
+ it('🔴 short-circuits the WHOLE of stat on a live absence', async () => {
505
+ // Not just its meta half — otherwise a key confirmed absent still paid the Range
506
+ // probe on every call, which is most of what an absent key costs.
507
+ const store = makeStore();
508
+ reply = () => ok(null, { status: 404 });
509
+ await store.stat('a');
510
+ const after = calls.length;
511
+ expect(await store.stat('a')).toBeNull();
512
+ expect(calls.length, 'stat must consult the memo before probing').toBe(after);
513
+ });
514
+
515
+ it('falls back to content-length when there is no content-range', async () => {
516
+ const store = makeStore();
517
+ reply = (url) =>
518
+ url.includes('/meta/')
519
+ ? ok(null, { status: 404 })
520
+ : ok(null, { status: 200, headers: { 'content-length': '7' } });
521
+ expect(await store.stat('a')).toEqual({ size: 7 });
522
+ });
523
+ });
524
+
525
+ describe('the repeat-key counter', () => {
526
+ it('stays silent on ordinary traffic', async () => {
527
+ const store = makeStore();
528
+ reply = () => ok(new Uint8Array([1]));
529
+ for (let i = 0; i < BINARY_REPEAT_THRESHOLD - 1; i++) await store.get('art/a');
530
+ expect(repeatedBinaryKeys(() => clock)).toEqual([]);
531
+ expect(warnings).toEqual([]);
532
+ });
533
+
534
+ it('🔴 reports a key at the threshold, and warns exactly once', async () => {
535
+ // Every quota incident this fleet has had was this shape: one key, over and over,
536
+ // visible in Cloudflare's top-talker list and in nobody's code.
537
+ const store = makeStore();
538
+ reply = () => ok(new Uint8Array([1]));
539
+ for (let i = 0; i < BINARY_REPEAT_THRESHOLD + 5; i++) await store.get('art/a');
540
+
541
+ const hot = repeatedBinaryKeys(() => clock);
542
+ expect(hot).toHaveLength(1);
543
+ expect(hot[0]).toMatchObject({ tenant: 'roms', key: 'art/a', windowMs: BINARY_REPEAT_WINDOW_MS });
544
+ expect(hot[0]!.reads).toBeGreaterThanOrEqual(BINARY_REPEAT_THRESHOLD);
545
+ expect(warnings, 'a line per read would be the same storm in the log file').toHaveLength(1);
546
+ expect(warnings[0]).toContain('has been read 50 times');
547
+ });
548
+
549
+ it('🔴 does not count memo hits — the memo working is the cure', async () => {
550
+ const store = makeStore();
551
+ reply = () => ok(null, { status: 404 });
552
+ for (let i = 0; i < BINARY_REPEAT_THRESHOLD + 5; i++) await store.get('art/missing');
553
+ expect(calls, 'only the first read reached the wire').toHaveLength(1);
554
+ expect(
555
+ repeatedBinaryKeys(() => clock),
556
+ 'the alarm would otherwise fire loudest on the apps that fixed themselves',
557
+ ).toEqual([]);
558
+ });
559
+
560
+ it('drops a burst that stopped, instead of accusing for ever', async () => {
561
+ const store = makeStore();
562
+ reply = () => ok(new Uint8Array([1]));
563
+ for (let i = 0; i < BINARY_REPEAT_THRESHOLD; i++) await store.get('art/a');
564
+ expect(repeatedBinaryKeys(() => clock)).toHaveLength(1);
565
+ expect(repeatedBinaryKeys(() => clock + BINARY_REPEAT_WINDOW_MS)).toEqual([]);
566
+ });
567
+
568
+ it('reports the worst offender first', async () => {
569
+ const store = makeStore();
570
+ reply = () => ok(new Uint8Array([1]));
571
+ for (let i = 0; i < BINARY_REPEAT_THRESHOLD; i++) await store.get('quiet');
572
+ for (let i = 0; i < BINARY_REPEAT_THRESHOLD + 10; i++) await store.get('loud');
573
+ expect(repeatedBinaryKeys(() => clock).map((r) => r.key)).toEqual(['loud', 'quiet']);
574
+ });
575
+ });
576
+
577
+ describe('mediaUrl and the stable window', () => {
578
+ it('🔴 mints a byte-identical URL inside one window', async () => {
579
+ // A fresh token per call means a fresh URL per call, and a browser's HTTP cache
580
+ // keys on the URL — so an <img src> re-rendered re-downloads bytes it already has.
581
+ const store = makeStore();
582
+ const a = await store.mediaUrl('art/z', { stableWindowSeconds: 3600 });
583
+ const b = await store.mediaUrl('art/z', { stableWindowSeconds: 3600 });
584
+ expect(b).toBe(a);
585
+
586
+ const { payload } = tokenOf(a);
587
+ expect(payload.iat % 3600, 'iat must be snapped to the window, not to the clock').toBe(0);
588
+ expect(payload.exp - payload.iat, 'valid for TWO windows').toBe(7200);
589
+ });
590
+
591
+ it('mints a fresh token per call when no window is asked for', async () => {
592
+ const store = makeStore();
593
+ const a = tokenOf(await store.mediaUrl('art/z'));
594
+ expect(a.payload.iat % 3600 === 0 && a.payload.exp - a.payload.iat === 7200).toBe(false);
595
+ });
596
+
597
+ it('honours an explicit ttlSeconds', async () => {
598
+ const store = makeStore();
599
+ const { payload } = tokenOf(await store.mediaUrl('art/z', { ttlSeconds: 15 }));
600
+ expect(payload.exp - payload.iat).toBe(15);
601
+ });
602
+
603
+ it('🔴 always uses the PUBLIC origin — a browser cannot resolve loopback', async () => {
604
+ const store = makeStore({ internalBaseUrl: INTERNAL });
605
+ expect(await store.mediaUrl('art/z')).toStartWith(`${BASE}/media/`);
606
+ });
607
+
608
+ it('strips a trailing slash from the configured base', async () => {
609
+ const store = makeStore({ baseUrl: `${BASE}///` });
610
+ expect(await store.mediaUrl('a')).toStartWith(`${BASE}/media/`);
611
+ });
612
+ });
613
+
614
+ describe('mediaPrefixUrl', () => {
615
+ it('🔴 signs the PREFIX but points at the master', async () => {
616
+ // An HLS player resolves rung playlists RELATIVE to the master's URL and discards
617
+ // the query string, so only a prefix token can authorize the tree.
618
+ const store = makeStore();
619
+ const url = await store.mediaPrefixUrl('video/x/hls', 'video/x/hls/master.m3u8');
620
+ expect(url).toContain('/media/roms/video/x/hls/master.m3u8?');
621
+
622
+ const { payload } = tokenOf(url);
623
+ expect(payload.p).toBe('roms/video/x/hls');
624
+ expect(payload.k, 'an exact-key token 403s every segment').toBeUndefined();
625
+ });
626
+
627
+ it('takes a stable window too', async () => {
628
+ const store = makeStore();
629
+ const a = await store.mediaPrefixUrl('t', 't/m.m3u8', { stableWindowSeconds: 3600 });
630
+ expect(await store.mediaPrefixUrl('t', 't/m.m3u8', { stableWindowSeconds: 3600 })).toBe(a);
631
+ });
632
+ });
633
+
634
+ describe('internalBaseUrl — reading bytes without leaving the box', () => {
635
+ it('🔴 sends get/meta/stat to the loopback origin', async () => {
636
+ // 2026-09-08: family made 15,689 server-side reads that went out to the public name
637
+ // and back to read bytes from a daemon on the same Mac, then met a 429 wall.
638
+ const store = makeStore({ internalBaseUrl: INTERNAL });
639
+ reply = () => json({ size: 1 });
640
+ await store.meta('a');
641
+ expect(lastUrl()).toStartWith(`${INTERNAL}/meta/`);
642
+
643
+ reply = () => ok(new Uint8Array([1]));
644
+ await store.get('b');
645
+ expect(lastUrl()).toStartWith(`${INTERNAL}/media/`);
646
+ });
647
+
648
+ it('defaults to the public base when the deployment does not share the Mac', async () => {
649
+ const store = makeStore();
650
+ reply = () => ok(new Uint8Array([1]));
651
+ await store.get('b');
652
+ expect(lastUrl()).toStartWith(`${BASE}/media/`);
653
+ });
654
+ });
655
+
656
+ describe('fetchMedia', () => {
657
+ it('forwards a Range header untouched — Range IS seeking', async () => {
658
+ const store = makeStore({ internalBaseUrl: INTERNAL });
659
+ reply = () => ok(null, { status: 206 });
660
+ const res = await store.fetchMedia('song/a', { range: 'bytes=100-200' });
661
+
662
+ expect(res.status).toBe(206);
663
+ expect(calls[0]?.init?.headers).toEqual({ range: 'bytes=100-200' });
664
+ expect(calls[0]?.url).toStartWith(`${INTERNAL}/media/`);
665
+ });
666
+
667
+ it('🔴 sends NO header at all when the caller has no range', async () => {
668
+ // A literal empty string is a malformed header some origins answer 416 to.
669
+ const store = makeStore();
670
+ reply = () => ok(null);
671
+ await store.fetchMedia('song/a', { range: '' });
672
+ expect(calls[0]?.init?.headers).toBeUndefined();
673
+
674
+ calls = [];
675
+ await store.fetchMedia('song/a', { range: null });
676
+ expect(calls[0]?.init?.headers).toBeUndefined();
677
+ });
678
+
679
+ it('honours HEAD and forwards an abort signal', async () => {
680
+ const store = makeStore();
681
+ reply = () => ok(null);
682
+ const controller = new AbortController();
683
+ await store.fetchMedia('song/a', { method: 'HEAD', signal: controller.signal });
684
+ expect(calls[0]?.init?.method).toBe('HEAD');
685
+ expect(calls[0]?.init?.signal).toBe(controller.signal);
686
+ });
687
+
688
+ it('uses a one-hour stable window so caches between here and the object can work', async () => {
689
+ const store = makeStore();
690
+ reply = () => ok(null);
691
+ await store.fetchMedia('song/a');
692
+ await store.fetchMedia('song/a');
693
+ expect(calls[0]?.url).toBe(calls[1]?.url ?? '');
694
+ expect(tokenOf(calls[0]!.url).payload.iat % 3600).toBe(0);
695
+ });
696
+ });
697
+
698
+ describe('remove and removePrefix — the two-barrier gate', () => {
699
+ it('🔴 refuses without the internal secret rather than sending a bare token', async () => {
700
+ await expect(makeStore().remove('a')).rejects.toThrow(/requires internalSecret/);
701
+ expect(calls, 'nothing may go on the wire').toHaveLength(0);
702
+ });
703
+
704
+ it('sends BOTH the secret header and an op:delete token', async () => {
705
+ const store = makeStore({ internalSecret: 's3cret' });
706
+ reply = () => ok(null, { status: 204 });
707
+ await store.remove('rom/a');
708
+
709
+ expect(calls[0]?.init?.method).toBe('DELETE');
710
+ expect(calls[0]?.init?.headers).toEqual({ 'x-internal-secret': 's3cret' });
711
+ const { payload } = tokenOf(calls[0]!.url);
712
+ expect(payload.op).toBe('delete');
713
+ expect(payload.k).toBe('roms/rom/a');
714
+ expect(payload.exp - payload.iat).toBe(120);
715
+ });
716
+
717
+ it('tolerates a 404 and still records the absence', async () => {
718
+ const store = makeStore({ internalSecret: 's' });
719
+ reply = () => ok(null, { status: 404 });
720
+ await store.remove('rom/a');
721
+ expect(store.known('rom/a'), 'a surviving positive memo would link to a 404').toBe('absent');
722
+ });
723
+
724
+ it('re-raises a real delete failure', async () => {
725
+ const store = makeStore({ internalSecret: 's' });
726
+ reply = () => ok(null, { status: 500 });
727
+ await expect(store.remove('rom/a')).rejects.toThrow('[binary-store] remove rom/a: 500');
728
+ });
729
+
730
+ it('removePrefix flags the container and voids every memo under it', async () => {
731
+ const store = makeStore({ internalSecret: 's' });
732
+ reply = () => json({ size: 1 });
733
+ await store.meta('tree/a');
734
+ reply = () => ok(null, { status: 404 });
735
+ await store.get('tree/b');
736
+ await store.get('elsewhere/c');
737
+
738
+ reply = () => ok(null, { status: 204 });
739
+ await store.removePrefix('tree/');
740
+
741
+ expect(lastUrl()).toContain('?container=1&token=');
742
+ expect(tokenOf(lastUrl()).payload.op).toBe('delete');
743
+ expect(store.known('tree/a')).toBe('unknown');
744
+ // 🔴 NOT recorded as absences: the keys are not enumerable from here and a
745
+ // container is routinely refilled.
746
+ expect(store.known('tree/b')).toBe('unknown');
747
+ expect(store.known('elsewhere/c'), 'a sibling tree is untouched').toBe('absent');
748
+ });
749
+ });
750
+
751
+ describe('wantsFresh — the owner`s hard refresh', () => {
752
+ it('reads a browser hard refresh off either header', () => {
753
+ expect(wantsFresh(new Headers({ 'cache-control': 'no-cache' }))).toBe(true);
754
+ expect(wantsFresh(new Headers({ 'cache-control': 'NO-CACHE, max-age=0' }))).toBe(true);
755
+ expect(wantsFresh(new Headers({ pragma: 'no-cache' }))).toBe(true);
756
+ });
757
+
758
+ it('is false for an ordinary request', () => {
759
+ expect(wantsFresh(new Headers())).toBe(false);
760
+ expect(wantsFresh(new Headers({ 'cache-control': 'max-age=0' }))).toBe(false);
761
+ });
762
+
763
+ it('accepts a Request-shaped object as well as bare Headers', () => {
764
+ expect(wantsFresh({ headers: new Headers({ pragma: 'no-cache' }) })).toBe(true);
765
+ });
766
+ });
767
+
768
+ describe('tenant resolution', () => {
769
+ it('prefers BINARY_STORE_TENANT over the app key, so a stage gets its own', () => {
770
+ // 2026-08-21: a literal tenant made an ephemeral stage structurally unable to store
771
+ // a byte — it signed as `collections-stage` while addressing `collections/…`.
772
+ expect(binaryStoreTenant('collections', { BINARY_STORE_TENANT: 'collections-stage' })).toBe(
773
+ 'collections-stage',
774
+ );
775
+ expect(binaryStoreTenant('collections', {})).toBe('collections');
776
+ });
777
+
778
+ it('🔴 throws on a blank tenant rather than signing for "undefined/<key>"', () => {
779
+ // binary-server answers a bare {"error":"forbidden"}, which reads as a broken
780
+ // credential — and was filed as one. The check has to live on this side of the wire.
781
+ for (const bad of ['', ' ', 'undefined', 'null']) {
782
+ expect(() => binaryStoreTenant(bad, {}), `"${bad}" must not pass`).toThrow(/no tenant id/);
783
+ }
784
+ expect(() => binaryStoreTenant(undefined as unknown as string, {})).toThrow(/no tenant id/);
785
+ });
786
+
787
+ it('resolvedMediaTenant answers null for a store-less shell instead of throwing', () => {
788
+ // The same question as binaryStoreTenant, asked where a throw would 500 /healthz.
789
+ expect(resolvedMediaTenant('roms', {})).toBeNull();
790
+ expect(
791
+ resolvedMediaTenant('roms', {
792
+ BINARY_SERVER_URL: BASE,
793
+ FILE_TOKEN_ISSUER: 'i',
794
+ FILE_TOKEN_PRIVATE_KEY: 'k',
795
+ BINARY_STORE_TENANT: 'roms-stage',
796
+ }),
797
+ ).toBe('roms-stage');
798
+ });
799
+ });
800
+
801
+ describe('readBinaryStoreEnv', () => {
802
+ const full = {
803
+ BINARY_SERVER_URL: BASE,
804
+ FILE_TOKEN_ISSUER: 'roms-issuer',
805
+ FILE_TOKEN_PRIVATE_KEY: PEM,
806
+ };
807
+
808
+ it('returns null unless all three required vars are present', () => {
809
+ expect(readBinaryStoreEnv('roms', {})).toBeNull();
810
+ for (const key of Object.keys(full)) {
811
+ const partial = { ...full, [key]: ' ' };
812
+ expect(readBinaryStoreEnv('roms', partial), `${key} blank must disable the store`).toBeNull();
813
+ }
814
+ });
815
+
816
+ it('maps the standard secrets-file surface onto the config', () => {
817
+ expect(
818
+ readBinaryStoreEnv('roms', {
819
+ ...full,
820
+ BINARY_SERVER_INTERNAL_SECRET: 's3cret',
821
+ BINARY_SERVER_INTERNAL_URL: INTERNAL,
822
+ }),
823
+ ).toEqual({
824
+ baseUrl: BASE,
825
+ appId: 'roms',
826
+ issuer: 'roms-issuer',
827
+ // Trimmed on the way out of the env — a trailing newline in a secrets file is
828
+ // invisible and must not reach `createPrivateKey`.
829
+ privateKey: PEM.trim(),
830
+ internalSecret: 's3cret',
831
+ internalBaseUrl: INTERNAL,
832
+ });
833
+ });
834
+
835
+ it('leaves the optional fields undefined rather than empty', () => {
836
+ const cfg = readBinaryStoreEnv('roms', { ...full, BINARY_SERVER_INTERNAL_SECRET: ' ' });
837
+ expect(cfg?.internalSecret).toBeUndefined();
838
+ expect(cfg?.internalBaseUrl).toBeUndefined();
839
+ });
840
+
841
+ it('produces a config that actually signs', async () => {
842
+ const store = createBinaryStore(readBinaryStoreEnv('roms', full)!);
843
+ const { signed, sig } = tokenOf(await store.mediaUrl('a'));
844
+ expect(cryptoVerify('sha256', Buffer.from(signed), publicKey, Buffer.from(sig, 'base64url'))).toBe(true);
845
+ });
846
+ });
847
+
848
+ describe('the threshold constant', () => {
849
+ it('is 64 MiB — early is free, late is a 413', () => {
850
+ expect(AUTO_CHUNK_THRESHOLD_BYTES).toBe(64 * 1024 * 1024);
851
+ });
852
+ });
853
+
854
+ // ═══════════════════════════════════════════════════════════════════════════════════
855
+
856
+ describe('binaryStoreFakeDefaults', () => {
857
+ it('satisfies every member of the interface', () => {
858
+ // 🔴 The assertion the fake exists for: when a method is added to `BinaryStore`,
859
+ // THIS is what fails to compile, in the package that owns the interface — instead
860
+ // of twenty-two suites in apps whose commits nobody is looking at.
861
+ const fake = binaryStoreFakeDefaults('collections');
862
+ const real = makeStore();
863
+ expect(Object.keys(fake).sort()).toEqual(Object.keys(real).sort());
864
+ });
865
+
866
+ it('answers "not here" without inventing a result', async () => {
867
+ const fake = binaryStoreFakeDefaults('collections');
868
+ expect(fake.appId).toBe('collections');
869
+ expect(await fake.get('a')).toBeNull();
870
+ expect(await fake.meta('a')).toBeNull();
871
+ expect(await fake.stat('a')).toBeNull();
872
+ expect(await fake.has('a')).toBe(false);
873
+ expect((await fake.fetchMedia('a')).status).toBe(404);
874
+ expect(fake.cacheStats()).toEqual({ present: 0, absent: 0 });
875
+ });
876
+
877
+ it('🔴 knows nothing, rather than claiming a key is absent', () => {
878
+ // "absent" would make a listing route's memo-only branch look exercised when
879
+ // nothing was ever memoized — the one thing a test of that branch is for.
880
+ expect(binaryStoreFakeDefaults('c').known('anything')).toBe('unknown');
881
+ });
882
+
883
+ it('🔴 never lies about work done', () => {
884
+ // An app's repair route reports this number to the owner; a fabricated one makes a
885
+ // broken repair read as a working one.
886
+ expect(binaryStoreFakeDefaults('c').forgetMisses()).toBe(0);
887
+ expect(binaryStoreFakeDefaults('c').forgetMisses('art/')).toBe(0);
888
+ });
889
+
890
+ it('carries the key in mediaUrl, and BOTH names in mediaPrefixUrl', async () => {
891
+ const fake = binaryStoreFakeDefaults('collections');
892
+ expect(await fake.mediaUrl('art/z')).toContain('/collections/art/z');
893
+ const prefixed = await fake.mediaPrefixUrl('tree', 'tree/master.m3u8');
894
+ // Signing the wrong one 403s every segment while the master itself loads fine.
895
+ expect(prefixed).toContain('/collections/tree/master.m3u8');
896
+ expect(prefixed).toContain('p=tree');
897
+ });
898
+
899
+ it('is inert on every mutator', async () => {
900
+ const fake = binaryStoreFakeDefaults('c');
901
+ await fake.put('a', new Uint8Array([1]));
902
+ await fake.putLarge('a', new Uint8Array([1]));
903
+ await fake.remove('a');
904
+ await fake.removePrefix('a');
905
+ expect(calls, 'a fake must never reach the network').toHaveLength(0);
906
+ expect(await fake.signToken({ k: 'c/a' }, 60)).toBe('fake-token');
907
+ });
908
+ });