Files
OmniRoute/tests/unit/model-catalog-cache-swr-8728.test.ts
Diego Rodrigues de Sa e Souza 37d70785c7 fix(build): repair broken production build, red lint gate and SWR crash (#10198)
The Build CI job is advisory, so eight module-level defects from eight
different PRs accumulated on release/v3.8.50 until `npm run build` failed
with 7 Turbopack errors and `npm run lint` with 14.

Build (link-time):
- modelSelectModalHelpers.ts: a lost `}` swallowed PROVIDER_TEST_CHUNK_SIZE
  into isProviderModelHidden's body (#9011).
- videoGeneration.ts: handleFalVideoGeneration imported twice; the standalone
  falHandler.ts is superseded by the provider-neutral mediaGeneration/fal.ts
  and is removed here (#9982 over #9969).
- catalog.ts: re-exported and called the injectable SWR policy that #9199
  deliberately replaced with a fixed 30s bound. Fixed on the consumer side —
  restoring the accessor would resurrect the unbounded window #9199 removed
  after measuring a 41s catalog build in production.
- tinycmsSigner.ts: generated wasm-bindgen glue kept a sidecar
  `new URL('wasm_signer_bg.wasm', import.meta.url)` that no file backs;
  Turbopack resolves it statically. The module ships inlined as WASM_BASE64
  and the only caller always passes it explicitly (#8736/#10087).
- conolDiscovery.ts: imported getProviderOutboundGuard from outboundUrlGuard,
  which does not export it. Fixed on the consumer side: outboundUrlGuard.ts is
  loaded by the packaged CLI without a tsconfig, so it must stay free of
  `@/`-aliased imports (#7682).

Runtime (the build never caught this one):
- catalogCache.ts::scheduleBackgroundRefresh had two dangling statements
  referencing undeclared `inFlight`/`promise`, so EVERY stale-while-revalidate
  read threw a ReferenceError. Surfaced by realigning the #8728 suite, which
  #9199 left asserting a removed contract.

Lint:
- driverFactory.test.ts: a case inserted between the preceding test's `finally`
  and its `});` left the file unparseable, so the SQLite driver-cascade suite
  (26 tests) had not run since 2026-08-11 (#9173).
- providerModelsConfig.ts: imported an executor directly, crossing the G14
  boundary; routed through a new open-sse/services/zaiWebCredentials.ts (#8451).
- image-combo.test.ts: 11 `any` violations, now typed (#9499).

Validation: npm run build exit 0, npm run lint clean, typecheck:core clean,
41/41 tests green across the affected suites.

Refs #9011 #9982 #9199 #8728 #8736 #10087 #8974 #9173 #8451 #9499

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-12 15:58:40 -03:00

144 lines
5.3 KiB
TypeScript

/**
* Stale-while-revalidate for the /v1/models catalog cache (#8728).
*
* Contract note: #8728 originally shipped an injectable `CatalogCachePolicy`
* (a per-call SWR accessor + refresh scheduler) and an unbounded
* `CATALOG_STALE_WHILE_REVALIDATE_MS = Number.POSITIVE_INFINITY`. #9199 landed
* afterwards and deliberately replaced both: the window is now a fixed 30 s
* constant and the refresh is scheduled internally via `setTimeout(…, 0)`.
* See catalogCache.ts — an unbounded window let a refresh that kept failing pin
* an ancient catalog forever.
*
* These tests were left asserting the removed API, which is what broke the
* production build (catalog.ts re-exported three symbols that no longer exist).
* They are realigned here to the shipped contract: the BEHAVIOR #8728 added —
* an expired-but-recent successful entry is served immediately while a refresh
* runs behind it — is still fully covered.
*/
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-catalog-cache-8728-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const readCache = await import("../../src/lib/db/readCache.ts");
const catalogCache = await import("../../src/app/api/v1/models/catalogCache.ts");
function request() {
return new Request("http://localhost/v1/models");
}
function payload(body: string, status = 200): catalogCache.CatalogPayload {
return {
body,
headers: { "content-type": "application/json" },
status,
cacheTTL: 60_000,
};
}
async function resolve(build: (request: Request) => Promise<catalogCache.CatalogPayload>) {
return catalogCache.resolveCachedCatalogResponse(
request(),
{ corsHeaders: {}, diagnosticHeaders: {} },
build
);
}
test.beforeEach(() => {
catalogCache.__resetCatalogBuilderRunsForTest();
});
test.after(() => {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("the SWR window is a bounded constant, not an unbounded accessor", () => {
// #9199 replaced the injectable POSITIVE_INFINITY accessor with this bound so a
// refresh that keeps failing cannot pin an old catalog forever.
assert.equal(catalogCache.CATALOG_STALE_WHILE_REVALIDATE_MS, 30_000);
assert.ok(Number.isFinite(catalogCache.CATALOG_STALE_WHILE_REVALIDATE_MS));
});
test("a fresh entry is replayed without running the builder again", async () => {
const first = await resolve(async () => payload("fresh"));
assert.equal(await first.text(), "fresh");
assert.equal(catalogCache.__getCatalogBuilderRunsForTest(), 1);
const second = await resolve(async () => payload("SHOULD NOT BUILD"));
assert.equal(await second.text(), "fresh");
assert.equal(catalogCache.__getCatalogBuilderRunsForTest(), 1);
});
test("an expired successful entry is served immediately while a refresh runs behind it", async () => {
await resolve(async () => payload("old"));
catalogCache.__expireCatalogCacheForTest();
// The stale body comes back on THIS call — the caller never waits for the rebuild.
const stale = await resolve(async () => payload("new"));
assert.equal(await stale.text(), "old", "the expired-but-recent entry must be served as-is");
await catalogCache.__flushCatalogBackgroundRefreshForTest();
const refreshed = await resolve(async () => payload("SHOULD NOT BUILD"));
assert.equal(await refreshed.text(), "new", "the background refresh must replace the snapshot");
});
test("an entry aged past the SWR window is not served stale", async () => {
await resolve(async () => payload("ancient"));
catalogCache.__expireCatalogCacheForTest(catalogCache.CATALOG_STALE_WHILE_REVALIDATE_MS + 1_000);
const rebuilt = await resolve(async () => payload("rebuilt"));
assert.equal(await rebuilt.text(), "rebuilt", "past the window the caller must wait for a build");
});
test("a cached non-200 is never replayed as stale", async () => {
// Replaying a cached error as "stale" would mask an intermittent failure behind
// a fake success forever.
catalogCache.__setCatalogCacheEntryForTest(request(), {
body: "boom",
headers: {},
status: 500,
expiresAt: Date.now() - 1,
});
const res = await resolve(async () => payload("recovered"));
assert.equal(res.status, 200);
assert.equal(await res.text(), "recovered");
});
test("concurrent cold requests share a single builder run", async () => {
let release!: () => void;
const gate = new Promise<void>((resolve) => {
release = resolve;
});
const build = async () => {
await gate;
return payload("coalesced");
};
const inFlight = [resolve(build), resolve(build), resolve(build)];
release();
const bodies = await Promise.all((await Promise.all(inFlight)).map((r) => r.text()));
assert.deepEqual(bodies, ["coalesced", "coalesced", "coalesced"]);
assert.equal(
catalogCache.__getCatalogBuilderRunsForTest(),
1,
"identical concurrent requests must coalesce onto one in-flight build (#6408)"
);
});
test("a state change invalidates the cache so the next read rebuilds", async () => {
await resolve(async () => payload("before"));
readCache.invalidateDbCache();
const after = await resolve(async () => payload("after"));
assert.equal(await after.text(), "after", "a write must be reflected on the very next read");
});