279 lines
8.3 KiB
TypeScript
279 lines
8.3 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { createChatPipelineHarness } from "../integration/_chatPipelineHarness.ts";
|
|
|
|
const harness = await createChatPipelineHarness("chat-route-edges");
|
|
const {
|
|
BaseExecutor,
|
|
buildClaudeResponse,
|
|
buildOpenAIResponse,
|
|
buildRequest,
|
|
handleChat,
|
|
resetStorage,
|
|
seedConnection,
|
|
settingsDb,
|
|
idempotencyLayerModule,
|
|
semanticCacheModule,
|
|
} = harness;
|
|
|
|
const { getBackgroundDegradationConfig } =
|
|
await import("../../open-sse/services/backgroundTaskDetector.ts");
|
|
const { setCustomAliases } = await import("../../open-sse/services/modelDeprecation.ts");
|
|
const { setModelAlias } = await import("../../src/lib/db/models.ts");
|
|
|
|
test.beforeEach(async () => {
|
|
BaseExecutor.RETRY_CONFIG.delayMs = 0;
|
|
await resetStorage();
|
|
});
|
|
|
|
test.afterEach(async () => {
|
|
await resetStorage();
|
|
});
|
|
|
|
test.after(async () => {
|
|
await harness.cleanup();
|
|
});
|
|
|
|
test("handleChat resolves model alias before routing", async () => {
|
|
await seedConnection("openai", { apiKey: "sk-openai" });
|
|
// setModelAlias writes to key_value namespace='modelAliases', which is the
|
|
// namespace that getModelAliases() (used by getModelInfo in chatCore) reads from.
|
|
// settingsDb.updateSettings({ modelAliases }) writes to namespace='settings' and
|
|
// triggers setCustomAliases (in-memory only) — a separate store not consulted here.
|
|
await setModelAlias("alias-model", "openai/gpt-4.1");
|
|
|
|
const seenModels = [];
|
|
globalThis.fetch = async (_url, init = {}) => {
|
|
try {
|
|
const body = JSON.parse(String(init.body));
|
|
seenModels.push(body.model);
|
|
} catch {}
|
|
return buildOpenAIResponse("Alias response");
|
|
};
|
|
|
|
const response = await handleChat(
|
|
buildRequest({
|
|
body: {
|
|
model: "alias-model",
|
|
stream: false,
|
|
messages: [{ role: "user", content: "Test alias" }],
|
|
},
|
|
})
|
|
);
|
|
|
|
assert.equal(response.status, 200, "Should succeed with 200 OK");
|
|
assert.equal(seenModels[0], "gpt-4.1", "Model alias should be resolved to gpt-4.1");
|
|
});
|
|
|
|
test("Test 3: handleChat returns cached response directly for Semantic Cache hits", async () => {
|
|
await seedConnection("openai", { apiKey: "sk-openai-semantic" });
|
|
let fetchCount = 0;
|
|
|
|
globalThis.fetch = async (_url, init) => {
|
|
fetchCount++;
|
|
const bodyStr = String(init.body);
|
|
const body = JSON.parse(bodyStr);
|
|
assert.equal(body.temperature, 0);
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
id: `chatcmpl_${fetchCount}`,
|
|
object: "chat.completion",
|
|
choices: [
|
|
{
|
|
index: 0,
|
|
message: { role: "assistant", content: `Cache Generation ${fetchCount}` },
|
|
finish_reason: "stop",
|
|
},
|
|
],
|
|
usage: { prompt_tokens: 2, completion_tokens: 4, total_tokens: 6 },
|
|
}),
|
|
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
);
|
|
};
|
|
|
|
const req1 = buildRequest({
|
|
body: {
|
|
model: "openai/gpt-4",
|
|
stream: false,
|
|
temperature: 0,
|
|
messages: [{ role: "user", content: "semantic hit" }],
|
|
},
|
|
});
|
|
|
|
const res1 = await handleChat(req1);
|
|
await res1.json();
|
|
assert.equal(fetchCount, 1);
|
|
await new Promise((r) => setTimeout(r, 100)); // allow background cache write
|
|
|
|
const req2 = buildRequest({
|
|
body: {
|
|
model: "openai/gpt-4",
|
|
stream: false,
|
|
temperature: 0,
|
|
messages: [{ role: "user", content: "semantic hit" }],
|
|
},
|
|
});
|
|
|
|
const res2 = await handleChat(req2);
|
|
const json2 = (await res2.json()) as any;
|
|
|
|
assert.equal(fetchCount, 1, "Should have hit the semantic cache without calling fetch again");
|
|
assert.equal(json2.choices[0].message.content, "Cache Generation 1");
|
|
assert.equal(res2.headers.get("X-OmniRoute-Cache"), "HIT");
|
|
});
|
|
|
|
test("Test 4: handleChat supports X-OmniRoute-Progress tracking header for streams", async () => {
|
|
await seedConnection("openai", { apiKey: "sk-openai-progress" });
|
|
|
|
globalThis.fetch = async () => {
|
|
return new Response(
|
|
[
|
|
`data: {"id":"chatcmpl","choices":[{"delta":{"content":"P"},"index":0}]}`,
|
|
`data: {"id":"chatcmpl","choices":[{"delta":{"content":"rogress"},"index":0}]}`,
|
|
`data: [DONE]`,
|
|
"",
|
|
].join("\n\n"),
|
|
{
|
|
status: 200,
|
|
headers: { "Content-Type": "text/event-stream" },
|
|
}
|
|
);
|
|
};
|
|
|
|
const response = await handleChat(
|
|
buildRequest({
|
|
headers: {
|
|
"X-OmniRoute-Progress": "true",
|
|
},
|
|
body: {
|
|
model: "openai/gpt-4",
|
|
stream: true,
|
|
messages: [{ role: "user", content: "progress check" }],
|
|
},
|
|
})
|
|
);
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(response.headers.get("X-OmniRoute-Progress"), "enabled");
|
|
|
|
const raw = await response.text();
|
|
assert.match(raw, /event: progress/); // check that progress chunks were injected
|
|
assert.match(raw, /content":"P"/);
|
|
});
|
|
|
|
test("Test 5: isTokenExpiringSoon detects token boundaries", async () => {
|
|
const { isTokenExpiringSoon } = await import("../../open-sse/handlers/chatCore.ts");
|
|
const now = Date.now();
|
|
|
|
assert.equal(isTokenExpiringSoon(null), false);
|
|
assert.equal(
|
|
isTokenExpiringSoon(new Date(now + 10 * 60 * 1000).toISOString(), 5 * 60 * 1000),
|
|
false
|
|
);
|
|
assert.equal(
|
|
isTokenExpiringSoon(new Date(now + 2 * 60 * 1000).toISOString(), 5 * 60 * 1000),
|
|
true
|
|
);
|
|
assert.equal(
|
|
isTokenExpiringSoon(new Date(now - 1 * 60 * 1000).toISOString(), 5 * 60 * 1000),
|
|
true
|
|
);
|
|
});
|
|
|
|
test("handleChat returns cached response directly for Idempotency hits", async () => {
|
|
await seedConnection("openai", { apiKey: "sk-openai-idem" });
|
|
|
|
globalThis.fetch = async () => buildOpenAIResponse("Original response");
|
|
|
|
const reqBody = {
|
|
model: "openai/gpt-4",
|
|
stream: false,
|
|
messages: [{ role: "user", content: "Idempotent req" }],
|
|
};
|
|
|
|
// First request: hits API and saves idempotency
|
|
const response1 = await handleChat(
|
|
buildRequest({
|
|
headers: { "idempotency-key": "req-idempotent-123" },
|
|
body: reqBody,
|
|
})
|
|
);
|
|
await response1.json(); // Consume body
|
|
|
|
const response2 = await handleChat(
|
|
buildRequest({
|
|
headers: { "idempotency-key": "req-idempotent-123" },
|
|
body: reqBody,
|
|
})
|
|
);
|
|
|
|
const json2 = (await response2.json()) as any;
|
|
assert.equal(response2.status, 200);
|
|
assert.equal(response2.headers.get("X-OmniRoute-Idempotent"), "true");
|
|
assert.equal(json2.choices[0].message.content, "Original response");
|
|
});
|
|
|
|
test("Test 6: handleChat correctly sets isResponsesEndpoint for /v1/responses", async () => {
|
|
await seedConnection("openai", { apiKey: "sk-openai-responses" });
|
|
|
|
globalThis.fetch = async (_url, init) => {
|
|
return new Response(
|
|
JSON.stringify({
|
|
id: "chatcmpl-responses",
|
|
object: "chat.completion",
|
|
choices: [
|
|
{ message: { role: "assistant", content: "Responses OK" }, finish_reason: "stop" },
|
|
],
|
|
}),
|
|
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
);
|
|
};
|
|
|
|
const response = await handleChat(
|
|
new Request("http://localhost/v1/responses", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
model: "openai/gpt-4",
|
|
stream: false,
|
|
messages: [{ role: "user", content: "hi" }],
|
|
}),
|
|
})
|
|
);
|
|
|
|
const json = (await response.json()) as any;
|
|
assert.equal(response.status, 200);
|
|
const responseText = json.output_text || json.output?.[0]?.content?.[0]?.text;
|
|
assert.equal(responseText, "Responses OK");
|
|
});
|
|
|
|
test("handleChat returns Semantic Cache hit", async () => {
|
|
await seedConnection("openai", { apiKey: "sk-openai-semantic" });
|
|
globalThis.fetch = async () => buildOpenAIResponse("Semantic API response");
|
|
|
|
const model = "openai/gpt-4";
|
|
const messages = [{ role: "user", content: "Semantic query test" }];
|
|
const reqBody = {
|
|
model,
|
|
stream: false,
|
|
temperature: 0, // required for cacheable
|
|
messages,
|
|
};
|
|
|
|
// First request: hits API and saves semantic cache
|
|
const response1 = await handleChat(buildRequest({ body: reqBody }));
|
|
await response1.json(); // Consume body
|
|
|
|
// Second request: should hit semantic cache
|
|
const response2 = await handleChat(buildRequest({ body: reqBody }));
|
|
|
|
const json2 = (await response2.json()) as any;
|
|
assert.equal(response2.status, 200);
|
|
assert.equal(response2.headers.get("X-OmniRoute-Cache"), "HIT");
|
|
assert.equal(json2.choices[0].message.content, "Semantic API response");
|
|
});
|