357 lines
8.9 KiB
TypeScript
357 lines
8.9 KiB
TypeScript
import { expect, tap } from '@push.rocks/tapbundle';
|
|
import { NetworkProxy } from '../ts/proxies/network-proxy/index.js';
|
|
import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js';
|
|
import type { IRouteContext } from '../ts/core/models/route-context.js';
|
|
import * as http from 'http';
|
|
import * as http2 from 'http2';
|
|
|
|
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
// Declare variables for tests
|
|
let networkProxy: NetworkProxy;
|
|
let testServer: http.Server;
|
|
let testServerHttp2: http2.Http2Server;
|
|
let serverPort: number;
|
|
let serverPortHttp2: number;
|
|
|
|
// Setup test environment
|
|
tap.test('setup NetworkProxy function-based targets test environment', async () => {
|
|
// Create simple HTTP server to respond to requests
|
|
testServer = http.createServer((req, res) => {
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({
|
|
url: req.url,
|
|
headers: req.headers,
|
|
method: req.method,
|
|
message: 'HTTP/1.1 Response'
|
|
}));
|
|
});
|
|
|
|
// Create simple HTTP/2 server to respond to requests
|
|
testServerHttp2 = http2.createServer();
|
|
testServerHttp2.on('stream', (stream, headers) => {
|
|
stream.respond({
|
|
'content-type': 'application/json',
|
|
':status': 200
|
|
});
|
|
stream.end(JSON.stringify({
|
|
path: headers[':path'],
|
|
headers,
|
|
method: headers[':method'],
|
|
message: 'HTTP/2 Response'
|
|
}));
|
|
});
|
|
|
|
// Start the servers
|
|
await new Promise<void>(resolve => {
|
|
testServer.listen(0, () => {
|
|
const address = testServer.address() as { port: number };
|
|
serverPort = address.port;
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
await new Promise<void>(resolve => {
|
|
testServerHttp2.listen(0, () => {
|
|
const address = testServerHttp2.address() as { port: number };
|
|
serverPortHttp2 = address.port;
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
// Create NetworkProxy instance
|
|
networkProxy = new NetworkProxy({
|
|
port: 0, // Use dynamic port
|
|
logLevel: 'error'
|
|
});
|
|
|
|
await networkProxy.start();
|
|
});
|
|
|
|
// Test static host/port routes
|
|
tap.test('should support static host/port routes', async () => {
|
|
const routes: IRouteConfig[] = [
|
|
{
|
|
name: 'static-route',
|
|
domain: 'example.com',
|
|
priority: 100,
|
|
match: {
|
|
domain: 'example.com'
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: {
|
|
host: 'localhost',
|
|
port: serverPort
|
|
}
|
|
}
|
|
}
|
|
];
|
|
|
|
await networkProxy.updateRouteConfigs(routes);
|
|
|
|
// Get proxy port
|
|
const proxyPort = networkProxy.getListeningPort();
|
|
|
|
// Make request to proxy
|
|
const response = await makeRequest({
|
|
hostname: 'localhost',
|
|
port: proxyPort,
|
|
path: '/test',
|
|
method: 'GET',
|
|
headers: {
|
|
'Host': 'example.com'
|
|
}
|
|
});
|
|
|
|
expect(response.statusCode).toEqual(200);
|
|
const body = JSON.parse(response.body);
|
|
expect(body.url).toEqual('/test');
|
|
expect(body.headers.host).toEqual(`localhost:${serverPort}`);
|
|
});
|
|
|
|
// Test function-based host
|
|
tap.test('should support function-based host', async () => {
|
|
const routes: IRouteConfig[] = [
|
|
{
|
|
name: 'function-host-route',
|
|
domain: 'function.example.com',
|
|
priority: 100,
|
|
match: {
|
|
domain: 'function.example.com'
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: {
|
|
host: (context: IRouteContext) => {
|
|
// Return localhost always in this test
|
|
return 'localhost';
|
|
},
|
|
port: serverPort
|
|
}
|
|
}
|
|
}
|
|
];
|
|
|
|
await networkProxy.updateRouteConfigs(routes);
|
|
|
|
// Get proxy port
|
|
const proxyPort = networkProxy.getListeningPort();
|
|
|
|
// Make request to proxy
|
|
const response = await makeRequest({
|
|
hostname: 'localhost',
|
|
port: proxyPort,
|
|
path: '/function-host',
|
|
method: 'GET',
|
|
headers: {
|
|
'Host': 'function.example.com'
|
|
}
|
|
});
|
|
|
|
expect(response.statusCode).toEqual(200);
|
|
const body = JSON.parse(response.body);
|
|
expect(body.url).toEqual('/function-host');
|
|
expect(body.headers.host).toEqual(`localhost:${serverPort}`);
|
|
});
|
|
|
|
// Test function-based port
|
|
tap.test('should support function-based port', async () => {
|
|
const routes: IRouteConfig[] = [
|
|
{
|
|
name: 'function-port-route',
|
|
domain: 'function-port.example.com',
|
|
priority: 100,
|
|
match: {
|
|
domain: 'function-port.example.com'
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: {
|
|
host: 'localhost',
|
|
port: (context: IRouteContext) => {
|
|
// Return test server port
|
|
return serverPort;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
];
|
|
|
|
await networkProxy.updateRouteConfigs(routes);
|
|
|
|
// Get proxy port
|
|
const proxyPort = networkProxy.getListeningPort();
|
|
|
|
// Make request to proxy
|
|
const response = await makeRequest({
|
|
hostname: 'localhost',
|
|
port: proxyPort,
|
|
path: '/function-port',
|
|
method: 'GET',
|
|
headers: {
|
|
'Host': 'function-port.example.com'
|
|
}
|
|
});
|
|
|
|
expect(response.statusCode).toEqual(200);
|
|
const body = JSON.parse(response.body);
|
|
expect(body.url).toEqual('/function-port');
|
|
expect(body.headers.host).toEqual(`localhost:${serverPort}`);
|
|
});
|
|
|
|
// Test function-based host AND port
|
|
tap.test('should support function-based host AND port', async () => {
|
|
const routes: IRouteConfig[] = [
|
|
{
|
|
name: 'function-both-route',
|
|
domain: 'function-both.example.com',
|
|
priority: 100,
|
|
match: {
|
|
domain: 'function-both.example.com'
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: {
|
|
host: (context: IRouteContext) => {
|
|
return 'localhost';
|
|
},
|
|
port: (context: IRouteContext) => {
|
|
return serverPort;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
];
|
|
|
|
await networkProxy.updateRouteConfigs(routes);
|
|
|
|
// Get proxy port
|
|
const proxyPort = networkProxy.getListeningPort();
|
|
|
|
// Make request to proxy
|
|
const response = await makeRequest({
|
|
hostname: 'localhost',
|
|
port: proxyPort,
|
|
path: '/function-both',
|
|
method: 'GET',
|
|
headers: {
|
|
'Host': 'function-both.example.com'
|
|
}
|
|
});
|
|
|
|
expect(response.statusCode).toEqual(200);
|
|
const body = JSON.parse(response.body);
|
|
expect(body.url).toEqual('/function-both');
|
|
expect(body.headers.host).toEqual(`localhost:${serverPort}`);
|
|
});
|
|
|
|
// Test context-based routing with path
|
|
tap.test('should support context-based routing with path', async () => {
|
|
const routes: IRouteConfig[] = [
|
|
{
|
|
name: 'context-path-route',
|
|
domain: 'context.example.com',
|
|
priority: 100,
|
|
match: {
|
|
domain: 'context.example.com'
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: {
|
|
host: (context: IRouteContext) => {
|
|
// Use path to determine host
|
|
if (context.path?.startsWith('/api')) {
|
|
return 'localhost';
|
|
} else {
|
|
return '127.0.0.1'; // Another way to reference localhost
|
|
}
|
|
},
|
|
port: serverPort
|
|
}
|
|
}
|
|
}
|
|
];
|
|
|
|
await networkProxy.updateRouteConfigs(routes);
|
|
|
|
// Get proxy port
|
|
const proxyPort = networkProxy.getListeningPort();
|
|
|
|
// Make request to proxy with /api path
|
|
const apiResponse = await makeRequest({
|
|
hostname: 'localhost',
|
|
port: proxyPort,
|
|
path: '/api/test',
|
|
method: 'GET',
|
|
headers: {
|
|
'Host': 'context.example.com'
|
|
}
|
|
});
|
|
|
|
expect(apiResponse.statusCode).toEqual(200);
|
|
const apiBody = JSON.parse(apiResponse.body);
|
|
expect(apiBody.url).toEqual('/api/test');
|
|
|
|
// Make request to proxy with non-api path
|
|
const nonApiResponse = await makeRequest({
|
|
hostname: 'localhost',
|
|
port: proxyPort,
|
|
path: '/web/test',
|
|
method: 'GET',
|
|
headers: {
|
|
'Host': 'context.example.com'
|
|
}
|
|
});
|
|
|
|
expect(nonApiResponse.statusCode).toEqual(200);
|
|
const nonApiBody = JSON.parse(nonApiResponse.body);
|
|
expect(nonApiBody.url).toEqual('/web/test');
|
|
});
|
|
|
|
// Cleanup test environment
|
|
tap.test('cleanup NetworkProxy function-based targets test environment', async () => {
|
|
if (networkProxy) {
|
|
await networkProxy.stop();
|
|
}
|
|
|
|
if (testServer) {
|
|
await new Promise<void>(resolve => {
|
|
testServer.close(() => resolve());
|
|
});
|
|
}
|
|
|
|
if (testServerHttp2) {
|
|
await new Promise<void>(resolve => {
|
|
testServerHttp2.close(() => resolve());
|
|
});
|
|
}
|
|
});
|
|
|
|
// Helper function to make HTTP requests
|
|
async function makeRequest(options: http.RequestOptions): Promise<{ statusCode: number, headers: http.IncomingHttpHeaders, body: string }> {
|
|
return new Promise((resolve, reject) => {
|
|
const req = http.request(options, (res) => {
|
|
let body = '';
|
|
res.on('data', (chunk) => {
|
|
body += chunk;
|
|
});
|
|
res.on('end', () => {
|
|
resolve({
|
|
statusCode: res.statusCode || 0,
|
|
headers: res.headers,
|
|
body
|
|
});
|
|
});
|
|
});
|
|
|
|
req.on('error', (err) => {
|
|
reject(err);
|
|
});
|
|
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
// Export the test runner to start tests
|
|
export default tap.start(); |