hub-api-no-retry-flag.test.mjs
64 lines 3.0 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day 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: the default branch for non-GET still allows one retry (we didn't
30 // accidentally remove the existing safety net for normal POSTs).
31 assert.match(hubJs, /method === 'GET' \|\| method === 'HEAD'\)\s*\?\s*2\s*:\s*1/);
32 });
33
34 test('api() strips noRetry from opts before forwarding to fetch()', () => {
35 // Otherwise fetch sees a non-standard init key. Some browsers warn / future ones may throw.
36 assert.match(
37 hubJs,
38 /const\s*\{\s*noRetry\s*:\s*_noRetry\s*,\s*\.\.\.fetchOpts\s*\}\s*=\s*opts/,
39 'api() should destructure noRetry out of opts before spreading into fetch()',
40 );
41 assert.match(hubJs, /\.\.\.fetchOpts/);
42 });
43
44 test('Re-index button POSTs /api/v1/index with noRetry: true', () => {
45 // The whole point of the flag — the only known caller right now MUST set it,
46 // otherwise the bridge double-fires under gateway timeout.
47 const reindexBlock = hubJs.match(
48 /btnReindex\.onclick\s*=\s*async[\s\S]{0,1200}?api\([^)]+\)/,
49 );
50 assert.ok(reindexBlock, 'btnReindex.onclick should call api(...)');
51 assert.match(
52 reindexBlock[0],
53 /api\('\/api\/v1\/index',\s*\{\s*method:\s*'POST',\s*noRetry:\s*true\s*\}\)/,
54 'Re-index call must include noRetry: true to prevent duplicate bridge invocations',
55 );
56 });
57
58 test('Re-index toast surfaces cache-skip detail when present (no regression in plain message)', () => {
59 // After PR feat/bridge-embed-hash-cache, the bridge returns chunksSkippedCached so the
60 // user can see when an incremental re-index was fast because most chunks were cached.
61 // We assert the toast composition logic; the wording itself can evolve.
62 assert.match(hubJs, /chunksSkippedCached/);
63 assert.match(hubJs, /chunksEmbedded/);
64 });
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 1 day ago