diff --git a/src/app/(dashboard)/dashboard/providers/hooks/useProviderModels.ts b/src/app/(dashboard)/dashboard/providers/hooks/useProviderModels.ts index 17dd089e12..eb23e39886 100644 --- a/src/app/(dashboard)/dashboard/providers/hooks/useProviderModels.ts +++ b/src/app/(dashboard)/dashboard/providers/hooks/useProviderModels.ts @@ -77,11 +77,18 @@ export function useProviderModels(providerId: string): UseProviderModelsResult { }>; }; if (cancelled) return; - const providerConn = connData.connections?.find( + const providerConnections = connData.connections?.filter( (c) => (c.provider === providerId || c.id === providerId) && c.isActive !== false ); + const providerConn = providerConnections?.[0]; - if (providerConn?.providerSpecificData?.autoFetchModels === true && !cancelled) { + if ( + providerConn && + providerConnections.every( + (connection) => connection.providerSpecificData?.autoFetchModels === true + ) && + !cancelled + ) { const syncRes = await fetch( `/api/providers/${encodeURIComponent(providerConn.id)}/sync-models?mode=sync`, { method: "POST" } diff --git a/tests/unit/ui/use-provider-models-auto-fetch.test.tsx b/tests/unit/ui/use-provider-models-auto-fetch.test.tsx index 08e93f6d03..b415339f10 100644 --- a/tests/unit/ui/use-provider-models-auto-fetch.test.tsx +++ b/tests/unit/ui/use-provider-models-auto-fetch.test.tsx @@ -6,9 +6,8 @@ vi.mock("next-intl", () => ({ useTranslations: () => (key: string) => key, })); -const { useProviderModels } = await import( - "@/app/(dashboard)/dashboard/providers/hooks/useProviderModels" -); +const { useProviderModels } = + await import("@/app/(dashboard)/dashboard/providers/hooks/useProviderModels"); function createResponse(body: unknown, ok = true): Response { return { @@ -17,6 +16,15 @@ function createResponse(body: unknown, ok = true): Response { } as Response; } +function connection(id: string, autoFetchModels: boolean, isActive = true) { + return { + id, + provider: "custom-provider", + isActive, + providerSpecificData: { autoFetchModels }, + }; +} + async function renderProviderModels(providerId = "custom-provider") { const container = document.createElement("div"); document.body.appendChild(container); @@ -101,8 +109,71 @@ describe("useProviderModels upstream auto-fetch", () => { const mounted = await renderProviderModels(); await flushQueuedSync(); + expect(fetchMock).toHaveBeenCalledWith("/api/providers/connection-1/sync-models?mode=sync", { + method: "POST", + }); + mounted.unmount(); + }); + + it.each([ + [ + "enabled connection first", + [connection("connection-on", true), connection("connection-off", false)], + ], + [ + "disabled connection first", + [connection("connection-off", false), connection("connection-on", true)], + ], + ])("does not synchronize a mixed provider when the %s", async (_name, connections) => { + const fetchMock = vi.fn(async (input: string) => { + if (input.startsWith("/api/v1/providers/")) { + return createResponse({ data: [] }); + } + if (input === "/api/providers") { + return createResponse({ connections }); + } + throw new Error(`Unexpected request: ${input}`); + }); + vi.stubGlobal("fetch", fetchMock); + + const mounted = await renderProviderModels(); + try { + await flushQueuedSync(); + + expect(fetchMock).not.toHaveBeenCalledWith( + expect.stringContaining("/sync-models?mode=sync"), + expect.anything() + ); + } finally { + mounted.unmount(); + } + }); + + it("ignores inactive opt-outs when every active connection is enabled", async () => { + const fetchMock = vi.fn(async (input: string) => { + if (input.startsWith("/api/v1/providers/")) { + return createResponse({ data: [] }); + } + if (input === "/api/providers") { + return createResponse({ + connections: [ + connection("connection-inactive", false, false), + connection("connection-active", true), + ], + }); + } + if (input === "/api/providers/connection-active/sync-models?mode=sync") { + return createResponse({}); + } + throw new Error(`Unexpected request: ${input}`); + }); + vi.stubGlobal("fetch", fetchMock); + + const mounted = await renderProviderModels(); + await flushQueuedSync(); + expect(fetchMock).toHaveBeenCalledWith( - "/api/providers/connection-1/sync-models?mode=sync", + "/api/providers/connection-active/sync-models?mode=sync", { method: "POST" } ); mounted.unmount();