diff options
author | 2023-03-22 21:22:31 -0700 | |
---|---|---|
committer | 2023-03-22 21:22:31 -0700 | |
commit | 732c5e7fa9b0bb89938ca001749a13501386485e (patch) | |
tree | 1e5a3a106d2a666328df240b427efbc33a89f981 | |
parent | 5fd406ca2ffa0cb9c1cb98140bedf0a3ba9e5022 (diff) | |
download | bun-732c5e7fa9b0bb89938ca001749a13501386485e.tar.gz bun-732c5e7fa9b0bb89938ca001749a13501386485e.tar.zst bun-732c5e7fa9b0bb89938ca001749a13501386485e.zip |
test(undici): rm external http reqs from tests (#2459)
* test(undici): rm external http reqs from tests
* cleanup(http-test-server): remove finished TODOs
* test(undici): fix server type, remove type:module to fix typings in test dir
* test(undici): make the typings better
* test(undici): fix typo
Diffstat (limited to '')
-rw-r--r-- | test/http-test-server.ts | 168 | ||||
-rw-r--r-- | test/js/first_party/undici/undici.test.ts | 61 | ||||
-rw-r--r-- | test/package.json | 1 |
3 files changed, 208 insertions, 22 deletions
diff --git a/test/http-test-server.ts b/test/http-test-server.ts new file mode 100644 index 000000000..ab85a5ec8 --- /dev/null +++ b/test/http-test-server.ts @@ -0,0 +1,168 @@ +import { serve } from "bun"; + +// This is obviously incomplete but these are probably the most common status codes + the ones we need for testing +type ValidStatusCode = 200 | 201 | 400 | 404 | 405 | 500; + +const defaultOpts = { + type: "json", + headers: { + "Content-Type": "application/json", + }, + status: 200, +}; + +const defaultResponseBodies = { + 200: "OK", + 201: "Created", + 400: "Bad Request", + 404: "Not Found", + 405: "Method Not Allowed", + 500: "Internal Server Error", +} as Record<ValidStatusCode, string>; + +function getDefaultJSONBody(request: Request) { + return { + url: request.url, + method: request.method, + }; +} + +function makeTestJsonResponse( + request: Request, + opts: ResponseInit & { type?: "plaintext" | "json" } = { status: 200, type: "json" }, + body?: { [k: string | number]: any } | string, +): Response { + const defaultJSONBody = getDefaultJSONBody(request); + + let type = opts.type || "json"; + let resBody; + let headers; + + // Setup headers + + if (!opts.headers) headers = new Headers(); + + if (!(opts.headers instanceof Headers)) headers = new Headers(opts.headers); + else headers = opts.headers; + + switch (type) { + case "json": + if (typeof body === "object" && body !== null) { + resBody = JSON.stringify({ ...defaultJSONBody, ...body }) as string; + } else if (typeof body === "string") { + resBody = JSON.stringify({ ...defaultJSONBody, data: body }) as string; + } else { + resBody = JSON.stringify(defaultJSONBody) as string; + } + // Check to set headers + headers.set("Content-Type", "application/json"); + break; + case "plaintext": + if (typeof body === "object") { + if (body === null) { + resBody = ""; + } else { + resBody = JSON.stringify(body); + } + } + // Check to set headers + headers.set("Content-Type", "text/plain"); + default: + } + + return new Response(resBody as string, { + ...defaultOpts, + ...opts, + headers: { ...defaultOpts.headers, ...headers }, + }); +} + +export function createServer() { + const server = serve({ + port: 0, + fetch: async req => { + const { pathname, search } = new URL(req.url); + const lowerPath = pathname.toLowerCase(); + + let response: Response; + switch (lowerPath.match(/\/\w+/)?.[0] || "") { + // START HTTP METHOD ROUTES + case "/get": + if (req.method.toUpperCase() !== "GET") { + response = makeTestJsonResponse(req, { status: 405 }); + break; + } + if (search !== "") { + const params = new URLSearchParams(search); + const args = {} as Record<string, string | number>; + params.forEach((v, k) => { + if (!isNaN(parseInt(v))) { + args[k] = parseInt(v); + } else { + args[k] = v; + } + }); + response = makeTestJsonResponse(req, { status: 200 }, { args }); + break; + } + // Normal case + response = makeTestJsonResponse(req); + break; + case "/post": + if (req.method.toUpperCase() !== "POST") { + response = makeTestJsonResponse(req, { status: 405 }); + break; + } + response = makeTestJsonResponse(req, { status: 201, type: "json" }, await req.text()); + break; + case "/head": + if (req.method.toUpperCase() !== "HEAD") { + response = makeTestJsonResponse(req, { status: 405 }); + break; + } + response = makeTestJsonResponse(req, { status: 200 }); + break; + + // END HTTP METHOD ROUTES + + case "/status": + // Parse the status from URL path params: /status/200 + const rawStatus = lowerPath.split("/").filter(Boolean)[1]; + if (rawStatus) { + const status = parseInt(rawStatus); + if (!isNaN(status) && status > 100 && status < 599) { + response = makeTestJsonResponse( + req, + { status }, + { data: defaultResponseBodies[(status || 200) as ValidStatusCode] }, + ); + break; + } + } + response = makeTestJsonResponse(req, { status: 400 }, { data: "Invalid status" }); + break; + case "/delay": + const rawDelay = lowerPath.split("/").filter(Boolean)[1]; + if (rawDelay) { + const delay = parseInt(rawDelay); + if (!isNaN(delay) && delay >= 0) { + await Bun.sleep(delay * 1000); + response = makeTestJsonResponse(req, { status: 200 }, { data: "Delayed" }); + break; + } + } + response = makeTestJsonResponse(req, { status: 400 }, { data: "Invalid delay" }); + break; + case "/headers": + response = makeTestJsonResponse(req, { status: 200 }, { headers: req.headers }); + break; + default: + response = makeTestJsonResponse(req, { status: 404 }); + } + + return response; + }, + }); + const { port, stop } = server; + return { server, port, stop }; +} diff --git a/test/js/first_party/undici/undici.test.ts b/test/js/first_party/undici/undici.test.ts index 603b7a03d..ab8430ee9 100644 --- a/test/js/first_party/undici/undici.test.ts +++ b/test/js/first_party/undici/undici.test.ts @@ -1,17 +1,36 @@ -import { describe, it, expect } from "bun:test"; +import { describe, it, expect, beforeAll, afterAll } from "bun:test"; import { request } from "undici"; +import { createServer } from "../../../http-test-server"; + describe("undici", () => { + let serverCtl: ReturnType<typeof createServer>; + let hostUrl: string; + let hostname = "localhost"; + let port: number; + let host: string; + + beforeAll(() => { + serverCtl = createServer(); + port = serverCtl.port; + host = `${hostname}:${port}`; + hostUrl = `http://${host}`; + }); + + afterAll(() => { + serverCtl.stop(); + }); + describe("request", () => { it("should make a GET request when passed a URL string", async () => { - const { body } = await request("https://httpbin.org/get"); + const { body } = await request(`${hostUrl}/get`); expect(body).toBeDefined(); const json = (await body.json()) as { url: string }; - expect(json.url).toBe("https://httpbin.org/get"); + expect(json.url).toBe(`${hostUrl}/get`); }); it("should error when body has already been consumed", async () => { - const { body } = await request("https://httpbin.org/get"); + const { body } = await request(`${hostUrl}/get`); await body.json(); expect(body.bodyUsed).toBe(true); try { @@ -23,7 +42,7 @@ describe("undici", () => { }); it("should make a POST request when provided a body and POST method", async () => { - const { body } = await request("https://httpbin.org/post", { + const { body } = await request(`${hostUrl}/post`, { method: "POST", body: "Hello world", }); @@ -33,23 +52,23 @@ describe("undici", () => { }); it("should accept a URL class object", async () => { - const { body } = await request(new URL("https://httpbin.org/get")); + const { body } = await request(new URL(`${hostUrl}/get`)); expect(body).toBeDefined(); const json = (await body.json()) as { url: string }; - expect(json.url).toBe("https://httpbin.org/get"); + expect(json.url).toBe(`${hostUrl}/get`); }); // it("should accept an undici UrlObject", async () => { // // @ts-ignore - // const { body } = await request({ protocol: "https:", hostname: "httpbin.org", path: "/get" }); + // const { body } = await request({ protocol: "https:", hostname: host, path: "/get" }); // expect(body).toBeDefined(); // const json = (await body.json()) as { url: string }; - // expect(json.url).toBe("https://httpbin.org/get"); + // expect(json.url).toBe(`${hostUrl}/get`); // }); it("should prevent body from being attached to GET or HEAD requests", async () => { try { - await request("https://httpbin.org/get", { + await request(`${hostUrl}/get`, { method: "GET", body: "Hello world", }); @@ -59,7 +78,7 @@ describe("undici", () => { } try { - await request("https://httpbin.org/head", { + await request(`${hostUrl}/head`, { method: "HEAD", body: "Hello world", }); @@ -70,12 +89,12 @@ describe("undici", () => { }); it("should allow a query string to be passed", async () => { - const { body } = await request("https://httpbin.org/get?foo=bar"); + const { body } = await request(`${hostUrl}/get?foo=bar`); expect(body).toBeDefined(); const json = (await body.json()) as { args: { foo: string } }; expect(json.args.foo).toBe("bar"); - const { body: body2 } = await request("https://httpbin.org/get", { + const { body: body2 } = await request(`${hostUrl}/get`, { query: { foo: "bar" }, }); expect(body2).toBeDefined(); @@ -85,14 +104,14 @@ describe("undici", () => { it("should throw on HTTP 4xx or 5xx error when throwOnError is true", async () => { try { - await request("https://httpbin.org/status/404", { throwOnError: true }); + await request(`${hostUrl}/status/404`, { throwOnError: true }); throw new Error("Should have errored"); } catch (e) { expect((e as Error).message).toBe("Request failed with status code 404"); } try { - await request("https://httpbin.org/status/500", { throwOnError: true }); + await request(`${hostUrl}/status/500`, { throwOnError: true }); throw new Error("Should have errored"); } catch (e) { expect((e as Error).message).toBe("Request failed with status code 500"); @@ -102,8 +121,8 @@ describe("undici", () => { it("should allow us to abort the request with a signal", async () => { const controller = new AbortController(); try { - setTimeout(() => controller.abort(), 1000); - const req = await request("https://httpbin.org/delay/5", { + setTimeout(() => controller.abort(), 500); + const req = await request(`${hostUrl}/delay/5`, { signal: controller.signal, }); await req.body.json(); @@ -114,20 +133,20 @@ describe("undici", () => { }); it("should properly append headers to the request", async () => { - const { body } = await request("https://httpbin.org/headers", { + const { body } = await request(`${hostUrl}/headers`, { headers: { "x-foo": "bar", }, }); expect(body).toBeDefined(); - const json = (await body.json()) as { headers: { "X-Foo": string } }; - expect(json.headers["X-Foo"]).toBe("bar"); + const json = (await body.json()) as { headers: { "x-foo": string } }; + expect(json.headers["x-foo"]).toBe("bar"); }); // it("should allow the use of FormData", async () => { // const form = new FormData(); // form.append("foo", "bar"); - // const { body } = await request("https://httpbin.org/post", { + // const { body } = await request(`${hostUrl}/post`, { // method: "POST", // body: form, // }); diff --git a/test/package.json b/test/package.json index 5688df194..d3fe8e70c 100644 --- a/test/package.json +++ b/test/package.json @@ -1,6 +1,5 @@ { "name": "test", - "type": "module", "devDependencies": {}, "dependencies": { "@swc/core": "^1.3.38", |