From 2f9e2f500f928f08c33cf478b2d2856fc0f5624c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 17 Mar 2026 08:28:15 +0000 Subject: [PATCH] test: merge embeddings provider selection cases --- src/memory/embeddings.test.ts | 198 +++++++++++++++++----------------- 1 file changed, 101 insertions(+), 97 deletions(-) diff --git a/src/memory/embeddings.test.ts b/src/memory/embeddings.test.ts index e9a533f4f9d..911ca01f884 100644 --- a/src/memory/embeddings.test.ts +++ b/src/memory/embeddings.test.ts @@ -302,41 +302,6 @@ describe("embedding provider remote overrides", () => { }); describe("embedding provider auto selection", () => { - it("prefers openai when a key resolves", async () => { - vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => { - if (provider === "openai") { - return { apiKey: "openai-key", source: "env: OPENAI_API_KEY", mode: "api-key" }; - } - throw new Error(`No API key found for provider "${provider}".`); - }); - - const result = await createAutoProvider(); - expectAutoSelectedProvider(result, "openai"); - }); - - it("uses gemini when openai is missing", async () => { - const fetchMock = createGeminiFetchMock(); - vi.stubGlobal("fetch", fetchMock); - mockPublicPinnedHostname(); - vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => { - if (provider === "openai") { - throw new Error('No API key found for provider "openai".'); - } - if (provider === "google") { - return { apiKey: "gemini-key", source: "env: GEMINI_API_KEY", mode: "api-key" }; - } - throw new Error(`Unexpected provider ${provider}`); - }); - - const result = await createAutoProvider(); - const provider = expectAutoSelectedProvider(result, "gemini"); - await provider.embedQuery("hello"); - const [url] = fetchMock.mock.calls[0] ?? []; - expect(url).toBe( - `https://generativelanguage.googleapis.com/v1beta/models/${DEFAULT_GEMINI_EMBEDDING_MODEL}:embedContent`, - ); - }); - it("keeps explicit model when openai is selected", async () => { const fetchMock = vi.fn(async (_input?: unknown, _init?: unknown) => ({ ok: true, @@ -370,22 +335,63 @@ describe("embedding provider auto selection", () => { expect(payload.model).toBe("text-embedding-3-small"); }); - it("uses mistral when openai/gemini/voyage are missing", async () => { - const fetchMock = createFetchMock(); - vi.stubGlobal("fetch", fetchMock); - mockPublicPinnedHostname(); - vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => { - if (provider === "mistral") { - return { apiKey: "mistral-key", source: "env: MISTRAL_API_KEY", mode: "api-key" }; // pragma: allowlist secret - } - throw new Error(`No API key found for provider "${provider}".`); - }); + it("selects the first available remote provider in auto mode", async () => { + for (const testCase of [ + { + name: "openai first", + expectedProvider: "openai" as const, + fetchMockFactory: createFetchMock, + resolveApiKey(provider: string) { + if (provider === "openai") { + return { apiKey: "openai-key", source: "env: OPENAI_API_KEY", mode: "api-key" }; + } + throw new Error(`No API key found for provider "${provider}".`); + }, + expectedUrl: "https://api.openai.com/v1/embeddings", + }, + { + name: "gemini fallback", + expectedProvider: "gemini" as const, + fetchMockFactory: createGeminiFetchMock, + resolveApiKey(provider: string) { + if (provider === "openai") { + throw new Error('No API key found for provider "openai".'); + } + if (provider === "google") { + return { apiKey: "gemini-key", source: "env: GEMINI_API_KEY", mode: "api-key" }; + } + throw new Error(`Unexpected provider ${provider}`); + }, + expectedUrl: `https://generativelanguage.googleapis.com/v1beta/models/${DEFAULT_GEMINI_EMBEDDING_MODEL}:embedContent`, + }, + { + name: "mistral after earlier misses", + expectedProvider: "mistral" as const, + fetchMockFactory: createFetchMock, + resolveApiKey(provider: string) { + if (provider === "mistral") { + return { apiKey: "mistral-key", source: "env: MISTRAL_API_KEY", mode: "api-key" }; + } + throw new Error(`No API key found for provider "${provider}".`); + }, + expectedUrl: "https://api.mistral.ai/v1/embeddings", + }, + ]) { + vi.resetAllMocks(); + vi.unstubAllGlobals(); + const fetchMock = testCase.fetchMockFactory(); + vi.stubGlobal("fetch", fetchMock); + mockPublicPinnedHostname(); + vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => + testCase.resolveApiKey(provider), + ); - const result = await createAutoProvider(); - const provider = expectAutoSelectedProvider(result, "mistral"); - await provider.embedQuery("hello"); - const [url] = fetchMock.mock.calls[0] ?? []; - expect(url).toBe("https://api.mistral.ai/v1/embeddings"); + const result = await createAutoProvider(); + const provider = expectAutoSelectedProvider(result, testCase.expectedProvider); + await provider.embedQuery("hello"); + const [url] = fetchMock.mock.calls[0] ?? []; + expect(url, testCase.name).toBe(testCase.expectedUrl); + } }); }); @@ -661,56 +667,54 @@ describe("local embedding ensureContext concurrency", () => { }); describe("FTS-only fallback when no provider available", () => { - it("returns null provider with reason when auto mode finds no providers", async () => { - vi.mocked(authModule.resolveApiKeyForProvider).mockRejectedValue( - new Error('No API key found for provider "openai"'), - ); - - const result = await createEmbeddingProvider({ - config: {} as never, - provider: "auto", - model: "", - fallback: "none", - }); - - expect(result.provider).toBeNull(); - expect(result.requestedProvider).toBe("auto"); - expect(result.providerUnavailableReason).toBeDefined(); - expect(result.providerUnavailableReason).toContain("No API key"); - }); - - it("returns null provider when explicit provider fails with missing API key", async () => { - vi.mocked(authModule.resolveApiKeyForProvider).mockRejectedValue( - new Error('No API key found for provider "openai"'), - ); - - const result = await createEmbeddingProvider({ - config: {} as never, - provider: "openai", - model: "text-embedding-3-small", - fallback: "none", - }); - - expect(result.provider).toBeNull(); - expect(result.requestedProvider).toBe("openai"); - expect(result.providerUnavailableReason).toBeDefined(); - }); - - it("returns null provider when both primary and fallback fail with missing API keys", async () => { + it("returns null provider when all requested auth paths fail", async () => { vi.mocked(authModule.resolveApiKeyForProvider).mockRejectedValue( new Error("No API key found for provider"), ); - const result = await createEmbeddingProvider({ - config: {} as never, - provider: "openai", - model: "text-embedding-3-small", - fallback: "gemini", - }); - - expect(result.provider).toBeNull(); - expect(result.requestedProvider).toBe("openai"); - expect(result.fallbackFrom).toBe("openai"); - expect(result.providerUnavailableReason).toContain("Fallback to gemini failed"); + for (const testCase of [ + { + name: "auto mode", + options: { + config: {} as never, + provider: "auto" as const, + model: "", + fallback: "none" as const, + }, + requestedProvider: "auto", + fallbackFrom: undefined, + reasonIncludes: "No API key", + }, + { + name: "explicit provider only", + options: { + config: {} as never, + provider: "openai" as const, + model: "text-embedding-3-small", + fallback: "none" as const, + }, + requestedProvider: "openai", + fallbackFrom: undefined, + reasonIncludes: "No API key", + }, + { + name: "primary and fallback", + options: { + config: {} as never, + provider: "openai" as const, + model: "text-embedding-3-small", + fallback: "gemini" as const, + }, + requestedProvider: "openai", + fallbackFrom: "openai", + reasonIncludes: "Fallback to gemini failed", + }, + ]) { + const result = await createEmbeddingProvider(testCase.options); + expect(result.provider, testCase.name).toBeNull(); + expect(result.requestedProvider, testCase.name).toBe(testCase.requestedProvider); + expect(result.fallbackFrom, testCase.name).toBe(testCase.fallbackFrom); + expect(result.providerUnavailableReason, testCase.name).toContain(testCase.reasonIncludes); + } }); });