/** * @see https://nodejs.org/api/net.html#class-netsocketaddress */ import { SocketAddress, SocketAddressInitOptions } from "node:net"; let v4: SocketAddress; let v6: SocketAddress; beforeEach(() => { v4 = new SocketAddress({ family: "ipv4" }); v6 = new SocketAddress({ family: "ipv6" }); }); describe("SocketAddress constructor", () => { it("is named SocketAddress", () => { expect(SocketAddress.name).toBe("SocketAddress"); }); it("is newable", () => { // @ts-expect-error -- types are wrong. default is kEmptyObject. expect(new SocketAddress()).toBeInstanceOf(SocketAddress); }); // FIXME: setting `call: false` in codegen has no effect, but should make the // constructor non-callable. it.skip("is not callable", () => { // @ts-expect-error -- types are wrong. expect(() => SocketAddress()).toThrow(TypeError); }); describe.each([ new SocketAddress(), new SocketAddress(undefined), new SocketAddress({}), new SocketAddress({ family: undefined }), new SocketAddress({ family: "ipv4" }), ])("new SocketAddress()", address => { it("creates an ipv4 address", () => { expect(address.family).toBe("ipv4"); }); it("address is 127.0.0.1", () => { expect(address.address).toBe("127.0.0.1"); }); it("port is 0", () => { expect(address.port).toBe(0); }); it("flowlabel is 0", () => { expect(address.flowlabel).toBe(0); }); }); // describe("new SocketAddress({ family: 'ipv6' })", () => { it("creates a new ipv6 any address", () => { expect(v6).toMatchObject({ address: "::", port: 0, family: "ipv6", flowlabel: 0, }); }); }); // it.each([ [ { family: "ipv4", address: "1.2.3.4", port: 1234, flowlabel: 9 }, { address: "1.2.3.4", port: 1234, family: "ipv4", flowlabel: 0 }, ], // family gets lowercased [{ family: "IPv4" }, { address: "127.0.0.1", family: "ipv4", port: 0 }], [{ family: "IPV6" }, { address: "::", family: "ipv6", port: 0 }], ] as [SocketAddressInitOptions, Partial][])( "new SocketAddress(%o) matches %o", (options, expected) => { const address = new SocketAddress(options); expect(address).toMatchObject(expected); }, ); // =========================================================================== // ============================ INVALID ARGUMENTS ============================ // =========================================================================== it.each([Symbol.for("ipv4"), function ipv4() {}, { family: "ipv4" }, "ipv1", "ip"])( "given an invalid family, throws ERR_INVALID_ARG_VALUE", (family: any) => { expect(() => new SocketAddress({ family })).toThrowWithCode(Error, "ERR_INVALID_ARG_VALUE"); }, ); // =========================================================================== // ============================= LEAK DETECTION ============================== // =========================================================================== it("does not leak memory", () => { const growthFactor = 3.0; // allowed growth factor for memory usage const warmup = 1_000; // # of warmup iterations const iters = 100_000; // # of iterations const debug = false; // we want to hit both cached and uncached code paths const options = [ undefined, { family: "ipv6" }, { family: "ipv4", address: "1.2.3.4", port: 3000 }, { family: "ipv6", address: "::3", port: 9 }, ] as SocketAddressInitOptions[]; // warmup var sa; for (let i = 0; i < warmup; i++) { sa = new SocketAddress(options[i % options.length]); } sa = undefined; Bun.gc(true); const before = process.memoryUsage(); if (debug) console.log("before", before); // actual test for (let i = 0; i < iters; i++) { sa = new SocketAddress(options[i % 2]); } sa = undefined; Bun.gc(true); const after = process.memoryUsage(); if (debug) console.log("after", after); expect(after.rss).toBeLessThanOrEqual(before.rss * growthFactor); }); }); // describe("SocketAddress.isSocketAddress", () => { it("is a function that takes 1 argument", () => { expect(SocketAddress).toHaveProperty("isSocketAddress"); expect(SocketAddress.isSocketAddress).toBeInstanceOf(Function); expect(SocketAddress.isSocketAddress).toHaveLength(1); }); it("has the correct property descriptor", () => { const desc = Object.getOwnPropertyDescriptor(SocketAddress, "isSocketAddress"); expect(desc).toEqual({ value: expect.any(Function), writable: true, enumerable: false, configurable: true, }); }); it("returns true for a SocketAddress instance", () => { expect(SocketAddress.isSocketAddress(v4)).toBeTrue(); expect(SocketAddress.isSocketAddress(v6)).toBeTrue(); }); it("returns false for POJOs that look like a SocketAddress", () => { const notASocketAddress = { address: "127.0.0.1", port: 0, family: "ipv4", flowlabel: 0, }; expect(SocketAddress.isSocketAddress(notASocketAddress)).toBeFalse(); }); it("returns false for faked SocketAddresses", () => { const fake = Object.create(SocketAddress.prototype); for (const key of Object.keys(v4)) { fake[key] = v4[key]; } expect(fake instanceof SocketAddress).toBeTrue(); expect(SocketAddress.isSocketAddress(fake)).toBeFalse(); }); it("returns false for subclasses", () => { class NotASocketAddress extends SocketAddress {} expect(SocketAddress.isSocketAddress(new NotASocketAddress())).toBeFalse(); }); }); // describe("SocketAddress.parse", () => { it("is a function that takes 1 argument", () => { expect(SocketAddress).toHaveProperty("parse"); expect(SocketAddress.parse).toBeInstanceOf(Function); expect(SocketAddress.parse).toHaveLength(1); }); it("has the correct property descriptor", () => { const desc = Object.getOwnPropertyDescriptor(SocketAddress, "parse"); expect(desc).toEqual({ value: expect.any(Function), writable: true, enumerable: false, configurable: true, }); }); it.each([ ["1.2.3.4", { address: "1.2.3.4", port: 0, family: "ipv4" }], ["192.168.257:1", { address: "192.168.1.1", port: 1, family: "ipv4" }], ["256", { address: "0.0.1.0", port: 0, family: "ipv4" }], ["999999999:12", { address: "59.154.201.255", port: 12, family: "ipv4" }], ["0xffffffff", { address: "255.255.255.255", port: 0, family: "ipv4" }], ["0x.0x.0", { address: "0.0.0.0", port: 0, family: "ipv4" }], ["[1:0::]", { address: "1::", port: 0, family: "ipv6" }], ["[1::8]:123", { address: "1::8", port: 123, family: "ipv6" }], ])("(%s) == %o", (input, expected) => { const sa = SocketAddress.parse(input); expect(sa).toBeDefined(); expect(sa.toJSON()).toMatchObject(expected); }); it.each([ "", "invalid", "1.2.3.4.5.6", "0.0.0.9999", "1.2.3.4:-1", "1.2.3.4:null", "1.2.3.4:65536", "[1:0:::::::]", // line break ])("parse('%s') == undefined", invalidInput => { expect(SocketAddress.parse(invalidInput)).toBeUndefined(); }); }); // describe("SocketAddress.prototype.address", () => { it("has the correct property descriptor", () => { const desc = Object.getOwnPropertyDescriptor(SocketAddress.prototype, "address"); expect(desc).toEqual({ get: expect.any(Function), set: undefined, enumerable: false, configurable: true, }); }); it("is read-only", () => { const addr = new SocketAddress(); // @ts-expect-error -- ofc it's read-only expect(() => (addr.address = "1.2.3.4")).toThrow(); }); }); // describe("SocketAddress.prototype.port", () => { it("has the correct property descriptor", () => { const desc = Object.getOwnPropertyDescriptor(SocketAddress.prototype, "port"); expect(desc).toEqual({ get: expect.any(Function), set: undefined, enumerable: false, configurable: true, }); }); }); // describe("SocketAddress.prototype.family", () => { it("has the correct property descriptor", () => { const desc = Object.getOwnPropertyDescriptor(SocketAddress.prototype, "family"); expect(desc).toEqual({ get: expect.any(Function), set: undefined, enumerable: false, configurable: true, }); }); }); // describe("SocketAddress.prototype.flowlabel", () => { it("has the correct property descriptor", () => { const desc = Object.getOwnPropertyDescriptor(SocketAddress.prototype, "flowlabel"); expect(desc).toEqual({ get: expect.any(Function), set: undefined, enumerable: false, configurable: true, }); }); }); // describe("SocketAddress.prototype.toJSON", () => { it("is a function that takes 0 arguments", () => { expect(SocketAddress.prototype).toHaveProperty("toJSON"); expect(SocketAddress.prototype.toJSON).toBeInstanceOf(Function); expect(SocketAddress.prototype.toJSON).toHaveLength(0); }); it("has the correct property descriptor", () => { const desc = Object.getOwnPropertyDescriptor(SocketAddress.prototype, "toJSON"); expect(desc).toEqual({ value: expect.any(Function), writable: true, enumerable: false, configurable: true, }); }); it("returns an object with address, port, family, and flowlabel", () => { expect(v4.toJSON()).toEqual({ address: "127.0.0.1", port: 0, family: "ipv4", flowlabel: 0, }); expect(v6.toJSON()).toEqual({ address: "::", port: 0, family: "ipv6", flowlabel: 0, }); }); describe("When called on a default SocketAddress", () => { let address: Record; beforeEach(() => { address = v4.toJSON(); }); it("SocketAddress.isSocketAddress() returns false", () => { expect(SocketAddress.isSocketAddress(address)).toBeFalse(); }); it("does not have SocketAddress as its prototype", () => { expect(Object.getPrototypeOf(address)).not.toBe(SocketAddress.prototype); expect(address instanceof SocketAddress).toBeFalse(); }); }); // }); //