diff options
Diffstat (limited to 'test/js/node/http/node-http.fixme.ts')
-rw-r--r-- | test/js/node/http/node-http.fixme.ts | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/test/js/node/http/node-http.fixme.ts b/test/js/node/http/node-http.fixme.ts new file mode 100644 index 000000000..d47002a6f --- /dev/null +++ b/test/js/node/http/node-http.fixme.ts @@ -0,0 +1,604 @@ +import { describe, expect, it, beforeAll, afterAll } from "bun:test"; +import { createServer, request, get, Agent, globalAgent, Server } from "node:http"; +import { createDoneDotAll } from "node-harness"; + +function listen(server: any): Promise<URL> { + return new Promise((resolve, reject) => { + server.listen({ port: 0 }, (err, hostname, port) => { + if (err) { + reject(err); + } else { + resolve(new URL(`http://${hostname}:${port}`)); + } + }); + setTimeout(() => reject("Timed out"), 5000); + }); +} + +describe("node:http", () => { + describe("createServer", async () => { + it("hello world", async () => { + const server = createServer((req, res) => { + expect(req.url).toBe("/hello?world"); + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello World"); + }); + const url = await listen(server); + const res = await fetch(new URL("/hello?world", url)); + expect(await res.text()).toBe("Hello World"); + server.close(); + }); + + it("request & response body streaming (large)", async () => { + const bodyBlob = new Blob(["hello world", "hello world".repeat(9000)]); + + const input = await bodyBlob.text(); + + const server = createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + req.on("data", chunk => { + res.write(chunk); + }); + + req.on("end", () => { + res.end(); + }); + }); + const url = await listen(server); + const res = await fetch(url, { + method: "POST", + body: bodyBlob, + }); + + const out = await res.text(); + expect(out).toBe(input); + server.close(); + }); + + it("request & response body streaming (small)", async () => { + const bodyBlob = new Blob(["hello world", "hello world".repeat(4)]); + + const input = await bodyBlob.text(); + + const server = createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + req.on("data", chunk => { + res.write(chunk); + }); + + req.on("end", () => { + res.end(); + }); + }); + const url = await listen(server); + const res = await fetch(url, { + method: "POST", + body: bodyBlob, + }); + + const out = await res.text(); + expect(out).toBe(input); + server.close(); + }); + + it("listen should return server", async () => { + const server = createServer(); + const listenResponse = server.listen(0); + expect(listenResponse instanceof Server).toBe(true); + expect(listenResponse).toBe(server); + listenResponse.close(); + }); + }); + + describe("request", () => { + let server; + let serverPort; + let timer: Timer | null = null; + beforeAll(() => { + server = createServer((req, res) => { + const reqUrl = new URL(req.url!, `http://${req.headers.host}`); + if (reqUrl.pathname) { + if (reqUrl.pathname === "/redirect") { + // Temporary redirect + res.writeHead(301, { + Location: `http://localhost:${serverPort}/redirected`, + }); + res.end("Got redirect!\n"); + return; + } + if (reqUrl.pathname === "/redirected") { + res.writeHead(404, { "Content-Type": "text/plain" }); + res.end("Not Found"); + return; + } + if (reqUrl.pathname === "/lowerCaseHeaders") { + res.writeHead(200, { "content-type": "text/plain", "X-Custom-Header": "custom_value" }); + res.end("Hello World"); + return; + } + if (reqUrl.pathname.includes("timeout")) { + if (timer) clearTimeout(timer); + timer = setTimeout(() => { + res.end("Hello World"); + timer = null; + }, 3000); + return; + } + if (reqUrl.pathname === "/pathTest") { + res.end("Path correct!\n"); + return; + } + } + + res.writeHead(200, { "Content-Type": "text/plain" }); + + if (req.headers["x-test"]) { + res.write(`x-test: ${req.headers["x-test"]}\n`); + } + + // Check for body + if (req.method === "POST") { + req.on("data", chunk => { + res.write(chunk); + }); + + req.on("end", () => { + res.write("POST\n"); + res.end("Hello World"); + }); + } else { + if (req.headers["X-Test"] !== undefined) { + res.write(`X-Test: test\n`); + } + res.write("Maybe GET maybe not\n"); + res.end("Hello World"); + } + }); + server.listen({ port: 0 }, (_, __, port) => { + serverPort = port; + }); + }); + afterAll(() => { + server.close(); + if (timer) clearTimeout(timer); + }); + + it("check for expected fields", done => { + const req = request({ host: "localhost", port: serverPort, method: "GET" }, res => { + res.on("end", () => { + done(); + }); + res.on("error", err => done(err)); + }); + expect(req.path).toEqual("/"); + expect(req.method).toEqual("GET"); + expect(req.host).toEqual("localhost"); + expect(req.protocol).toEqual("http:"); + req.end(); + }); + + it("should make a standard GET request when passed string as first arg", done => { + const req = request(`http://localhost:${serverPort}`, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Maybe GET maybe not\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + }); + + it("should make a https:// GET request when passed string as first arg", done => { + const req = request("https://example.com", res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toContain("This domain is for use in illustrative examples in documents"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + }); + + it("should make a POST request when provided POST method, even without a body", done => { + const req = request({ host: "localhost", port: serverPort, method: "POST" }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("POST\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + }); + + it("should correctly handle a POST request with a body", done => { + const req = request({ host: "localhost", port: serverPort, method: "POST" }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Posting\nPOST\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.write("Posting\n"); + req.end(); + }); + + it("should noop request.setSocketKeepAlive without error", () => { + const req = request(`http://localhost:${serverPort}`); + req.setSocketKeepAlive(true, 1000); + req.end(); + expect(true).toBe(true); + }); + + it("should allow us to set timeout with request.setTimeout or `timeout` in options", done => { + const createDone = createDoneDotAll(done); + const req1Done = createDone(); + const req2Done = createDone(); + + // const start = Date.now(); + const req1 = request( + { + host: "localhost", + port: serverPort, + path: "/timeout", + timeout: 500, + }, + res => { + req1Done(new Error("Should not have received response")); + }, + ); + req1.on("timeout", () => req1Done()); + + const req2 = request( + { + host: "localhost", + port: serverPort, + path: "/timeout", + }, + res => { + req2Done(new Error("Should not have received response")); + }, + ); + + req2.setTimeout(500, () => { + req2Done(); + }); + req1.end(); + req2.end(); + }); + + it("should correctly set path when path provided", done => { + const createDone = createDoneDotAll(done); + const req1Done = createDone(); + const req2Done = createDone(); + + const req1 = request(`http://localhost:${serverPort}/pathTest`, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Path correct!\n"); + req1Done(); + }); + res.on("error", err => req1Done(err)); + }); + + const req2 = request(`http://localhost:${serverPort}`, { path: "/pathTest" }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Path correct!\n"); + req2Done(); + }); + res.on("error", err => req2Done(err)); + }); + + req1.end(); + req2.end(); + + expect(req1.path).toBe("/pathTest"); + expect(req2.path).toBe("/pathTest"); + }); + + it("should emit response when response received", done => { + const req = request(`http://localhost:${serverPort}`); + + req.on("response", res => { + expect(res.statusCode).toBe(200); + done(); + }); + req.end(); + }); + + // NOTE: Node http.request doesn't follow redirects by default + it("should handle redirects properly", done => { + const req = request(`http://localhost:${serverPort}/redirect`, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Got redirect!\n"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + }); + + it("should correctly attach headers to request", done => { + const req = request({ host: "localhost", port: serverPort, headers: { "X-Test": "test" } }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("x-test: test\nMaybe GET maybe not\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + expect(req.getHeader("X-Test")).toBe("test"); + }); + + it("should correct casing of method param", done => { + const req = request({ host: "localhost", port: serverPort, method: "get" }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Maybe GET maybe not\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + }); + + it("should allow for port as a string", done => { + const req = request({ host: "localhost", port: `${serverPort}`, method: "GET" }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Maybe GET maybe not\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + }); + + it("should allow us to pass a URL object", done => { + const req = request(new URL(`http://localhost:${serverPort}`), { method: "POST" }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Hello WorldPOST\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.write("Hello World"); + req.end(); + }); + + it("should ignore body when method is GET/HEAD/OPTIONS", done => { + const createDone = createDoneDotAll(done); + const methods = ["GET", "HEAD", "OPTIONS"]; + const dones = {}; + for (const method of methods) { + dones[method] = createDone(); + } + for (const method of methods) { + const req = request(`http://localhost:${serverPort}`, { method }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe(method === "GET" ? "Maybe GET maybe not\nHello World" : ""); + dones[method](); + }); + res.on("error", err => dones[method](err)); + }); + req.write("BODY"); + req.end(); + } + }); + + it("should return response with lowercase headers", done => { + const req = request(`http://localhost:${serverPort}/lowerCaseHeaders`, res => { + console.log(res.headers); + expect(res.headers["content-type"]).toBe("text/plain"); + expect(res.headers["x-custom-header"]).toBe("custom_value"); + done(); + }); + req.end(); + }); + }); + + describe("signal", () => { + it("should abort and close the server", done => { + const server = createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello World"); + }); + + //force timeout to not hang tests + const interval = setTimeout(() => { + expect(false).toBe(true); + server.close(); + done(); + }, 100); + + const signal = AbortSignal.timeout(30); + signal.addEventListener("abort", () => { + clearTimeout(interval); + expect(true).toBe(true); + done(); + }); + + server.listen({ signal, port: 0 }); + }); + }); + + describe("get", () => { + let server; + let url; + beforeAll(async () => { + server = createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello World"); + }); + url = await listen(server); + }); + afterAll(() => { + server.close(); + }); + it("should make a standard GET request, like request", done => { + get(url, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Hello World"); + done(); + }); + res.on("error", err => done(err)); + }); + }); + }); + + describe("Agent", () => { + let server; + let dummyReq; + let dummyAgent; + beforeAll(() => { + dummyAgent = new Agent(); + server = createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello World"); + }); + server.listen({ port: 0 }, (_, host, port) => { + // Setup request after server is listening + dummyReq = request( + { + host, + port, + agent: dummyAgent, + }, + res => {}, + ); + dummyReq.on("error", () => {}); + }); + }); + + afterAll(() => { + dummyReq.end(); + server.close(); + }); + + it("should be a class", () => { + expect(Agent instanceof Function).toBe(true); + }); + + it("should have a default maxSockets of Infinity", () => { + expect(dummyAgent.maxSockets).toBe(Infinity); + }); + + it("should have a keepAlive value", () => { + expect(dummyAgent.keepAlive).toBe(false); + }); + + it("should noop keepSocketAlive", () => { + const agent = new Agent({ keepAlive: true }); + // @ts-ignore + expect(agent.keepAlive).toBe(true); + + agent.keepSocketAlive(dummyReq.socket); + }); + + it("should provide globalAgent", () => { + expect(globalAgent instanceof Agent).toBe(true); + }); + }); + + describe("ClientRequest.signal", () => { + let server; + let server_port; + let server_host; + beforeAll(() => { + server = createServer((req, res) => { + Bun.sleep(10).then(() => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello World"); + }); + }); + server.listen({ port: 0 }, (_err, host, port) => { + server_port = port; + server_host = host; + }); + }); + afterAll(() => { + server.close(); + }); + it("should attempt to make a standard GET request and abort", done => { + get(`http://${server_host}:${server_port}`, { signal: AbortSignal.timeout(5) }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(true).toBeFalsy(); + done(); + }); + res.on("error", _ => { + expect(true).toBeFalsy(); + done(); + }); + }).on("error", err => { + expect(err?.name).toBe("AbortError"); + done(); + }); + }); + }); +}); |