diff --git a/frontend/src/core/artifacts/loader.ts b/frontend/src/core/artifacts/loader.ts index afcf70a6a..eb8e8a92f 100644 --- a/frontend/src/core/artifacts/loader.ts +++ b/frontend/src/core/artifacts/loader.ts @@ -1,5 +1,7 @@ import type { BaseStream } from "@langchain/langgraph-sdk/react"; +import { fetch } from "@/core/api/fetcher"; + import type { AgentThreadState } from "../threads"; import { buildWriteFileDraftContent } from "./preview"; diff --git a/frontend/src/core/models/api.ts b/frontend/src/core/models/api.ts index d924e3529..8af78089e 100644 --- a/frontend/src/core/models/api.ts +++ b/frontend/src/core/models/api.ts @@ -1,3 +1,6 @@ +import { throwGatewayApiError } from "@/core/api/errors"; +import { fetch } from "@/core/api/fetcher"; + import { getBackendBaseURL } from "../config"; import { isStaticWebsiteOnly } from "../static-mode"; @@ -14,6 +17,12 @@ export async function loadModels(): Promise { } const res = await fetch(`${getBackendBaseURL()}/api/models`); + if (!res.ok) { + await throwGatewayApiError( + res, + `Failed to load models: ${res.status} ${res.statusText}`.trim(), + ); + } const data = (await res.json()) as Partial; return { models: data.models ?? [], diff --git a/frontend/src/core/workspace-changes/api.ts b/frontend/src/core/workspace-changes/api.ts index 34577d472..b175db728 100644 --- a/frontend/src/core/workspace-changes/api.ts +++ b/frontend/src/core/workspace-changes/api.ts @@ -1,3 +1,4 @@ +import { fetch } from "@/core/api/fetcher"; import { getBackendBaseURL } from "@/core/config"; import type { WorkspaceChangesResponse } from "./types"; diff --git a/frontend/tests/unit/core/artifacts/loader.test.ts b/frontend/tests/unit/core/artifacts/loader.test.ts index b8aee01b4..ca2947e1e 100644 --- a/frontend/tests/unit/core/artifacts/loader.test.ts +++ b/frontend/tests/unit/core/artifacts/loader.test.ts @@ -51,6 +51,7 @@ describe("loadArtifactContent", () => { expect(new Headers(init?.headers).get("Range")).toBe( `bytes=0-${ARTIFACT_PREVIEW_MAX_BYTES - 1}`, ); + expect(init?.credentials).toBe("include"); return new Response(bytes, { status: 206, headers: { diff --git a/frontend/tests/unit/core/models/api.test.ts b/frontend/tests/unit/core/models/api.test.ts new file mode 100644 index 000000000..ca1ca28cf --- /dev/null +++ b/frontend/tests/unit/core/models/api.test.ts @@ -0,0 +1,71 @@ +import { afterEach, expect, test, rs } from "@rstest/core"; + +afterEach(() => { + rs.unstubAllGlobals(); +}); + +test("loadModels includes credentials for authenticated gateway requests", async () => { + let requestedInit: RequestInit | undefined; + const fetchMock = rs.fn( + async (_input: RequestInfo | URL, init?: RequestInit) => { + requestedInit = init; + return new Response( + JSON.stringify({ + models: [ + { + id: "model-1", + name: "model-1", + model: "model-1", + display_name: "Model 1", + }, + ], + token_usage: { enabled: true }, + }), + { status: 200 }, + ); + }, + ); + rs.stubGlobal("fetch", fetchMock); + + const { loadModels } = await import("@/core/models/api"); + + await expect(loadModels()).resolves.toMatchObject({ + models: [{ id: "model-1" }], + token_usage: { enabled: true }, + }); + expect(requestedInit?.credentials).toBe("include"); +}); + +test("loadModels rejects unsuccessful gateway responses", async () => { + rs.stubGlobal( + "fetch", + rs.fn( + async () => + new Response(JSON.stringify({ detail: "Model registry unavailable" }), { + status: 503, + statusText: "Service Unavailable", + }), + ), + ); + + const { loadModels } = await import("@/core/models/api"); + + await expect(loadModels()).rejects.toThrow("Model registry unavailable"); +}); + +test("loadModels includes the status code when statusText is empty", async () => { + rs.stubGlobal( + "fetch", + rs.fn( + async () => + new Response("upstream unavailable", { + status: 503, + statusText: "", + }), + ), + ); + + const { loadModels } = await import("@/core/models/api"); + + await expect(loadModels()).rejects.toThrow("Failed to load models: 503"); +}); diff --git a/frontend/tests/unit/core/workspace-changes/api.test.ts b/frontend/tests/unit/core/workspace-changes/api.test.ts index 4ff402df1..0b5dcb2d8 100644 --- a/frontend/tests/unit/core/workspace-changes/api.test.ts +++ b/frontend/tests/unit/core/workspace-changes/api.test.ts @@ -47,4 +47,8 @@ test("fetchWorkspaceChanges can request file metadata without diffs", async () = const url = new URL(requestedUrl, "http://localhost"); expect(url.searchParams.get("include_files")).toBe("true"); expect(url.searchParams.get("include_diff")).toBe("false"); + expect(fetchMock).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ credentials: "include" }), + ); });