smartproxy/test/test.forwarding-fix-verification.ts
2025-05-29 12:54:31 +00:00

151 lines
3.9 KiB
TypeScript

import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as net from 'net';
import { SmartProxy } from '../ts/proxies/smart-proxy/smart-proxy.js';
let testServer: net.Server;
let smartProxy: SmartProxy;
tap.test('setup test server', async () => {
// Create a test server that handles connections
testServer = await new Promise<net.Server>((resolve) => {
const server = net.createServer((socket) => {
console.log('Test server: Client connected');
socket.write('Welcome from test server\n');
socket.on('data', (data) => {
console.log(`Test server received: ${data.toString().trim()}`);
socket.write(`Echo: ${data}`);
});
socket.on('close', () => {
console.log('Test server: Client disconnected');
});
});
server.listen(6789, () => {
console.log('Test server listening on port 6789');
resolve(server);
});
});
});
tap.test('regular forward route should work correctly', async () => {
smartProxy = new SmartProxy({
routes: [{
id: 'test-forward',
name: 'Test Forward Route',
match: { ports: 7890 },
action: {
type: 'forward',
target: { host: 'localhost', port: 6789 }
}
}]
});
await smartProxy.start();
// Create a client connection
const client = await new Promise<net.Socket>((resolve, reject) => {
const socket = net.connect(7890, 'localhost', () => {
console.log('Client connected to proxy');
resolve(socket);
});
socket.on('error', reject);
});
// Test data exchange with timeout
const response = await new Promise<string>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Timeout waiting for initial response'));
}, 5000);
client.on('data', (data) => {
clearTimeout(timeout);
resolve(data.toString());
});
client.on('error', (err) => {
clearTimeout(timeout);
reject(err);
});
});
expect(response).toContain('Welcome from test server');
// Send data through proxy
client.write('Test message');
const echo = await new Promise<string>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Timeout waiting for echo response'));
}, 5000);
client.once('data', (data) => {
clearTimeout(timeout);
resolve(data.toString());
});
client.on('error', (err) => {
clearTimeout(timeout);
reject(err);
});
});
expect(echo).toContain('Echo: Test message');
client.end();
await smartProxy.stop();
});
tap.skip.test('NFTables forward route should not terminate connections (requires root)', async () => {
smartProxy = new SmartProxy({
routes: [{
id: 'nftables-test',
name: 'NFTables Test Route',
match: { ports: 7891 },
action: {
type: 'forward',
forwardingEngine: 'nftables',
target: { host: 'localhost', port: 6789 }
}
}]
});
await smartProxy.start();
// Create a client connection
const client = await new Promise<net.Socket>((resolve, reject) => {
const socket = net.connect(7891, 'localhost', () => {
console.log('Client connected to NFTables proxy');
resolve(socket);
});
socket.on('error', reject);
});
// With NFTables, the connection should stay open at the application level
// even though forwarding happens at kernel level
let connectionClosed = false;
client.on('close', () => {
connectionClosed = true;
});
// Wait a bit to ensure connection isn't immediately closed
await new Promise(resolve => setTimeout(resolve, 1000));
expect(connectionClosed).toEqual(false);
console.log('NFTables connection stayed open as expected');
client.end();
await smartProxy.stop();
});
tap.test('cleanup', async () => {
if (testServer) {
testServer.close();
}
if (smartProxy) {
await smartProxy.stop();
}
});
export default tap.start();