223 lines
7.7 KiB
TypeScript
223 lines
7.7 KiB
TypeScript
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';
|
|
|
|
let sharedSn: SmartNetwork;
|
|
|
|
tap.test('setup: create and start SmartNetwork', async () => {
|
|
sharedSn = new SmartNetwork();
|
|
await sharedSn.start();
|
|
});
|
|
|
|
// DNS resolution
|
|
tap.test('resolveDns should return A records for localhost', async () => {
|
|
const res = await sharedSn.resolveDns('localhost');
|
|
expect(res.A.length).toBeGreaterThan(0);
|
|
expect(Array.isArray(res.A)).toBeTrue();
|
|
expect(Array.isArray(res.AAAA)).toBeTrue();
|
|
expect(Array.isArray(res.MX)).toBeTrue();
|
|
});
|
|
|
|
// DNS resolution edge cases and MX records
|
|
tap.test('resolveDns should handle non-existent domains', async () => {
|
|
const res = await sharedSn.resolveDns('no.such.domain.invalid');
|
|
expect(Array.isArray(res.A)).toBeTrue();
|
|
expect(Array.isArray(res.AAAA)).toBeTrue();
|
|
expect(Array.isArray(res.MX)).toBeTrue();
|
|
expect(res.A.length).toEqual(0);
|
|
expect(res.AAAA.length).toEqual(0);
|
|
expect(res.MX.length).toEqual(0);
|
|
});
|
|
|
|
tap.test('resolveDns MX records for google.com', async () => {
|
|
const res = await sharedSn.resolveDns('google.com');
|
|
expect(Array.isArray(res.MX)).toBeTrue();
|
|
if (res.MX.length > 0) {
|
|
expect(typeof res.MX[0].exchange).toEqual('string');
|
|
expect(typeof res.MX[0].priority).toEqual('number');
|
|
}
|
|
});
|
|
|
|
// HTTP endpoint health-check
|
|
tap.test('checkEndpoint should return status and headers', async () => {
|
|
const result = await sharedSn.checkEndpoint('https://example.com', { rejectUnauthorized: false });
|
|
expect(result.status).toEqual(200);
|
|
expect(typeof result.rtt).toEqual('number');
|
|
expect(typeof result.headers).toEqual('object');
|
|
expect(result.headers).toHaveProperty('content-type');
|
|
});
|
|
|
|
// Traceroute
|
|
tap.test('traceroute should return at least one hop', async () => {
|
|
const hops = await sharedSn.traceroute('127.0.0.1');
|
|
expect(Array.isArray(hops)).toBeTrue();
|
|
expect(hops.length).toBeGreaterThanOrEqual(1);
|
|
const hop = hops[0];
|
|
expect(typeof hop.ttl).toEqual('number');
|
|
expect(typeof hop.ip).toEqual('string');
|
|
expect(hop.rtt === null || typeof hop.rtt === 'number').toBeTrue();
|
|
});
|
|
|
|
// getSpeed options
|
|
tap.test('getSpeed should accept options and return speeds', async () => {
|
|
const opts = { parallelStreams: 2, duration: 1 };
|
|
const result = await sharedSn.getSpeed(opts);
|
|
expect(typeof result.downloadSpeed).toEqual('string');
|
|
expect(typeof result.uploadSpeed).toEqual('string');
|
|
expect(parseFloat(result.downloadSpeed)).toBeGreaterThan(0);
|
|
expect(parseFloat(result.uploadSpeed)).toBeGreaterThan(0);
|
|
});
|
|
|
|
// Ping multiple count
|
|
tap.test('ping with count > 1 should return stats', async () => {
|
|
const stats = await sharedSn.ping('127.0.0.1', { count: 3 });
|
|
expect(stats.count).toEqual(3);
|
|
expect(Array.isArray(stats.times)).toBeTrue();
|
|
expect(stats.times.length).toEqual(3);
|
|
expect(typeof stats.min).toEqual('number');
|
|
expect(typeof stats.max).toEqual('number');
|
|
expect(typeof stats.avg).toEqual('number');
|
|
expect(typeof stats.stddev).toEqual('number');
|
|
expect(typeof stats.packetLoss).toEqual('number');
|
|
expect(typeof stats.alive).toEqual('boolean');
|
|
});
|
|
|
|
// Remote port UDP not supported
|
|
tap.test('isRemotePortAvailable should throw on UDP', async () => {
|
|
try {
|
|
await sharedSn.isRemotePortAvailable('example.com', { protocol: 'udp' });
|
|
throw new Error('Expected isRemotePortAvailable to throw for UDP');
|
|
} catch (err: any) {
|
|
expect(err).toBeInstanceOf(NetworkError);
|
|
expect(err.code).toEqual('ENOTSUP');
|
|
}
|
|
});
|
|
|
|
// Plugin registry
|
|
tap.test('should register and unregister plugin', async () => {
|
|
class Dummy {}
|
|
SmartNetwork.registerPlugin('dummy', Dummy);
|
|
expect(SmartNetwork.pluginsRegistry.has('dummy')).toBeTrue();
|
|
SmartNetwork.unregisterPlugin('dummy');
|
|
expect(SmartNetwork.pluginsRegistry.has('dummy')).toBeFalse();
|
|
});
|
|
|
|
tap.test('getGateways should respect cacheTtl', async () => {
|
|
const sn = new SmartNetwork({ cacheTtl: 1000 });
|
|
const first = await sn.getGateways();
|
|
const second = await sn.getGateways();
|
|
expect(first).toEqual(second);
|
|
});
|
|
|
|
// Remote port checks: missing port should error
|
|
tap.test('isRemotePortAvailable should require a port', async () => {
|
|
try {
|
|
await sharedSn.isRemotePortAvailable('example.com');
|
|
throw new Error('Expected error when port is not specified');
|
|
} catch (err: any) {
|
|
expect(err).toBeInstanceOf(NetworkError);
|
|
expect(err.code).toEqual('EINVAL');
|
|
}
|
|
});
|
|
|
|
// Remote port checks: detect open TCP port
|
|
tap.test('isRemotePortAvailable should detect open TCP port via string target', async () => {
|
|
const open = await sharedSn.isRemotePortAvailable('example.com:80');
|
|
expect(open).toBeTrue();
|
|
});
|
|
|
|
tap.test('isRemotePortAvailable should detect open TCP port via numeric arg', async () => {
|
|
const open = await sharedSn.isRemotePortAvailable('example.com', 80);
|
|
expect(open).toBeTrue();
|
|
});
|
|
|
|
// Caching public IPs
|
|
tap.test('getPublicIps should respect cacheTtl', async () => {
|
|
const sn = new SmartNetwork({ cacheTtl: 1000 });
|
|
const first = await sn.getPublicIps();
|
|
const second = await sn.getPublicIps();
|
|
expect(first).toEqual(second);
|
|
});
|
|
|
|
// Local port usage detection
|
|
tap.test('isLocalPortUnused should detect used local port', async () => {
|
|
const server = net.createServer();
|
|
await new Promise<void>((res) => server.listen(0, res));
|
|
const addr = server.address() as AddressInfo;
|
|
const inUse = await sharedSn.isLocalPortUnused(addr.port);
|
|
expect(inUse).toBeFalse();
|
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
});
|
|
|
|
// findFreePort tests
|
|
tap.test('findFreePort should find an available port in range', async () => {
|
|
const freePort = await sharedSn.findFreePort(49152, 49200);
|
|
expect(freePort).toBeGreaterThanOrEqual(49152);
|
|
expect(freePort).toBeLessThanOrEqual(49200);
|
|
|
|
const isUnused = await sharedSn.isLocalPortUnused(freePort);
|
|
expect(isUnused).toBeTrue();
|
|
});
|
|
|
|
tap.test('findFreePort should return null when all ports are occupied', async () => {
|
|
const servers = [];
|
|
const startPort = 49300;
|
|
const endPort = 49302;
|
|
|
|
for (let port = startPort; port <= endPort; port++) {
|
|
const server = net.createServer();
|
|
await new Promise<void>((res) => server.listen(port, res));
|
|
servers.push(server);
|
|
}
|
|
|
|
const freePort = await sharedSn.findFreePort(startPort, endPort);
|
|
expect(freePort).toBeNull();
|
|
|
|
await Promise.all(servers.map(s => new Promise<void>((res) => s.close(() => res()))));
|
|
});
|
|
|
|
tap.test('findFreePort should validate port range', async () => {
|
|
try {
|
|
await sharedSn.findFreePort(0, 100);
|
|
throw new Error('Expected error for port < 1');
|
|
} catch (err: any) {
|
|
expect(err).toBeInstanceOf(NetworkError);
|
|
expect(err.code).toEqual('EINVAL');
|
|
}
|
|
|
|
try {
|
|
await sharedSn.findFreePort(100, 70000);
|
|
throw new Error('Expected error for port > 65535');
|
|
} catch (err: any) {
|
|
expect(err).toBeInstanceOf(NetworkError);
|
|
expect(err.code).toEqual('EINVAL');
|
|
}
|
|
|
|
try {
|
|
await sharedSn.findFreePort(200, 100);
|
|
throw new Error('Expected error for startPort > endPort');
|
|
} catch (err: any) {
|
|
expect(err).toBeInstanceOf(NetworkError);
|
|
expect(err.code).toEqual('EINVAL');
|
|
}
|
|
});
|
|
|
|
// Real traceroute integration test
|
|
tap.test('traceroute real integration against google.com', async () => {
|
|
const hops = await sharedSn.traceroute('google.com', { maxHops: 5, timeout: 5000 });
|
|
expect(Array.isArray(hops)).toBeTrue();
|
|
expect(hops.length).toBeGreaterThanOrEqual(1);
|
|
for (const hop of hops) {
|
|
expect(typeof hop.ttl).toEqual('number');
|
|
expect(typeof hop.ip).toEqual('string');
|
|
expect(hop.rtt === null || typeof hop.rtt === 'number').toBeTrue();
|
|
}
|
|
});
|
|
|
|
tap.test('teardown: stop SmartNetwork', async () => {
|
|
await sharedSn.stop();
|
|
});
|
|
|
|
export default tap.start();
|