|
| 1 | +import { afterEach, describe, expect, test } from "bun:test"; |
| 2 | + |
| 3 | +import { |
| 4 | + discoverOllamaModels, |
| 5 | + isOllamaProviderId, |
| 6 | + ollamaOpenAIBaseURL, |
| 7 | + type OllamaDiscoveryState, |
| 8 | +} from "./ollama.js"; |
| 9 | + |
| 10 | +const originalFetch = globalThis.fetch; |
| 11 | + |
| 12 | +afterEach(() => { |
| 13 | + globalThis.fetch = originalFetch; |
| 14 | +}); |
| 15 | + |
| 16 | +describe("Ollama provider identity", () => { |
| 17 | + test("recognizes the reserved family without matching unrelated keyless providers", () => { |
| 18 | + expect(isOllamaProviderId("ollama")).toBe(true); |
| 19 | + expect(isOllamaProviderId("ollama/workstation")).toBe(true); |
| 20 | + expect(isOllamaProviderId("local")).toBe(false); |
| 21 | + expect(isOllamaProviderId("ollama-cloud")).toBe(false); |
| 22 | + }); |
| 23 | +}); |
| 24 | + |
| 25 | +describe("ollamaOpenAIBaseURL", () => { |
| 26 | + test("projects a root URL to exactly one /v1", () => { |
| 27 | + expect(ollamaOpenAIBaseURL("http://localhost:11434")).toBe("http://localhost:11434/v1"); |
| 28 | + expect(ollamaOpenAIBaseURL("http://localhost:11434/")).toBe("http://localhost:11434/v1"); |
| 29 | + }); |
| 30 | + |
| 31 | + test("rejects non-root paths instead of ambiguously appending /v1", () => { |
| 32 | + expect(() => ollamaOpenAIBaseURL("http://localhost:11434/v1")).toThrow( |
| 33 | + "expected a server root without a path", |
| 34 | + ); |
| 35 | + expect(() => ollamaOpenAIBaseURL("http://localhost:11434/team")).toThrow( |
| 36 | + "expected a server root without a path", |
| 37 | + ); |
| 38 | + }); |
| 39 | +}); |
| 40 | + |
| 41 | +describe("discoverOllamaModels", () => { |
| 42 | + test("requests the OpenAI models endpoint and validates model ids", async () => { |
| 43 | + const fetchMock = async (input: RequestInfo | URL, init?: RequestInit) => { |
| 44 | + expect(String(input)).toBe("http://localhost:11434/v1/models"); |
| 45 | + expect(init?.method).toBe("GET"); |
| 46 | + return Response.json({ data: [{ id: "qwen3" }, { id: "deepseek-r1" }] }); |
| 47 | + }; |
| 48 | + globalThis.fetch = fetchMock as unknown as typeof fetch; |
| 49 | + |
| 50 | + await expect(discoverOllamaModels({ rootURL: "http://localhost:11434/" })).resolves.toEqual({ |
| 51 | + status: "models", |
| 52 | + models: ["qwen3", "deepseek-r1"], |
| 53 | + }); |
| 54 | + }); |
| 55 | + |
| 56 | + test("distinguishes empty, unavailable, HTTP, and malformed responses", async () => { |
| 57 | + const cases: { |
| 58 | + response: () => Promise<Response>; |
| 59 | + expected: OllamaDiscoveryState["status"]; |
| 60 | + }[] = [ |
| 61 | + { response: async () => Response.json({ data: [] }), expected: "empty" }, |
| 62 | + { response: async () => new Response("no", { status: 503 }), expected: "unavailable" }, |
| 63 | + { response: async () => Response.json({ models: [] }), expected: "malformed" }, |
| 64 | + ]; |
| 65 | + |
| 66 | + for (const item of cases) { |
| 67 | + globalThis.fetch = item.response as unknown as typeof fetch; |
| 68 | + expect((await discoverOllamaModels({ rootURL: "http://localhost:11434" })).status).toBe( |
| 69 | + item.expected, |
| 70 | + ); |
| 71 | + } |
| 72 | + |
| 73 | + globalThis.fetch = (async () => { |
| 74 | + throw new Error("connection refused"); |
| 75 | + }) as unknown as typeof fetch; |
| 76 | + expect((await discoverOllamaModels({ rootURL: "http://localhost:11434" })).status).toBe( |
| 77 | + "unavailable", |
| 78 | + ); |
| 79 | + }); |
| 80 | +}); |
0 commit comments