|
1 | 1 | // Exa tests cover exa web search provider plugin behavior. |
2 | | -import { describe, expect, it } from "vitest"; |
| 2 | +import { describe, expect, it, vi } from "vitest"; |
3 | 3 | import { testing } from "../test-api.js"; |
4 | 4 | import { createExaWebSearchProvider as createContractExaWebSearchProvider } from "../web-search-contract-api.js"; |
5 | 5 | import { createExaWebSearchProvider } from "./exa-web-search-provider.js"; |
6 | 6 | |
| 7 | +function cancelTrackedResponse( |
| 8 | +text: string, |
| 9 | +init: ResponseInit, |
| 10 | +): { |
| 11 | +response: Response; |
| 12 | +wasCanceled: () => boolean; |
| 13 | +} { |
| 14 | +let canceled = false; |
| 15 | +const stream = new ReadableStream<Uint8Array>({ |
| 16 | +start(controller) { |
| 17 | +controller.enqueue(new TextEncoder().encode(text)); |
| 18 | +}, |
| 19 | +cancel() { |
| 20 | +canceled = true; |
| 21 | +}, |
| 22 | +}); |
| 23 | +return { |
| 24 | +response: new Response(stream, init), |
| 25 | +wasCanceled: () => canceled, |
| 26 | +}; |
| 27 | +} |
| 28 | + |
7 | 29 | describe("exa web search provider", () => { |
8 | 30 | it("exposes the expected metadata and selection wiring", () => { |
9 | 31 | const provider = createExaWebSearchProvider(); |
@@ -242,4 +264,20 @@ describe("exa web search provider", () => {
|
242 | 264 | "Exa API returned malformed JSON", |
243 | 265 | ); |
244 | 266 | }); |
| 267 | + |
| 268 | +it("bounds Exa API error bodies without using response.text()", async () => { |
| 269 | +const tracked = cancelTrackedResponse(`${"exa upstream unavailable ".repeat(1024)}tail`, { |
| 270 | +status: 503, |
| 271 | +headers: { "content-type": "text/plain" }, |
| 272 | +}); |
| 273 | +const textSpy = vi.spyOn(tracked.response, "text").mockRejectedValue(new Error("unbounded")); |
| 274 | + |
| 275 | +const detail = await testing.readExaErrorDetail(tracked.response); |
| 276 | + |
| 277 | +expect(detail).toContain("exa upstream unavailable"); |
| 278 | +expect(detail).not.toContain("tail"); |
| 279 | +expect(await testing.readExaErrorDetail(new Response("short"))).toBe("short"); |
| 280 | +expect(tracked.wasCanceled()).toBe(true); |
| 281 | +expect(textSpy).not.toHaveBeenCalled(); |
| 282 | +}); |
245 | 283 | }); |