Files
2026-07-13 13:39:12 +08:00

189 lines
6.2 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { ensureTestEnvironment, MODEL, BASE_URL, API_KEY } from "./liveGeminiShared.ts";
const DIRECT_MODEL = process.env.TEST_GEMINI_DIRECT_MODEL || "gemini/gemini-2.0-flash";
const skip = !API_KEY ? "OMNIROUTE_API_KEY not set — skipping live test" : undefined;
test.before(async () => {
await ensureTestEnvironment();
});
async function readSSEStream(response: Response): Promise<{
fullContent: string;
finishReason: string;
model: string;
totalTokens: number;
}> {
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
let fullContent = "";
let finishReason = "unknown";
let model = "";
let totalTokens = 0;
let chunkCount = 0;
let sampleLines: string[] = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
chunkCount++;
const data = line.slice(6).trim();
if (data === "[DONE]") continue;
try {
const parsed = JSON.parse(data) as Record<string, unknown>;
const choice = ((parsed.choices ?? []) as Array<Record<string, unknown>>)[0];
if (choice) {
const delta = choice.delta as Record<string, unknown> | undefined;
if (delta?.content) {
fullContent += delta.content as string;
} else if (delta?.reasoning_content) {
fullContent += delta.reasoning_content as string;
}
if (choice.finish_reason) finishReason = choice.finish_reason as string;
}
if (!model) {
if (parsed.model) {
model = parsed.model as string;
} else if (choice?.model) {
model = choice.model as string;
}
}
const usage = parsed.usage as Record<string, number> | undefined;
if (usage) {
totalTokens =
usage.total_tokens ?? (usage.prompt_tokens ?? 0) + (usage.completion_tokens ?? 0);
}
} catch {
// skip malformed chunks
}
if (sampleLines.length < 3) {
sampleLines.push(data.slice(0, 200));
}
}
}
console.log(
`[SSE] total raw chunks: ${chunkCount}, content length: ${fullContent.length}, finish: ${finishReason}, model: ${model}, tokens: ${totalTokens}`
);
if (sampleLines.length > 0) {
console.log(`[SSE] sample lines: ${JSON.stringify(sampleLines)}`);
}
return { fullContent, finishReason, model, totalTokens };
}
test("live Gemini — single hello-world request via combo 'default'", { skip }, async (t) => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 120_000);
try {
console.log(`[TEST] Sending request with model=${MODEL} to ${BASE_URL}/v1/chat/completions`);
const response = await fetch(`${BASE_URL}/v1/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
model: MODEL,
messages: [{ role: "user", content: "Say 'Hello world' and nothing else." }],
stream: true,
max_tokens: 50,
temperature: 0,
}),
signal: controller.signal,
});
clearTimeout(timeout);
console.log(`[TEST] Response status: ${response.status}`);
if (!response.ok) {
const body = await response.text().catch(() => "unknown");
console.log(`[TEST] Response body: ${body}`);
}
assert.equal(response.status, 200, "Expected HTTP 200 from combo request");
const { fullContent, finishReason, model, totalTokens } = await readSSEStream(response);
assert.ok(fullContent.length > 0, "response should have content");
assert.ok(
finishReason === "stop" || finishReason === "length",
`expected stop/length finish, got ${finishReason}`
);
assert.ok(totalTokens > 0, `should have non-zero token count, got ${totalTokens}`);
assert.ok(
model.toLowerCase().includes("gemini") || model.toLowerCase().includes("gemma"),
`response model "${model}" should be a Gemini/Gemma model`
);
console.log(
`[TEST] OK: model=${model}, finish=${finishReason}, tokens=${totalTokens}, content=${fullContent.length} chars`
);
} catch (err) {
clearTimeout(timeout);
throw err;
}
});
test("live Gemini — direct model request (skip combo)", { skip }, async (t) => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 120_000);
try {
console.log(`[TEST] Direct model request with model=${DIRECT_MODEL}`);
const response = await fetch(`${BASE_URL}/v1/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
model: DIRECT_MODEL,
messages: [{ role: "user", content: "Say 'Hello world' and nothing else." }],
stream: true,
max_tokens: 50,
temperature: 0,
}),
signal: controller.signal,
});
clearTimeout(timeout);
console.log(`[TEST] Direct response status: ${response.status}`);
if (!response.ok) {
const body = await response.text().catch(() => "unknown");
console.log(`[TEST] Direct response body: ${body}`);
}
assert.equal(response.status, 200, "Expected HTTP 200 for direct model");
const { fullContent, finishReason, model, totalTokens } = await readSSEStream(response);
assert.ok(fullContent.length > 0, "response should have content");
assert.ok(
finishReason === "stop" || finishReason === "length",
`expected stop/length finish, got ${finishReason}`
);
assert.ok(totalTokens > 0, `should have non-zero token count, got ${totalTokens}`);
assert.ok(
model.toLowerCase().includes("gemini") || model.toLowerCase().includes("gemma"),
`response model "${model}" should be a Gemini/Gemma model`
);
console.log(`[TEST] Direct OK: model=${model}, finish=${finishReason}, tokens=${totalTokens}`);
} catch (err) {
clearTimeout(timeout);
throw err;
}
});