aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Derrick Farris <mr.dcfarris@gmail.com> 2023-03-22 21:22:31 -0700
committerGravatar GitHub <noreply@github.com> 2023-03-22 21:22:31 -0700
commit732c5e7fa9b0bb89938ca001749a13501386485e (patch)
tree1e5a3a106d2a666328df240b427efbc33a89f981
parent5fd406ca2ffa0cb9c1cb98140bedf0a3ba9e5022 (diff)
downloadbun-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.ts168
-rw-r--r--test/js/first_party/undici/undici.test.ts61
-rw-r--r--test/package.json1
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",