hub-api-no-retry-flag.test.mjs
sha256:8d46372e39d2d5a54fd93a8b1c27922fe0d9b22a72197345f1d2c71701cc4ce2
feat(auth): persistent login system + C7 session introspection
Human
minor
⚠ breaking
17 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: 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:8d46372e39d2d5a54fd93a8b1c27922fe0d9b22a72197345f1d2c71701cc4ce2
feat(auth): persistent login system + C7 session introspection
Human
minor
⚠
17 days ago
sha256:6a102aafafdfe7e70a24f4e59740200f0ee713ce7915f1b53e9d4ba5ee8b4410
Initial Muse snapshot
Human
48 days ago