diff --git a/desktop/src/main/__tests__/ports.test.ts b/desktop/src/main/__tests__/ports.test.ts index 67a93569..ef5688d0 100644 --- a/desktop/src/main/__tests__/ports.test.ts +++ b/desktop/src/main/__tests__/ports.test.ts @@ -3,49 +3,96 @@ import net from 'node:net'; import { findAvailablePort, isPortAvailable } from '../ports.js'; +async function listenOnEphemeralPort(server: net.Server, host: string): Promise { + await new Promise((resolve, reject) => { + const onError = (error: Error) => { + server.off('listening', onListening); + reject(error); + }; + const onListening = () => { + server.off('error', onError); + resolve(); + }; + + server.once('error', onError); + server.once('listening', onListening); + server.listen(0, host); + }); + + const address = server.address(); + if (!address || typeof address === 'string') { + throw new Error(`Unable to resolve ${host} test listener port`); + } + return address.port; +} + +async function closeServer(server: net.Server): Promise { + if (!server.listening) { + return; + } + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); +} + +async function acquireAvailablePort(host = '127.0.0.1'): Promise { + const reservation = net.createServer(); + try { + return await listenOnEphemeralPort(reservation, host); + } finally { + await closeServer(reservation); + } +} + describe('port selection', () => { it('returns the preferred port when it is available', async () => { - const port = await findAvailablePort(47631, '127.0.0.1', 1); - expect(port).toBe(47631); + const preferredPort = await acquireAvailablePort(); + + const port = await findAvailablePort(preferredPort, '127.0.0.1', 1); + + expect(port).toBe(preferredPort); }); it('falls forward when the preferred port is busy', async () => { const server = net.createServer(); - await new Promise((resolve) => server.listen(47632, '127.0.0.1', resolve)); + const preferredPort = await listenOnEphemeralPort(server, '127.0.0.1'); try { - expect(await isPortAvailable(47632)).toBe(false); - const port = await findAvailablePort(47632, '127.0.0.1', 3); - expect(port).toBeGreaterThan(47632); + expect(await isPortAvailable(preferredPort)).toBe(false); + + const port = await findAvailablePort(preferredPort, '127.0.0.1', 1); + + expect(port).not.toBe(preferredPort); + expect(await isPortAvailable(port)).toBe(true); } finally { - await new Promise((resolve) => server.close(() => resolve())); + await closeServer(server); } }); it('keeps separately selected desktop fallback ports distinct', async () => { const busyServer = net.createServer(); - await new Promise((resolve) => busyServer.listen(47633, '127.0.0.1', resolve)); + const preferredPort = await listenOnEphemeralPort(busyServer, '127.0.0.1'); try { - const serverPort = await findAvailablePort(47633, '127.0.0.1', 3); - const webPort = await findAvailablePort(47633, '127.0.0.1', 3, new Set([serverPort])); + const serverPort = await findAvailablePort(preferredPort, '127.0.0.1', 1); + const webPort = await findAvailablePort(preferredPort, '127.0.0.1', 1, new Set([serverPort])); - expect(serverPort).toBeGreaterThan(47633); - expect(webPort).toBeGreaterThan(47633); + expect(serverPort).not.toBe(preferredPort); + expect(webPort).not.toBe(preferredPort); expect(webPort).not.toBe(serverPort); + expect(await isPortAvailable(serverPort)).toBe(true); + expect(await isPortAvailable(webPort)).toBe(true); } finally { - await new Promise((resolve) => busyServer.close(() => resolve())); + await closeServer(busyServer); } }); it('falls forward when the preferred port is busy on an IPv6 wildcard', async () => { const server = net.createServer(); + let preferredPort: number; try { - await new Promise((resolve, reject) => { - server.once('error', reject); - server.listen(47634, '::', resolve); - }); + preferredPort = await listenOnEphemeralPort(server, '::'); } catch (error) { const code = (error as NodeJS.ErrnoException).code; if (code === 'EAFNOSUPPORT' || code === 'EADDRNOTAVAIL') { @@ -55,11 +102,14 @@ describe('port selection', () => { } try { - expect(await isPortAvailable(47634)).toBe(false); - const port = await findAvailablePort(47634, '127.0.0.1', 3); - expect(port).toBeGreaterThan(47634); + expect(await isPortAvailable(preferredPort)).toBe(false); + + const port = await findAvailablePort(preferredPort, '127.0.0.1', 1); + + expect(port).not.toBe(preferredPort); + expect(await isPortAvailable(port)).toBe(true); } finally { - await new Promise((resolve) => server.close(() => resolve())); + await closeServer(server); } });