import { afterEach, describe, expect, it, vi } from "vitest"; import { OpenAIResponsesClient } from "../src/services/openaiResponsesClient"; describe("openai local responses fallback", () => { const originalFetch = global.fetch; afterEach(() => { vi.restoreAllMocks(); global.fetch = originalFetch; }); it("falls back to /chat/completions when /responses payload is parseable JSON but has no output_text", async () => { const fetchMock = vi .fn() .mockResolvedValueOnce( new Response(JSON.stringify({ id: "resp-1", output: [], usage: { prompt_tokens: 4, completion_tokens: 3 } }), { status: 200, headers: { "content-type": "application/json" } }) ) .mockResolvedValueOnce( new Response( JSON.stringify({ id: "chat-1", choices: [{ message: { content: "{\"schema_version\":\"normalized_query_v2_0_2\"}" } }], usage: { prompt_tokens: 7, completion_tokens: 5, total_tokens: 12 } }), { status: 200, headers: { "content-type": "application/json" } } ) ); global.fetch = fetchMock as unknown as typeof fetch; const client = new OpenAIResponsesClient(); const response = await client.normalize( { llmProvider: "local", apiKey: "", model: "qwen2.5-14b-instruct-1m", baseUrl: "http://127.0.0.1:1234", temperature: 0 }, { systemPrompt: "system", developerPrompt: "developer", domainPrompt: "domain", userQuestion: "question", schemaVersion: "v2_0_2" } ); expect(response.outputText).toContain("\"schema_version\":\"normalized_query_v2_0_2\""); expect(response.usage.total_tokens).toBe(12); expect(fetchMock).toHaveBeenCalledTimes(2); expect(String(fetchMock.mock.calls[0]?.[0] ?? "")).toContain("/responses"); expect(String(fetchMock.mock.calls[1]?.[0] ?? "")).toContain("/chat/completions"); }); it("retries alternative local base when endpoint mismatch payload is returned for chat completions", async () => { const fetchMock = vi .fn() .mockResolvedValueOnce( new Response(JSON.stringify({ error: { message: "Unexpected endpoint or method. (POST /responses)" } }), { status: 404, headers: { "content-type": "application/json" } }) ) .mockResolvedValueOnce( new Response(JSON.stringify({ error: { message: "Unexpected endpoint or method. (POST /responses)" } }), { status: 404, headers: { "content-type": "application/json" } }) ) .mockResolvedValueOnce( new Response(JSON.stringify({ error: "Unexpected endpoint or method. (POST /chat/completions)" }), { status: 200, headers: { "content-type": "application/json" } }) ) .mockResolvedValueOnce( new Response( JSON.stringify({ id: "chat-v1", choices: [{ message: { content: "{\"schema_version\":\"normalized_query_v2_0_2\"}" } }], usage: { prompt_tokens: 10, completion_tokens: 7, total_tokens: 17 } }), { status: 200, headers: { "content-type": "application/json" } } ) ); global.fetch = fetchMock as unknown as typeof fetch; const client = new OpenAIResponsesClient(); const response = await client.normalize( { llmProvider: "local", apiKey: "", model: "qwen2.5-14b-instruct-1m", baseUrl: "http://127.0.0.1:1234", temperature: 0 }, { systemPrompt: "system", developerPrompt: "developer", domainPrompt: "domain", userQuestion: "question", schemaVersion: "v2_0_2" } ); expect(response.outputText).toContain("\"schema_version\":\"normalized_query_v2_0_2\""); expect(response.usage.total_tokens).toBe(17); expect(fetchMock).toHaveBeenCalledTimes(4); expect(String(fetchMock.mock.calls[0]?.[0] ?? "")).toBe("http://127.0.0.1:1234/responses"); expect(String(fetchMock.mock.calls[1]?.[0] ?? "")).toBe("http://127.0.0.1:1234/v1/responses"); expect(String(fetchMock.mock.calls[2]?.[0] ?? "")).toBe("http://127.0.0.1:1234/chat/completions"); expect(String(fetchMock.mock.calls[3]?.[0] ?? "")).toBe("http://127.0.0.1:1234/v1/chat/completions"); }); });