From e9d8506575956910b0a0bafccc281f4ea3c8d304 Mon Sep 17 00:00:00 2001 From: jirist Date: Wed, 7 Aug 2024 11:09:16 +0200 Subject: [PATCH] Allow falsy values (except undefined) as a valid body --- packages/openapi-fetch/src/index.js | 2 +- packages/openapi-fetch/test/index.test.ts | 182 ++++++++++++++++++++++ 2 files changed, 183 insertions(+), 1 deletion(-) diff --git a/packages/openapi-fetch/src/index.js b/packages/openapi-fetch/src/index.js index 75fadaa0f..36248c52f 100644 --- a/packages/openapi-fetch/src/index.js +++ b/packages/openapi-fetch/src/index.js @@ -84,7 +84,7 @@ export default function createClient(clientOptions) { ...init, headers: mergeHeaders(baseHeaders, headers, params.header), }; - if (requestInit.body) { + if (requestInit.body !== undefined) { requestInit.body = bodySerializer(requestInit.body); // remove `Content-Type` if serialized body is FormData; browser will correctly set Content-Type & boundary expression if (requestInit.body instanceof FormData) { diff --git a/packages/openapi-fetch/test/index.test.ts b/packages/openapi-fetch/test/index.test.ts index 14c7a9438..79362d072 100644 --- a/packages/openapi-fetch/test/index.test.ts +++ b/packages/openapi-fetch/test/index.test.ts @@ -1,6 +1,8 @@ import { HttpResponse, type StrictResponse } from "msw"; import { afterAll, beforeAll, describe, expect, expectTypeOf, it } from "vitest"; import createClient, { + type BodySerializer, + type FetchOptions, type MethodResponse, type Middleware, type MiddlewareCallbackParams, @@ -1335,6 +1337,186 @@ describe("client", () => { }); }); + describe("body serialization", () => { + const BODY_ACCEPTING_METHODS = [["PUT"], ["POST"], ["DELETE"], ["OPTIONS"], ["PATCH"]] as const; + const ALL_METHODS = [...BODY_ACCEPTING_METHODS, ["GET"], ["HEAD"]] as const; + + const fireRequestAndGetBodyInformation = async (options: { + bodySerializer?: BodySerializer; + method: (typeof ALL_METHODS)[number][number]; + fetchOptions: FetchOptions; + }) => { + const client = createClient({ baseUrl, bodySerializer: options.bodySerializer }); + const { getRequest } = useMockRequestHandler({ + baseUrl, + method: "all", + path: "/blogposts-optional", + status: 200, + }); + await client[options.method]("/blogposts-optional", options.fetchOptions as any); + + const request = getRequest(); + const bodyText = await request.text(); + + return { bodyUsed: request.bodyUsed, bodyText }; + }; + + it.each(ALL_METHODS)("missing body (with body serializer) - %s", async (method) => { + const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`); + const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ + bodySerializer, + method, + fetchOptions: {}, + }); + + expect(bodyUsed).toBe(false); + expect(bodyText).toBe(""); + expect(bodySerializer).not.toBeCalled(); + }); + + it.each(ALL_METHODS)("missing body (without body serializer) - %s", async (method) => { + const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ method, fetchOptions: {} }); + + expect(bodyUsed).toBe(false); + expect(bodyText).toBe(""); + }); + + it.each(ALL_METHODS)("`undefined` body (with body serializer) - %s", async (method) => { + const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`); + const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ + bodySerializer, + method, + fetchOptions: { + body: undefined, + }, + }); + + expect(bodyUsed).toBe(false); + expect(bodyText).toBe(""); + expect(bodySerializer).not.toBeCalled(); + }); + + it.each(ALL_METHODS)("`undefined` body (without body serializer) - %s", async (method) => { + const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ + method, + fetchOptions: { + body: undefined, + }, + }); + + expect(bodyUsed).toBe(false); + expect(bodyText).toBe(""); + }); + + it.each(BODY_ACCEPTING_METHODS)("`null` body (with body serializer) - %s", async (method) => { + const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`); + const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ + bodySerializer, + method, + fetchOptions: { + body: null, + }, + }); + + expect(bodyUsed).toBe(true); + expect(bodyText).toBe("Serialized: null"); + expect(bodySerializer).toBeCalled(); + }); + + it.each(BODY_ACCEPTING_METHODS)("`null` body (without body serializer) - %s", async (method) => { + const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ + method, + fetchOptions: { + body: null, + }, + }); + + expect(bodyUsed).toBe(true); + expect(bodyText).toBe("null"); + }); + + it.each(BODY_ACCEPTING_METHODS)("`false` body (with body serializer) - %s", async (method) => { + const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`); + const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ + bodySerializer, + method, + fetchOptions: { + body: false, + }, + }); + + expect(bodyUsed).toBe(true); + expect(bodyText).toBe("Serialized: false"); + expect(bodySerializer).toBeCalled(); + }); + + it.each(BODY_ACCEPTING_METHODS)("`false` body (without body serializer) - %s", async (method) => { + const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ + method, + fetchOptions: { + body: false, + }, + }); + + expect(bodyUsed).toBe(true); + expect(bodyText).toBe("false"); + }); + + it.each(BODY_ACCEPTING_METHODS)("`''` body (with body serializer) - %s", async (method) => { + const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`); + const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ + bodySerializer, + method, + fetchOptions: { + body: "", + }, + }); + + expect(bodyUsed).toBe(true); + expect(bodyText).toBe('Serialized: ""'); + expect(bodySerializer).toBeCalled(); + }); + + it.each(BODY_ACCEPTING_METHODS)("`''` body (without body serializer) - %s", async (method) => { + const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ + method, + fetchOptions: { + body: "", + }, + }); + + expect(bodyUsed).toBe(true); + expect(bodyText).toBe('""'); + }); + + it.each(BODY_ACCEPTING_METHODS)("`0` body (with body serializer) - %s", async (method) => { + const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`); + const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ + bodySerializer, + method, + fetchOptions: { + body: 0, + }, + }); + + expect(bodyUsed).toBe(true); + expect(bodyText).toBe("Serialized: 0"); + expect(bodySerializer).toBeCalled(); + }); + + it.each(BODY_ACCEPTING_METHODS)("`0` body (without body serializer) - %s", async (method) => { + const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ + method, + fetchOptions: { + body: 0, + }, + }); + + expect(bodyUsed).toBe(true); + expect(bodyText).toBe("0"); + }); + }); + describe("requests", () => { it("multipart/form-data", async () => { const client = createClient({ baseUrl });