smartproxy/test/test.http-fix-verification.ts
Philipp Kunz c7c325a7d8 fix(tests): update AcmeStateManager tests to use socket-handler for challenge routes
fix(tests): enhance non-TLS connection detection with range support in HttpProxy tests
2025-06-01 07:06:11 +00:00

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();