1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
import type { ApiKeyCredentials, ModelInfo, ProviderContract } from "@dispatch/kernel";
import type { FetchLike } from "@dispatch/trace-replay";
import { describe, expect, it, vi } from "vitest";
import { parseModelList } from "./listModels.js";
import { createOpenAICompatProvider } from "./provider.js";
function makeProvider(fetchFn: FetchLike, apiKey = "sk-test-1234567890abcdef"): ProviderContract {
const creds: ApiKeyCredentials = {
type: "api-key",
apiKey,
baseURL: "https://api.example.com/v1",
};
return createOpenAICompatProvider({
credentials: creds,
model: "test-model",
id: "openai-compat",
fetchFn,
});
}
function jsonResponse(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
describe("listModels — pure mapping (parseModelList)", () => {
it("maps OpenAI model entries to ModelInfo", () => {
const result = parseModelList([{ id: "a" }, { id: "b" }]);
expect(result).toEqual([{ id: "a" }, { id: "b" }]);
});
it("returns empty array for empty input", () => {
const result = parseModelList([]);
expect(result).toEqual([]);
});
});
describe("listModels — provider contract", () => {
it("GETs models endpoint with bearer key and returns mapped ModelInfo[]", async () => {
const fetchFn = vi.fn(
() => jsonResponse({ data: [{ id: "a" }, { id: "b" }] }) as unknown as ReturnType<FetchLike>,
);
const provider = makeProvider(fetchFn);
const listModels = provider.listModels;
if (!listModels) throw new Error("listModels not defined");
const models = await listModels();
expect(fetchFn).toHaveBeenCalledOnce();
const callArgs = fetchFn.mock.calls[0];
if (!callArgs) throw new Error("no call args");
const [url, init] = callArgs as unknown as [string, RequestInit];
expect(url).toBe("https://api.example.com/v1/models");
expect(init.method).toBe("GET");
expect(init.headers).toEqual({ Authorization: "Bearer sk-test-1234567890abcdef" });
expect(models).toEqual([{ id: "a" }, { id: "b" }] as readonly ModelInfo[]);
});
it("throws on non-OK HTTP status with a clear message", async () => {
const fetchFn = vi.fn(
() =>
new Response("Unauthorized", {
status: 401,
headers: { "Content-Type": "text/plain" },
}) as unknown as ReturnType<FetchLike>,
);
const provider = makeProvider(fetchFn);
const listModels = provider.listModels;
if (!listModels) throw new Error("listModels not defined");
await expect(listModels()).rejects.toThrow(
"listModels[openai-compat]: HTTP 401 — Unauthorized",
);
});
it("throws on network error with a clear message", async () => {
const fetchFn = vi.fn(() => {
throw new Error("connection refused");
}) as unknown as FetchLike;
const provider = makeProvider(fetchFn);
const listModels = provider.listModels;
if (!listModels) throw new Error("listModels not defined");
await expect(listModels()).rejects.toThrow(
"listModels[openai-compat]: network error — connection refused",
);
});
it("throws when response shape is missing data array", async () => {
const fetchFn = vi.fn(() => jsonResponse({ models: [] }) as unknown as ReturnType<FetchLike>);
const provider = makeProvider(fetchFn);
const listModels = provider.listModels;
if (!listModels) throw new Error("listModels not defined");
await expect(listModels()).rejects.toThrow(
'listModels[openai-compat]: unexpected response shape — missing "data" array',
);
});
});
|