test: merge embeddings provider selection cases
This commit is contained in:
parent
47a78a03a3
commit
2f9e2f500f
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user