fix(tests): enhance non-TLS connection detection with range support in HttpProxy tests
249 lines
7.2 KiB
TypeScript
249 lines
7.2 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import { RouteConnectionHandler } from '../ts/proxies/smart-proxy/route-connection-handler.js';
|
|
import type { ISmartProxyOptions } from '../ts/proxies/smart-proxy/models/interfaces.js';
|
|
import * as net from 'net';
|
|
|
|
// Direct test of the fix in RouteConnectionHandler
|
|
tap.test('should detect and forward non-TLS connections on useHttpProxy ports', async (tapTest) => {
|
|
// Create mock objects
|
|
const mockSettings: ISmartProxyOptions = {
|
|
useHttpProxy: [8080],
|
|
httpProxyPort: 8844,
|
|
routes: [{
|
|
name: 'test-route',
|
|
match: { ports: 8080 },
|
|
action: {
|
|
type: 'forward',
|
|
target: { host: 'localhost', port: 8181 }
|
|
}
|
|
}]
|
|
};
|
|
|
|
let httpProxyForwardCalled = false;
|
|
let directConnectionCalled = false;
|
|
|
|
// Create mocks for dependencies
|
|
const mockHttpProxyBridge = {
|
|
getHttpProxy: () => ({ available: true }),
|
|
forwardToHttpProxy: async (...args: any[]) => {
|
|
console.log('Mock: forwardToHttpProxy called');
|
|
httpProxyForwardCalled = true;
|
|
}
|
|
};
|
|
|
|
// Mock connection manager
|
|
const mockConnectionManager = {
|
|
createConnection: (socket: any) => ({
|
|
id: 'test-connection',
|
|
localPort: 8080,
|
|
remoteIP: '127.0.0.1',
|
|
isTLS: false
|
|
}),
|
|
initiateCleanupOnce: () => {},
|
|
cleanupConnection: () => {},
|
|
getConnectionCount: () => 1,
|
|
handleError: (type: string, record: any) => {
|
|
return (error: Error) => {
|
|
console.log(`Mock: Error handled for ${type}: ${error.message}`);
|
|
};
|
|
}
|
|
};
|
|
|
|
// Mock route manager that returns a matching route
|
|
const mockRouteManager = {
|
|
findMatchingRoute: (criteria: any) => ({
|
|
route: mockSettings.routes[0]
|
|
}),
|
|
getAllRoutes: () => mockSettings.routes,
|
|
getRoutesForPort: (port: number) => mockSettings.routes.filter(r => {
|
|
const ports = Array.isArray(r.match.ports) ? r.match.ports : [r.match.ports];
|
|
return ports.some(p => {
|
|
if (typeof p === 'number') {
|
|
return p === port;
|
|
} else if (p && typeof p === 'object' && 'from' in p && 'to' in p) {
|
|
return port >= p.from && port <= p.to;
|
|
}
|
|
return false;
|
|
});
|
|
})
|
|
};
|
|
|
|
// Mock security manager
|
|
const mockSecurityManager = {
|
|
validateIP: () => ({ allowed: true })
|
|
};
|
|
|
|
// Create route connection handler instance
|
|
const handler = new RouteConnectionHandler(
|
|
mockSettings,
|
|
mockConnectionManager as any,
|
|
mockSecurityManager as any, // security manager
|
|
{} as any, // tls manager
|
|
mockHttpProxyBridge as any,
|
|
{} as any, // timeout manager
|
|
mockRouteManager as any
|
|
);
|
|
|
|
// Override setupDirectConnection to track if it's called
|
|
handler['setupDirectConnection'] = (...args: any[]) => {
|
|
console.log('Mock: setupDirectConnection called');
|
|
directConnectionCalled = true;
|
|
};
|
|
|
|
// Test: Create a mock socket representing non-TLS connection on port 8080
|
|
const mockSocket = {
|
|
localPort: 8080,
|
|
remoteAddress: '127.0.0.1',
|
|
on: function(event: string, handler: Function) { return this; },
|
|
once: function(event: string, handler: Function) {
|
|
// Capture the data handler
|
|
if (event === 'data') {
|
|
this._dataHandler = handler;
|
|
}
|
|
return this;
|
|
},
|
|
end: () => {},
|
|
destroy: () => {},
|
|
pause: () => {},
|
|
resume: () => {},
|
|
removeListener: function() { return this; },
|
|
emit: () => {},
|
|
setNoDelay: () => {},
|
|
setKeepAlive: () => {},
|
|
_dataHandler: null as any
|
|
} as any;
|
|
|
|
// Simulate the handler processing the connection
|
|
handler.handleConnection(mockSocket);
|
|
|
|
// Simulate receiving non-TLS data
|
|
if (mockSocket._dataHandler) {
|
|
mockSocket._dataHandler(Buffer.from('GET / HTTP/1.1\r\nHost: test.local\r\n\r\n'));
|
|
}
|
|
|
|
// Give it a moment to process
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
// Verify that the connection was forwarded to HttpProxy, not direct connection
|
|
expect(httpProxyForwardCalled).toEqual(true);
|
|
expect(directConnectionCalled).toEqual(false);
|
|
});
|
|
|
|
// Test that verifies TLS connections still work normally
|
|
tap.test('should handle TLS connections normally', async (tapTest) => {
|
|
const mockSettings: ISmartProxyOptions = {
|
|
useHttpProxy: [443],
|
|
httpProxyPort: 8844,
|
|
routes: [{
|
|
name: 'tls-route',
|
|
match: { ports: 443 },
|
|
action: {
|
|
type: 'forward',
|
|
target: { host: 'localhost', port: 8443 },
|
|
tls: { mode: 'terminate' }
|
|
}
|
|
}]
|
|
};
|
|
|
|
let httpProxyForwardCalled = false;
|
|
|
|
const mockHttpProxyBridge = {
|
|
getHttpProxy: () => ({ available: true }),
|
|
forwardToHttpProxy: async (...args: any[]) => {
|
|
httpProxyForwardCalled = true;
|
|
}
|
|
};
|
|
|
|
const mockConnectionManager = {
|
|
createConnection: (socket: any) => ({
|
|
id: 'test-tls-connection',
|
|
localPort: 443,
|
|
remoteIP: '127.0.0.1',
|
|
isTLS: true,
|
|
tlsHandshakeComplete: false
|
|
}),
|
|
initiateCleanupOnce: () => {},
|
|
cleanupConnection: () => {},
|
|
getConnectionCount: () => 1,
|
|
handleError: (type: string, record: any) => {
|
|
return (error: Error) => {
|
|
console.log(`Mock: Error handled for ${type}: ${error.message}`);
|
|
};
|
|
}
|
|
};
|
|
|
|
const mockTlsManager = {
|
|
isTlsHandshake: (chunk: Buffer) => true,
|
|
isClientHello: (chunk: Buffer) => true,
|
|
extractSNI: (chunk: Buffer) => 'test.local'
|
|
};
|
|
|
|
const mockRouteManager = {
|
|
findMatchingRoute: (criteria: any) => ({
|
|
route: mockSettings.routes[0]
|
|
}),
|
|
getAllRoutes: () => mockSettings.routes,
|
|
getRoutesForPort: (port: number) => mockSettings.routes.filter(r => {
|
|
const ports = Array.isArray(r.match.ports) ? r.match.ports : [r.match.ports];
|
|
return ports.some(p => {
|
|
if (typeof p === 'number') {
|
|
return p === port;
|
|
} else if (p && typeof p === 'object' && 'from' in p && 'to' in p) {
|
|
return port >= p.from && port <= p.to;
|
|
}
|
|
return false;
|
|
});
|
|
})
|
|
};
|
|
|
|
const mockSecurityManager = {
|
|
validateIP: () => ({ allowed: true })
|
|
};
|
|
|
|
const handler = new RouteConnectionHandler(
|
|
mockSettings,
|
|
mockConnectionManager as any,
|
|
mockSecurityManager as any,
|
|
mockTlsManager as any,
|
|
mockHttpProxyBridge as any,
|
|
{} as any,
|
|
mockRouteManager as any
|
|
);
|
|
|
|
const mockSocket = {
|
|
localPort: 443,
|
|
remoteAddress: '127.0.0.1',
|
|
on: function(event: string, handler: Function) { return this; },
|
|
once: function(event: string, handler: Function) {
|
|
// Capture the data handler
|
|
if (event === 'data') {
|
|
this._dataHandler = handler;
|
|
}
|
|
return this;
|
|
},
|
|
end: () => {},
|
|
destroy: () => {},
|
|
pause: () => {},
|
|
resume: () => {},
|
|
removeListener: function() { return this; },
|
|
emit: () => {},
|
|
setNoDelay: () => {},
|
|
setKeepAlive: () => {},
|
|
_dataHandler: null as any
|
|
} as any;
|
|
|
|
handler.handleConnection(mockSocket);
|
|
|
|
// Simulate TLS handshake
|
|
if (mockSocket._dataHandler) {
|
|
const tlsHandshake = Buffer.from([0x16, 0x03, 0x01, 0x00, 0x05]);
|
|
mockSocket._dataHandler(tlsHandshake);
|
|
}
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
// TLS connections with 'terminate' mode should go to HttpProxy
|
|
expect(httpProxyForwardCalled).toEqual(true);
|
|
});
|
|
|
|
export default tap.start(); |