137 lines
4.0 KiB
TypeScript
137 lines
4.0 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import * as plugins from '../ts/plugins.js';
|
|
import * as http from 'http';
|
|
import { SmartProxy, SocketHandlers } from '../ts/index.js';
|
|
|
|
/**
|
|
* Helper to make HTTP requests using Node's http module (unlike fetch/undici,
|
|
* http.request doesn't keep the event loop alive via a connection pool).
|
|
*/
|
|
function httpRequest(url: string, options: { method?: string; headers?: Record<string, string> } = {}): Promise<{ status: number; headers: http.IncomingHttpHeaders; body: string }> {
|
|
return new Promise((resolve, reject) => {
|
|
const parsed = new URL(url);
|
|
const req = http.request({
|
|
hostname: parsed.hostname,
|
|
port: parsed.port,
|
|
path: parsed.pathname + parsed.search,
|
|
method: options.method || 'GET',
|
|
headers: options.headers,
|
|
}, (res) => {
|
|
let body = '';
|
|
res.on('data', (chunk: Buffer) => { body += chunk.toString(); });
|
|
res.on('end', () => resolve({ status: res.statusCode!, headers: res.headers, body }));
|
|
});
|
|
req.on('error', reject);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
tap.test('should handle HTTP requests on port 80 for ACME challenges', async (tools) => {
|
|
tools.timeout(10000);
|
|
|
|
// Track HTTP requests that are handled
|
|
const handledRequests: any[] = [];
|
|
|
|
const settings = {
|
|
routes: [
|
|
{
|
|
name: 'acme-test-route',
|
|
match: {
|
|
ports: [18080], // Use high port to avoid permission issues
|
|
path: '/.well-known/acme-challenge/*'
|
|
},
|
|
action: {
|
|
type: 'socket-handler' as const,
|
|
socketHandler: SocketHandlers.httpServer((req, res) => {
|
|
handledRequests.push({
|
|
path: req.url,
|
|
method: req.method,
|
|
headers: req.headers
|
|
});
|
|
|
|
// Simulate ACME challenge response
|
|
const token = req.url?.split('/').pop() || '';
|
|
res.header('Content-Type', 'text/plain');
|
|
res.send(`challenge-response-for-${token}`);
|
|
})
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
const proxy = new SmartProxy(settings);
|
|
|
|
await proxy.start();
|
|
|
|
// Make an HTTP request to the challenge endpoint
|
|
const response = await httpRequest('http://localhost:18080/.well-known/acme-challenge/test-token');
|
|
|
|
// Verify response
|
|
expect(response.status).toEqual(200);
|
|
expect(response.body).toEqual('challenge-response-for-test-token');
|
|
|
|
// Verify request was handled
|
|
expect(handledRequests.length).toEqual(1);
|
|
expect(handledRequests[0].path).toEqual('/.well-known/acme-challenge/test-token');
|
|
expect(handledRequests[0].method).toEqual('GET');
|
|
|
|
await proxy.stop();
|
|
});
|
|
|
|
tap.test('should parse HTTP headers correctly', async (tools) => {
|
|
tools.timeout(10000);
|
|
|
|
const capturedContext: any = {};
|
|
|
|
const settings = {
|
|
routes: [
|
|
{
|
|
name: 'header-test-route',
|
|
match: {
|
|
ports: [18081]
|
|
},
|
|
action: {
|
|
type: 'socket-handler' as const,
|
|
socketHandler: SocketHandlers.httpServer((req, res) => {
|
|
Object.assign(capturedContext, {
|
|
path: req.url,
|
|
method: req.method,
|
|
headers: req.headers
|
|
});
|
|
res.header('Content-Type', 'application/json');
|
|
res.send(JSON.stringify({
|
|
received: req.headers
|
|
}));
|
|
})
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
const proxy = new SmartProxy(settings);
|
|
|
|
await proxy.start();
|
|
|
|
// Make request with custom headers
|
|
const response = await httpRequest('http://localhost:18081/test', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-Custom-Header': 'test-value',
|
|
'User-Agent': 'test-agent'
|
|
}
|
|
});
|
|
|
|
expect(response.status).toEqual(200);
|
|
const body = JSON.parse(response.body);
|
|
|
|
// Verify headers were parsed correctly
|
|
expect(capturedContext.headers['x-custom-header']).toEqual('test-value');
|
|
expect(capturedContext.headers['user-agent']).toEqual('test-agent');
|
|
expect(capturedContext.method).toEqual('POST');
|
|
expect(capturedContext.path).toEqual('/test');
|
|
|
|
await proxy.stop();
|
|
});
|
|
|
|
export default tap.start();
|