126 lines
3.3 KiB
TypeScript
126 lines
3.3 KiB
TypeScript
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<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);
|
|
});
|
|
});
|
|
}
|
|
|
|
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<void>((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<string>[] = [];
|
|
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();
|