hub-api-no-retry-flag.test.mjs
63 lines 2.9 KB
Raw
sha256:fbe982a22c05c6fe2e93876f250deecdb43d883f648b4e1810c7546caa9f17db docs: activate KNOWTATION- board identity and preserve livi… Human minor ⚠ breaking 2 days ago
1 /**
2 * Contract: web/hub/hub.js api() must support `noRetry: true` and the Re-index
3 * button must use it for POST /api/v1/index. Without this, a 30s gateway timeout
4 * (Netlify Function cap) causes the browser to fire a SECOND bridge index while
5 * the first is still running, double-billing DeepInfra and worsening contention.
6 *
7 * String-grep test (matches the existing `test/hub-index-stale-banner.test.mjs`
8 * convention) — hub.js is browser code without a Node-runnable harness.
9 */
10
11 import { readFileSync } from 'node:fs';
12 import { fileURLToPath } from 'node:url';
13 import { dirname, join } from 'node:path';
14 import test from 'node:test';
15 import assert from 'node:assert/strict';
16
17 const root = join(dirname(fileURLToPath(import.meta.url)), '..');
18 const hubJs = readFileSync(join(root, 'web/hub/hub.js'), 'utf8');
19
20 test('api() recognizes noRetry: true (zero retries when set, regardless of method)', () => {
21 // Implementation detail we want to lock in: the noRetry branch must short-circuit
22 // maxNetworkRetries to 0 for ANY method (including GET), so future callers of
23 // expensive idempotent endpoints can opt out of the default 2x GET retry too.
24 assert.match(
25 hubJs,
26 /opts\.noRetry\s*===\s*true[\s\S]{0,80}\?\s*0/,
27 'api() should set maxNetworkRetries to 0 when opts.noRetry === true',
28 );
29 // Sanity: mutations default to zero network retries (401→refresh→once only).
30 assert.match(hubJs, /method === 'GET' \|\| method === 'HEAD'\s*\?\s*2\s*:\s*0/);
31 });
32
33 test('api() strips noRetry from opts before forwarding to fetch()', () => {
34 // Otherwise fetch sees a non-standard init key. Some browsers warn / future ones may throw.
35 assert.match(
36 hubJs,
37 /const\s*\{\s*noRetry\s*:\s*_noRetry\s*,\s*\.\.\.fetchOpts\s*\}\s*=\s*opts/,
38 'api() should destructure noRetry out of opts before spreading into fetch()',
39 );
40 assert.match(hubJs, /\.\.\.fetchOpts/);
41 });
42
43 test('Re-index button POSTs /api/v1/index with noRetry: true', () => {
44 // The whole point of the flag — the only known caller right now MUST set it,
45 // otherwise the bridge double-fires under gateway timeout.
46 const reindexBlock = hubJs.match(
47 /btnReindex\.onclick\s*=\s*async[\s\S]{0,1200}?api\([^)]+\)/,
48 );
49 assert.ok(reindexBlock, 'btnReindex.onclick should call api(...)');
50 assert.match(
51 reindexBlock[0],
52 /api\('\/api\/v1\/index',\s*\{\s*method:\s*'POST',\s*noRetry:\s*true\s*\}\)/,
53 'Re-index call must include noRetry: true to prevent duplicate bridge invocations',
54 );
55 });
56
57 test('Re-index toast surfaces cache-skip detail when present (no regression in plain message)', () => {
58 // After PR feat/bridge-embed-hash-cache, the bridge returns chunksSkippedCached so the
59 // user can see when an incremental re-index was fast because most chunks were cached.
60 // We assert the toast composition logic; the wording itself can evolve.
61 assert.match(hubJs, /chunksSkippedCached/);
62 assert.match(hubJs, /chunksEmbedded/);
63 });
File History 1 commit
sha256:fbe982a22c05c6fe2e93876f250deecdb43d883f648b4e1810c7546caa9f17db docs: activate KNOWTATION- board identity and preserve livi… Human minor 2 days ago