diff --git a/changelog.md b/changelog.md index 59d0a77..a78fb9a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Changelog +## 2026-02-12 - 23.1.4 - fix(tests) +make tests more robust and bump small dependencies + +- Bump dependencies: @push.rocks/smartrust ^1.2.1 and minimatch ^10.2.0 +- Replace hardcoded ports with named constants (ECHO_PORT, PROXY_PORT, PROXY_PORT_1/2) to avoid collisions between tests +- Add server 'error' handlers and reject listen promises on server errors to prevent silent hangs +- Reduce test timeouts and intervals (shorter test durations, more frequent pings) to speed up test runs +- Ensure proxy is stopped between tests and remove forced process.exit; export tap.start() consistently +- Adjust assertions to match the new shorter ping/response counts + ## 2026-02-12 - 23.1.3 - fix(rustproxy) install default rustls crypto provider early; detect and skip raw fast-path for HTTP connections and return proper HTTP 502 when no route matches diff --git a/package.json b/package.json index 1843a30..023572e 100644 --- a/package.json +++ b/package.json @@ -34,14 +34,14 @@ "@push.rocks/smartnetwork": "^4.4.0", "@push.rocks/smartpromise": "^4.2.3", "@push.rocks/smartrequest": "^5.0.1", - "@push.rocks/smartrust": "^1.2.0", + "@push.rocks/smartrust": "^1.2.1", "@push.rocks/smartrx": "^3.0.10", "@push.rocks/smartstring": "^4.1.0", "@push.rocks/taskbuffer": "^4.2.0", "@tsclass/tsclass": "^9.3.0", "@types/minimatch": "^6.0.0", "@types/ws": "^8.18.1", - "minimatch": "^10.1.2", + "minimatch": "^10.2.0", "pretty-ms": "^9.3.0", "ws": "^8.19.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80aa253..f2310a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,8 +36,8 @@ importers: specifier: ^5.0.1 version: 5.0.1 '@push.rocks/smartrust': - specifier: ^1.2.0 - version: 1.2.0 + specifier: ^1.2.1 + version: 1.2.1 '@push.rocks/smartrx': specifier: ^3.0.10 version: 3.0.10 @@ -57,8 +57,8 @@ importers: specifier: ^8.18.1 version: 8.18.1 minimatch: - specifier: ^10.1.2 - version: 10.1.2 + specifier: ^10.2.0 + version: 10.2.0 pretty-ms: specifier: ^9.3.0 version: 9.3.0 @@ -570,14 +570,6 @@ packages: resolution: {integrity: sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==} engines: {node: '>=18'} - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.1': - resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==} - engines: {node: 20 || >=22} - '@isaacs/cliui@9.0.0': resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} engines: {node: '>=18'} @@ -883,8 +875,8 @@ packages: '@push.rocks/smartrouter@1.3.3': resolution: {integrity: sha512-1+xZEnWlhzqLWAaJ1zFNhQ0zgbfCWQl1DBT72LygLxTs+P0K8AwJKgqo/IX6CT55kGCFnPAZIYSbVJlGsgrB0w==} - '@push.rocks/smartrust@1.2.0': - resolution: {integrity: sha512-JlaALselIHoP6C3ceQbrvz424G21cND/QsH/KI3E/JrO4XphJiGZwM6f4yJWrijdPYR/YYMoaIiYN7ybZp0C4w==} + '@push.rocks/smartrust@1.2.1': + resolution: {integrity: sha512-ANwXXibUwoHNWF1hhXhXVVrfzYlhgHYRa2205Jkd/s/wXzcWHftYZthilJj+52B7nkzSB76umfxKfK5eBYY2Ug==} '@push.rocks/smartrx@3.0.10': resolution: {integrity: sha512-USjIYcsSfzn14cwOsxgq/bBmWDTTzy3ouWAnW5NdMyRRzEbmeNrvmy6TRqNeDlJ2PsYNTt1rr/zGUqvIy72ITg==} @@ -1649,6 +1641,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.2: + resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==} + engines: {node: 20 || >=22} + bare-events@2.8.2: resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} peerDependencies: @@ -1714,6 +1710,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.2: + resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} + engines: {node: 20 || >=22} + broadcast-channel@7.2.0: resolution: {integrity: sha512-JgraikEriG/TxBUi2W/w2O0jhHjXZUtXAvCZH0Yr3whjxYVgAg0hSe6r/teM+I5H5Q/q6RhyuKdC2pHNlFyepQ==} @@ -2750,8 +2750,8 @@ packages: minimalistic-crypto-utils@1.0.1: resolution: {integrity: sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=} - minimatch@10.1.2: - resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==} + minimatch@10.2.0: + resolution: {integrity: sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==} engines: {node: 20 || >=22} minimatch@3.1.2: @@ -4654,12 +4654,6 @@ snapshots: dependencies: mute-stream: 1.0.0 - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.1': - dependencies: - '@isaacs/balanced-match': 4.0.1 - '@isaacs/cliui@9.0.0': {} '@leichtgewicht/ip-codec@2.0.5': {} @@ -5034,7 +5028,7 @@ snapshots: '@push.rocks/smartstring': 4.1.0 '@push.rocks/smartunique': 3.0.9 '@tsclass/tsclass': 9.3.0 - minimatch: 10.1.2 + minimatch: 10.2.0 transitivePeerDependencies: - aws-crt @@ -5127,7 +5121,7 @@ snapshots: acme-client: 5.4.0 dns-packet: 5.6.1 elliptic: 6.6.1 - minimatch: 10.1.2 + minimatch: 10.2.0 transitivePeerDependencies: - supports-color @@ -5143,7 +5137,7 @@ snapshots: acme-client: 5.4.0 dns-packet: 5.6.1 elliptic: 6.6.1 - minimatch: 10.1.2 + minimatch: 10.2.0 transitivePeerDependencies: - supports-color @@ -5497,7 +5491,7 @@ snapshots: '@push.rocks/smartrx': 3.0.10 path-to-regexp: 8.3.0 - '@push.rocks/smartrust@1.2.0': + '@push.rocks/smartrust@1.2.1': dependencies: '@push.rocks/smartpath': 6.0.0 @@ -6323,7 +6317,7 @@ snapshots: '@types/minimatch@6.0.0': dependencies: - minimatch: 10.1.2 + minimatch: 10.2.0 '@types/ms@2.1.0': {} @@ -6494,6 +6488,10 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.2: + dependencies: + jackspeak: 4.2.3 + bare-events@2.8.2: {} bare-fs@4.5.3: @@ -6564,6 +6562,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.2: + dependencies: + balanced-match: 4.0.2 + broadcast-channel@7.2.0: dependencies: '@babel/runtime': 7.28.4 @@ -7157,7 +7159,7 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 4.2.3 - minimatch: 10.1.2 + minimatch: 10.2.0 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 2.0.1 @@ -7862,9 +7864,9 @@ snapshots: minimalistic-crypto-utils@1.0.1: {} - minimatch@10.1.2: + minimatch@10.2.0: dependencies: - '@isaacs/brace-expansion': 5.0.1 + brace-expansion: 5.0.2 minimatch@3.1.2: dependencies: diff --git a/test/test.long-lived-connections.ts b/test/test.long-lived-connections.ts index 6376e59..b7fa551 100644 --- a/test/test.long-lived-connections.ts +++ b/test/test.long-lived-connections.ts @@ -6,42 +6,49 @@ import { SmartProxy } from '../ts/index.js'; let testProxy: SmartProxy; let targetServer: net.Server; +const ECHO_PORT = 47200; +const PROXY_PORT = 47201; + // Create a simple echo server as target tap.test('setup test environment', async () => { // Create target server that echoes data back targetServer = net.createServer((socket) => { console.log('Target server: client connected'); - + // Echo data back socket.on('data', (data) => { console.log(`Target server received: ${data.toString().trim()}`); socket.write(data); }); - + socket.on('close', () => { console.log('Target server: client disconnected'); }); }); - - await new Promise((resolve) => { - targetServer.listen(9876, () => { - console.log('Target server listening on port 9876'); + + await new Promise((resolve, reject) => { + targetServer.on('error', (err) => { + console.error(`Echo server error: ${err.message}`); + reject(err); + }); + targetServer.listen(ECHO_PORT, () => { + console.log(`Target server listening on port ${ECHO_PORT}`); resolve(); }); }); - + // Create proxy with simple TCP forwarding (no TLS) testProxy = new SmartProxy({ routes: [{ name: 'tcp-forward-test', match: { - ports: 8888 // Plain TCP port + ports: PROXY_PORT // Plain TCP port }, action: { type: 'forward', targets: [{ host: 'localhost', - port: 9876 + port: ECHO_PORT }] // No TLS configuration - just plain TCP forwarding } @@ -49,7 +56,7 @@ tap.test('setup test environment', async () => { defaults: { target: { host: 'localhost', - port: 9876 + port: ECHO_PORT } }, enableDetailedLogging: true, @@ -59,72 +66,72 @@ tap.test('setup test environment', async () => { keepAlive: true, keepAliveInitialDelay: 1000 }); - + await testProxy.start(); }); tap.test('should keep WebSocket-like connection open for extended period', async (tools) => { - tools.timeout(60000); // 60 second test timeout - + tools.timeout(15000); // 15 second test timeout + const client = new net.Socket(); let messagesReceived = 0; let connectionClosed = false; - + // Connect to proxy await new Promise((resolve, reject) => { - client.connect(8888, 'localhost', () => { + client.connect(PROXY_PORT, 'localhost', () => { console.log('Client connected to proxy'); resolve(); }); - + client.on('error', reject); }); - + // Set up data handler client.on('data', (data) => { console.log(`Client received: ${data.toString().trim()}`); messagesReceived++; }); - + client.on('close', () => { console.log('Client connection closed'); connectionClosed = true; }); - + // Send initial handshake-like data client.write('HELLO\n'); - + // Wait for response await new Promise(resolve => setTimeout(resolve, 100)); expect(messagesReceived).toEqual(1); - + // Simulate WebSocket-like keep-alive pattern - // Send periodic messages over 60 seconds + // Send periodic messages over 5 seconds const startTime = Date.now(); const pingInterval = setInterval(() => { - if (!connectionClosed && Date.now() - startTime < 60000) { + if (!connectionClosed && Date.now() - startTime < 5000) { console.log('Sending ping...'); client.write('PING\n'); } else { clearInterval(pingInterval); } - }, 10000); // Every 10 seconds - - // Wait for 55 seconds (must complete within 60s runner timeout) - await new Promise(resolve => setTimeout(resolve, 55000)); - + }, 1000); // Every 1 second + + // Wait for 5 seconds — sufficient to verify the connection stays open + await new Promise(resolve => setTimeout(resolve, 5000)); + // Clean up interval clearInterval(pingInterval); - + // Connection should still be open expect(connectionClosed).toEqual(false); - - // Should have received responses (1 hello + 6 pings) - expect(messagesReceived).toBeGreaterThan(5); - + + // Should have received responses (1 hello + ~5 pings) + expect(messagesReceived).toBeGreaterThan(3); + // Close connection gracefully client.end(); - + // Wait for close await new Promise(resolve => setTimeout(resolve, 100)); expect(connectionClosed).toEqual(true); @@ -134,7 +141,7 @@ tap.test('should keep WebSocket-like connection open for extended period', async tap.test('cleanup', async () => { await testProxy.stop(); - + await new Promise((resolve) => { targetServer.close(() => { console.log('Target server closed'); diff --git a/test/test.metrics-new.ts b/test/test.metrics-new.ts index 5e1460e..40b0c4f 100644 --- a/test/test.metrics-new.ts +++ b/test/test.metrics-new.ts @@ -5,8 +5,8 @@ import * as net from 'net'; let smartProxyInstance: SmartProxy; let echoServer: net.Server; -const echoServerPort = 9876; -const proxyPort = 8080; +const echoServerPort = 47300; +const proxyPort = 47301; // Create an echo server for testing tap.test('should create echo server for testing', async () => { @@ -16,7 +16,11 @@ tap.test('should create echo server for testing', async () => { }); }); - await new Promise((resolve) => { + await new Promise((resolve, reject) => { + echoServer.on('error', (err) => { + console.error(`Echo server error: ${err.message}`); + reject(err); + }); echoServer.listen(echoServerPort, () => { console.log(`Echo server listening on port ${echoServerPort}`); resolve(); @@ -265,4 +269,4 @@ tap.test('should clean up resources', async () => { }); }); -export default tap.start(); +export default tap.start(); \ No newline at end of file diff --git a/test/test.port-forwarding-fix.ts b/test/test.port-forwarding-fix.ts index 54f4f10..45cc858 100644 --- a/test/test.port-forwarding-fix.ts +++ b/test/test.port-forwarding-fix.ts @@ -5,19 +5,27 @@ import { SmartProxy } from '../ts/proxies/smart-proxy/smart-proxy.js'; let echoServer: net.Server; let proxy: SmartProxy; +const ECHO_PORT = 47400; +const PROXY_PORT_1 = 47401; +const PROXY_PORT_2 = 47402; + tap.test('port forwarding should not immediately close connections', async (tools) => { // Set a timeout for this test tools.timeout(10000); // 10 seconds // Create an echo server - echoServer = await new Promise((resolve) => { + echoServer = await new Promise((resolve, reject) => { const server = net.createServer((socket) => { socket.on('data', (data) => { socket.write(`ECHO: ${data}`); }); }); - - server.listen(8888, () => { - console.log('Echo server listening on port 8888'); + + server.on('error', (err) => { + console.error(`Echo server error: ${err.message}`); + reject(err); + }); + server.listen(ECHO_PORT, () => { + console.log(`Echo server listening on port ${ECHO_PORT}`); resolve(server); }); }); @@ -26,10 +34,10 @@ tap.test('port forwarding should not immediately close connections', async (tool proxy = new SmartProxy({ routes: [{ name: 'test-forward', - match: { ports: 9999 }, + match: { ports: PROXY_PORT_1 }, action: { type: 'forward', - targets: [{ host: 'localhost', port: 8888 }] + targets: [{ host: 'localhost', port: ECHO_PORT }] } }] }); @@ -37,21 +45,24 @@ tap.test('port forwarding should not immediately close connections', async (tool await proxy.start(); // Test connection through proxy - const client = net.createConnection(9999, 'localhost'); - + const client = net.createConnection(PROXY_PORT_1, 'localhost'); + const result = await new Promise((resolve, reject) => { client.on('data', (data) => { const response = data.toString(); client.end(); // Close the connection after receiving data resolve(response); }); - + client.on('error', reject); - + client.write('Hello'); }); expect(result).toEqual('ECHO: Hello'); + + // Stop proxy from test 1 before test 2 reassigns the variable + await proxy.stop(); }); tap.test('TLS passthrough should work correctly', async () => { @@ -59,7 +70,7 @@ tap.test('TLS passthrough should work correctly', async () => { proxy = new SmartProxy({ routes: [{ name: 'tls-test', - match: { ports: 8443, domains: 'test.example.com' }, + match: { ports: PROXY_PORT_2, domains: 'test.example.com' }, action: { type: 'forward', tls: { mode: 'passthrough' }, @@ -85,16 +96,6 @@ tap.test('cleanup', async () => { }); }); } - if (proxy) { - await proxy.stop(); - console.log('Proxy stopped'); - } }); -export default tap.start().then(() => { - // Force exit after tests complete - setTimeout(() => { - console.log('Forcing process exit'); - process.exit(0); - }, 1000); -}); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 7a5e979..9317b94 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartproxy', - version: '23.1.3', + version: '23.1.4', description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.' }