From 6b993349d666c611a225256d60932bc50a31d169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=A5lm?= Date: Thu, 20 Aug 2026 11:29:35 +0200 Subject: [PATCH] fix(fetch): forward endpoint, isImport, and body unchanged on retry The retry call after a 5xx rebuilt the request arguments and dropped endpoint and isImport while pre-stringifying the body. Retried requests went to the bare node URL with no path and got a 404, and non-import bodies were double-encoded. Fixes #17 --- src/http/fetch/request.ts | 4 ++- tests/fetch/request.test.ts | 62 +++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/http/fetch/request.ts b/src/http/fetch/request.ts index 5f12dbb..0170efe 100644 --- a/src/http/fetch/request.ts +++ b/src/http/fetch/request.ts @@ -69,8 +69,10 @@ async function makeRequest({ return makeRequest({ method, config, - body: isImport ? body : JSON.stringify(body), + body, params, + endpoint, + isImport, currentNodeIndex: node.nextIndex, attempt: attemptNum + 1, }); diff --git a/tests/fetch/request.test.ts b/tests/fetch/request.test.ts index 2473148..626a2b9 100644 --- a/tests/fetch/request.test.ts +++ b/tests/fetch/request.test.ts @@ -87,6 +87,68 @@ describe("makeRequest", () => { expect(urls[1].searchParams.get("q")).toBe("query"); }); + it("should keep the endpoint and body when retrying after a 5xx", async () => { + const nodes = [createNode(0, true), createNode(1, true)]; + + fetchMocker + .mockResponseOnce("Server Error", { status: 500 }) + .mockResponseOnce(JSON.stringify({ data: "success" })); + + await makeRequest({ + method: "POST", + endpoint: "/collections", + config: { + nodes, + apiKey: "test-key", + healthcheckIntervalSeconds: 1, + retryIntervalSeconds: 0, + numRetries: 3, + }, + body: { test: "data" }, + }); + + expect(fetchMocker.requests()).toHaveLength(2); + expect(new URL(fetchMocker.requests()[1]!.url).pathname).toBe( + "/collections", + ); + expect(fetchMocker.mock.calls[1]?.[1]?.body).toBe( + JSON.stringify({ test: "data" }), + ); + }); + + it("should keep the import body and content type when retrying after a 5xx", async () => { + const nodes = [createNode(0, true), createNode(1, true)]; + const importBody = `${JSON.stringify({ id: "1" })}\n${JSON.stringify({ id: "2" })}`; + + fetchMocker + .mockResponseOnce("Server Error", { status: 500 }) + .mockResponseOnce(JSON.stringify({ success: true })); + + await makeRequest({ + method: "POST", + endpoint: "/collections/test/documents/import", + isImport: true, + config: { + nodes, + apiKey: "test-key", + healthcheckIntervalSeconds: 1, + retryIntervalSeconds: 0, + numRetries: 3, + }, + body: importBody, + }); + + expect(fetchMocker.requests()).toHaveLength(2); + expect(new URL(fetchMocker.requests()[1]!.url).pathname).toBe( + "/collections/test/documents/import", + ); + const retryInit = fetchMocker.mock.calls[1]?.[1]; + expect(retryInit?.body).toBe(importBody); + expect((retryInit?.headers as Record)["Content-Type"]).toBe( + "text/plain", + ); + }); + it("should throw RequestError immediately on 4xx error", async () => { const nodes = [createNode(0, true)];