test(gateway): reuse shared openai timeout e2e helpers
This commit is contained in:
@@ -149,12 +149,7 @@ function decodeBodyText(body: unknown): string {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildOpenAIResponsesSse(params: OpenAIResponsesParams): Promise<Response> {
|
function buildSseResponse(events: unknown[]): Response {
|
||||||
const events: OpenAIResponseStreamEvent[] = [];
|
|
||||||
for await (const event of fakeOpenAIResponsesStream(params)) {
|
|
||||||
events.push(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sse = `${events.map((e) => `data: ${JSON.stringify(e)}\n\n`).join("")}data: [DONE]\n\n`;
|
const sse = `${events.map((e) => `data: ${JSON.stringify(e)}\n\n`).join("")}data: [DONE]\n\n`;
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const body = new ReadableStream<Uint8Array>({
|
const body = new ReadableStream<Uint8Array>({
|
||||||
@@ -169,6 +164,46 @@ async function buildOpenAIResponsesSse(params: OpenAIResponsesParams): Promise<R
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildOpenAIResponsesTextSse(text: string): Response {
|
||||||
|
return buildSseResponse([
|
||||||
|
{
|
||||||
|
type: "response.output_item.added",
|
||||||
|
item: {
|
||||||
|
type: "message",
|
||||||
|
id: "msg_test_1",
|
||||||
|
role: "assistant",
|
||||||
|
content: [],
|
||||||
|
status: "in_progress",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "response.output_item.done",
|
||||||
|
item: {
|
||||||
|
type: "message",
|
||||||
|
id: "msg_test_1",
|
||||||
|
role: "assistant",
|
||||||
|
status: "completed",
|
||||||
|
content: [{ type: "output_text", text, annotations: [] }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "response.completed",
|
||||||
|
response: {
|
||||||
|
status: "completed",
|
||||||
|
usage: { input_tokens: 10, output_tokens: 10, total_tokens: 20 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildOpenAIResponsesSse(params: OpenAIResponsesParams): Promise<Response> {
|
||||||
|
const events: OpenAIResponseStreamEvent[] = [];
|
||||||
|
for await (const event of fakeOpenAIResponsesStream(params)) {
|
||||||
|
events.push(event);
|
||||||
|
}
|
||||||
|
return buildSseResponse(events);
|
||||||
|
}
|
||||||
|
|
||||||
export function installOpenAiResponsesMock(params?: { baseUrl?: string }) {
|
export function installOpenAiResponsesMock(params?: { baseUrl?: string }) {
|
||||||
const originalFetch = globalThis.fetch;
|
const originalFetch = globalThis.fetch;
|
||||||
const baseUrl = params?.baseUrl ?? "https://api.openai.com/v1";
|
const baseUrl = params?.baseUrl ?? "https://api.openai.com/v1";
|
||||||
|
|||||||
@@ -3,78 +3,11 @@ import fs from "node:fs/promises";
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { extractPayloadText } from "../src/gateway/test-helpers.agent-results.js";
|
||||||
import { startGatewayWithClient } from "../src/gateway/test-helpers.e2e.js";
|
import { startGatewayWithClient } from "../src/gateway/test-helpers.e2e.js";
|
||||||
|
import { buildOpenAIResponsesTextSse } from "../src/gateway/test-helpers.openai-mock.js";
|
||||||
import { buildOpenAiResponsesProviderConfig } from "../src/gateway/test-openai-responses-model.js";
|
import { buildOpenAiResponsesProviderConfig } from "../src/gateway/test-openai-responses-model.js";
|
||||||
|
|
||||||
type OpenAIResponseStreamEvent =
|
|
||||||
| { type: "response.output_item.added"; item: Record<string, unknown> }
|
|
||||||
| { type: "response.output_item.done"; item: Record<string, unknown> }
|
|
||||||
| {
|
|
||||||
type: "response.completed";
|
|
||||||
response: {
|
|
||||||
status: "completed";
|
|
||||||
usage: {
|
|
||||||
input_tokens: number;
|
|
||||||
output_tokens: number;
|
|
||||||
total_tokens: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function buildOpenAIResponsesSse(text: string): Response {
|
|
||||||
const events: OpenAIResponseStreamEvent[] = [
|
|
||||||
{
|
|
||||||
type: "response.output_item.added",
|
|
||||||
item: {
|
|
||||||
type: "message",
|
|
||||||
id: "msg_test_1",
|
|
||||||
role: "assistant",
|
|
||||||
content: [],
|
|
||||||
status: "in_progress",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "response.output_item.done",
|
|
||||||
item: {
|
|
||||||
type: "message",
|
|
||||||
id: "msg_test_1",
|
|
||||||
role: "assistant",
|
|
||||||
status: "completed",
|
|
||||||
content: [{ type: "output_text", text, annotations: [] }],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "response.completed",
|
|
||||||
response: {
|
|
||||||
status: "completed",
|
|
||||||
usage: { input_tokens: 10, output_tokens: 10, total_tokens: 20 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const sse = `${events.map((e) => `data: ${JSON.stringify(e)}\n\n`).join("")}data: [DONE]\n\n`;
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const body = new ReadableStream<Uint8Array>({
|
|
||||||
start(controller) {
|
|
||||||
controller.enqueue(encoder.encode(sse));
|
|
||||||
controller.close();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return new Response(body, {
|
|
||||||
status: 200,
|
|
||||||
headers: { "content-type": "text/event-stream" },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractPayloadText(result: unknown): string {
|
|
||||||
const record = result as Record<string, unknown>;
|
|
||||||
const payloads = Array.isArray(record.payloads) ? record.payloads : [];
|
|
||||||
const texts = payloads
|
|
||||||
.map((p) => (p && typeof p === "object" ? (p as Record<string, unknown>).text : undefined))
|
|
||||||
.filter((t): t is string => typeof t === "string" && t.trim().length > 0);
|
|
||||||
return texts.join("\n").trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("provider timeouts (e2e)", () => {
|
describe("provider timeouts (e2e)", () => {
|
||||||
it(
|
it(
|
||||||
"falls back when the primary provider aborts with a timeout-like AbortError",
|
"falls back when the primary provider aborts with a timeout-like AbortError",
|
||||||
@@ -107,7 +40,7 @@ describe("provider timeouts (e2e)", () => {
|
|||||||
|
|
||||||
if (url.startsWith(`${fallbackBaseUrl}/responses`)) {
|
if (url.startsWith(`${fallbackBaseUrl}/responses`)) {
|
||||||
counts.fallback += 1;
|
counts.fallback += 1;
|
||||||
return buildOpenAIResponsesSse("fallback-ok");
|
return buildOpenAIResponsesTextSse("fallback-ok");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!originalFetch) {
|
if (!originalFetch) {
|
||||||
|
|||||||
Reference in New Issue
Block a user