summaryrefslogtreecommitdiff
path: root/tests/unit_node/http_test.ts
diff options
context:
space:
mode:
authorMatt Mastracci <matthew@mastracci.com>2024-02-10 13:22:13 -0700
committerGitHub <noreply@github.com>2024-02-10 20:22:13 +0000
commitf5e46c9bf2f50d66a953fa133161fc829cecff06 (patch)
tree8faf2f5831c1c7b11d842cd9908d141082c869a5 /tests/unit_node/http_test.ts
parentd2477f780630a812bfd65e3987b70c0d309385bb (diff)
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests -> tests, and updates of relative paths for files. This is the first step towards aggregate all of the integration test files under tests/, which will lead to a set of integration tests that can run without the CLI binary being built. While we could leave these tests under `cli`, it would require us to keep a more complex directory structure for the various test runners. In addition, we have a lot of complexity to ignore various test files in the `cli` project itself (cargo publish exclusion rules, autotests = false, etc). And finally, the `tests/` folder will eventually house the `test_ffi`, `test_napi` and other testing code, reducing the size of the root repo directory. For easier review, the extremely large and noisy "move" is in the first commit (with no changes -- just a move), while the remainder of the changes to actual files is in the second commit.
Diffstat (limited to 'tests/unit_node/http_test.ts')
-rw-r--r--tests/unit_node/http_test.ts940
1 files changed, 940 insertions, 0 deletions
diff --git a/tests/unit_node/http_test.ts b/tests/unit_node/http_test.ts
new file mode 100644
index 000000000..28e67ddad
--- /dev/null
+++ b/tests/unit_node/http_test.ts
@@ -0,0 +1,940 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import EventEmitter from "node:events";
+import http, { type RequestOptions } from "node:http";
+import https from "node:https";
+import { assert, assertEquals, fail } from "@test_util/std/assert/mod.ts";
+import { assertSpyCalls, spy } from "@test_util/std/testing/mock.ts";
+
+import { gzip } from "node:zlib";
+import { Buffer } from "node:buffer";
+import { serve } from "@test_util/std/http/server.ts";
+import { execCode } from "../unit/test_util.ts";
+
+Deno.test("[node/http listen]", async () => {
+ {
+ const server = http.createServer();
+ assertEquals(0, EventEmitter.listenerCount(server, "request"));
+ }
+
+ {
+ const server = http.createServer(() => {});
+ assertEquals(1, EventEmitter.listenerCount(server, "request"));
+ }
+
+ {
+ const { promise, resolve } = Promise.withResolvers<void>();
+ const server = http.createServer();
+
+ server.listen(() => {
+ server.close();
+ });
+ server.on("close", () => {
+ resolve();
+ });
+
+ await promise;
+ }
+
+ {
+ const { promise, resolve } = Promise.withResolvers<void>();
+ const server = http.createServer();
+
+ server.listen().on("listening", () => {
+ server.close();
+ });
+ server.on("close", () => {
+ resolve();
+ });
+
+ await promise;
+ }
+
+ for (const port of [0, -0, 0.0, "0", null, undefined]) {
+ const { promise, resolve } = Promise.withResolvers<void>();
+ const server = http.createServer();
+
+ server.listen(port, () => {
+ server.close();
+ });
+ server.on("close", () => {
+ resolve();
+ });
+
+ await promise;
+ }
+});
+
+Deno.test("[node/http close]", async () => {
+ {
+ const deferred1 = Promise.withResolvers<void>();
+ const deferred2 = Promise.withResolvers<void>();
+ // Node quirk: callback gets exception object, event listener does not.
+ // deno-lint-ignore no-explicit-any
+ const server = http.createServer().close((err: any) => {
+ assertEquals(err.code, "ERR_SERVER_NOT_RUNNING");
+ deferred1.resolve();
+ });
+ // deno-lint-ignore no-explicit-any
+ server.on("close", (err: any) => {
+ assertEquals(err, undefined);
+ deferred2.resolve();
+ });
+ server.on("listening", () => {
+ throw Error("unreachable");
+ });
+ await deferred1.promise;
+ await deferred2.promise;
+ }
+
+ {
+ const deferred1 = Promise.withResolvers<void>();
+ const deferred2 = Promise.withResolvers<void>();
+ const server = http.createServer().listen().close((err) => {
+ assertEquals(err, undefined);
+ deferred1.resolve();
+ });
+ // deno-lint-ignore no-explicit-any
+ server.on("close", (err: any) => {
+ assertEquals(err, undefined);
+ deferred2.resolve();
+ });
+ server.on("listening", () => {
+ throw Error("unreachable");
+ });
+ await deferred1.promise;
+ await deferred2.promise;
+ }
+});
+
+Deno.test("[node/http] chunked response", async () => {
+ for (
+ const body of [undefined, "", "ok"]
+ ) {
+ const expected = body ?? "";
+ const { promise, resolve } = Promise.withResolvers<void>();
+
+ const server = http.createServer((_req, res) => {
+ res.writeHead(200, { "transfer-encoding": "chunked" });
+ res.end(body);
+ });
+
+ server.listen(async () => {
+ const res = await fetch(
+ // deno-lint-ignore no-explicit-any
+ `http://127.0.0.1:${(server.address() as any).port}/`,
+ );
+ assert(res.ok);
+
+ const actual = await res.text();
+ assertEquals(actual, expected);
+
+ server.close(() => resolve());
+ });
+
+ await promise;
+ }
+});
+
+// Test empty chunks: https://github.com/denoland/deno/issues/17194
+Deno.test("[node/http] empty chunk in the middle of response", async () => {
+ const { promise, resolve } = Promise.withResolvers<void>();
+
+ const server = http.createServer((_req, res) => {
+ res.write("a");
+ res.write("");
+ res.write("b");
+ res.end();
+ });
+
+ server.listen(async () => {
+ const res = await fetch(
+ // deno-lint-ignore no-explicit-any
+ `http://127.0.0.1:${(server.address() as any).port}/`,
+ );
+ const actual = await res.text();
+ assertEquals(actual, "ab");
+ server.close(() => resolve());
+ });
+
+ await promise;
+});
+
+Deno.test("[node/http] server can respond with 101, 204, 205, 304 status", async () => {
+ for (const status of [101, 204, 205, 304]) {
+ const { promise, resolve } = Promise.withResolvers<void>();
+ const server = http.createServer((_req, res) => {
+ res.statusCode = status;
+ res.end("");
+ });
+ server.listen(async () => {
+ const res = await fetch(
+ // deno-lint-ignore no-explicit-any
+ `http://127.0.0.1:${(server.address() as any).port}/`,
+ );
+ await res.arrayBuffer();
+ assertEquals(res.status, status);
+ server.close(() => resolve());
+ });
+ await promise;
+ }
+});
+
+Deno.test("[node/http] IncomingRequest socket has remoteAddress + remotePort", async () => {
+ const { promise, resolve } = Promise.withResolvers<void>();
+
+ let remoteAddress: string | undefined;
+ let remotePort: number | undefined;
+ const server = http.createServer((req, res) => {
+ remoteAddress = req.socket.remoteAddress;
+ remotePort = req.socket.remotePort;
+ res.end();
+ });
+ server.listen(async () => {
+ // deno-lint-ignore no-explicit-any
+ const port = (server.address() as any).port;
+ const res = await fetch(
+ `http://127.0.0.1:${port}/`,
+ );
+ await res.arrayBuffer();
+ assertEquals(remoteAddress, "127.0.0.1");
+ assertEquals(typeof remotePort, "number");
+ server.close(() => resolve());
+ });
+ await promise;
+});
+
+Deno.test("[node/http] request default protocol", async () => {
+ const deferred1 = Promise.withResolvers<void>();
+ const deferred2 = Promise.withResolvers<void>();
+ const server = http.createServer((_, res) => {
+ res.end("ok");
+ });
+
+ // @ts-ignore IncomingMessageForClient
+ // deno-lint-ignore no-explicit-any
+ let clientRes: any;
+ // deno-lint-ignore no-explicit-any
+ let clientReq: any;
+ server.listen(() => {
+ clientReq = http.request(
+ // deno-lint-ignore no-explicit-any
+ { host: "localhost", port: (server.address() as any).port },
+ (res) => {
+ assert(res.socket instanceof EventEmitter);
+ assertEquals(res.complete, false);
+ res.on("data", () => {});
+ res.on("end", () => {
+ server.close();
+ });
+ clientRes = res;
+ assertEquals(res.statusCode, 200);
+ deferred2.resolve();
+ },
+ );
+ clientReq.end();
+ });
+ server.on("close", () => {
+ deferred1.resolve();
+ });
+ await deferred1.promise;
+ await deferred2.promise;
+ assert(clientReq.socket instanceof EventEmitter);
+ assertEquals(clientRes!.complete, true);
+});
+
+Deno.test("[node/http] request with headers", async () => {
+ const { promise, resolve } = Promise.withResolvers<void>();
+ const server = http.createServer((req, res) => {
+ assertEquals(req.headers["x-foo"], "bar");
+ res.end("ok");
+ });
+ server.listen(() => {
+ const req = http.request(
+ {
+ host: "localhost",
+ // deno-lint-ignore no-explicit-any
+ port: (server.address() as any).port,
+ headers: { "x-foo": "bar" },
+ },
+ (res) => {
+ res.on("data", () => {});
+ res.on("end", () => {
+ server.close();
+ });
+ assertEquals(res.statusCode, 200);
+ },
+ );
+ req.end();
+ });
+ server.on("close", () => {
+ resolve();
+ });
+ await promise;
+});
+
+Deno.test("[node/http] non-string buffer response", {
+ // TODO(kt3k): Enable sanitizer. A "zlib" resource is leaked in this test case.
+ sanitizeResources: false,
+}, async () => {
+ const { promise, resolve } = Promise.withResolvers<void>();
+ const server = http.createServer((_, res) => {
+ res.socket!.end();
+ gzip(
+ Buffer.from("a".repeat(100), "utf8"),
+ {},
+ (_err: Error | null, data: Buffer) => {
+ res.setHeader("Content-Encoding", "gzip");
+ res.end(data);
+ },
+ );
+ });
+ server.listen(async () => {
+ const res = await fetch(
+ // deno-lint-ignore no-explicit-any
+ `http://localhost:${(server.address() as any).port}`,
+ );
+ try {
+ const text = await res.text();
+ assertEquals(text, "a".repeat(100));
+ } catch (e) {
+ server.emit("error", e);
+ } finally {
+ server.close(() => resolve());
+ }
+ });
+ await promise;
+});
+
+// TODO(kt3k): Enable this test
+// Currently IncomingMessage constructor has incompatible signature.
+/*
+Deno.test("[node/http] http.IncomingMessage can be created without url", () => {
+ const message = new http.IncomingMessage(
+ // adapted from https://github.com/dougmoscrop/serverless-http/blob/80bfb3e940057d694874a8b0bc12ad96d2abe7ab/lib/request.js#L7
+ {
+ // @ts-expect-error - non-request properties will also be passed in, e.g. by serverless-http
+ encrypted: true,
+ readable: false,
+ remoteAddress: "foo",
+ address: () => ({ port: 443 }),
+ // deno-lint-ignore no-explicit-any
+ end: Function.prototype as any,
+ // deno-lint-ignore no-explicit-any
+ destroy: Function.prototype as any,
+ },
+ );
+ message.url = "https://example.com";
+});
+*/
+
+Deno.test("[node/http] send request with non-chunked body", async () => {
+ let requestHeaders: Headers;
+ let requestBody = "";
+
+ const hostname = "localhost";
+ const port = 4505;
+
+ // NOTE: Instead of node/http.createServer(), serve() in std/http/server.ts is used.
+ // https://github.com/denoland/deno_std/pull/2755#discussion_r1005592634
+ const handler = async (req: Request) => {
+ requestHeaders = req.headers;
+ requestBody = await req.text();
+ return new Response("ok");
+ };
+ const abortController = new AbortController();
+ const servePromise = serve(handler, {
+ hostname,
+ port,
+ signal: abortController.signal,
+ onListen: undefined,
+ });
+
+ const opts: RequestOptions = {
+ host: hostname,
+ port,
+ method: "POST",
+ headers: {
+ "Content-Type": "text/plain; charset=utf-8",
+ "Content-Length": "11",
+ },
+ };
+ const req = http.request(opts, (res) => {
+ res.on("data", () => {});
+ res.on("end", () => {
+ abortController.abort();
+ });
+ assertEquals(res.statusCode, 200);
+ assertEquals(requestHeaders.get("content-length"), "11");
+ assertEquals(requestHeaders.has("transfer-encoding"), false);
+ assertEquals(requestBody, "hello world");
+ });
+ req.on("socket", (socket) => {
+ assert(socket.writable);
+ assert(socket.readable);
+ socket.setKeepAlive();
+ socket.destroy();
+ socket.setTimeout(100);
+ });
+ req.write("hello ");
+ req.write("world");
+ req.end();
+
+ await servePromise;
+});
+
+Deno.test("[node/http] send request with chunked body", async () => {
+ let requestHeaders: Headers;
+ let requestBody = "";
+
+ const hostname = "localhost";
+ const port = 4505;
+
+ // NOTE: Instead of node/http.createServer(), serve() in std/http/server.ts is used.
+ // https://github.com/denoland/deno_std/pull/2755#discussion_r1005592634
+ const handler = async (req: Request) => {
+ requestHeaders = req.headers;
+ requestBody = await req.text();
+ return new Response("ok");
+ };
+ const abortController = new AbortController();
+ const servePromise = serve(handler, {
+ hostname,
+ port,
+ signal: abortController.signal,
+ onListen: undefined,
+ });
+
+ const opts: RequestOptions = {
+ host: hostname,
+ port,
+ method: "POST",
+ headers: {
+ "Content-Type": "text/plain; charset=utf-8",
+ "Content-Length": "11",
+ "Transfer-Encoding": "chunked",
+ },
+ };
+ const req = http.request(opts, (res) => {
+ res.on("data", () => {});
+ res.on("end", () => {
+ abortController.abort();
+ });
+ assertEquals(res.statusCode, 200);
+ assertEquals(requestHeaders.has("content-length"), false);
+ assertEquals(requestHeaders.get("transfer-encoding"), "chunked");
+ assertEquals(requestBody, "hello world");
+ });
+ req.write("hello ");
+ req.write("world");
+ req.end();
+
+ await servePromise;
+});
+
+Deno.test("[node/http] send request with chunked body as default", async () => {
+ let requestHeaders: Headers;
+ let requestBody = "";
+
+ const hostname = "localhost";
+ const port = 4505;
+
+ // NOTE: Instead of node/http.createServer(), serve() in std/http/server.ts is used.
+ // https://github.com/denoland/deno_std/pull/2755#discussion_r1005592634
+ const handler = async (req: Request) => {
+ requestHeaders = req.headers;
+ requestBody = await req.text();
+ return new Response("ok");
+ };
+ const abortController = new AbortController();
+ const servePromise = serve(handler, {
+ hostname,
+ port,
+ signal: abortController.signal,
+ onListen: undefined,
+ });
+
+ const opts: RequestOptions = {
+ host: hostname,
+ port,
+ method: "POST",
+ headers: {
+ "Content-Type": "text/plain; charset=utf-8",
+ },
+ };
+ const req = http.request(opts, (res) => {
+ res.on("data", () => {});
+ res.on("end", () => {
+ abortController.abort();
+ });
+ assertEquals(res.statusCode, 200);
+ assertEquals(requestHeaders.has("content-length"), false);
+ assertEquals(requestHeaders.get("transfer-encoding"), "chunked");
+ assertEquals(requestBody, "hello world");
+ });
+ req.write("hello ");
+ req.write("world");
+ req.end();
+
+ await servePromise;
+});
+
+Deno.test("[node/http] ServerResponse _implicitHeader", async () => {
+ const { promise, resolve } = Promise.withResolvers<void>();
+ const server = http.createServer((_req, res) => {
+ const writeHeadSpy = spy(res, "writeHead");
+ // deno-lint-ignore no-explicit-any
+ (res as any)._implicitHeader();
+ assertSpyCalls(writeHeadSpy, 1);
+ writeHeadSpy.restore();
+ res.end("Hello World");
+ });
+
+ server.listen(async () => {
+ const { port } = server.address() as { port: number };
+ const res = await fetch(`http://localhost:${port}`);
+ assertEquals(await res.text(), "Hello World");
+ server.close(() => {
+ resolve();
+ });
+ });
+
+ await promise;
+});
+
+// https://github.com/denoland/deno/issues/21509
+Deno.test("[node/http] ServerResponse flushHeaders", async () => {
+ const { promise, resolve } = Promise.withResolvers<void>();
+ const server = http.createServer((_req, res) => {
+ res.flushHeaders(); // no-op
+ res.end("Hello World");
+ });
+
+ server.listen(async () => {
+ const { port } = server.address() as { port: number };
+ const res = await fetch(`http://localhost:${port}`);
+ assertEquals(await res.text(), "Hello World");
+ server.close(() => {
+ resolve();
+ });
+ });
+
+ await promise;
+});
+
+Deno.test("[node/http] server unref", async () => {
+ const [statusCode, _output] = await execCode(`
+ import http from "node:http";
+ const server = http.createServer((_req, res) => {
+ res.statusCode = status;
+ res.end("");
+ });
+
+ // This should let the program to exit without waiting for the
+ // server to close.
+ server.unref();
+
+ server.listen(async () => {
+ });
+ `);
+ assertEquals(statusCode, 0);
+});
+
+Deno.test("[node/http] ClientRequest handle non-string headers", async () => {
+ // deno-lint-ignore no-explicit-any
+ let headers: any;
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
+ const req = http.request("http://localhost:4545/echo_server", {
+ method: "POST",
+ headers: { 1: 2 },
+ }, (resp) => {
+ headers = resp.headers;
+
+ resp.on("data", () => {});
+
+ resp.on("end", () => {
+ resolve();
+ });
+ });
+ req.once("error", (e) => reject(e));
+ req.end();
+ await promise;
+ assertEquals(headers!["1"], "2");
+});
+
+Deno.test("[node/http] ClientRequest uses HTTP/1.1", async () => {
+ let body = "";
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
+ const req = https.request("https://localhost:5545/http_version", {
+ method: "POST",
+ headers: { 1: 2 },
+ }, (resp) => {
+ resp.on("data", (chunk) => {
+ body += chunk;
+ });
+
+ resp.on("end", () => {
+ resolve();
+ });
+ });
+ req.once("error", (e) => reject(e));
+ req.end();
+ await promise;
+ assertEquals(body, "HTTP/1.1");
+});
+
+Deno.test("[node/http] ClientRequest setTimeout", async () => {
+ let body = "";
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
+ const timer = setTimeout(() => reject("timed out"), 50000);
+ const req = http.request("http://localhost:4545/http_version", (resp) => {
+ resp.on("data", (chunk) => {
+ body += chunk;
+ });
+
+ resp.on("end", () => {
+ resolve();
+ });
+ });
+ req.setTimeout(120000);
+ req.once("error", (e) => reject(e));
+ req.end();
+ await promise;
+ clearTimeout(timer);
+ assertEquals(body, "HTTP/1.1");
+});
+
+Deno.test("[node/http] ClientRequest setNoDelay", async () => {
+ let body = "";
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
+ const timer = setTimeout(() => reject("timed out"), 50000);
+ const req = http.request("http://localhost:4545/http_version", (resp) => {
+ resp.on("data", (chunk) => {
+ body += chunk;
+ });
+
+ resp.on("end", () => {
+ resolve();
+ });
+ });
+ req.setNoDelay(true);
+ req.once("error", (e) => reject(e));
+ req.end();
+ await promise;
+ clearTimeout(timer);
+ assertEquals(body, "HTTP/1.1");
+});
+
+Deno.test("[node/http] ClientRequest PATCH", async () => {
+ let body = "";
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
+ const req = http.request("http://localhost:4545/echo_server", {
+ method: "PATCH",
+ }, (resp) => {
+ resp.on("data", (chunk) => {
+ body += chunk;
+ });
+
+ resp.on("end", () => {
+ resolve();
+ });
+ });
+ req.write("hello ");
+ req.write("world");
+ req.once("error", (e) => reject(e));
+ req.end();
+ await promise;
+ assertEquals(body, "hello world");
+});
+
+Deno.test("[node/http] ClientRequest PUT", async () => {
+ let body = "";
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
+ const req = http.request("http://localhost:4545/echo_server", {
+ method: "PUT",
+ }, (resp) => {
+ resp.on("data", (chunk) => {
+ body += chunk;
+ });
+
+ resp.on("end", () => {
+ resolve();
+ });
+ });
+ req.write("hello ");
+ req.write("world");
+ req.once("error", (e) => reject(e));
+ req.end();
+ await promise;
+ assertEquals(body, "hello world");
+});
+
+Deno.test("[node/http] ClientRequest search params", async () => {
+ let body = "";
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
+ const req = http.request({
+ host: "localhost:4545",
+ path: "search_params?foo=bar",
+ }, (resp) => {
+ resp.on("data", (chunk) => {
+ body += chunk;
+ });
+
+ resp.on("end", () => {
+ resolve();
+ });
+ });
+ req.once("error", (e) => reject(e));
+ req.end();
+ await promise;
+ assertEquals(body, "foo=bar");
+});
+
+Deno.test("[node/http] HTTPS server", async () => {
+ const deferred = Promise.withResolvers<void>();
+ const deferred2 = Promise.withResolvers<void>();
+ const client = Deno.createHttpClient({
+ caCerts: [Deno.readTextFileSync("tests/testdata/tls/RootCA.pem")],
+ });
+ const server = https.createServer({
+ cert: Deno.readTextFileSync("tests/testdata/tls/localhost.crt"),
+ key: Deno.readTextFileSync("tests/testdata/tls/localhost.key"),
+ }, (req, res) => {
+ // @ts-ignore: It exists on TLSSocket
+ assert(req.socket.encrypted);
+ res.end("success!");
+ });
+ server.listen(() => {
+ // deno-lint-ignore no-explicit-any
+ fetch(`https://localhost:${(server.address() as any).port}`, {
+ client,
+ }).then(async (res) => {
+ assertEquals(res.status, 200);
+ assertEquals(await res.text(), "success!");
+ server.close();
+ deferred2.resolve();
+ });
+ })
+ .on("error", () => fail());
+ server.on("close", () => {
+ deferred.resolve();
+ });
+ await Promise.all([deferred.promise, deferred2.promise]);
+ client.close();
+});
+
+Deno.test(
+ "[node/http] client upgrade",
+ { permissions: { net: true } },
+ async () => {
+ const { promise, resolve } = Promise.withResolvers<void>();
+ const server = http.createServer((req, res) => {
+ // @ts-ignore: It exists on TLSSocket
+ assert(!req.socket.encrypted);
+ res.writeHead(200, { "Content-Type": "text/plain" });
+ res.end("okay");
+ });
+ // @ts-ignore it's a socket for real
+ let serverSocket;
+ server.on("upgrade", (req, socket, _head) => {
+ // https://github.com/denoland/deno/issues/21979
+ assert(req.socket?.write);
+ socket.write(
+ "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" +
+ "Upgrade: WebSocket\r\n" +
+ "Connection: Upgrade\r\n" +
+ "\r\n",
+ );
+ serverSocket = socket;
+ });
+
+ // Now that server is running
+ server.listen(1337, "127.0.0.1", () => {
+ // make a request
+ const options = {
+ port: 1337,
+ host: "127.0.0.1",
+ headers: {
+ "Connection": "Upgrade",
+ "Upgrade": "websocket",
+ },
+ };
+
+ const req = http.request(options);
+ req.end();
+
+ req.on("upgrade", (_res, socket, _upgradeHead) => {
+ socket.end();
+ // @ts-ignore it's a socket for real
+ serverSocket!.end();
+ server.close(() => {
+ resolve();
+ });
+ });
+ });
+
+ await promise;
+ },
+);
+
+Deno.test(
+ "[node/http] client end with callback",
+ { permissions: { net: true } },
+ async () => {
+ let received = false;
+ const ac = new AbortController();
+ const server = Deno.serve({ port: 5928, signal: ac.signal }, (_req) => {
+ received = true;
+ return new Response("hello");
+ });
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
+ let body = "";
+
+ const request = http.request(
+ "http://localhost:5928/",
+ (resp) => {
+ resp.on("data", (chunk) => {
+ body += chunk;
+ });
+
+ resp.on("end", () => {
+ resolve();
+ });
+ },
+ );
+ request.on("error", reject);
+ request.end(() => {
+ assert(received);
+ });
+
+ await promise;
+ ac.abort();
+ await server.finished;
+
+ assertEquals(body, "hello");
+ },
+);
+
+Deno.test("[node/http] server emits error if addr in use", async () => {
+ const deferred1 = Promise.withResolvers<void>();
+ const deferred2 = Promise.withResolvers<Error>();
+
+ const server = http.createServer();
+ server.listen(9001);
+
+ const server2 = http.createServer();
+ server2.on("error", (e) => {
+ deferred2.resolve(e);
+ });
+ server2.listen(9001);
+
+ const err = await deferred2.promise;
+ server.close(() => deferred1.resolve());
+ server2.close();
+ await deferred1.promise;
+ const expectedMsg = Deno.build.os === "windows"
+ ? "Only one usage of each socket address"
+ : "Address already in use";
+ assert(
+ err.message.startsWith(expectedMsg),
+ `Wrong error: ${err.message}`,
+ );
+});
+
+Deno.test(
+ "[node/http] client destroy doesn't leak",
+ { permissions: { net: true } },
+ async () => {
+ const ac = new AbortController();
+ let timerId;
+
+ const server = Deno.serve(
+ { port: 5929, signal: ac.signal },
+ async (_req) => {
+ await new Promise((resolve) => {
+ timerId = setTimeout(resolve, 5000);
+ });
+ return new Response("hello");
+ },
+ );
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
+
+ const request = http.request("http://localhost:5929/");
+ request.on("error", reject);
+ request.on("close", () => {});
+ request.end();
+ setTimeout(() => {
+ request.destroy(new Error());
+ resolve();
+ }, 100);
+
+ await promise;
+ clearTimeout(timerId);
+ ac.abort();
+ await server.finished;
+ },
+);
+
+Deno.test("[node/http] node:http exports globalAgent", async () => {
+ const http = await import("node:http");
+ assert(
+ http.globalAgent,
+ "node:http must export 'globalAgent' on module namespace",
+ );
+ assert(
+ http.default.globalAgent,
+ "node:http must export 'globalAgent' on module default export",
+ );
+});
+
+Deno.test("[node/https] node:https exports globalAgent", async () => {
+ const https = await import("node:https");
+ assert(
+ https.globalAgent,
+ "node:https must export 'globalAgent' on module namespace",
+ );
+ assert(
+ https.default.globalAgent,
+ "node:https must export 'globalAgent' on module default export",
+ );
+});
+
+Deno.test("[node/http] node:http request.setHeader(header, null) doesn't throw", () => {
+ {
+ const req = http.request("http://localhost:4545/");
+ req.on("error", () => {});
+ // @ts-expect-error - null is not a valid header value
+ req.setHeader("foo", null);
+ req.end();
+ req.destroy();
+ }
+ {
+ const req = https.request("https://localhost:4545/");
+ req.on("error", () => {});
+ // @ts-expect-error - null is not a valid header value
+ req.setHeader("foo", null);
+ req.end();
+ req.destroy();
+ }
+});
+
+Deno.test("[node/http] ServerResponse getHeader", async () => {
+ const { promise, resolve } = Promise.withResolvers<void>();
+ const server = http.createServer((_req, res) => {
+ res.setHeader("foo", "bar");
+ assertEquals(res.getHeader("foo"), "bar");
+ assertEquals(res.getHeader("ligma"), undefined);
+ res.end("Hello World");
+ });
+
+ server.listen(async () => {
+ const { port } = server.address() as { port: number };
+ const res = await fetch(`http://localhost:${port}`);
+ assertEquals(await res.text(), "Hello World");
+ server.close(() => {
+ resolve();
+ });
+ });
+
+ await promise;
+});