import { tap, expect } from '@git.zone/tstest/tapbundle'; import { SmartProxy } from '../ts/index.js'; import * as net from 'net'; // Test that verifies HTTP connections on ports configured in useHttpProxy are properly forwarded tap.test('should detect and forward non-TLS connections on HttpProxy ports', async (tapTest) => { // Track whether the connection was forwarded to HttpProxy let forwardedToHttpProxy = false; let connectionPath = ''; // Create a SmartProxy instance first const proxy = new SmartProxy({ useHttpProxy: [8081], // Use different port to avoid conflicts httpProxyPort: 8847, // Use different port to avoid conflicts routes: [{ name: 'test-http-forward', match: { ports: 8081 }, action: { type: 'forward', target: { host: 'localhost', port: 8181 } } }] }); // Add detailed logging to the existing proxy instance proxy.settings.enableDetailedLogging = true; // Override the HttpProxy initialization to avoid actual HttpProxy setup proxy['httpProxyBridge'].initialize = async () => { console.log('Mock: HttpProxyBridge initialized'); }; proxy['httpProxyBridge'].start = async () => { console.log('Mock: HttpProxyBridge started'); }; proxy['httpProxyBridge'].stop = async () => { console.log('Mock: HttpProxyBridge stopped'); }; await proxy.start(); // Mock the HttpProxy forwarding AFTER start to ensure it's not overridden const originalForward = (proxy as any).httpProxyBridge.forwardToHttpProxy; (proxy as any).httpProxyBridge.forwardToHttpProxy = async function(...args: any[]) { forwardedToHttpProxy = true; connectionPath = 'httpproxy'; console.log('Mock: Connection forwarded to HttpProxy with args:', args[0], 'on port:', args[2]?.localPort); // Just close the connection for the test args[1].end(); // socket.end() }; // No need to mock getHttpProxy - the bridge already handles HttpProxy availability // Make a connection to port 8080 const client = new net.Socket(); await new Promise((resolve, reject) => { client.connect(8081, 'localhost', () => { console.log('Client connected to proxy on port 8081'); // Send a non-TLS HTTP request client.write('GET / HTTP/1.1\r\nHost: test.local\r\n\r\n'); // Add a small delay to ensure data is sent setTimeout(() => resolve(), 50); }); client.on('error', reject); }); // Give it a moment to process await new Promise(resolve => setTimeout(resolve, 100)); // Verify the connection was forwarded to HttpProxy expect(forwardedToHttpProxy).toEqual(true); expect(connectionPath).toEqual('httpproxy'); client.destroy(); await proxy.stop(); // Wait a bit to ensure port is released await new Promise(resolve => setTimeout(resolve, 100)); // Restore original method (proxy as any).httpProxyBridge.forwardToHttpProxy = originalForward; }); // Test that verifies the fix detects non-TLS connections tap.test('should properly detect non-TLS connections on HttpProxy ports', async (tapTest) => { const targetPort = 8182; let receivedConnection = false; // Create a target server that never receives the connection (because it goes to HttpProxy) const targetServer = net.createServer((socket) => { receivedConnection = true; socket.end(); }); await new Promise((resolve) => { targetServer.listen(targetPort, () => { console.log(`Target server listening on port ${targetPort}`); resolve(); }); }); // Mock HttpProxyBridge to track forwarding let httpProxyForwardCalled = false; const proxy = new SmartProxy({ useHttpProxy: [8082], // Use different port to avoid conflicts httpProxyPort: 8848, // Use different port to avoid conflicts routes: [{ name: 'test-route', match: { ports: 8082 }, action: { type: 'forward', target: { host: 'localhost', port: targetPort } } }] }); // Override the forwardToHttpProxy method to track calls const originalForward = proxy['httpProxyBridge'].forwardToHttpProxy; proxy['httpProxyBridge'].forwardToHttpProxy = async function(...args: any[]) { httpProxyForwardCalled = true; console.log('HttpProxy forward called with connectionId:', args[0]); // Just end the connection args[1].end(); }; // Mock HttpProxyBridge methods proxy['httpProxyBridge'].initialize = async () => { console.log('Mock: HttpProxyBridge initialized'); }; proxy['httpProxyBridge'].start = async () => { console.log('Mock: HttpProxyBridge started'); }; proxy['httpProxyBridge'].stop = async () => { console.log('Mock: HttpProxyBridge stopped'); }; // Mock getHttpProxy to return a truthy value proxy['httpProxyBridge'].getHttpProxy = () => ({} as any); await proxy.start(); // Make a non-TLS connection const client = new net.Socket(); await new Promise((resolve, reject) => { client.connect(8082, 'localhost', () => { console.log('Connected to proxy'); client.write('GET / HTTP/1.1\r\nHost: test.local\r\n\r\n'); // Add a small delay to ensure data is sent setTimeout(() => resolve(), 50); }); client.on('error', () => resolve()); // Ignore errors since we're ending the connection }); await new Promise(resolve => setTimeout(resolve, 100)); // Verify that HttpProxy was called, not direct connection expect(httpProxyForwardCalled).toEqual(true); expect(receivedConnection).toEqual(false); // Target should not receive direct connection client.destroy(); await proxy.stop(); await new Promise((resolve) => { targetServer.close(() => resolve()); }); // Wait a bit to ensure port is released await new Promise(resolve => setTimeout(resolve, 100)); // Restore original method proxy['httpProxyBridge'].forwardToHttpProxy = originalForward; }); export default tap.start();