/** * test/desktop/port.test.ts — unit tests for pickFreePort (B1). * * Real loopback binds (not mocks) exercise both branches: the preferred port is * free (return it) and the preferred port is occupied (fall back to an OS port). */ import { describe, test, expect } from 'vitest' import net from 'node:net' import { pickFreePort } from '../../desktop/src/port.js' const LOOPBACK = '127.0.0.1' const MIN_PORT = 1 const MAX_PORT = 65535 /** Bind a server on 127.0.0.1:0 and resolve with the live server + its OS-assigned port. */ function occupyEphemeralPort(): Promise<{ server: net.Server; port: number }> { return new Promise((resolve, reject) => { const server = net.createServer() server.on('error', reject) server.listen(0, LOOPBACK, () => { const address = server.address() if (address !== null && typeof address === 'object') resolve({ server, port: address.port }) else reject(new Error('no AddressInfo')) }) }) } function closeServer(server: net.Server): Promise { return new Promise((resolve) => server.close(() => resolve())) } /** Acquire a currently-free port number (bind ephemeral, then release it). */ async function getFreePort(): Promise { const { server, port } = await occupyEphemeralPort() await closeServer(server) return port } /** Confirm a port can be bound right now, then release it. */ async function isBindable(port: number): Promise { const server = net.createServer() try { await new Promise((resolve, reject) => { server.on('error', reject) server.listen(port, LOOPBACK, () => resolve()) }) await closeServer(server) return true } catch { return false } } describe('pickFreePort', () => { test('returns the preferred port when it is free', async () => { // Arrange const preferred = await getFreePort() // Act const result = await pickFreePort(preferred) // Assert expect(result).toBe(preferred) }) test('returns a different, bindable port when the preferred port is occupied', async () => { // Arrange const { server, port: occupied } = await occupyEphemeralPort() try { // Act const result = await pickFreePort(occupied) // Assert expect(result).not.toBe(occupied) expect(Number.isInteger(result)).toBe(true) expect(result).toBeGreaterThanOrEqual(MIN_PORT) expect(result).toBeLessThanOrEqual(MAX_PORT) expect(await isBindable(result)).toBe(true) } finally { await closeServer(server) } }) test('returns an integer within the valid TCP port range', async () => { // Arrange const preferred = await getFreePort() // Act const result = await pickFreePort(preferred) // Assert expect(Number.isInteger(result)).toBe(true) expect(result).toBeGreaterThanOrEqual(MIN_PORT) expect(result).toBeLessThanOrEqual(MAX_PORT) }) })