From 40862f26e6aaba56e563632ff5d6cdc07441d269 Mon Sep 17 00:00:00 2001 From: "R.D." Date: Sun, 29 Mar 2026 14:21:39 -0400 Subject: [PATCH] Bypass semantic cache in combo live tests --- src/app/api/combos/test/route.ts | 5 ++ src/lib/semanticCache.ts | 23 ++++++- tests/unit/chat-combo-live-test.test.mjs | 80 ++++++++++++++++++++++++ tests/unit/combo-test-route.test.mjs | 2 + 4 files changed, 109 insertions(+), 1 deletion(-) diff --git a/src/app/api/combos/test/route.ts b/src/app/api/combos/test/route.ts index af49e6d1c1..fb9888ea29 100644 --- a/src/app/api/combos/test/route.ts +++ b/src/app/api/combos/test/route.ts @@ -1,3 +1,4 @@ +import { randomUUID } from "node:crypto"; import { NextResponse } from "next/server"; import { buildComboTestRequestBody, extractComboTestResponseText } from "@/lib/combos/testHealth"; import { getComboByName } from "@/lib/localDb"; @@ -67,6 +68,10 @@ export async function POST(request) { // Internal dashboard tests still use the normal /v1 pipeline but // bypass REQUIRE_API_KEY so admins can test with local session auth. "X-Internal-Test": "combo-health-check", + // Force a fresh execution path so combo tests cannot be satisfied by + // OmniRoute's semantic cache or other request reuse layers. + "X-OmniRoute-No-Cache": "true", + "X-Request-Id": `combo-test-${randomUUID()}`, }, body: JSON.stringify(testBody), signal: controller.signal, diff --git a/src/lib/semanticCache.ts b/src/lib/semanticCache.ts index c8674e0caa..d20e4b560e 100644 --- a/src/lib/semanticCache.ts +++ b/src/lib/semanticCache.ts @@ -29,6 +29,25 @@ function toNumber(value: unknown, fallback = 0): number { return fallback; } +function getHeaderValue( + headers: { get?: (name: string) => string | null } | Record | null | undefined, + name: string +): string | null { + if (!headers) return null; + + if (typeof headers.get === "function") { + return headers.get(name); + } + + const needle = name.toLowerCase(); + for (const [key, value] of Object.entries(headers)) { + if (key.toLowerCase() !== needle) continue; + return typeof value === "string" ? value : null; + } + + return null; +} + // ─── Singleton ───────────────── let memoryCache: LRUCache | null = null; @@ -309,7 +328,9 @@ export function getCacheStats() { * Only non-streaming, deterministic (temperature=0) requests. */ export function isCacheable(body, headers) { - if (headers?.get?.("x-omniroute-no-cache") === "true") return false; + if ((getHeaderValue(headers, "x-omniroute-no-cache") || "").toLowerCase() === "true") { + return false; + } if (body.stream !== false) return false; if ((body.temperature ?? 0) !== 0) return false; return true; diff --git a/tests/unit/chat-combo-live-test.test.mjs b/tests/unit/chat-combo-live-test.test.mjs index ea4741e785..6e3c753656 100644 --- a/tests/unit/chat-combo-live-test.test.mjs +++ b/tests/unit/chat-combo-live-test.test.mjs @@ -10,6 +10,11 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const chatRoute = await import("../../src/app/api/v1/chat/completions/route.ts"); +const { + generateSignature, + invalidateBySignature, + setCachedResponse, +} = await import("../../src/lib/semanticCache.ts"); const { clearModelUnavailability, resetAllAvailability, @@ -43,6 +48,17 @@ async function seedSuppressedConnection() { }); } +async function seedHealthyConnection() { + return providersDb.createProviderConnection({ + provider: "openai", + authType: "apikey", + name: "openai-cache-test", + apiKey: "sk-cache-test", + isActive: true, + testStatus: "active", + }); +} + function makeRequest(extraHeaders = {}) { return new Request("http://localhost/v1/chat/completions", { method: "POST", @@ -126,3 +142,67 @@ test("combo live test bypasses local cooldown and breaker state to perform a rea const updated = await providersDb.getProviderConnectionById(created.id); assert.equal(updated.testStatus, "active"); }); + +test("combo live test bypasses semantic cache and forces a fresh upstream request", async () => { + await seedHealthyConnection(); + + const signature = generateSignature( + "gpt-4o-mini", + [{ role: "user", content: "Reply with OK only." }], + 0, + 1 + ); + + setCachedResponse(signature, "gpt-4o-mini", { + id: "chatcmpl-cached", + choices: [ + { + message: { + role: "assistant", + content: "CACHED", + }, + }, + ], + }); + + const fetchCalls = []; + globalThis.fetch = async (url, init = {}) => { + fetchCalls.push({ url: String(url), init }); + return Response.json({ + id: "chatcmpl-live", + choices: [ + { + message: { + role: "assistant", + content: "LIVE", + }, + }, + ], + }); + }; + + try { + const cachedResponse = await chatRoute.POST(makeRequest()); + const cachedBody = await cachedResponse.json(); + + assert.equal(cachedResponse.status, 200); + assert.equal(fetchCalls.length, 0); + assert.equal(cachedBody.choices[0].message.content, "CACHED"); + + const liveResponse = await chatRoute.POST( + makeRequest({ + "X-Internal-Test": "combo-health-check", + "X-OmniRoute-No-Cache": "true", + "X-Request-Id": "combo-test-cache-bypass", + }) + ); + const liveBody = await liveResponse.json(); + + assert.equal(liveResponse.status, 200); + assert.equal(fetchCalls.length, 1); + assert.match(fetchCalls[0].url, /\/chat\/completions$/); + assert.equal(liveBody.choices[0].message.content, "LIVE"); + } finally { + invalidateBySignature(signature); + } +}); diff --git a/tests/unit/combo-test-route.test.mjs b/tests/unit/combo-test-route.test.mjs index 2e2ad6a325..2cadf2ec1c 100644 --- a/tests/unit/combo-test-route.test.mjs +++ b/tests/unit/combo-test-route.test.mjs @@ -82,6 +82,8 @@ test("combo test route marks a model healthy only when it returns assistant text assert.equal(fetchCalls.length, 1); assert.equal(fetchCalls[0].url, "http://localhost/v1/chat/completions"); assert.equal(fetchCalls[0].init.headers["X-Internal-Test"], "combo-health-check"); + assert.equal(fetchCalls[0].init.headers["X-OmniRoute-No-Cache"], "true"); + assert.match(fetchCalls[0].init.headers["X-Request-Id"], /^combo-test-/); assert.equal(forwardedBody.model, "openrouter/openai/gpt-5.4"); assert.equal(forwardedBody.messages[0].content, "Reply with OK only."); assert.equal(body.resolvedBy, "openrouter/openai/gpt-5.4");