diff --git a/src/createClient.ts b/src/createClient.ts index bb422d1..37f25f8 100644 --- a/src/createClient.ts +++ b/src/createClient.ts @@ -122,7 +122,7 @@ export const createClient = (options: ClientOptions = {}): Client => { } catch (e) { const error = new JsonParseError( request, - response.clone(), + response, e ); await callbacks.onJsonParseError.emit({ diff --git a/test/lib.test.ts b/test/lib.test.ts index 3df8f38..eb098df 100644 --- a/test/lib.test.ts +++ b/test/lib.test.ts @@ -19,27 +19,26 @@ const mockConsoleError = () => { return vi.spyOn(console, "error").mockImplementation(() => null); }; -const API_HOST = "http://that-is-so-fetch.com"; +const HOST = "http://that-is-so-fetch.com"; describe("f.get()", () => { test("It performs a GET request and returns a response", async () => { - nock(API_HOST) + nock(HOST) .get("/api/user") .reply(200, { firstName: "Shane", lastName: "MacGowan", age: 65 }); - const response = await f.get("/api/user"); - + const response = await f.get(HOST + "/api/user"); expect(response).toBeInstanceOf(Response); expect(response.status).toBe(200); expect(response.statusText).toBe("OK"); }); test("You can unwrap the JSON response directly on the return promise", async () => { - nock(API_HOST) + nock(HOST) .get("/api/user") .reply(200, { firstName: "Shane", lastName: "MacGowan", age: 65 }); - const user = await f.get("/api/user").json(); + const user = await f.get(HOST + "/api/user").json(); expect(user).toEqual({ firstName: "Shane", @@ -49,12 +48,12 @@ describe("f.get()", () => { }); test("You can unwrap the JSON response on the response instance", async () => { - nock(API_HOST) + nock(HOST) .get("/api/user") .reply(200, { firstName: "Shane", lastName: "MacGowan", age: 65 }); const user = await f - .get("/api/user") + .get(HOST + "/api/user") .then(response => response.json()); expect(user).toEqual({ @@ -65,24 +64,21 @@ describe("f.get()", () => { }); test("4xx responses throw an HttpError", async () => { - nock(API_HOST) + nock(HOST) .get("/api/user") .reply(404, "Oh no, that page don't exist baby"); try { - await f.get("/api/user"); + await f.get(HOST + "/api/user"); expect(true).toBe(false); // Fail the test if no error is thrown } catch (e) { expect(e).toBeInstanceOf(HttpError); - expect(e).not.toBeInstanceOf(NetworkError); - expect(e).not.toBeInstanceOf(JsonParseError); - expect(e).not.toBeInstanceOf(JsonStringifyError); // Lets check we have everything we need on the error const error = e as HttpError; expect(error.request).toBeInstanceOf(Request); - expect(error.request.url).toBe("/api/user"); + expect(error.request.url).toBe(HOST + "/api/user"); expect(error.request.method).toBe("GET"); expect(error.statusCode).toBe(404); @@ -90,17 +86,17 @@ describe("f.get()", () => { "Oh no, that page don't exist baby" ); expect(error.message).toBe( - "Request failed with status code 404 Not Found: GET /api/user" + `Request failed with status code 404 Not Found: GET ${HOST}/api/user` ); } // Now we've dived into detail on a single error response lets also // check we get an HttpError for other 4xx status codes for (const statusCode of [400, 401, 402, 403, 405]) { - nock(API_HOST).get("/api/user").reply(statusCode); + nock(HOST).get("/api/user").reply(statusCode); try { - await f.get("/api/user"); + await f.get(HOST + "/api/user"); expect(true).toBe(false); // Fail the test if no error is thrown } catch (error) { expect(error).toBeInstanceOf(HttpError); @@ -110,10 +106,10 @@ describe("f.get()", () => { test("5xx responses throw an HttpError", async () => { for (const statusCode of [500, 501, 502, 503, 505]) { - nock(API_HOST).get("/api/user").reply(statusCode); + nock(HOST).get("/api/user").reply(statusCode); try { - await f.get("/api/user"); + await f.get(HOST + "/api/user"); expect(true).toBe(false); // Fail the test if no error is thrown } catch (error) { expect(error).toBeInstanceOf(HttpError); @@ -121,141 +117,89 @@ describe("f.get()", () => { } }); - test("Network errors will be retried up-to 3 times", async () => { - // nock is going to log a load of errors to the console here so lets - // silence that just for this test - const logSpy = mockConsoleError(); - - nock(API_HOST) - .get("/api/user") - .twice() - .replyWithError("Something bad happened") - .get("/api/user") - .reply(200, { firstName: "Shane", lastName: "MacGowan", age: 65 }); - - const req = f.get("/api/user").json(); - - const user = await req; - - expect(user).toEqual({ - firstName: "Shane", - lastName: "MacGowan", - age: 65, - }); - - // restore console.error and setTimeout - logSpy.mockRestore(); - }); - - test("Network errors will throw a NetworkError after the 3rd failed attempt", async () => { + test("Network errors will throw a NetworkError", async () => { const err = new Error("something bad happened"); - nock(API_HOST).get("/api/user").replyWithError(err); + nock(HOST).get("/api/user").replyWithError(err); try { - await f.get("/api/user"); + await f.get(HOST + "/api/user"); expect(true).toBe(false); // Fail the test if no error is thrown } catch (e) { expect(e).toBeInstanceOf(NetworkError); - expect(e).not.toBeInstanceOf(HttpError); - expect(e).not.toBeInstanceOf(JsonParseError); - expect(e).not.toBeInstanceOf(JsonStringifyError); // Lets check we have everything we need on the error const error = e as NetworkError; - expect(error.request).toBeInstanceOf(Request); - expect(error.request.url).toBe("/api/user"); + expect(error.request.url).toBe(HOST + "/api/user"); expect(error.request.method).toBe("GET"); } }); test("Calling .json() on a non JSON response will throw a JsonParseError", async () => { - nock(API_HOST).get("/api/user").reply(200, "Oh hello"); + nock(HOST).get("/api/user").reply(200, "Oh hello"); try { - await f.get("/api/user").json(); + await f.get(HOST + "/api/user").json(); expect(true).toBe(false); // Fail the test if no error is thrown } catch (e) { expect(e).toBeInstanceOf(JsonParseError); - expect(e).not.toBeInstanceOf(HttpError); - expect(e).not.toBeInstanceOf(NetworkError); - expect(e).not.toBeInstanceOf(JsonStringifyError); } }); test("Calling Response.json() on a non JSON response body will throw a JsonParseError", async () => { - nock(API_HOST).get("/api/user").reply(200, "Oh hello"); + nock(HOST).get("/api/user").reply(200, "Oh hello"); try { - await f.get("/api/user").then(response => response.json()); + await f.get(HOST + "/api/user").then(response => response.json()); expect(true).toBe(false); // Fail the test if no error is thrown } catch (e) { expect(e).toBeInstanceOf(JsonParseError); - expect(e).not.toBeInstanceOf(HttpError); - expect(e).not.toBeInstanceOf(NetworkError); - expect(e).not.toBeInstanceOf(JsonStringifyError); } }); - describe("headers", () => { - test("It adds some generally useful default headers", async () => { - nock(API_HOST) - .matchHeader("Content-Type", "application/json") - .matchHeader("Accept", "application/json") - .matchHeader("Cache", "no-cache") - .get("/api/user") - .reply(200); - - await f.get("/api/user"); - }); + test("You can set headers", async () => { + nock(HOST) + .matchHeader("Content-Type", "application/text") + .matchHeader("Accept", "application/text") + .matchHeader("Foo", "bar") + .get("/api/user") + .reply(200); - test("You can add and overwrite headers manually", async () => { - nock(API_HOST) - .matchHeader("Content-Type", "application/text") - .matchHeader("Accept", "application/text") - .matchHeader("Cache", "no-cache") - .matchHeader("Foo", "bar") - .get("/api/user") - .reply(200); - - await f.get("/api/user", { - headers: { - "Content-Type": "application/text", - Accept: "application/text", - Foo: "bar", - }, - }); + await f.get(HOST + "/api/user", { + headers: { + "Content-Type": "application/text", + Accept: "application/text", + Foo: "bar", + }, }); }); }); describe("f.post()", () => { test("It performs a POST request with some standard headers and returns a response", async () => { - nock(API_HOST) - .matchHeader("Content-Type", "application/json") - .matchHeader("Accept", "application/json") - .matchHeader("Cache", "no-cache") + nock(HOST) .post("/api/user", { firstName: "Mad Dog", lastName: "Mcrea" }) .reply(200, { id: "567" }); - const response = await f.post("/api/user", { + const response = await f.post(HOST + "/api/user", { json: { firstName: "Mad Dog", lastName: "Mcrea" }, }); + const body = await response.json(); expect(response).toBeInstanceOf(Response); expect(response.status).toBe(200); expect(response.statusText).toBe("OK"); - expect(await response.json()).toEqual({ id: "567" }); + expect(body).toEqual({ id: "567" }); }); test("If you try to post non-serializeable JSON data it throws a JsonStringifyError", async () => { - nock(API_HOST).post("/api/user").reply(200, { id: "567" }); + nock(HOST).post("/api/user").reply(200, { id: "567" }); try { - await f.post("/api/user", { + await f.post(HOST + "/api/user", { json: { // JS doesn't know how to parse a BigInt into JSON userId: BigInt(9007199254740991), @@ -265,20 +209,17 @@ describe("f.post()", () => { expect(true).toBe(false); } catch (e) { expect(e).toBeInstanceOf(JsonStringifyError); - expect(e).not.toBeInstanceOf(HttpError); - expect(e).not.toBeInstanceOf(NetworkError); - expect(e).not.toBeInstanceOf(JsonParseError); const error = e as JsonStringifyError; - expect(error.request.url).toBe("/api/user"); + expect(error.request.url).toBe(HOST + "/api/user"); expect(error.request.method).toBe("POST"); } }); test("JsonStringifyError when you await the request, not when you make the request", async () => { - nock(API_HOST).post("/api/user").reply(200, { id: "567" }); + nock(HOST).post("/api/user").reply(200, { id: "567" }); - const request = f.post("/api/user", { + const request = f.post(HOST + "/api/user", { json: { // JS doesn't know how to parse a BigInt into JSON userId: BigInt(9007199254740991), @@ -297,14 +238,11 @@ describe("f.post()", () => { describe("f.put()", () => { test("It performs a PUT request with some standard headers and returns a response", async () => { - nock(API_HOST) - .matchHeader("Content-Type", "application/json") - .matchHeader("Accept", "application/json") - .matchHeader("Cache", "no-cache") + nock(HOST) .put("/api/user", { firstName: "Mad Dog", lastName: "Mcrea" }) .reply(200, { id: "567" }); - const response = await f.put("/api/user", { + const response = await f.put(HOST + "/api/user", { json: { firstName: "Mad Dog", lastName: "Mcrea" }, }); @@ -317,14 +255,11 @@ describe("f.put()", () => { describe("f.patch()", () => { test("It performs a PATCH request with some standard headers and returns a response", async () => { - nock(API_HOST) - .matchHeader("Content-Type", "application/json") - .matchHeader("Accept", "application/json") - .matchHeader("Cache", "no-cache") + nock(HOST) .patch("/api/user", { firstName: "Mad Dog", lastName: "Mcrea" }) .reply(200, { id: "567" }); - const response = await f.patch("/api/user", { + const response = await f.patch(HOST + "/api/user", { json: { firstName: "Mad Dog", lastName: "Mcrea" }, }); @@ -337,13 +272,11 @@ describe("f.patch()", () => { describe("f.delete()", () => { test("It performs a DELETE request with some standard headers and returns a response", async () => { - nock(API_HOST) - .matchHeader("Pragma", "no-cache") - .matchHeader("Cache", "no-cache") + nock(HOST) .delete("/api/user", { id: "4" }) .reply(200, { success: true }); - const response = await f.delete("/api/user", { + const response = await f.delete(HOST + "/api/user", { json: { id: "4" }, }); @@ -362,9 +295,9 @@ describe("onRequestStart callback", () => { const unsubscribe1 = f.callbacks.onRequestStart(callback1); const unsubscribe2 = f.callbacks.onRequestStart(callback2); - nock(API_HOST).get("/api/user?workspace=3").reply(200); + nock(HOST).get("/api/user?workspace=3").reply(200); - const request = f.get("/api/user?workspace=3"); + const request = f.get(HOST + "/api/user?workspace=3"); await vi.waitFor(() => { expect(callback1).toHaveBeenCalled(); @@ -377,7 +310,7 @@ describe("onRequestStart callback", () => { const argRequest = args[0].request as Request; expect(argRequest.method).toBe("GET"); - expect(argRequest.url).toBe("/api/user?workspace=3"); + expect(argRequest.url).toBe(HOST + "/api/user?workspace=3"); // cleanup await request; @@ -403,9 +336,9 @@ describe("onRequestStart callback", () => { const unsubscribe1 = f.callbacks.onRequestStart(callback1); const unsubscribe2 = f.callbacks.onRequestStart(callback2); - nock(API_HOST).get("/api/user?workspace=3").reply(200); + nock(HOST).get("/api/user?workspace=3").reply(200); - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); expect(output).toEqual([ "callback1 start", @@ -426,16 +359,16 @@ describe("onRequestStart callback", () => { const unsubscribe1 = f.callbacks.onRequestStart(callback1); const unsubscribe2 = f.callbacks.onRequestStart(callback2); - nock(API_HOST).get("/api/user?workspace=3").twice().reply(200); + nock(HOST).get("/api/user?workspace=3").twice().reply(200); - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); expect(callback1).toHaveBeenCalledTimes(1); expect(callback2).toHaveBeenCalledTimes(1); unsubscribe1(); - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); expect(callback1).toHaveBeenCalledTimes(1); expect(callback2).toHaveBeenCalledTimes(2); @@ -452,9 +385,9 @@ describe("onSuccessResponse callback", () => { const unsubscribe1 = f.callbacks.onSuccessResponse(callback1); const unsubscribe2 = f.callbacks.onSuccessResponse(callback2); - nock(API_HOST).get("/api/user?workspace=3").reply(200); + nock(HOST).get("/api/user?workspace=3").reply(200); - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); expect(callback1).toHaveBeenCalled(); expect(callback2).toHaveBeenCalled(); @@ -470,7 +403,7 @@ describe("onSuccessResponse callback", () => { const argRequest = args[0].request as Request; expect(argRequest.method).toBe("GET"); - expect(argRequest.url).toBe("/api/user?workspace=3"); + expect(argRequest.url).toBe(HOST + "/api/user?workspace=3"); // cleanup unsubscribe1(); @@ -495,9 +428,9 @@ describe("onSuccessResponse callback", () => { const unsubscribe1 = f.callbacks.onSuccessResponse(callback1); const unsubscribe2 = f.callbacks.onSuccessResponse(callback2); - nock(API_HOST).get("/api/user?workspace=3").reply(200); + nock(HOST).get("/api/user?workspace=3").reply(200); - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); expect(output).toEqual([ "callback1 start", @@ -518,16 +451,16 @@ describe("onSuccessResponse callback", () => { const unsubscribe1 = f.callbacks.onSuccessResponse(callback1); const unsubscribe2 = f.callbacks.onSuccessResponse(callback2); - nock(API_HOST).get("/api/user?workspace=3").twice().reply(200); + nock(HOST).get("/api/user?workspace=3").twice().reply(200); - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); expect(callback1).toHaveBeenCalledTimes(1); expect(callback2).toHaveBeenCalledTimes(1); unsubscribe1(); - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); expect(callback1).toHaveBeenCalledTimes(1); expect(callback2).toHaveBeenCalledTimes(2); @@ -545,10 +478,10 @@ describe("onSuccessResponse callback", () => { const unsubscribe1 = f.callbacks.onSuccessResponse(callback1); const unsubscribe2 = f.callbacks.onSuccessResponse(callback2); - nock(API_HOST).get("/api/user?workspace=3").reply(statusCode); + nock(HOST).get("/api/user?workspace=3").reply(statusCode); try { - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); } catch (e) { // } @@ -571,12 +504,12 @@ describe("onSuccessResponse callback", () => { const unsubscribe1 = f.callbacks.onSuccessResponse(callback1); const unsubscribe2 = f.callbacks.onSuccessResponse(callback2); - nock(API_HOST) + nock(HOST) .get("/api/user?workspace=3") .replyWithError(new Error("something bad happened")); try { - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); } catch (e) { // } @@ -602,10 +535,10 @@ describe("onErrorResponse callback", () => { const unsubscribe1 = f.callbacks.onErrorResponse(callback1); const unsubscribe2 = f.callbacks.onErrorResponse(callback2); - nock(API_HOST).get("/api/user?workspace=3").reply(statusCode); + nock(HOST).get("/api/user?workspace=3").reply(statusCode); try { - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); } catch (e) { // } @@ -625,7 +558,7 @@ describe("onErrorResponse callback", () => { const argError = args[0].error as HttpError; expect(argRequest.method).toBe("GET"); - expect(argRequest.url).toBe("/api/user?workspace=3"); + expect(argRequest.url).toBe(HOST + "/api/user?workspace=3"); expect(argError.statusCode).toBe(statusCode); // cleanup @@ -643,12 +576,12 @@ describe("onErrorResponse callback", () => { const unsubscribe1 = f.callbacks.onErrorResponse(callback1); const unsubscribe2 = f.callbacks.onErrorResponse(callback2); - nock(API_HOST) + nock(HOST) .get("/api/user?workspace=3") .replyWithError(new Error("something bad happened")); try { - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); } catch (e) { // } @@ -667,7 +600,7 @@ describe("onErrorResponse callback", () => { const argRequest = args[0].request as Request; expect(argRequest.method).toBe("GET"); - expect(argRequest.url).toBe("/api/user?workspace=3"); + expect(argRequest.url).toBe(HOST + "/api/user?workspace=3"); // cleanup unsubscribe1(); @@ -693,10 +626,10 @@ describe("onErrorResponse callback", () => { const unsubscribe1 = f.callbacks.onErrorResponse(callback1); const unsubscribe2 = f.callbacks.onErrorResponse(callback2); - nock(API_HOST).get("/api/user?workspace=3").reply(400); + nock(HOST).get("/api/user?workspace=3").reply(400); try { - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); } catch (e) { // } @@ -720,10 +653,10 @@ describe("onErrorResponse callback", () => { const unsubscribe1 = f.callbacks.onErrorResponse(callback1); const unsubscribe2 = f.callbacks.onErrorResponse(callback2); - nock(API_HOST).get("/api/user?workspace=3").twice().reply(400); + nock(HOST).get("/api/user?workspace=3").twice().reply(400); try { - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); } catch (e) { // } @@ -734,7 +667,7 @@ describe("onErrorResponse callback", () => { unsubscribe1(); try { - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); } catch (e) { // } @@ -752,10 +685,10 @@ describe("onErrorResponse callback", () => { const unsubscribe1 = f.callbacks.onErrorResponse(callback1); const unsubscribe2 = f.callbacks.onErrorResponse(callback2); - nock(API_HOST).get("/api/user?workspace=3").reply(200); + nock(HOST).get("/api/user?workspace=3").reply(200); try { - await f.get("/api/user?workspace=3"); + await f.get(HOST + "/api/user?workspace=3"); } catch (e) { // } @@ -777,10 +710,10 @@ describe("onJsonParseError callback", () => { const unsubscribe1 = f.callbacks.onJsonParseError(callback1); const unsubscribe2 = f.callbacks.onJsonParseError(callback2); - nock(API_HOST).get("/api/user").reply(200, "Oh hello"); + nock(HOST).get("/api/user").reply(200, "Oh hello"); try { - await f.get("/api/user").json(); + await f.get(HOST + "/api/user").json(); expect(true).toBe(false); // Fail the test if no error is thrown } catch (e) { // @@ -802,10 +735,10 @@ describe("onJsonStringifyError callback", () => { const unsubscribe1 = f.callbacks.onJsonStringifyError(callback1); const unsubscribe2 = f.callbacks.onJsonStringifyError(callback2); - nock(API_HOST).get("/api/user").reply(200, "Oh hello"); + nock(HOST).get("/api/user").reply(200, "Oh hello"); try { - await f.post("/api/user", { + await f.post(HOST + "/api/user", { json: { // JS doesn't know how to parse a BigInt into JSON userId: BigInt(9007199254740991), @@ -830,10 +763,10 @@ describe("onJsonStringifyError callback", () => { const unsubscribe1 = f.callbacks.onJsonStringifyError(callback1); const unsubscribe2 = f.callbacks.onJsonStringifyError(callback2); - nock(API_HOST).get("/api/user").reply(200, "Oh hello"); + nock(HOST).get("/api/user").reply(200, "Oh hello"); try { - await f.post("/api/user", { + await f.post(HOST + "/api/user", { json: { userId: 12, },