import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as dgram from 'dgram'; import { SmartProxy } from '../ts/index.js'; import type { TDatagramHandler, IDatagramInfo } from '../ts/index.js'; import { findFreePorts, assertPortsFree } from './helpers/port-allocator.js'; let smartProxy: SmartProxy; let PROXY_PORT: number; // Helper: send a single UDP datagram and wait for a response function sendDatagram(port: number, msg: string, timeoutMs = 5000): Promise { 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); }); }); } tap.test('setup: start SmartProxy with datagramHandler', async () => { [PROXY_PORT] = await findFreePorts(1); const handler: TDatagramHandler = (datagram, info, reply) => { reply(Buffer.from(`Handled: ${datagram.toString()}`)); }; smartProxy = new SmartProxy({ routes: [ { name: 'dgram-handler-test', match: { ports: PROXY_PORT, transport: 'udp' as const, }, action: { type: 'socket-handler', datagramHandler: handler, }, }, ], defaults: { security: { ipAllowList: ['127.0.0.1', '::1', '::ffff:127.0.0.1'], }, }, }); await smartProxy.start(); }); tap.test('datagram handler: receives and replies to datagram', async () => { const response = await sendDatagram(PROXY_PORT, 'Hello Handler'); expect(response).toEqual('Handled: Hello Handler'); }); tap.test('datagram handler: async handler works', async () => { // Stop and restart with async handler await smartProxy.stop(); [PROXY_PORT] = await findFreePorts(1); const asyncHandler: TDatagramHandler = async (datagram, info, reply) => { // Simulate async work await new Promise((resolve) => setTimeout(resolve, 10)); reply(Buffer.from(`Async: ${datagram.toString()}`)); }; smartProxy = new SmartProxy({ routes: [ { name: 'dgram-async-handler', match: { ports: PROXY_PORT, transport: 'udp' as const, }, action: { type: 'socket-handler', datagramHandler: asyncHandler, }, }, ], defaults: { security: { ipAllowList: ['127.0.0.1', '::1', '::ffff:127.0.0.1'], }, }, }); await smartProxy.start(); const response = await sendDatagram(PROXY_PORT, 'Test Async'); expect(response).toEqual('Async: Test Async'); }); tap.test('datagram handler: multiple rapid datagrams', async () => { const promises: Promise[] = []; for (let i = 0; i < 5; i++) { promises.push(sendDatagram(PROXY_PORT, `msg-${i}`)); } const responses = await Promise.all(promises); for (let i = 0; i < 5; i++) { expect(responses).toContain(`Async: msg-${i}`); } }); tap.test('cleanup: stop SmartProxy', async () => { await smartProxy.stop(); await assertPortsFree([PROXY_PORT]); }); export default tap.start();