test: merge embeddings provider selection cases

This commit is contained in:
Peter Steinberger 2026-03-17 08:28:15 +00:00
parent 47a78a03a3
commit 2f9e2f500f

View File

@ -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);
}
});
});