261 lines
7.5 KiB
TypeScript
261 lines
7.5 KiB
TypeScript
![]() |
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||
|
import * as plugins from '../ts/plugins.js';
|
||
|
import { SmartProxy } from '../ts/index.js';
|
||
|
import * as net from 'net';
|
||
|
|
||
|
let smartProxyInstance: SmartProxy;
|
||
|
let echoServer: net.Server;
|
||
|
const echoServerPort = 9876;
|
||
|
const proxyPort = 8080;
|
||
|
|
||
|
// Create an echo server for testing
|
||
|
tap.test('should create echo server for testing', async () => {
|
||
|
echoServer = net.createServer((socket) => {
|
||
|
socket.on('data', (data) => {
|
||
|
socket.write(data); // Echo back the data
|
||
|
});
|
||
|
});
|
||
|
|
||
|
await new Promise<void>((resolve) => {
|
||
|
echoServer.listen(echoServerPort, () => {
|
||
|
console.log(`Echo server listening on port ${echoServerPort}`);
|
||
|
resolve();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
tap.test('should create SmartProxy instance with new metrics', async () => {
|
||
|
smartProxyInstance = new SmartProxy({
|
||
|
routes: [{
|
||
|
name: 'test-route',
|
||
|
match: {
|
||
|
matchType: 'startsWith',
|
||
|
matchAgainst: 'domain',
|
||
|
value: ['*'],
|
||
|
ports: [proxyPort] // Add the port to match on
|
||
|
},
|
||
|
action: {
|
||
|
type: 'forward',
|
||
|
target: {
|
||
|
host: 'localhost',
|
||
|
port: echoServerPort
|
||
|
},
|
||
|
tls: {
|
||
|
mode: 'passthrough'
|
||
|
}
|
||
|
}
|
||
|
}],
|
||
|
defaultTarget: {
|
||
|
host: 'localhost',
|
||
|
port: echoServerPort
|
||
|
},
|
||
|
metrics: {
|
||
|
enabled: true,
|
||
|
sampleIntervalMs: 100, // Sample every 100ms for faster testing
|
||
|
retentionSeconds: 60
|
||
|
}
|
||
|
});
|
||
|
|
||
|
await smartProxyInstance.start();
|
||
|
});
|
||
|
|
||
|
tap.test('should verify new metrics API structure', async () => {
|
||
|
const metrics = smartProxyInstance.getMetrics();
|
||
|
|
||
|
// Check API structure
|
||
|
expect(metrics).toHaveProperty('connections');
|
||
|
expect(metrics).toHaveProperty('throughput');
|
||
|
expect(metrics).toHaveProperty('requests');
|
||
|
expect(metrics).toHaveProperty('totals');
|
||
|
expect(metrics).toHaveProperty('percentiles');
|
||
|
|
||
|
// Check connections methods
|
||
|
expect(metrics.connections).toHaveProperty('active');
|
||
|
expect(metrics.connections).toHaveProperty('total');
|
||
|
expect(metrics.connections).toHaveProperty('byRoute');
|
||
|
expect(metrics.connections).toHaveProperty('byIP');
|
||
|
expect(metrics.connections).toHaveProperty('topIPs');
|
||
|
|
||
|
// Check throughput methods
|
||
|
expect(metrics.throughput).toHaveProperty('instant');
|
||
|
expect(metrics.throughput).toHaveProperty('recent');
|
||
|
expect(metrics.throughput).toHaveProperty('average');
|
||
|
expect(metrics.throughput).toHaveProperty('custom');
|
||
|
expect(metrics.throughput).toHaveProperty('history');
|
||
|
expect(metrics.throughput).toHaveProperty('byRoute');
|
||
|
expect(metrics.throughput).toHaveProperty('byIP');
|
||
|
});
|
||
|
|
||
|
tap.test('should track throughput correctly', async (tools) => {
|
||
|
const metrics = smartProxyInstance.getMetrics();
|
||
|
|
||
|
// Initial state - no connections yet
|
||
|
expect(metrics.connections.active()).toEqual(0);
|
||
|
expect(metrics.throughput.instant()).toEqual({ in: 0, out: 0 });
|
||
|
|
||
|
// Create a test connection
|
||
|
const client = new net.Socket();
|
||
|
|
||
|
await new Promise<void>((resolve, reject) => {
|
||
|
client.connect(proxyPort, 'localhost', () => {
|
||
|
console.log('Connected to proxy');
|
||
|
resolve();
|
||
|
});
|
||
|
|
||
|
client.on('error', reject);
|
||
|
});
|
||
|
|
||
|
// Send some data
|
||
|
const testData = Buffer.from('Hello, World!'.repeat(100)); // ~1.3KB
|
||
|
|
||
|
await new Promise<void>((resolve) => {
|
||
|
client.write(testData, () => {
|
||
|
console.log('Data sent');
|
||
|
resolve();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// Wait for echo response
|
||
|
await new Promise<void>((resolve) => {
|
||
|
client.once('data', (data) => {
|
||
|
console.log(`Received ${data.length} bytes back`);
|
||
|
resolve();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// Wait for metrics to be sampled
|
||
|
await tools.delayFor(200);
|
||
|
|
||
|
// Check metrics
|
||
|
expect(metrics.connections.active()).toEqual(1);
|
||
|
expect(metrics.requests.total()).toBeGreaterThan(0);
|
||
|
|
||
|
// Check throughput - should show bytes transferred
|
||
|
const instant = metrics.throughput.instant();
|
||
|
console.log('Instant throughput:', instant);
|
||
|
|
||
|
// Should have recorded some throughput
|
||
|
expect(instant.in).toBeGreaterThan(0);
|
||
|
expect(instant.out).toBeGreaterThan(0);
|
||
|
|
||
|
// Check totals
|
||
|
expect(metrics.totals.bytesIn()).toBeGreaterThan(0);
|
||
|
expect(metrics.totals.bytesOut()).toBeGreaterThan(0);
|
||
|
|
||
|
// Clean up
|
||
|
client.destroy();
|
||
|
await tools.delayFor(100);
|
||
|
|
||
|
// Verify connection was cleaned up
|
||
|
expect(metrics.connections.active()).toEqual(0);
|
||
|
});
|
||
|
|
||
|
tap.test('should track multiple connections and routes', async (tools) => {
|
||
|
const metrics = smartProxyInstance.getMetrics();
|
||
|
|
||
|
// Create multiple connections
|
||
|
const clients: net.Socket[] = [];
|
||
|
const connectionCount = 5;
|
||
|
|
||
|
for (let i = 0; i < connectionCount; i++) {
|
||
|
const client = new net.Socket();
|
||
|
|
||
|
await new Promise<void>((resolve, reject) => {
|
||
|
client.connect(proxyPort, 'localhost', () => {
|
||
|
resolve();
|
||
|
});
|
||
|
|
||
|
client.on('error', reject);
|
||
|
});
|
||
|
|
||
|
clients.push(client);
|
||
|
}
|
||
|
|
||
|
// Verify active connections
|
||
|
expect(metrics.connections.active()).toEqual(connectionCount);
|
||
|
|
||
|
// Send data on each connection
|
||
|
const dataPromises = clients.map((client, index) => {
|
||
|
return new Promise<void>((resolve) => {
|
||
|
const data = Buffer.from(`Connection ${index}: `.repeat(50));
|
||
|
client.write(data, () => {
|
||
|
client.once('data', () => resolve());
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
await Promise.all(dataPromises);
|
||
|
await tools.delayFor(200);
|
||
|
|
||
|
// Check metrics by route
|
||
|
const routeConnections = metrics.connections.byRoute();
|
||
|
console.log('Connections by route:', Array.from(routeConnections.entries()));
|
||
|
expect(routeConnections.get('test-route')).toEqual(connectionCount);
|
||
|
|
||
|
// Check top IPs
|
||
|
const topIPs = metrics.connections.topIPs(5);
|
||
|
console.log('Top IPs:', topIPs);
|
||
|
expect(topIPs.length).toBeGreaterThan(0);
|
||
|
expect(topIPs[0].count).toEqual(connectionCount);
|
||
|
|
||
|
// Clean up all connections
|
||
|
clients.forEach(client => client.destroy());
|
||
|
await tools.delayFor(100);
|
||
|
|
||
|
expect(metrics.connections.active()).toEqual(0);
|
||
|
});
|
||
|
|
||
|
tap.test('should provide throughput history', async (tools) => {
|
||
|
const metrics = smartProxyInstance.getMetrics();
|
||
|
|
||
|
// Create a connection and send data periodically
|
||
|
const client = new net.Socket();
|
||
|
|
||
|
await new Promise<void>((resolve, reject) => {
|
||
|
client.connect(proxyPort, 'localhost', () => resolve());
|
||
|
client.on('error', reject);
|
||
|
});
|
||
|
|
||
|
// Send data every 100ms for 1 second
|
||
|
for (let i = 0; i < 10; i++) {
|
||
|
const data = Buffer.from(`Packet ${i}: `.repeat(100));
|
||
|
client.write(data);
|
||
|
await tools.delayFor(100);
|
||
|
}
|
||
|
|
||
|
// Get throughput history
|
||
|
const history = metrics.throughput.history(2); // Last 2 seconds
|
||
|
console.log('Throughput history entries:', history.length);
|
||
|
console.log('Sample history entry:', history[0]);
|
||
|
|
||
|
expect(history.length).toBeGreaterThan(0);
|
||
|
expect(history[0]).toHaveProperty('timestamp');
|
||
|
expect(history[0]).toHaveProperty('in');
|
||
|
expect(history[0]).toHaveProperty('out');
|
||
|
|
||
|
// Verify different time windows show different rates
|
||
|
const instant = metrics.throughput.instant();
|
||
|
const recent = metrics.throughput.recent();
|
||
|
const average = metrics.throughput.average();
|
||
|
|
||
|
console.log('Throughput windows:');
|
||
|
console.log(' Instant (1s):', instant);
|
||
|
console.log(' Recent (10s):', recent);
|
||
|
console.log(' Average (60s):', average);
|
||
|
|
||
|
// Clean up
|
||
|
client.destroy();
|
||
|
});
|
||
|
|
||
|
tap.test('should clean up resources', async () => {
|
||
|
await smartProxyInstance.stop();
|
||
|
|
||
|
await new Promise<void>((resolve) => {
|
||
|
echoServer.close(() => {
|
||
|
console.log('Echo server closed');
|
||
|
resolve();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
tap.start();
|