smartproxy/test/test.ts

170 lines
4.4 KiB
TypeScript

import { expect, tap } from '@push.rocks/tapbundle';
import * as smartproxy from '../ts/index.js';
import { testCertificates } from './helpers/certificates.js';
import * as https from 'https';
import * as http from 'http';
import { WebSocket, WebSocketServer } from 'ws';
let testProxy: smartproxy.NetworkProxy;
let testServer: http.Server;
let wsServer: WebSocketServer;
// 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 () => {
// Create a test HTTP server
testServer = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello from test server!');
});
// Create a WebSocket server
wsServer = new WebSocketServer({ noServer: true });
wsServer.on('connection', (ws) => {
ws.on('message', (message) => {
ws.send('Echo: ' + message);
});
});
// Handle upgrade requests
testServer.on('upgrade', (request, socket, head) => {
wsServer.handleUpgrade(request, socket, head, (ws) => {
wsServer.emit('connection', ws, request);
});
});
await new Promise<void>((resolve) => testServer.listen(3000, resolve));
});
tap.test('should create proxy instance', async () => {
testProxy = new smartproxy.NetworkProxy({
port: 3001,
});
expect(testProxy).toEqual(testProxy); // Instance equality check
});
tap.test('should start the proxy server', async () => {
await testProxy.start();
// Configure proxy with test certificates
testProxy.updateProxyConfigs([
{
destinationIp: '127.0.0.1',
destinationPort: '3000',
hostName: 'push.rocks',
publicKey: testCertificates.publicKey,
privateKey: testCertificates.privateKey,
},
]);
});
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 () => {
return new Promise<void>((resolve, reject) => {
console.log('Starting WebSocket test...');
const ws = new WebSocket('wss://localhost:3001', {
rejectUnauthorized: false,
headers: {
'Host': 'push.rocks'
}
});
const timeout = setTimeout(() => {
ws.close();
reject(new Error('WebSocket test timed out after 5 seconds'));
}, 5000);
ws.on('open', () => {
console.log('WebSocket connection opened');
ws.send('Hello WebSocket');
});
ws.on('message', (data) => {
console.log('Received message:', data.toString());
expect(data.toString()).toEqual('Echo: Hello WebSocket');
clearTimeout(timeout);
ws.close();
resolve();
});
ws.on('error', (err) => {
console.error('WebSocket error:', err);
clearTimeout(timeout);
reject(err);
});
ws.on('close', () => {
console.log('WebSocket connection closed');
});
});
});
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');
});
tap.test('cleanup', async () => {
// Clean up all servers
await new Promise<void>((resolve) => wsServer.close(() => resolve()));
await new Promise<void>((resolve) => testServer.close(() => resolve()));
await testProxy.stop();
});
tap.start();