2023-07-27 16:27:50 +02:00
|
|
|
import { expect, tap } from '@push.rocks/tapbundle';
|
2022-07-29 00:49:46 +02:00
|
|
|
import * as smartproxy from '../ts/index.js';
|
2025-02-04 00:38:39 +01:00
|
|
|
import { loadTestCertificates } from './helpers/certificates.js';
|
2025-02-03 23:41:13 +01:00
|
|
|
import * as https from 'https';
|
|
|
|
import * as http from 'http';
|
|
|
|
import { WebSocket, WebSocketServer } from 'ws';
|
2019-08-20 17:50:17 +02:00
|
|
|
|
2022-07-29 00:49:46 +02:00
|
|
|
let testProxy: smartproxy.NetworkProxy;
|
2025-02-03 23:41:13 +01:00
|
|
|
let testServer: http.Server;
|
|
|
|
let wsServer: WebSocketServer;
|
2025-02-04 00:38:39 +01:00
|
|
|
let testCertificates: { privateKey: string; publicKey: string };
|
2019-08-21 23:41:06 +02:00
|
|
|
|
2025-02-03 23:41:13 +01:00
|
|
|
// Helper function to make HTTPS requests
|
|
|
|
async function makeHttpsRequest(
|
|
|
|
options: https.RequestOptions,
|
|
|
|
): Promise<{ statusCode: number; headers: http.IncomingHttpHeaders; body: string }> {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const req = https.request(options, (res) => {
|
|
|
|
let data = '';
|
|
|
|
res.on('data', (chunk) => (data += chunk));
|
|
|
|
res.on('end', () =>
|
|
|
|
resolve({
|
|
|
|
statusCode: res.statusCode!,
|
|
|
|
headers: res.headers,
|
|
|
|
body: data,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
req.on('error', reject);
|
|
|
|
req.end();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup test environment
|
|
|
|
tap.test('setup test environment', async () => {
|
2025-02-04 00:38:39 +01:00
|
|
|
// Load and validate certificates
|
|
|
|
console.log('[TEST] Loading and validating certificates');
|
|
|
|
testCertificates = loadTestCertificates();
|
|
|
|
console.log('[TEST] Certificates loaded and validated');
|
2025-02-03 23:41:13 +01:00
|
|
|
// Create a test HTTP server
|
|
|
|
testServer = http.createServer((req, res) => {
|
2025-02-04 00:38:39 +01:00
|
|
|
console.log('[TEST SERVER] Received HTTP request:', {
|
|
|
|
url: req.url,
|
|
|
|
method: req.method,
|
|
|
|
headers: req.headers
|
|
|
|
});
|
2025-02-03 23:41:13 +01:00
|
|
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
|
|
res.end('Hello from test server!');
|
|
|
|
});
|
|
|
|
|
2025-02-04 00:38:39 +01:00
|
|
|
// Handle WebSocket upgrade requests
|
|
|
|
testServer.on('upgrade', (request, socket, head) => {
|
|
|
|
console.log('[TEST SERVER] Received WebSocket upgrade request:', {
|
|
|
|
url: request.url,
|
|
|
|
method: request.method,
|
|
|
|
headers: {
|
|
|
|
host: request.headers.host,
|
|
|
|
upgrade: request.headers.upgrade,
|
|
|
|
connection: request.headers.connection,
|
|
|
|
'sec-websocket-key': request.headers['sec-websocket-key'],
|
|
|
|
'sec-websocket-version': request.headers['sec-websocket-version'],
|
|
|
|
'sec-websocket-protocol': request.headers['sec-websocket-protocol']
|
|
|
|
}
|
2025-02-03 23:41:13 +01:00
|
|
|
});
|
|
|
|
|
2025-02-04 00:38:39 +01:00
|
|
|
if (request.headers.upgrade?.toLowerCase() !== 'websocket') {
|
|
|
|
console.log('[TEST SERVER] Not a WebSocket upgrade request');
|
|
|
|
socket.destroy();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('[TEST SERVER] Handling WebSocket upgrade');
|
2025-02-03 23:41:13 +01:00
|
|
|
wsServer.handleUpgrade(request, socket, head, (ws) => {
|
2025-02-04 00:38:39 +01:00
|
|
|
console.log('[TEST SERVER] WebSocket connection upgraded');
|
2025-02-03 23:41:13 +01:00
|
|
|
wsServer.emit('connection', ws, request);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2025-02-04 00:38:39 +01:00
|
|
|
// Create a WebSocket server
|
|
|
|
console.log('[TEST SERVER] Creating WebSocket server');
|
|
|
|
wsServer = new WebSocketServer({
|
|
|
|
noServer: true,
|
|
|
|
perMessageDeflate: false,
|
|
|
|
clientTracking: true,
|
|
|
|
handleProtocols: () => 'echo-protocol'
|
|
|
|
});
|
|
|
|
|
|
|
|
wsServer.on('connection', (ws, request) => {
|
|
|
|
console.log('[TEST SERVER] WebSocket connection established:', {
|
|
|
|
url: request.url,
|
|
|
|
headers: {
|
|
|
|
host: request.headers.host,
|
|
|
|
upgrade: request.headers.upgrade,
|
|
|
|
connection: request.headers.connection,
|
|
|
|
'sec-websocket-key': request.headers['sec-websocket-key'],
|
|
|
|
'sec-websocket-version': request.headers['sec-websocket-version'],
|
|
|
|
'sec-websocket-protocol': request.headers['sec-websocket-protocol']
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Set up connection timeout
|
|
|
|
const connectionTimeout = setTimeout(() => {
|
|
|
|
console.error('[TEST SERVER] WebSocket connection timed out');
|
|
|
|
ws.terminate();
|
|
|
|
}, 5000);
|
|
|
|
|
|
|
|
// Clear timeout when connection is properly closed
|
|
|
|
const clearConnectionTimeout = () => {
|
|
|
|
clearTimeout(connectionTimeout);
|
|
|
|
};
|
|
|
|
|
|
|
|
ws.on('message', (message) => {
|
|
|
|
const msg = message.toString();
|
|
|
|
console.log('[TEST SERVER] Received message:', msg);
|
|
|
|
try {
|
|
|
|
const response = `Echo: ${msg}`;
|
|
|
|
console.log('[TEST SERVER] Sending response:', response);
|
|
|
|
ws.send(response);
|
|
|
|
// Clear timeout on successful message exchange
|
|
|
|
clearConnectionTimeout();
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[TEST SERVER] Error sending message:', error);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
ws.on('error', (error) => {
|
|
|
|
console.error('[TEST SERVER] WebSocket error:', error);
|
|
|
|
clearConnectionTimeout();
|
|
|
|
});
|
|
|
|
|
|
|
|
ws.on('close', (code, reason) => {
|
|
|
|
console.log('[TEST SERVER] WebSocket connection closed:', {
|
|
|
|
code,
|
|
|
|
reason: reason.toString(),
|
|
|
|
wasClean: code === 1000 || code === 1001
|
|
|
|
});
|
|
|
|
clearConnectionTimeout();
|
|
|
|
});
|
|
|
|
|
|
|
|
ws.on('ping', (data) => {
|
|
|
|
try {
|
|
|
|
console.log('[TEST SERVER] Received ping, sending pong');
|
|
|
|
ws.pong(data);
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[TEST SERVER] Error sending pong:', error);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
ws.on('pong', (data) => {
|
|
|
|
console.log('[TEST SERVER] Received pong');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
wsServer.on('error', (error) => {
|
|
|
|
console.error('Test server: WebSocket server error:', error);
|
|
|
|
});
|
|
|
|
|
|
|
|
wsServer.on('headers', (headers) => {
|
|
|
|
console.log('Test server: WebSocket headers:', headers);
|
|
|
|
});
|
|
|
|
|
|
|
|
wsServer.on('close', () => {
|
|
|
|
console.log('Test server: WebSocket server closed');
|
|
|
|
});
|
|
|
|
|
2025-02-03 23:41:13 +01:00
|
|
|
await new Promise<void>((resolve) => testServer.listen(3000, resolve));
|
2025-02-04 00:38:39 +01:00
|
|
|
console.log('Test server listening on port 3000');
|
2025-02-03 23:41:13 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
tap.test('should create proxy instance', async () => {
|
2022-07-29 00:49:46 +02:00
|
|
|
testProxy = new smartproxy.NetworkProxy({
|
2022-07-29 01:52:34 +02:00
|
|
|
port: 3001,
|
2022-07-29 00:49:46 +02:00
|
|
|
});
|
2025-02-03 23:41:13 +01:00
|
|
|
expect(testProxy).toEqual(testProxy); // Instance equality check
|
2019-08-21 23:41:06 +02:00
|
|
|
});
|
|
|
|
|
2025-02-03 23:41:13 +01:00
|
|
|
tap.test('should start the proxy server', async () => {
|
2025-02-04 00:38:39 +01:00
|
|
|
console.log('[TEST] Starting the proxy server');
|
2019-08-21 23:41:06 +02:00
|
|
|
await testProxy.start();
|
2025-02-04 00:38:39 +01:00
|
|
|
console.log('[TEST] Proxy server started');
|
2019-08-21 23:41:06 +02:00
|
|
|
|
2025-02-03 23:41:13 +01:00
|
|
|
// Configure proxy with test certificates
|
2022-07-29 00:49:46 +02:00
|
|
|
testProxy.updateProxyConfigs([
|
2020-02-07 13:04:11 +00:00
|
|
|
{
|
2022-07-29 00:49:46 +02:00
|
|
|
destinationIp: '127.0.0.1',
|
2020-02-07 13:04:11 +00:00
|
|
|
destinationPort: '3000',
|
|
|
|
hostName: 'push.rocks',
|
2025-02-03 23:41:13 +01:00
|
|
|
publicKey: testCertificates.publicKey,
|
|
|
|
privateKey: testCertificates.privateKey,
|
2025-02-04 00:38:39 +01:00
|
|
|
}
|
2020-02-07 13:04:11 +00:00
|
|
|
]);
|
2025-02-04 00:38:39 +01:00
|
|
|
|
|
|
|
console.log('[TEST] Proxy configuration updated');
|
2019-09-20 18:40:55 +02:00
|
|
|
});
|
|
|
|
|
2025-02-03 23:41:13 +01:00
|
|
|
tap.test('should route HTTPS requests based on host header', async () => {
|
|
|
|
const response = await makeHttpsRequest({
|
|
|
|
hostname: 'push.rocks',
|
|
|
|
port: 3001,
|
|
|
|
path: '/',
|
|
|
|
method: 'GET',
|
|
|
|
rejectUnauthorized: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(response.statusCode).toEqual(200);
|
|
|
|
expect(response.body).toEqual('Hello from test server!');
|
|
|
|
});
|
|
|
|
|
|
|
|
tap.test('should handle unknown host headers', async () => {
|
|
|
|
const response = await makeHttpsRequest({
|
|
|
|
hostname: 'unknown.host',
|
|
|
|
port: 3001,
|
|
|
|
path: '/',
|
|
|
|
method: 'GET',
|
|
|
|
rejectUnauthorized: false,
|
|
|
|
}).catch((e) => e);
|
|
|
|
|
|
|
|
expect(response instanceof Error).toEqual(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
tap.test('should support WebSocket connections', async () => {
|
2025-02-04 00:38:39 +01:00
|
|
|
console.log('\n[TEST] ====== WebSocket Test Started ======');
|
|
|
|
console.log('[TEST] Test server port:', 3000);
|
|
|
|
console.log('[TEST] Proxy server port:', 3001);
|
|
|
|
console.log('\n[TEST] Starting WebSocket test');
|
|
|
|
|
|
|
|
// First configure the proxy with test certificates
|
|
|
|
console.log('[TEST] Configuring proxy with test certificates');
|
|
|
|
testProxy.updateProxyConfigs([
|
|
|
|
{
|
|
|
|
destinationIp: '127.0.0.1',
|
|
|
|
destinationPort: '3000',
|
|
|
|
hostName: 'push.rocks',
|
|
|
|
publicKey: testCertificates.publicKey,
|
|
|
|
privateKey: testCertificates.privateKey,
|
|
|
|
}
|
|
|
|
]);
|
|
|
|
|
2025-02-03 23:41:13 +01:00
|
|
|
return new Promise<void>((resolve, reject) => {
|
2025-02-04 00:38:39 +01:00
|
|
|
console.log('[TEST] Creating WebSocket client');
|
|
|
|
|
|
|
|
// Create WebSocket client with SSL/TLS options
|
|
|
|
const wsUrl = 'wss://push.rocks:3001';
|
|
|
|
console.log('[TEST] Creating WebSocket connection to:', wsUrl);
|
|
|
|
|
|
|
|
const ws = new WebSocket(wsUrl, {
|
|
|
|
rejectUnauthorized: false, // Accept self-signed certificates
|
|
|
|
handshakeTimeout: 5000,
|
|
|
|
perMessageDeflate: false,
|
2025-02-03 23:41:13 +01:00
|
|
|
headers: {
|
2025-02-04 00:38:39 +01:00
|
|
|
'Host': 'push.rocks',
|
|
|
|
'Connection': 'Upgrade',
|
|
|
|
'Upgrade': 'websocket',
|
|
|
|
'Sec-WebSocket-Version': '13'
|
|
|
|
},
|
|
|
|
protocol: 'echo-protocol',
|
|
|
|
agent: new https.Agent({
|
|
|
|
rejectUnauthorized: false // Also needed for the underlying HTTPS connection
|
|
|
|
})
|
2025-02-03 23:41:13 +01:00
|
|
|
});
|
|
|
|
|
2025-02-04 00:38:39 +01:00
|
|
|
console.log('[TEST] WebSocket client created');
|
|
|
|
|
|
|
|
let resolved = false;
|
|
|
|
const cleanup = () => {
|
|
|
|
if (!resolved) {
|
|
|
|
resolved = true;
|
|
|
|
try {
|
|
|
|
console.log('[TEST] Cleaning up WebSocket connection');
|
|
|
|
ws.close();
|
|
|
|
resolve();
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[TEST] Error during cleanup:', error);
|
|
|
|
reject(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-02-03 23:41:13 +01:00
|
|
|
const timeout = setTimeout(() => {
|
2025-02-04 00:38:39 +01:00
|
|
|
console.error('[TEST] WebSocket test timed out');
|
|
|
|
cleanup();
|
2025-02-03 23:41:13 +01:00
|
|
|
reject(new Error('WebSocket test timed out after 5 seconds'));
|
|
|
|
}, 5000);
|
|
|
|
|
2025-02-04 00:38:39 +01:00
|
|
|
// Connection establishment events
|
|
|
|
ws.on('upgrade', (response) => {
|
|
|
|
console.log('[TEST] WebSocket upgrade response received:', {
|
|
|
|
headers: response.headers,
|
|
|
|
statusCode: response.statusCode
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2025-02-03 23:41:13 +01:00
|
|
|
ws.on('open', () => {
|
2025-02-04 00:38:39 +01:00
|
|
|
console.log('[TEST] WebSocket connection opened');
|
|
|
|
try {
|
|
|
|
console.log('[TEST] Sending test message');
|
|
|
|
ws.send('Hello WebSocket');
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[TEST] Error sending message:', error);
|
|
|
|
cleanup();
|
|
|
|
reject(error);
|
|
|
|
}
|
2025-02-03 23:41:13 +01:00
|
|
|
});
|
|
|
|
|
2025-02-04 00:38:39 +01:00
|
|
|
ws.on('message', (message) => {
|
|
|
|
console.log('[TEST] Received message:', message.toString());
|
|
|
|
if (message.toString() === 'Hello WebSocket') {
|
|
|
|
console.log('[TEST] Message received correctly');
|
|
|
|
clearTimeout(timeout);
|
|
|
|
cleanup();
|
|
|
|
}
|
2025-02-03 23:41:13 +01:00
|
|
|
});
|
|
|
|
|
2025-02-04 00:38:39 +01:00
|
|
|
ws.on('error', (error) => {
|
|
|
|
console.error('[TEST] WebSocket error:', error);
|
|
|
|
cleanup();
|
|
|
|
reject(error);
|
2025-02-03 23:41:13 +01:00
|
|
|
});
|
|
|
|
|
2025-02-04 00:38:39 +01:00
|
|
|
ws.on('close', (code, reason) => {
|
|
|
|
console.log('[TEST] WebSocket connection closed:', {
|
|
|
|
code,
|
|
|
|
reason: reason.toString()
|
|
|
|
});
|
|
|
|
cleanup();
|
2025-02-03 23:41:13 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
tap.test('should handle custom headers', async () => {
|
|
|
|
testProxy.addDefaultHeaders({
|
|
|
|
'X-Proxy-Header': 'test-value',
|
|
|
|
});
|
|
|
|
|
|
|
|
const response = await makeHttpsRequest({
|
|
|
|
hostname: 'push.rocks',
|
|
|
|
port: 3001,
|
|
|
|
path: '/',
|
|
|
|
method: 'GET',
|
|
|
|
rejectUnauthorized: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(response.headers['x-proxy-header']).toEqual('test-value');
|
2019-08-22 13:20:41 +02:00
|
|
|
});
|
|
|
|
|
2025-02-03 23:41:13 +01:00
|
|
|
tap.test('cleanup', async () => {
|
2025-02-04 00:38:39 +01:00
|
|
|
console.log('[TEST] Starting cleanup');
|
|
|
|
|
2025-02-03 23:41:13 +01:00
|
|
|
// Clean up all servers
|
2025-02-04 00:38:39 +01:00
|
|
|
console.log('[TEST] Terminating WebSocket clients');
|
|
|
|
wsServer.clients.forEach((client) => {
|
|
|
|
client.terminate();
|
|
|
|
});
|
|
|
|
|
|
|
|
console.log('[TEST] Closing WebSocket server');
|
|
|
|
await new Promise<void>((resolve) => wsServer.close(() => {
|
|
|
|
console.log('[TEST] WebSocket server closed');
|
|
|
|
resolve();
|
|
|
|
}));
|
|
|
|
|
|
|
|
console.log('[TEST] Closing test server');
|
|
|
|
await new Promise<void>((resolve) => testServer.close(() => {
|
|
|
|
console.log('[TEST] Test server closed');
|
|
|
|
resolve();
|
|
|
|
}));
|
|
|
|
|
|
|
|
console.log('[TEST] Stopping proxy');
|
2019-08-21 23:41:06 +02:00
|
|
|
await testProxy.stop();
|
2025-02-04 00:38:39 +01:00
|
|
|
console.log('[TEST] Cleanup complete');
|
|
|
|
});
|
|
|
|
|
|
|
|
process.on('exit', () => {
|
|
|
|
console.log('[TEST] Shutting down test server');
|
|
|
|
testServer.close(() => console.log('[TEST] Test server shut down'));
|
|
|
|
wsServer.close(() => console.log('[TEST] WebSocket server shut down'));
|
|
|
|
testProxy.stop().then(() => console.log('[TEST] Proxy server stopped'));
|
2019-08-20 18:42:52 +02:00
|
|
|
});
|
2019-08-20 17:50:17 +02:00
|
|
|
|
2019-08-20 18:42:52 +02:00
|
|
|
tap.start();
|