From e20e2a6bd4530e51bd8139aaefc8f9d0231304bb Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Thu, 14 Aug 2025 03:23:57 +0000 Subject: [PATCH] fix: Set IncomingMessage.url to empty string for HTTPS requests to match Node.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes issue #13820 where https.request() returned "/" for response.url instead of an empty string like Node.js. The issue was that for FetchResponse type IncomingMessage objects, the url property was inheriting from the fetch Response.url which returns the pathname. Changes: - Modified IncomingMessage constructor to explicitly set url to "" for FetchResponse type - Added comprehensive tests covering various request scenarios - Ensures Node.js compatibility for response.url property 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/js/node/_http_incoming.ts | 5 + .../issue/https-request-url-property.test.ts | 130 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 test/regression/issue/https-request-url-property.test.ts diff --git a/src/js/node/_http_incoming.ts b/src/js/node/_http_incoming.ts index f98be938ce..a9799681ca 100644 --- a/src/js/node/_http_incoming.ts +++ b/src/js/node/_http_incoming.ts @@ -147,6 +147,11 @@ function IncomingMessage(req, options = defaultIncomingOpts) { if (!assignHeaders(this, req)) { this[fakeSocketSymbol] = req; } + + // For fetch responses, set URL to empty string to match Node.js behavior + if (type === NodeHTTPIncomingRequestType.FetchResponse) { + this.url = ""; + } } else { // Node defaults url and method to null. this.url = ""; diff --git a/test/regression/issue/https-request-url-property.test.ts b/test/regression/issue/https-request-url-property.test.ts new file mode 100644 index 0000000000..b1342258eb --- /dev/null +++ b/test/regression/issue/https-request-url-property.test.ts @@ -0,0 +1,130 @@ +import { test, expect } from "bun:test"; +import { request as httpsRequest } from "https"; +import { request as httpRequest } from "http"; + +test("https.request URL property should be empty string like Node.js - issue #13820", async () => { + const url = await new Promise((resolve, reject) => { + const req = httpsRequest("https://google.com", (res) => { + resolve(res.url); + res.resume(); // Drain response to avoid hanging + }); + req.on("error", reject); + req.setTimeout(5000, () => reject(new Error("Timeout"))); + req.end(); + }); + + // Node.js returns empty string, not "/" + expect(url).toBe(""); +}); + +test("https.request URL property for root path with explicit slash", async () => { + const url = await new Promise((resolve, reject) => { + const req = httpsRequest("https://google.com/", (res) => { + resolve(res.url); + res.resume(); + }); + req.on("error", reject); + req.setTimeout(5000, () => reject(new Error("Timeout"))); + req.end(); + }); + + expect(url).toBe(""); +}); + +test("https.request URL property for path", async () => { + const url = await new Promise((resolve, reject) => { + const req = httpsRequest("https://httpbin.org/json", (res) => { + resolve(res.url); + res.resume(); + }); + req.on("error", reject); + req.setTimeout(5000, () => reject(new Error("Timeout"))); + req.end(); + }); + + expect(url).toBe(""); +}); + +test("http.request URL property should also be empty string", async () => { + const url = await new Promise((resolve, reject) => { + const req = httpRequest("http://httpbin.org/json", (res) => { + resolve(res.url); + res.resume(); + }); + req.on("error", reject); + req.setTimeout(5000, () => reject(new Error("Timeout"))); + req.end(); + }); + + expect(url).toBe(""); +}); + +test("https.request URL property with redirect", async () => { + const url = await new Promise((resolve, reject) => { + const req = httpsRequest("https://httpbin.org/redirect/1", (res) => { + resolve(res.url); + res.resume(); + }); + req.on("error", reject); + req.setTimeout(5000, () => reject(new Error("Timeout"))); + req.end(); + }); + + // Even after redirect, URL should still be empty string (Node.js behavior) + expect(url).toBe(""); +}); + +test("https.request URL property consistency across multiple requests", async () => { + const urls = await Promise.all([ + new Promise((resolve, reject) => { + const req = httpsRequest("https://httpbin.org/status/200", (res) => { + resolve(res.url); + res.resume(); + }); + req.on("error", reject); + req.setTimeout(5000, () => reject(new Error("Timeout"))); + req.end(); + }), + new Promise((resolve, reject) => { + const req = httpsRequest("https://httpbin.org/status/404", (res) => { + resolve(res.url); + res.resume(); + }); + req.on("error", reject); + req.setTimeout(5000, () => reject(new Error("Timeout"))); + req.end(); + }), + ]); + + expect(urls[0]).toBe(""); + expect(urls[1]).toBe(""); +}); + +test("https.request URL property type should be string", async () => { + const url = await new Promise((resolve, reject) => { + const req = httpsRequest("https://httpbin.org/json", (res) => { + resolve(res.url); + res.resume(); + }); + req.on("error", reject); + req.setTimeout(5000, () => reject(new Error("Timeout"))); + req.end(); + }); + + expect(typeof url).toBe("string"); + expect(url).toBe(""); +}); + +test("https.request response object should have url property", async () => { + const hasUrlProperty = await new Promise((resolve, reject) => { + const req = httpsRequest("https://httpbin.org/json", (res) => { + resolve("url" in res); + res.resume(); + }); + req.on("error", reject); + req.setTimeout(5000, () => reject(new Error("Timeout"))); + req.end(); + }); + + expect(hasUrlProperty).toBe(true); +}); \ No newline at end of file