import test from "node:test"; import assert from "node:assert/strict"; import net from "node:net"; import { PortAllocator, parsePortRange } from "../src/ports.js"; test("a port range is read only in the operator's declared form", () => { assert.deepEqual(parsePortRange("5200-5219"), { from: 5200, to: 5219 }); assert.deepEqual(parsePortRange(undefined), { from: 5200, to: 5219 }); for (const bad of ["5200", "5219-5200", "80-90", "abc", "5200_5219"]) { assert.throws(() => parsePortRange(bad), /DEV_SERVER_PORT_RANGE/, `accepted ${bad}`); } }); test("each instance gets its own port, and a released one comes back", async () => { const allocator = new PortAllocator({ from: 5400, to: 5401 }); const first = await allocator.take("a"); const second = await allocator.take("b"); assert.notEqual(first, second); allocator.release(first); assert.equal(await allocator.take("c"), first); }); test("a port held by a process this worker does not know about is skipped", async () => { // An orphan from an earlier run holds its port. Handing that port out would produce two servers // fighting over one address - which is how a preview ends up at an address nobody looks at. const squatter = net.createServer(); await new Promise((resolve) => squatter.listen(5402, "0.0.0.0", resolve)); try { const allocator = new PortAllocator({ from: 5402, to: 5403 }); assert.equal(await allocator.take("a"), 5403); } finally { await new Promise((resolve) => squatter.close(resolve)); } }); test("an exhausted range names itself and the setting that widens it", async () => { const allocator = new PortAllocator({ from: 5404, to: 5404 }); await allocator.take("a"); await assert.rejects(() => allocator.take("b"), /No free port in 5404-5404: raise DEV_SERVER_PORT_RANGE/); }); test("releasing by owner frees every port that instance held", async () => { const allocator = new PortAllocator({ from: 5405, to: 5407 }); await allocator.take("a"); await allocator.take("a"); assert.equal(allocator.size, 2); allocator.releaseAllOf("a"); assert.equal(allocator.size, 0); });