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((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((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((resolve) => { client.write(testData, () => { console.log('Data sent'); resolve(); }); }); // Wait for echo response await new Promise((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((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((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((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((resolve) => { echoServer.close(() => { console.log('Echo server closed'); resolve(); }); }); }); tap.start();