Files
smartnetwork/test/test.ports.ts

320 lines
10 KiB
TypeScript
Raw Normal View History

import { tap, expect } from '@git.zone/tstest/tapbundle';
import { SmartNetwork, NetworkError } from '../ts/index.js';
import * as net from 'node:net';
import type { AddressInfo } from 'node:net';
// Helper to create a server on a specific port
const createServerOnPort = async (port: number): Promise<net.Server> => {
const server = net.createServer();
await new Promise<void>((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<void> => {
await Promise.all(servers.map(s => new Promise<void>((res) => s.close(() => res()))));
};
let sharedSn: SmartNetwork;
tap.test('setup: create and start SmartNetwork', async () => {
sharedSn = new SmartNetwork();
await sharedSn.start();
});
// ========= isLocalPortUnused Tests =========
tap.test('isLocalPortUnused - should detect free port correctly', async () => {
const result = await sharedSn.isLocalPortUnused(54321);
expect(typeof result).toEqual('boolean');
});
tap.test('isLocalPortUnused - should detect occupied port', async () => {
const server = net.createServer();
await new Promise<void>((res) => server.listen(0, res));
const addr = server.address() as AddressInfo;
const isUnused = await sharedSn.isLocalPortUnused(addr.port);
expect(isUnused).toBeFalse();
await new Promise<void>((resolve) => server.close(() => resolve()));
});
tap.test('isLocalPortUnused - should handle multiple simultaneous checks', async () => {
const ports = [55001, 55002, 55003, 55004, 55005];
const results = await Promise.all(
ports.map(port => sharedSn.isLocalPortUnused(port))
);
results.forEach(result => {
expect(typeof result).toEqual('boolean');
});
});
tap.test('isLocalPortUnused - should work with IPv6 loopback', async () => {
const server = net.createServer();
await new Promise<void>((res) => server.listen(55100, '::', res));
const addr = server.address() as AddressInfo;
const isUnused = await sharedSn.isLocalPortUnused(addr.port);
expect(isUnused).toBeFalse();
await new Promise<void>((resolve) => server.close(() => resolve()));
});
tap.test('isLocalPortUnused - boundary port numbers', async () => {
const port1Result = await sharedSn.isLocalPortUnused(1);
expect(typeof port1Result).toEqual('boolean');
const port65535Result = await sharedSn.isLocalPortUnused(65535);
expect(typeof port65535Result).toEqual('boolean');
});
// ========= findFreePort Tests =========
tap.test('findFreePort - should find free port in small range', async () => {
const freePort = await sharedSn.findFreePort(50000, 50010);
expect(freePort).not.toBeNull();
expect(freePort).toBeGreaterThanOrEqual(50000);
expect(freePort).toBeLessThanOrEqual(50010);
if (freePort !== null) {
const isUnused = await sharedSn.isLocalPortUnused(freePort);
expect(isUnused).toBeTrue();
}
});
tap.test('findFreePort - should find first available port', async () => {
const servers = [];
servers.push(await createServerOnPort(50100));
servers.push(await createServerOnPort(50101));
const freePort = await sharedSn.findFreePort(50100, 50105);
expect(freePort).toEqual(50102);
await cleanupServers(servers);
});
tap.test('findFreePort - should handle fully occupied range', async () => {
const servers = [];
const startPort = 50200;
const endPort = 50202;
for (let port = startPort; port <= endPort; port++) {
servers.push(await createServerOnPort(port));
}
const freePort = await sharedSn.findFreePort(startPort, endPort);
expect(freePort).toBeNull();
await cleanupServers(servers);
});
tap.test('findFreePort - should validate port boundaries', async () => {
try {
await sharedSn.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');
}
try {
await sharedSn.findFreePort(100, 70000);
throw new Error('Should have thrown for port > 65535');
} catch (err: any) {
expect(err).toBeInstanceOf(NetworkError);
expect(err.code).toEqual('EINVAL');
}
try {
await sharedSn.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 () => {
try {
await sharedSn.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 freePort = await sharedSn.findFreePort(50300, 50300);
expect(freePort === 50300 || freePort === null).toBeTrue();
});
tap.test('findFreePort - should work with large ranges', async () => {
const freePort = await sharedSn.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 servers = [];
servers.push(await createServerOnPort(50400));
servers.push(await createServerOnPort(50402));
servers.push(await createServerOnPort(50404));
const freePort = await sharedSn.findFreePort(50400, 50405);
expect([50401, 50403, 50405]).toContain(freePort);
await cleanupServers(servers);
});
// ========= isRemotePortAvailable Tests =========
tap.test('isRemotePortAvailable - should detect open HTTP port', async () => {
const open1 = await sharedSn.isRemotePortAvailable('example.com:80');
expect(open1).toBeTrue();
const open2 = await sharedSn.isRemotePortAvailable('example.com', 80);
expect(open2).toBeTrue();
const open3 = await sharedSn.isRemotePortAvailable('example.com', { port: 80 });
expect(open3).toBeTrue();
});
tap.test('isRemotePortAvailable - should detect closed port', async () => {
const closed = await sharedSn.isRemotePortAvailable('example.com', 12345);
expect(closed).toBeFalse();
});
tap.test('isRemotePortAvailable - should handle retries', async () => {
const result = await sharedSn.isRemotePortAvailable('example.com', {
port: 80,
retries: 3,
timeout: 1000
});
expect(result).toBeTrue();
});
tap.test('isRemotePortAvailable - should reject UDP protocol', async () => {
try {
await sharedSn.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 () => {
try {
await sharedSn.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 result1 = await sharedSn.isRemotePortAvailable('example.com:443');
expect(result1).toBeTrue();
const result2 = await sharedSn.isRemotePortAvailable('example.com:8080', { port: 80 });
expect(result2).toBeTrue();
});
tap.test('isRemotePortAvailable - should handle localhost', async () => {
const server = net.createServer();
await new Promise<void>((res) => server.listen(51000, 'localhost', res));
const isOpen = await sharedSn.isRemotePortAvailable('localhost', 51000);
expect(isOpen).toBeTrue();
await new Promise<void>((resolve) => server.close(() => resolve()));
});
tap.test('isRemotePortAvailable - should handle invalid hosts gracefully', async () => {
const result = await sharedSn.isRemotePortAvailable('this-domain-definitely-does-not-exist-12345.com', 80);
expect(result).toBeFalse();
});
tap.test('isRemotePortAvailable - edge case ports', async () => {
const https = await sharedSn.isRemotePortAvailable('example.com', 443);
expect(https).toBeTrue();
const ssh = await sharedSn.isRemotePortAvailable('example.com', 22);
expect(ssh).toBeFalse();
});
// ========= Integration Tests =========
tap.test('Integration - findFreePort and isLocalPortUnused consistency', async () => {
const freePort = await sharedSn.findFreePort(52000, 52100);
expect(freePort).not.toBeNull();
if (freePort !== null) {
const isUnused1 = await sharedSn.isLocalPortUnused(freePort);
expect(isUnused1).toBeTrue();
const server = await createServerOnPort(freePort);
const isUnused2 = await sharedSn.isLocalPortUnused(freePort);
expect(isUnused2).toBeFalse();
const nextFreePort = await sharedSn.findFreePort(freePort, freePort + 10);
expect(nextFreePort).not.toEqual(freePort);
await cleanupServers([server]);
}
});
tap.test('Integration - stress test with many concurrent port checks', async () => {
const portRange = Array.from({ length: 20 }, (_, i) => 53000 + i);
const results = await Promise.all(
portRange.map(async port => ({
port,
isUnused: await sharedSn.isLocalPortUnused(port)
}))
);
results.forEach(result => {
expect(typeof result.isUnused).toEqual('boolean');
});
});
tap.test('Performance - findFreePort with large range', async () => {
const startTime = Date.now();
const freePort = await sharedSn.findFreePort(30000, 60000);
const duration = Date.now() - startTime;
expect(freePort).not.toBeNull();
// Should complete quickly since it finds a port early
expect(duration).toBeLessThan(5000);
});
tap.test('teardown: stop SmartNetwork', async () => {
await sharedSn.stop();
});
export default tap.start();