115 lines
3.5 KiB
TypeScript
115 lines
3.5 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import * as dgram from 'dgram';
|
|
import { SmartProxy } from '../ts/index.js';
|
|
import { findFreePorts, assertPortsFree } from './helpers/port-allocator.js';
|
|
|
|
let smartProxy: SmartProxy;
|
|
let backendServer: dgram.Socket;
|
|
let PROXY_PORT: number;
|
|
let BACKEND_PORT: number;
|
|
|
|
// Helper: send a single UDP datagram and wait for a response
|
|
function sendDatagram(port: number, msg: string, timeoutMs = 5000): Promise<string> {
|
|
return new Promise((resolve, reject) => {
|
|
const client = dgram.createSocket('udp4');
|
|
const timeout = setTimeout(() => {
|
|
client.close();
|
|
reject(new Error(`UDP response timeout after ${timeoutMs}ms`));
|
|
}, timeoutMs);
|
|
client.send(Buffer.from(msg), port, '127.0.0.1');
|
|
client.on('message', (data) => {
|
|
clearTimeout(timeout);
|
|
client.close();
|
|
resolve(data.toString());
|
|
});
|
|
client.on('error', (err) => {
|
|
clearTimeout(timeout);
|
|
client.close();
|
|
reject(err);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Helper: create a UDP echo server
|
|
function createUdpEchoServer(port: number): Promise<dgram.Socket> {
|
|
return new Promise((resolve) => {
|
|
const server = dgram.createSocket('udp4');
|
|
server.on('message', (msg, rinfo) => {
|
|
server.send(Buffer.from(`Echo: ${msg.toString()}`), rinfo.port, rinfo.address);
|
|
});
|
|
server.bind(port, '127.0.0.1', () => resolve(server));
|
|
});
|
|
}
|
|
|
|
tap.test('setup: start UDP echo server and SmartProxy with metrics', async () => {
|
|
[PROXY_PORT, BACKEND_PORT] = await findFreePorts(2);
|
|
|
|
backendServer = await createUdpEchoServer(BACKEND_PORT);
|
|
|
|
smartProxy = new SmartProxy({
|
|
routes: [
|
|
{
|
|
name: 'udp-metrics-test',
|
|
match: {
|
|
ports: PROXY_PORT,
|
|
transport: 'udp' as const,
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
targets: [{ host: '127.0.0.1', port: BACKEND_PORT }],
|
|
udp: {
|
|
sessionTimeout: 10000,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
defaults: {
|
|
security: {
|
|
ipAllowList: ['127.0.0.1', '::1', '::ffff:127.0.0.1'],
|
|
},
|
|
},
|
|
metrics: {
|
|
enabled: true,
|
|
sampleIntervalMs: 1000,
|
|
retentionSeconds: 60,
|
|
},
|
|
});
|
|
|
|
await smartProxy.start();
|
|
});
|
|
|
|
tap.test('UDP metrics: counters increase after traffic', async () => {
|
|
// Send a few datagrams
|
|
const resp1 = await sendDatagram(PROXY_PORT, 'metrics-test-1');
|
|
expect(resp1).toEqual('Echo: metrics-test-1');
|
|
|
|
const resp2 = await sendDatagram(PROXY_PORT, 'metrics-test-2');
|
|
expect(resp2).toEqual('Echo: metrics-test-2');
|
|
|
|
// Wait for metrics to propagate and cache to refresh
|
|
await new Promise<void>((resolve) => setTimeout(resolve, 2000));
|
|
|
|
// Get metrics (returns the adapter, need to ensure cache is fresh)
|
|
const metrics = smartProxy.getMetrics();
|
|
|
|
// The udp property reads from the Rust JSON snapshot
|
|
expect(metrics.udp).toBeDefined();
|
|
const totalSessions = metrics.udp.totalSessions();
|
|
const datagramsIn = metrics.udp.datagramsIn();
|
|
const datagramsOut = metrics.udp.datagramsOut();
|
|
|
|
console.log(`UDP metrics: sessions=${totalSessions}, in=${datagramsIn}, out=${datagramsOut}`);
|
|
|
|
expect(totalSessions).toBeGreaterThan(0);
|
|
expect(datagramsIn).toBeGreaterThan(0);
|
|
expect(datagramsOut).toBeGreaterThan(0);
|
|
});
|
|
|
|
tap.test('cleanup: stop SmartProxy and backend', async () => {
|
|
await smartProxy.stop();
|
|
await new Promise<void>((resolve) => backendServer.close(() => resolve()));
|
|
await assertPortsFree([PROXY_PORT, BACKEND_PORT]);
|
|
});
|
|
|
|
export default tap.start();
|