import { tap, expect } from '@git.zone/tstest/tapbundle'; import { SmartNetwork, NetworkError } from '../ts/index.js'; import * as net from 'net'; import type { AddressInfo } from 'net'; // Helper to create a server on a specific port const createServerOnPort = async (port: number): Promise => { const server = net.createServer(); await new Promise((resolve, reject) => { server.once('error', reject); server.listen(port, () => { server.removeListener('error', reject); resolve(); }); }); return server; }; // Helper to clean up servers const cleanupServers = async (servers: net.Server[]): Promise => { await Promise.all(servers.map(s => new Promise((res) => s.close(() => res())))); }; // ========= isLocalPortUnused Tests ========= tap.test('isLocalPortUnused - should detect free port correctly', async () => { const sn = new SmartNetwork(); // Port 0 lets the OS assign a free port, we'll use a high range instead const result = await sn.isLocalPortUnused(54321); expect(typeof result).toEqual('boolean'); // Most likely this high port is free, but we can't guarantee it }); tap.test('isLocalPortUnused - should detect occupied port', async () => { const sn = new SmartNetwork(); const server = net.createServer(); await new Promise((res) => server.listen(0, res)); const addr = server.address() as AddressInfo; const isUnused = await sn.isLocalPortUnused(addr.port); expect(isUnused).toBeFalse(); await new Promise((resolve) => server.close(() => resolve())); }); tap.test('isLocalPortUnused - should handle multiple simultaneous checks', async () => { const sn = new SmartNetwork(); const ports = [55001, 55002, 55003, 55004, 55005]; // Check all ports simultaneously const results = await Promise.all( ports.map(port => sn.isLocalPortUnused(port)) ); // All should likely be free results.forEach(result => { expect(typeof result).toEqual('boolean'); }); }); tap.test('isLocalPortUnused - should work with IPv6 loopback', async () => { const sn = new SmartNetwork(); const server = net.createServer(); // Explicitly bind to IPv6 await new Promise((res) => server.listen(55100, '::', res)); const addr = server.address() as AddressInfo; const isUnused = await sn.isLocalPortUnused(addr.port); expect(isUnused).toBeFalse(); await new Promise((resolve) => server.close(() => resolve())); }); tap.test('isLocalPortUnused - boundary port numbers', async () => { const sn = new SmartNetwork(); // Test port 1 (usually requires root) const port1Result = await sn.isLocalPortUnused(1); expect(typeof port1Result).toEqual('boolean'); // Test port 65535 const port65535Result = await sn.isLocalPortUnused(65535); expect(typeof port65535Result).toEqual('boolean'); }); // ========= findFreePort Tests ========= tap.test('findFreePort - should find free port in small range', async () => { const sn = new SmartNetwork(); const freePort = await sn.findFreePort(50000, 50010); expect(freePort).not.toBeNull(); expect(freePort).toBeGreaterThanOrEqual(50000); expect(freePort).toBeLessThanOrEqual(50010); // Verify the port is actually free if (freePort !== null) { const isUnused = await sn.isLocalPortUnused(freePort); expect(isUnused).toBeTrue(); } }); tap.test('findFreePort - should find first available port', async () => { const sn = new SmartNetwork(); const servers = []; // Occupy ports 50100 and 50101 servers.push(await createServerOnPort(50100)); servers.push(await createServerOnPort(50101)); // Port 50102 should be free const freePort = await sn.findFreePort(50100, 50105); expect(freePort).toEqual(50102); await cleanupServers(servers); }); tap.test('findFreePort - should handle fully occupied range', async () => { const sn = new SmartNetwork(); const servers = []; const startPort = 50200; const endPort = 50202; // Occupy all ports in range for (let port = startPort; port <= endPort; port++) { servers.push(await createServerOnPort(port)); } const freePort = await sn.findFreePort(startPort, endPort); expect(freePort).toBeNull(); await cleanupServers(servers); }); tap.test('findFreePort - should validate port boundaries', async () => { const sn = new SmartNetwork(); // Test port < 1 try { await sn.findFreePort(0, 100); throw new Error('Should have thrown for port < 1'); } catch (err: any) { expect(err).toBeInstanceOf(NetworkError); expect(err.code).toEqual('EINVAL'); expect(err.message).toContain('between 1 and 65535'); } // Test port > 65535 try { await sn.findFreePort(100, 70000); throw new Error('Should have thrown for port > 65535'); } catch (err: any) { expect(err).toBeInstanceOf(NetworkError); expect(err.code).toEqual('EINVAL'); } // Test negative ports try { await sn.findFreePort(-100, 100); throw new Error('Should have thrown for negative port'); } catch (err: any) { expect(err).toBeInstanceOf(NetworkError); expect(err.code).toEqual('EINVAL'); } }); tap.test('findFreePort - should validate range order', async () => { const sn = new SmartNetwork(); try { await sn.findFreePort(200, 100); throw new Error('Should have thrown for startPort > endPort'); } catch (err: any) { expect(err).toBeInstanceOf(NetworkError); expect(err.code).toEqual('EINVAL'); expect(err.message).toContain('less than or equal to end port'); } }); tap.test('findFreePort - should handle single port range', async () => { const sn = new SmartNetwork(); // Test when start and end are the same const freePort = await sn.findFreePort(50300, 50300); // Should either be 50300 or null expect(freePort === 50300 || freePort === null).toBeTrue(); }); tap.test('findFreePort - should work with large ranges', async () => { const sn = new SmartNetwork(); // Test with a large range const freePort = await sn.findFreePort(40000, 50000); expect(freePort).not.toBeNull(); expect(freePort).toBeGreaterThanOrEqual(40000); expect(freePort).toBeLessThanOrEqual(50000); }); tap.test('findFreePort - should handle intermittent occupied ports', async () => { const sn = new SmartNetwork(); const servers = []; // Occupy every other port servers.push(await createServerOnPort(50400)); servers.push(await createServerOnPort(50402)); servers.push(await createServerOnPort(50404)); // Should find 50401, 50403, or 50405 const freePort = await sn.findFreePort(50400, 50405); expect([50401, 50403, 50405]).toContain(freePort); await cleanupServers(servers); }); // ========= isRemotePortAvailable Tests ========= tap.test('isRemotePortAvailable - should detect open HTTP port', async () => { const sn = new SmartNetwork(); // Test with string format const open1 = await sn.isRemotePortAvailable('example.com:80'); expect(open1).toBeTrue(); // Test with separate parameters const open2 = await sn.isRemotePortAvailable('example.com', 80); expect(open2).toBeTrue(); // Test with options object const open3 = await sn.isRemotePortAvailable('example.com', { port: 80 }); expect(open3).toBeTrue(); }); tap.test('isRemotePortAvailable - should detect closed port', async () => { const sn = new SmartNetwork(); // Port 12345 is likely closed on example.com const closed = await sn.isRemotePortAvailable('example.com', 12345); expect(closed).toBeFalse(); }); tap.test('isRemotePortAvailable - should handle retries', async () => { const sn = new SmartNetwork(); // Test with retries const result = await sn.isRemotePortAvailable('example.com', { port: 80, retries: 3, timeout: 1000 }); expect(result).toBeTrue(); }); tap.test('isRemotePortAvailable - should reject UDP protocol', async () => { const sn = new SmartNetwork(); try { await sn.isRemotePortAvailable('example.com', { port: 53, protocol: 'udp' }); throw new Error('Should have thrown for UDP protocol'); } catch (err: any) { expect(err).toBeInstanceOf(NetworkError); expect(err.code).toEqual('ENOTSUP'); expect(err.message).toContain('UDP port check not supported'); } }); tap.test('isRemotePortAvailable - should require port specification', async () => { const sn = new SmartNetwork(); try { await sn.isRemotePortAvailable('example.com'); throw new Error('Should have thrown for missing port'); } catch (err: any) { expect(err).toBeInstanceOf(NetworkError); expect(err.code).toEqual('EINVAL'); expect(err.message).toContain('Port not specified'); } }); tap.test('isRemotePortAvailable - should parse port from host:port string', async () => { const sn = new SmartNetwork(); // Valid formats const result1 = await sn.isRemotePortAvailable('example.com:443'); expect(result1).toBeTrue(); // With options overriding the string port const result2 = await sn.isRemotePortAvailable('example.com:8080', { port: 80 }); expect(result2).toBeTrue(); // Should use port 80 from options, not 8080 }); tap.test('isRemotePortAvailable - should handle localhost', async () => { const sn = new SmartNetwork(); const server = net.createServer(); // Start a local server await new Promise((res) => server.listen(51000, 'localhost', res)); // Should detect it as open const isOpen = await sn.isRemotePortAvailable('localhost', 51000); expect(isOpen).toBeTrue(); await new Promise((resolve) => server.close(() => resolve())); // After closing, might still show as open due to TIME_WAIT, or closed // We won't assert on this as it's OS-dependent }); tap.test('isRemotePortAvailable - should handle invalid hosts gracefully', async () => { const sn = new SmartNetwork(); // Non-existent domain const result = await sn.isRemotePortAvailable('this-domain-definitely-does-not-exist-12345.com', 80); expect(result).toBeFalse(); }); tap.test('isRemotePortAvailable - edge case ports', async () => { const sn = new SmartNetwork(); // Test HTTPS port const https = await sn.isRemotePortAvailable('example.com', 443); expect(https).toBeTrue(); // Test SSH port (likely closed on example.com) const ssh = await sn.isRemotePortAvailable('example.com', 22); expect(ssh).toBeFalse(); }); // ========= Integration Tests ========= tap.test('Integration - findFreePort and isLocalPortUnused consistency', async () => { const sn = new SmartNetwork(); // Find a free port const freePort = await sn.findFreePort(52000, 52100); expect(freePort).not.toBeNull(); if (freePort !== null) { // Verify it's actually free const isUnused1 = await sn.isLocalPortUnused(freePort); expect(isUnused1).toBeTrue(); // Start a server on it const server = await createServerOnPort(freePort); // Now it should be in use const isUnused2 = await sn.isLocalPortUnused(freePort); expect(isUnused2).toBeFalse(); // findFreePort should skip it const nextFreePort = await sn.findFreePort(freePort, freePort + 10); expect(nextFreePort).not.toEqual(freePort); await cleanupServers([server]); } }); tap.test('Integration - stress test with many concurrent port checks', async () => { const sn = new SmartNetwork(); const portRange = Array.from({ length: 20 }, (_, i) => 53000 + i); // Check all ports concurrently const results = await Promise.all( portRange.map(async port => ({ port, isUnused: await sn.isLocalPortUnused(port) })) ); // All operations should complete without error results.forEach(result => { expect(typeof result.isUnused).toEqual('boolean'); }); }); tap.test('Performance - findFreePort with large range', async () => { const sn = new SmartNetwork(); const startTime = Date.now(); // This should be fast even with a large range const freePort = await sn.findFreePort(30000, 60000); const duration = Date.now() - startTime; expect(freePort).not.toBeNull(); // Should complete quickly (within 100ms) as it should find a port early expect(duration).toBeLessThan(100); }); tap.start();