diff --git a/app/api/cases/start/route.js b/app/api/cases/start/route.js index 4ea1ebc..ce5d220 100644 --- a/app/api/cases/start/route.js +++ b/app/api/cases/start/route.js @@ -10,6 +10,17 @@ async function post(request) { return Response.json(result, { status: 200 }); } + if (result.code === "PROVIDER_UNAVAILABLE") { + console.error("[api/cases/start] provider unavailable", { + error: result.error ?? "Start case failed", + providerApiPath: result.providerApiPath, + }); + return Response.json( + { success: false, error: "Reasoning service is temporarily unavailable." }, + { status: 503 }, + ); + } + const status = result.statusCode === 400 ? 400 diff --git a/tests/app/api/cases-start-route.test.js b/tests/app/api/cases-start-route.test.js index 4bac8c8..d36afca 100644 --- a/tests/app/api/cases-start-route.test.js +++ b/tests/app/api/cases-start-route.test.js @@ -8,6 +8,10 @@ vi.mock("@/lib/graph/orchestrator.js", () => ({ startCase: (...args) => mockStartCase(...args), })); +vi.mock("@/lib/supabase/api-auth.js", () => ({ + withAuthenticatedApi: (handler) => handler, +})); + describe("app/api/cases/start route", () => { beforeEach(() => { vi.resetModules(); @@ -123,6 +127,33 @@ describe("app/api/cases/start route", () => { expect(errorSpy).not.toHaveBeenCalled(); }); + it("returns a sanitized 503 when the provider is unavailable", async () => { + mockStartCase.mockResolvedValue({ + success: false, + code: "PROVIDER_UNAVAILABLE", + error: "Ollama /api/generate request timed out after 5 minutes", + providerApiPath: "/api/generate", + providerExecution: { generateRequestAttempted: true }, + }); + + const { POST } = await import("@/app/api/cases/start/route.js"); + const response = await POST( + new Request("http://localhost/api/cases/start", { + method: "POST", + body: JSON.stringify({ scenario: "Scenario text" }), + headers: { "content-type": "application/json" }, + }), + ); + + expect(response.status).toBe(503); + const body = await response.json(); + expect(body).toEqual({ + success: false, + error: "Reasoning service is temporarily unavailable.", + }); + expect(JSON.stringify(body)).not.toMatch(/ollama|generate|timed out/i); + }); + it("returns provider/internal failures as 5xx without stack traces", async () => { const rawResponse = `{"reconstruction":{"observedStates":[{"id":"obs-1"${"x".repeat(2500)}}]}}`; mockStartCase.mockResolvedValue({