update
This commit is contained in:
parent
200a735876
commit
6a08bbc558
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"expiryDate": "2025-08-17T16:58:47.999Z",
|
"expiryDate": "2025-08-27T01:45:41.917Z",
|
||||||
"issueDate": "2025-05-19T16:58:47.999Z",
|
"issueDate": "2025-05-29T01:45:41.917Z",
|
||||||
"savedAt": "2025-05-19T16:58:48.001Z"
|
"savedAt": "2025-05-29T01:45:41.919Z"
|
||||||
}
|
}
|
@ -9,7 +9,7 @@
|
|||||||
"author": "Lossless GmbH",
|
"author": "Lossless GmbH",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(tstest test/**/test*.ts --verbose)",
|
"test": "(tstest test/**/test*.ts --verbose --timeout 600)",
|
||||||
"build": "(tsbuild tsfolders --allowimplicitany)",
|
"build": "(tsbuild tsfolders --allowimplicitany)",
|
||||||
"format": "(gitzone format)",
|
"format": "(gitzone format)",
|
||||||
"buildDocs": "tsdoc"
|
"buildDocs": "tsdoc"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
import * as plugins from '../ts/plugins.js';
|
import * as plugins from '../ts/plugins.js';
|
||||||
import { SmartProxy } from '../ts/index.js';
|
import { SmartProxy, SocketHandlers } from '../ts/index.js';
|
||||||
|
|
||||||
tap.test('should handle HTTP requests on port 80 for ACME challenges', async (tools) => {
|
tap.test('should handle HTTP requests on port 80 for ACME challenges', async (tools) => {
|
||||||
tools.timeout(10000);
|
tools.timeout(10000);
|
||||||
@ -17,22 +17,19 @@ tap.test('should handle HTTP requests on port 80 for ACME challenges', async (to
|
|||||||
path: '/.well-known/acme-challenge/*'
|
path: '/.well-known/acme-challenge/*'
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
type: 'static' as const,
|
type: 'socket-handler' as const,
|
||||||
handler: async (context) => {
|
socketHandler: SocketHandlers.httpServer((req, res) => {
|
||||||
handledRequests.push({
|
handledRequests.push({
|
||||||
path: context.path,
|
path: req.url,
|
||||||
method: context.method,
|
method: req.method,
|
||||||
headers: context.headers
|
headers: req.headers
|
||||||
});
|
});
|
||||||
|
|
||||||
// Simulate ACME challenge response
|
// Simulate ACME challenge response
|
||||||
const token = context.path?.split('/').pop() || '';
|
const token = req.url?.split('/').pop() || '';
|
||||||
return {
|
res.header('Content-Type', 'text/plain');
|
||||||
status: 200,
|
res.send(`challenge-response-for-${token}`);
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
})
|
||||||
body: `challenge-response-for-${token}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -79,17 +76,18 @@ tap.test('should parse HTTP headers correctly', async (tools) => {
|
|||||||
ports: [18081]
|
ports: [18081]
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
type: 'static' as const,
|
type: 'socket-handler' as const,
|
||||||
handler: async (context) => {
|
socketHandler: SocketHandlers.httpServer((req, res) => {
|
||||||
Object.assign(capturedContext, context);
|
Object.assign(capturedContext, {
|
||||||
return {
|
path: req.url,
|
||||||
status: 200,
|
method: req.method,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: req.headers
|
||||||
body: JSON.stringify({
|
});
|
||||||
received: context.headers
|
res.header('Content-Type', 'application/json');
|
||||||
|
res.send(JSON.stringify({
|
||||||
|
received: req.headers
|
||||||
|
}));
|
||||||
})
|
})
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
import { SmartProxy } from '../ts/index.js';
|
import { SmartProxy, SocketHandlers } from '../ts/index.js';
|
||||||
import * as net from 'net';
|
import * as net from 'net';
|
||||||
|
|
||||||
// Test that HTTP-01 challenges are properly processed when the initial data arrives
|
// Test that HTTP-01 challenges are properly processed when the initial data arrives
|
||||||
@ -9,36 +9,28 @@ tap.test('should correctly handle HTTP-01 challenge requests with initial data c
|
|||||||
const challengeResponse = 'mock-response-for-challenge';
|
const challengeResponse = 'mock-response-for-challenge';
|
||||||
const challengePath = `/.well-known/acme-challenge/${challengeToken}`;
|
const challengePath = `/.well-known/acme-challenge/${challengeToken}`;
|
||||||
|
|
||||||
// Create a handler function that responds to ACME challenges
|
// Create a socket handler that responds to ACME challenges using httpServer
|
||||||
const acmeHandler = async (context: any) => {
|
const acmeHandler = SocketHandlers.httpServer((req, res) => {
|
||||||
// Log request details for debugging
|
// Log request details for debugging
|
||||||
console.log(`Received request: ${context.method} ${context.path}`);
|
console.log(`Received request: ${req.method} ${req.url}`);
|
||||||
|
|
||||||
// Check if this is an ACME challenge request
|
// Check if this is an ACME challenge request
|
||||||
if (context.path.startsWith('/.well-known/acme-challenge/')) {
|
if (req.url?.startsWith('/.well-known/acme-challenge/')) {
|
||||||
const token = context.path.substring('/.well-known/acme-challenge/'.length);
|
const token = req.url.substring('/.well-known/acme-challenge/'.length);
|
||||||
|
|
||||||
// If the token matches our test token, return the response
|
// If the token matches our test token, return the response
|
||||||
if (token === challengeToken) {
|
if (token === challengeToken) {
|
||||||
return {
|
res.header('Content-Type', 'text/plain');
|
||||||
status: 200,
|
res.send(challengeResponse);
|
||||||
headers: {
|
return;
|
||||||
'Content-Type': 'text/plain'
|
|
||||||
},
|
|
||||||
body: challengeResponse
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For any other requests, return 404
|
// For any other requests, return 404
|
||||||
return {
|
res.status(404);
|
||||||
status: 404,
|
res.header('Content-Type', 'text/plain');
|
||||||
headers: {
|
res.send('Not found');
|
||||||
'Content-Type': 'text/plain'
|
});
|
||||||
},
|
|
||||||
body: 'Not found'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a proxy with the ACME challenge route
|
// Create a proxy with the ACME challenge route
|
||||||
const proxy = new SmartProxy({
|
const proxy = new SmartProxy({
|
||||||
@ -49,8 +41,8 @@ tap.test('should correctly handle HTTP-01 challenge requests with initial data c
|
|||||||
path: '/.well-known/acme-challenge/*'
|
path: '/.well-known/acme-challenge/*'
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
type: 'static',
|
type: 'socket-handler',
|
||||||
handler: acmeHandler
|
socketHandler: acmeHandler
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
@ -98,27 +90,23 @@ tap.test('should correctly handle HTTP-01 challenge requests with initial data c
|
|||||||
|
|
||||||
// Test that non-existent challenge tokens return 404
|
// Test that non-existent challenge tokens return 404
|
||||||
tap.test('should return 404 for non-existent challenge tokens', async (tapTest) => {
|
tap.test('should return 404 for non-existent challenge tokens', async (tapTest) => {
|
||||||
// Create a handler function that behaves like a real ACME handler
|
// Create a socket handler that behaves like a real ACME handler
|
||||||
const acmeHandler = async (context: any) => {
|
const acmeHandler = SocketHandlers.httpServer((req, res) => {
|
||||||
if (context.path.startsWith('/.well-known/acme-challenge/')) {
|
if (req.url?.startsWith('/.well-known/acme-challenge/')) {
|
||||||
const token = context.path.substring('/.well-known/acme-challenge/'.length);
|
const token = req.url.substring('/.well-known/acme-challenge/'.length);
|
||||||
// In this test, we only recognize one specific token
|
// In this test, we only recognize one specific token
|
||||||
if (token === 'valid-token') {
|
if (token === 'valid-token') {
|
||||||
return {
|
res.header('Content-Type', 'text/plain');
|
||||||
status: 200,
|
res.send('valid-response');
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
return;
|
||||||
body: 'valid-response'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For all other paths or unrecognized tokens, return 404
|
// For all other paths or unrecognized tokens, return 404
|
||||||
return {
|
res.status(404);
|
||||||
status: 404,
|
res.header('Content-Type', 'text/plain');
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
res.send('Not found');
|
||||||
body: 'Not found'
|
});
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a proxy with the ACME challenge route
|
// Create a proxy with the ACME challenge route
|
||||||
const proxy = new SmartProxy({
|
const proxy = new SmartProxy({
|
||||||
@ -129,8 +117,8 @@ tap.test('should return 404 for non-existent challenge tokens', async (tapTest)
|
|||||||
path: '/.well-known/acme-challenge/*'
|
path: '/.well-known/acme-challenge/*'
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
type: 'static',
|
type: 'socket-handler',
|
||||||
handler: acmeHandler
|
socketHandler: acmeHandler
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
@ -53,7 +53,7 @@ tap.test('should create ACME challenge route with high ports', async (tools) =>
|
|||||||
expect(challengeRoute).toBeDefined();
|
expect(challengeRoute).toBeDefined();
|
||||||
expect(challengeRoute.match.path).toEqual('/.well-known/acme-challenge/*');
|
expect(challengeRoute.match.path).toEqual('/.well-known/acme-challenge/*');
|
||||||
expect(challengeRoute.match.ports).toEqual(18080);
|
expect(challengeRoute.match.ports).toEqual(18080);
|
||||||
expect(challengeRoute.action.type).toEqual('static');
|
expect(challengeRoute.action.type).toEqual('socket-handler');
|
||||||
expect(challengeRoute.priority).toEqual(1000);
|
expect(challengeRoute.priority).toEqual(1000);
|
||||||
|
|
||||||
await proxy.stop();
|
await proxy.stop();
|
||||||
@ -74,15 +74,30 @@ tap.test('should handle HTTP request parsing correctly', async (tools) => {
|
|||||||
path: '/test/*'
|
path: '/test/*'
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
type: 'static' as const,
|
type: 'socket-handler' as const,
|
||||||
handler: async (context) => {
|
socketHandler: (socket, context) => {
|
||||||
handlerCalled = true;
|
handlerCalled = true;
|
||||||
receivedContext = context;
|
receivedContext = context;
|
||||||
return {
|
|
||||||
status: 200,
|
// Parse HTTP request from socket
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
socket.once('data', (data) => {
|
||||||
body: 'OK'
|
const request = data.toString();
|
||||||
};
|
const lines = request.split('\r\n');
|
||||||
|
const [method, path, protocol] = lines[0].split(' ');
|
||||||
|
|
||||||
|
// Send HTTP response
|
||||||
|
const response = [
|
||||||
|
'HTTP/1.1 200 OK',
|
||||||
|
'Content-Type: text/plain',
|
||||||
|
'Content-Length: 2',
|
||||||
|
'Connection: close',
|
||||||
|
'',
|
||||||
|
'OK'
|
||||||
|
].join('\r\n');
|
||||||
|
|
||||||
|
socket.write(response);
|
||||||
|
socket.end();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,14 +84,26 @@ tap.test('should configure ACME challenge route', async () => {
|
|||||||
path: '/.well-known/acme-challenge/*'
|
path: '/.well-known/acme-challenge/*'
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
type: 'static',
|
type: 'socket-handler',
|
||||||
handler: async (context: any) => {
|
socketHandler: (socket: any, context: any) => {
|
||||||
const token = context.path?.split('/').pop() || '';
|
socket.once('data', (data: Buffer) => {
|
||||||
return {
|
const request = data.toString();
|
||||||
status: 200,
|
const lines = request.split('\r\n');
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
const [method, path] = lines[0].split(' ');
|
||||||
body: `challenge-response-${token}`
|
const token = path?.split('/').pop() || '';
|
||||||
};
|
|
||||||
|
const response = [
|
||||||
|
'HTTP/1.1 200 OK',
|
||||||
|
'Content-Type: text/plain',
|
||||||
|
`Content-Length: ${('challenge-response-' + token).length}`,
|
||||||
|
'Connection: close',
|
||||||
|
'',
|
||||||
|
`challenge-response-${token}`
|
||||||
|
].join('\r\n');
|
||||||
|
|
||||||
|
socket.write(response);
|
||||||
|
socket.end();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -101,16 +113,8 @@ tap.test('should configure ACME challenge route', async () => {
|
|||||||
expect(challengeRoute.match.ports).toEqual(80);
|
expect(challengeRoute.match.ports).toEqual(80);
|
||||||
expect(challengeRoute.priority).toEqual(1000);
|
expect(challengeRoute.priority).toEqual(1000);
|
||||||
|
|
||||||
// Test the handler
|
// Socket handlers are tested differently - they handle raw sockets
|
||||||
const context = {
|
expect(challengeRoute.action.socketHandler).toBeDefined();
|
||||||
path: '/.well-known/acme-challenge/test-token',
|
|
||||||
method: 'GET',
|
|
||||||
headers: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await challengeRoute.action.handler(context);
|
|
||||||
expect(response.status).toEqual(200);
|
|
||||||
expect(response.body).toEqual('challenge-response-test-token');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.start();
|
tap.start();
|
@ -4,7 +4,7 @@ import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|||||||
const testProxy = new SmartProxy({
|
const testProxy = new SmartProxy({
|
||||||
routes: [{
|
routes: [{
|
||||||
name: 'test-route',
|
name: 'test-route',
|
||||||
match: { ports: 443, domains: 'test.example.com' },
|
match: { ports: 9443, domains: 'test.example.com' },
|
||||||
action: {
|
action: {
|
||||||
type: 'forward',
|
type: 'forward',
|
||||||
target: { host: 'localhost', port: 8080 },
|
target: { host: 'localhost', port: 8080 },
|
||||||
@ -17,7 +17,10 @@ const testProxy = new SmartProxy({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]
|
}],
|
||||||
|
acme: {
|
||||||
|
port: 9080 // Use high port for ACME challenges
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should provision certificate automatically', async () => {
|
tap.test('should provision certificate automatically', async () => {
|
||||||
@ -38,7 +41,7 @@ tap.test('should handle static certificates', async () => {
|
|||||||
const proxy = new SmartProxy({
|
const proxy = new SmartProxy({
|
||||||
routes: [{
|
routes: [{
|
||||||
name: 'static-route',
|
name: 'static-route',
|
||||||
match: { ports: 443, domains: 'static.example.com' },
|
match: { ports: 9444, domains: 'static.example.com' },
|
||||||
action: {
|
action: {
|
||||||
type: 'forward',
|
type: 'forward',
|
||||||
target: { host: 'localhost', port: 8080 },
|
target: { host: 'localhost', port: 8080 },
|
||||||
@ -67,7 +70,7 @@ tap.test('should handle ACME challenge routes', async () => {
|
|||||||
const proxy = new SmartProxy({
|
const proxy = new SmartProxy({
|
||||||
routes: [{
|
routes: [{
|
||||||
name: 'auto-cert-route',
|
name: 'auto-cert-route',
|
||||||
match: { ports: 443, domains: 'acme.example.com' },
|
match: { ports: 9445, domains: 'acme.example.com' },
|
||||||
action: {
|
action: {
|
||||||
type: 'forward',
|
type: 'forward',
|
||||||
target: { host: 'localhost', port: 8080 },
|
target: { host: 'localhost', port: 8080 },
|
||||||
@ -77,18 +80,21 @@ tap.test('should handle ACME challenge routes', async () => {
|
|||||||
acme: {
|
acme: {
|
||||||
email: 'acme@example.com',
|
email: 'acme@example.com',
|
||||||
useProduction: false,
|
useProduction: false,
|
||||||
challengePort: 80
|
challengePort: 9081
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
name: 'port-80-route',
|
name: 'port-9081-route',
|
||||||
match: { ports: 80, domains: 'acme.example.com' },
|
match: { ports: 9081, domains: 'acme.example.com' },
|
||||||
action: {
|
action: {
|
||||||
type: 'forward',
|
type: 'forward',
|
||||||
target: { host: 'localhost', port: 8080 }
|
target: { host: 'localhost', port: 8080 }
|
||||||
}
|
}
|
||||||
}]
|
}],
|
||||||
|
acme: {
|
||||||
|
port: 9081 // Use high port for ACME challenges
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await proxy.start();
|
await proxy.start();
|
||||||
@ -109,7 +115,7 @@ tap.test('should renew certificates', async () => {
|
|||||||
const proxy = new SmartProxy({
|
const proxy = new SmartProxy({
|
||||||
routes: [{
|
routes: [{
|
||||||
name: 'renew-route',
|
name: 'renew-route',
|
||||||
match: { ports: 443, domains: 'renew.example.com' },
|
match: { ports: 9446, domains: 'renew.example.com' },
|
||||||
action: {
|
action: {
|
||||||
type: 'forward',
|
type: 'forward',
|
||||||
target: { host: 'localhost', port: 8080 },
|
target: { host: 'localhost', port: 8080 },
|
||||||
@ -123,7 +129,10 @@ tap.test('should renew certificates', async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]
|
}],
|
||||||
|
acme: {
|
||||||
|
port: 9082 // Use high port for ACME challenges
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await proxy.start();
|
await proxy.start();
|
||||||
|
@ -25,41 +25,36 @@ tap.test('should create SmartProxy with certificate routes', async () => {
|
|||||||
expect(proxy.settings.routes.length).toEqual(1);
|
expect(proxy.settings.routes.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should handle static route type', async () => {
|
tap.test('should handle socket handler route type', async () => {
|
||||||
// Create a test route with static handler
|
// Create a test route with socket handler
|
||||||
const testResponse = {
|
|
||||||
status: 200,
|
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
|
||||||
body: 'Hello from static route'
|
|
||||||
};
|
|
||||||
|
|
||||||
const proxy = new SmartProxy({
|
const proxy = new SmartProxy({
|
||||||
routes: [{
|
routes: [{
|
||||||
name: 'static-test',
|
name: 'socket-handler-test',
|
||||||
match: { ports: 8080, path: '/test' },
|
match: { ports: 8080, path: '/test' },
|
||||||
action: {
|
action: {
|
||||||
type: 'static',
|
type: 'socket-handler',
|
||||||
handler: async () => testResponse
|
socketHandler: (socket, context) => {
|
||||||
|
socket.once('data', (data) => {
|
||||||
|
const response = [
|
||||||
|
'HTTP/1.1 200 OK',
|
||||||
|
'Content-Type: text/plain',
|
||||||
|
'Content-Length: 23',
|
||||||
|
'Connection: close',
|
||||||
|
'',
|
||||||
|
'Hello from socket handler'
|
||||||
|
].join('\r\n');
|
||||||
|
|
||||||
|
socket.write(response);
|
||||||
|
socket.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
const route = proxy.settings.routes[0];
|
const route = proxy.settings.routes[0];
|
||||||
expect(route.action.type).toEqual('static');
|
expect(route.action.type).toEqual('socket-handler');
|
||||||
expect(route.action.handler).toBeDefined();
|
expect(route.action.socketHandler).toBeDefined();
|
||||||
|
|
||||||
// Test the handler
|
|
||||||
const result = await route.action.handler!({
|
|
||||||
port: 8080,
|
|
||||||
path: '/test',
|
|
||||||
clientIp: '127.0.0.1',
|
|
||||||
serverIp: '127.0.0.1',
|
|
||||||
isTls: false,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
connectionId: 'test-123'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toEqual(testResponse);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.start();
|
tap.start();
|
@ -9,7 +9,6 @@ import {
|
|||||||
createHttpToHttpsRedirect,
|
createHttpToHttpsRedirect,
|
||||||
createCompleteHttpsServer,
|
createCompleteHttpsServer,
|
||||||
createLoadBalancerRoute,
|
createLoadBalancerRoute,
|
||||||
createStaticFileRoute,
|
|
||||||
createApiRoute,
|
createApiRoute,
|
||||||
createWebSocketRoute
|
createWebSocketRoute
|
||||||
} from '../ts/proxies/smart-proxy/utils/route-helpers.js';
|
} from '../ts/proxies/smart-proxy/utils/route-helpers.js';
|
||||||
@ -73,7 +72,7 @@ tap.test('Route-based configuration examples', async (tools) => {
|
|||||||
|
|
||||||
expect(terminateToHttpRoute).toBeTruthy();
|
expect(terminateToHttpRoute).toBeTruthy();
|
||||||
expect(terminateToHttpRoute.action.tls?.mode).toEqual('terminate');
|
expect(terminateToHttpRoute.action.tls?.mode).toEqual('terminate');
|
||||||
expect(httpToHttpsRedirect.action.type).toEqual('redirect');
|
expect(httpToHttpsRedirect.action.type).toEqual('socket-handler');
|
||||||
|
|
||||||
// Example 4: Load Balancer with HTTPS
|
// Example 4: Load Balancer with HTTPS
|
||||||
const loadBalancerRoute = createLoadBalancerRoute(
|
const loadBalancerRoute = createLoadBalancerRoute(
|
||||||
@ -124,21 +123,9 @@ tap.test('Route-based configuration examples', async (tools) => {
|
|||||||
expect(Array.isArray(httpsServerRoutes)).toBeTrue();
|
expect(Array.isArray(httpsServerRoutes)).toBeTrue();
|
||||||
expect(httpsServerRoutes.length).toEqual(2); // HTTPS route and HTTP redirect
|
expect(httpsServerRoutes.length).toEqual(2); // HTTPS route and HTTP redirect
|
||||||
expect(httpsServerRoutes[0].action.tls?.mode).toEqual('terminate');
|
expect(httpsServerRoutes[0].action.tls?.mode).toEqual('terminate');
|
||||||
expect(httpsServerRoutes[1].action.type).toEqual('redirect');
|
expect(httpsServerRoutes[1].action.type).toEqual('socket-handler');
|
||||||
|
|
||||||
// Example 7: Static File Server
|
// Example 7: Static File Server - removed (use nginx/apache behind proxy)
|
||||||
const staticFileRoute = createStaticFileRoute(
|
|
||||||
'static.example.com',
|
|
||||||
'/var/www/static',
|
|
||||||
{
|
|
||||||
serveOnHttps: true,
|
|
||||||
certificate: 'auto',
|
|
||||||
name: 'Static File Server'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(staticFileRoute.action.type).toEqual('static');
|
|
||||||
expect(staticFileRoute.action.static?.root).toEqual('/var/www/static');
|
|
||||||
|
|
||||||
// Example 8: WebSocket Route
|
// Example 8: WebSocket Route
|
||||||
const webSocketRoute = createWebSocketRoute(
|
const webSocketRoute = createWebSocketRoute(
|
||||||
@ -163,7 +150,6 @@ tap.test('Route-based configuration examples', async (tools) => {
|
|||||||
loadBalancerRoute,
|
loadBalancerRoute,
|
||||||
apiRoute,
|
apiRoute,
|
||||||
...httpsServerRoutes,
|
...httpsServerRoutes,
|
||||||
staticFileRoute,
|
|
||||||
webSocketRoute
|
webSocketRoute
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -175,7 +161,7 @@ tap.test('Route-based configuration examples', async (tools) => {
|
|||||||
|
|
||||||
// Just verify that all routes are configured correctly
|
// Just verify that all routes are configured correctly
|
||||||
console.log(`Created ${allRoutes.length} example routes`);
|
console.log(`Created ${allRoutes.length} example routes`);
|
||||||
expect(allRoutes.length).toEqual(10);
|
expect(allRoutes.length).toEqual(9); // One less without static file route
|
||||||
});
|
});
|
||||||
|
|
||||||
export default tap.start();
|
export default tap.start();
|
@ -72,9 +72,10 @@ tap.test('Route Helpers - Create complete HTTPS server with redirect', async ()
|
|||||||
|
|
||||||
expect(routes.length).toEqual(2);
|
expect(routes.length).toEqual(2);
|
||||||
|
|
||||||
// Check HTTP to HTTPS redirect - find route by action type
|
// Check HTTP to HTTPS redirect - find route by port
|
||||||
const redirectRoute = routes.find(r => r.action.type === 'redirect');
|
const redirectRoute = routes.find(r => r.match.ports === 80);
|
||||||
expect(redirectRoute.action.type).toEqual('redirect');
|
expect(redirectRoute.action.type).toEqual('socket-handler');
|
||||||
|
expect(redirectRoute.action.socketHandler).toBeDefined();
|
||||||
expect(redirectRoute.match.ports).toEqual(80);
|
expect(redirectRoute.match.ports).toEqual(80);
|
||||||
|
|
||||||
// Check HTTPS route
|
// Check HTTPS route
|
||||||
|
@ -35,7 +35,6 @@ import {
|
|||||||
createHttpToHttpsRedirect,
|
createHttpToHttpsRedirect,
|
||||||
createCompleteHttpsServer,
|
createCompleteHttpsServer,
|
||||||
createLoadBalancerRoute,
|
createLoadBalancerRoute,
|
||||||
createStaticFileRoute,
|
|
||||||
createApiRoute,
|
createApiRoute,
|
||||||
createWebSocketRoute
|
createWebSocketRoute
|
||||||
} from '../ts/proxies/smart-proxy/utils/route-helpers.js';
|
} from '../ts/proxies/smart-proxy/utils/route-helpers.js';
|
||||||
@ -87,9 +86,8 @@ tap.test('Routes: Should create HTTP to HTTPS redirect', async () => {
|
|||||||
// Validate the route configuration
|
// Validate the route configuration
|
||||||
expect(redirectRoute.match.ports).toEqual(80);
|
expect(redirectRoute.match.ports).toEqual(80);
|
||||||
expect(redirectRoute.match.domains).toEqual('example.com');
|
expect(redirectRoute.match.domains).toEqual('example.com');
|
||||||
expect(redirectRoute.action.type).toEqual('redirect');
|
expect(redirectRoute.action.type).toEqual('socket-handler');
|
||||||
expect(redirectRoute.action.redirect?.to).toEqual('https://{domain}:443{path}');
|
expect(redirectRoute.action.socketHandler).toBeDefined();
|
||||||
expect(redirectRoute.action.redirect?.status).toEqual(301);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('Routes: Should create complete HTTPS server with redirects', async () => {
|
tap.test('Routes: Should create complete HTTPS server with redirects', async () => {
|
||||||
@ -111,8 +109,8 @@ tap.test('Routes: Should create complete HTTPS server with redirects', async ()
|
|||||||
// Validate HTTP redirect route
|
// Validate HTTP redirect route
|
||||||
const redirectRoute = routes[1];
|
const redirectRoute = routes[1];
|
||||||
expect(redirectRoute.match.ports).toEqual(80);
|
expect(redirectRoute.match.ports).toEqual(80);
|
||||||
expect(redirectRoute.action.type).toEqual('redirect');
|
expect(redirectRoute.action.type).toEqual('socket-handler');
|
||||||
expect(redirectRoute.action.redirect?.to).toEqual('https://{domain}:443{path}');
|
expect(redirectRoute.action.socketHandler).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('Routes: Should create load balancer route', async () => {
|
tap.test('Routes: Should create load balancer route', async () => {
|
||||||
@ -190,24 +188,7 @@ tap.test('Routes: Should create WebSocket route', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('Routes: Should create static file route', async () => {
|
// Static file serving has been removed - should be handled by external servers
|
||||||
// Create a static file route
|
|
||||||
const staticRoute = createStaticFileRoute('static.example.com', '/var/www/html', {
|
|
||||||
serveOnHttps: true,
|
|
||||||
certificate: 'auto',
|
|
||||||
indexFiles: ['index.html', 'index.htm', 'default.html'],
|
|
||||||
name: 'Static File Route'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Validate the route configuration
|
|
||||||
expect(staticRoute.match.domains).toEqual('static.example.com');
|
|
||||||
expect(staticRoute.action.type).toEqual('static');
|
|
||||||
expect(staticRoute.action.static?.root).toEqual('/var/www/html');
|
|
||||||
expect(staticRoute.action.static?.index).toBeInstanceOf(Array);
|
|
||||||
expect(staticRoute.action.static?.index).toInclude('index.html');
|
|
||||||
expect(staticRoute.action.static?.index).toInclude('default.html');
|
|
||||||
expect(staticRoute.action.tls?.mode).toEqual('terminate');
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('SmartProxy: Should create instance with route-based config', async () => {
|
tap.test('SmartProxy: Should create instance with route-based config', async () => {
|
||||||
// Create TLS certificates for testing
|
// Create TLS certificates for testing
|
||||||
@ -515,11 +496,6 @@ tap.test('Route Integration - Combining Multiple Route Types', async () => {
|
|||||||
certificate: 'auto'
|
certificate: 'auto'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Static assets
|
|
||||||
createStaticFileRoute('static.example.com', '/var/www/assets', {
|
|
||||||
serveOnHttps: true,
|
|
||||||
certificate: 'auto'
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Legacy system with passthrough
|
// Legacy system with passthrough
|
||||||
createHttpsPassthroughRoute('legacy.example.com', { host: 'legacy-server', port: 443 })
|
createHttpsPassthroughRoute('legacy.example.com', { host: 'legacy-server', port: 443 })
|
||||||
@ -540,11 +516,11 @@ tap.test('Route Integration - Combining Multiple Route Types', async () => {
|
|||||||
expect(webServerMatch.action.target.host).toEqual('web-server');
|
expect(webServerMatch.action.target.host).toEqual('web-server');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Web server (HTTP redirect)
|
// Web server (HTTP redirect via socket handler)
|
||||||
const webRedirectMatch = findBestMatchingRoute(routes, { domain: 'example.com', port: 80 });
|
const webRedirectMatch = findBestMatchingRoute(routes, { domain: 'example.com', port: 80 });
|
||||||
expect(webRedirectMatch).not.toBeUndefined();
|
expect(webRedirectMatch).not.toBeUndefined();
|
||||||
if (webRedirectMatch) {
|
if (webRedirectMatch) {
|
||||||
expect(webRedirectMatch.action.type).toEqual('redirect');
|
expect(webRedirectMatch.action.type).toEqual('socket-handler');
|
||||||
}
|
}
|
||||||
|
|
||||||
// API server
|
// API server
|
||||||
@ -572,16 +548,7 @@ tap.test('Route Integration - Combining Multiple Route Types', async () => {
|
|||||||
expect(wsMatch.action.websocket?.enabled).toBeTrue();
|
expect(wsMatch.action.websocket?.enabled).toBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static assets
|
// Static assets route was removed - static file serving should be handled externally
|
||||||
const staticMatch = findBestMatchingRoute(routes, {
|
|
||||||
domain: 'static.example.com',
|
|
||||||
port: 443
|
|
||||||
});
|
|
||||||
expect(staticMatch).not.toBeUndefined();
|
|
||||||
if (staticMatch) {
|
|
||||||
expect(staticMatch.action.type).toEqual('static');
|
|
||||||
expect(staticMatch.action.static.root).toEqual('/var/www/assets');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Legacy system
|
// Legacy system
|
||||||
const legacyMatch = findBestMatchingRoute(routes, {
|
const legacyMatch = findBestMatchingRoute(routes, {
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
// Route helpers
|
// Route helpers
|
||||||
createHttpRoute,
|
createHttpRoute,
|
||||||
createHttpsTerminateRoute,
|
createHttpsTerminateRoute,
|
||||||
createStaticFileRoute,
|
|
||||||
createApiRoute,
|
createApiRoute,
|
||||||
createWebSocketRoute,
|
createWebSocketRoute,
|
||||||
createHttpToHttpsRedirect,
|
createHttpToHttpsRedirect,
|
||||||
@ -43,7 +42,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
// Route patterns
|
// Route patterns
|
||||||
createApiGatewayRoute,
|
createApiGatewayRoute,
|
||||||
createStaticFileServerRoute,
|
|
||||||
createWebSocketRoute as createWebSocketPattern,
|
createWebSocketRoute as createWebSocketPattern,
|
||||||
createLoadBalancerRoute as createLbPattern,
|
createLoadBalancerRoute as createLbPattern,
|
||||||
addRateLimiting,
|
addRateLimiting,
|
||||||
@ -145,28 +143,16 @@ tap.test('Route Validation - validateRouteAction', async () => {
|
|||||||
expect(validForwardResult.valid).toBeTrue();
|
expect(validForwardResult.valid).toBeTrue();
|
||||||
expect(validForwardResult.errors.length).toEqual(0);
|
expect(validForwardResult.errors.length).toEqual(0);
|
||||||
|
|
||||||
// Valid redirect action
|
// Valid socket-handler action
|
||||||
const validRedirectAction: IRouteAction = {
|
const validSocketAction: IRouteAction = {
|
||||||
type: 'redirect',
|
type: 'socket-handler',
|
||||||
redirect: {
|
socketHandler: (socket, context) => {
|
||||||
to: 'https://example.com',
|
socket.end();
|
||||||
status: 301
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const validRedirectResult = validateRouteAction(validRedirectAction);
|
const validSocketResult = validateRouteAction(validSocketAction);
|
||||||
expect(validRedirectResult.valid).toBeTrue();
|
expect(validSocketResult.valid).toBeTrue();
|
||||||
expect(validRedirectResult.errors.length).toEqual(0);
|
expect(validSocketResult.errors.length).toEqual(0);
|
||||||
|
|
||||||
// Valid static action
|
|
||||||
const validStaticAction: IRouteAction = {
|
|
||||||
type: 'static',
|
|
||||||
static: {
|
|
||||||
root: '/var/www/html'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const validStaticResult = validateRouteAction(validStaticAction);
|
|
||||||
expect(validStaticResult.valid).toBeTrue();
|
|
||||||
expect(validStaticResult.errors.length).toEqual(0);
|
|
||||||
|
|
||||||
// Invalid action (missing target)
|
// Invalid action (missing target)
|
||||||
const invalidAction: IRouteAction = {
|
const invalidAction: IRouteAction = {
|
||||||
@ -177,24 +163,14 @@ tap.test('Route Validation - validateRouteAction', async () => {
|
|||||||
expect(invalidResult.errors.length).toBeGreaterThan(0);
|
expect(invalidResult.errors.length).toBeGreaterThan(0);
|
||||||
expect(invalidResult.errors[0]).toInclude('Target is required');
|
expect(invalidResult.errors[0]).toInclude('Target is required');
|
||||||
|
|
||||||
// Invalid action (missing redirect configuration)
|
// Invalid action (missing socket handler)
|
||||||
const invalidRedirectAction: IRouteAction = {
|
const invalidSocketAction: IRouteAction = {
|
||||||
type: 'redirect'
|
type: 'socket-handler'
|
||||||
};
|
};
|
||||||
const invalidRedirectResult = validateRouteAction(invalidRedirectAction);
|
const invalidSocketResult = validateRouteAction(invalidSocketAction);
|
||||||
expect(invalidRedirectResult.valid).toBeFalse();
|
expect(invalidSocketResult.valid).toBeFalse();
|
||||||
expect(invalidRedirectResult.errors.length).toBeGreaterThan(0);
|
expect(invalidSocketResult.errors.length).toBeGreaterThan(0);
|
||||||
expect(invalidRedirectResult.errors[0]).toInclude('Redirect configuration is required');
|
expect(invalidSocketResult.errors[0]).toInclude('Socket handler function is required');
|
||||||
|
|
||||||
// Invalid action (missing static root)
|
|
||||||
const invalidStaticAction: IRouteAction = {
|
|
||||||
type: 'static',
|
|
||||||
static: {} as any // Testing invalid static config without required 'root' property
|
|
||||||
};
|
|
||||||
const invalidStaticResult = validateRouteAction(invalidStaticAction);
|
|
||||||
expect(invalidStaticResult.valid).toBeFalse();
|
|
||||||
expect(invalidStaticResult.errors.length).toBeGreaterThan(0);
|
|
||||||
expect(invalidStaticResult.errors[0]).toInclude('Static file root directory is required');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('Route Validation - validateRouteConfig', async () => {
|
tap.test('Route Validation - validateRouteConfig', async () => {
|
||||||
@ -253,26 +229,25 @@ tap.test('Route Validation - hasRequiredPropertiesForAction', async () => {
|
|||||||
const forwardRoute = createHttpRoute('example.com', { host: 'localhost', port: 3000 });
|
const forwardRoute = createHttpRoute('example.com', { host: 'localhost', port: 3000 });
|
||||||
expect(hasRequiredPropertiesForAction(forwardRoute, 'forward')).toBeTrue();
|
expect(hasRequiredPropertiesForAction(forwardRoute, 'forward')).toBeTrue();
|
||||||
|
|
||||||
// Redirect action
|
// Socket handler action (redirect functionality)
|
||||||
const redirectRoute = createHttpToHttpsRedirect('example.com');
|
const redirectRoute = createHttpToHttpsRedirect('example.com');
|
||||||
expect(hasRequiredPropertiesForAction(redirectRoute, 'redirect')).toBeTrue();
|
expect(hasRequiredPropertiesForAction(redirectRoute, 'socket-handler')).toBeTrue();
|
||||||
|
|
||||||
// Static action
|
// Socket handler action
|
||||||
const staticRoute = createStaticFileRoute('example.com', '/var/www/html');
|
const socketRoute: IRouteConfig = {
|
||||||
expect(hasRequiredPropertiesForAction(staticRoute, 'static')).toBeTrue();
|
|
||||||
|
|
||||||
// Block action
|
|
||||||
const blockRoute: IRouteConfig = {
|
|
||||||
match: {
|
match: {
|
||||||
domains: 'blocked.example.com',
|
domains: 'socket.example.com',
|
||||||
ports: 80
|
ports: 80
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
type: 'block'
|
type: 'socket-handler',
|
||||||
|
socketHandler: (socket, context) => {
|
||||||
|
socket.end();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
name: 'Block Route'
|
name: 'Socket Handler Route'
|
||||||
};
|
};
|
||||||
expect(hasRequiredPropertiesForAction(blockRoute, 'block')).toBeTrue();
|
expect(hasRequiredPropertiesForAction(socketRoute, 'socket-handler')).toBeTrue();
|
||||||
|
|
||||||
// Missing required properties
|
// Missing required properties
|
||||||
const invalidForwardRoute: IRouteConfig = {
|
const invalidForwardRoute: IRouteConfig = {
|
||||||
@ -345,20 +320,22 @@ tap.test('Route Utilities - mergeRouteConfigs', async () => {
|
|||||||
expect(actionMergedRoute.action.target.host).toEqual('new-host.local');
|
expect(actionMergedRoute.action.target.host).toEqual('new-host.local');
|
||||||
expect(actionMergedRoute.action.target.port).toEqual(5000);
|
expect(actionMergedRoute.action.target.port).toEqual(5000);
|
||||||
|
|
||||||
// Test replacing action with different type
|
// Test replacing action with socket handler
|
||||||
const typeChangeOverride: Partial<IRouteConfig> = {
|
const typeChangeOverride: Partial<IRouteConfig> = {
|
||||||
action: {
|
action: {
|
||||||
type: 'redirect',
|
type: 'socket-handler',
|
||||||
redirect: {
|
socketHandler: (socket, context) => {
|
||||||
to: 'https://example.com',
|
socket.write('HTTP/1.1 301 Moved Permanently\r\n');
|
||||||
status: 301
|
socket.write('Location: https://example.com\r\n');
|
||||||
|
socket.write('\r\n');
|
||||||
|
socket.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeChangedRoute = mergeRouteConfigs(baseRoute, typeChangeOverride);
|
const typeChangedRoute = mergeRouteConfigs(baseRoute, typeChangeOverride);
|
||||||
expect(typeChangedRoute.action.type).toEqual('redirect');
|
expect(typeChangedRoute.action.type).toEqual('socket-handler');
|
||||||
expect(typeChangedRoute.action.redirect.to).toEqual('https://example.com');
|
expect(typeChangedRoute.action.socketHandler).toBeDefined();
|
||||||
expect(typeChangedRoute.action.target).toBeUndefined();
|
expect(typeChangedRoute.action.target).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -705,9 +682,8 @@ tap.test('Route Helpers - createHttpToHttpsRedirect', async () => {
|
|||||||
|
|
||||||
expect(route.match.domains).toEqual('example.com');
|
expect(route.match.domains).toEqual('example.com');
|
||||||
expect(route.match.ports).toEqual(80);
|
expect(route.match.ports).toEqual(80);
|
||||||
expect(route.action.type).toEqual('redirect');
|
expect(route.action.type).toEqual('socket-handler');
|
||||||
expect(route.action.redirect.to).toEqual('https://{domain}:443{path}');
|
expect(route.action.socketHandler).toBeDefined();
|
||||||
expect(route.action.redirect.status).toEqual(301);
|
|
||||||
|
|
||||||
const validationResult = validateRouteConfig(route);
|
const validationResult = validateRouteConfig(route);
|
||||||
expect(validationResult.valid).toBeTrue();
|
expect(validationResult.valid).toBeTrue();
|
||||||
@ -741,7 +717,7 @@ tap.test('Route Helpers - createCompleteHttpsServer', async () => {
|
|||||||
// HTTP redirect route
|
// HTTP redirect route
|
||||||
expect(routes[1].match.domains).toEqual('example.com');
|
expect(routes[1].match.domains).toEqual('example.com');
|
||||||
expect(routes[1].match.ports).toEqual(80);
|
expect(routes[1].match.ports).toEqual(80);
|
||||||
expect(routes[1].action.type).toEqual('redirect');
|
expect(routes[1].action.type).toEqual('socket-handler');
|
||||||
|
|
||||||
const validation1 = validateRouteConfig(routes[0]);
|
const validation1 = validateRouteConfig(routes[0]);
|
||||||
const validation2 = validateRouteConfig(routes[1]);
|
const validation2 = validateRouteConfig(routes[1]);
|
||||||
@ -749,24 +725,8 @@ tap.test('Route Helpers - createCompleteHttpsServer', async () => {
|
|||||||
expect(validation2.valid).toBeTrue();
|
expect(validation2.valid).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('Route Helpers - createStaticFileRoute', async () => {
|
// createStaticFileRoute has been removed - static file serving should be handled by
|
||||||
const route = createStaticFileRoute('example.com', '/var/www/html', {
|
// external servers (nginx/apache) behind the proxy
|
||||||
serveOnHttps: true,
|
|
||||||
certificate: 'auto',
|
|
||||||
indexFiles: ['index.html', 'index.htm', 'default.html']
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(route.match.domains).toEqual('example.com');
|
|
||||||
expect(route.match.ports).toEqual(443);
|
|
||||||
expect(route.action.type).toEqual('static');
|
|
||||||
expect(route.action.static.root).toEqual('/var/www/html');
|
|
||||||
expect(route.action.static.index).toInclude('index.html');
|
|
||||||
expect(route.action.static.index).toInclude('default.html');
|
|
||||||
expect(route.action.tls.mode).toEqual('terminate');
|
|
||||||
|
|
||||||
const validationResult = validateRouteConfig(route);
|
|
||||||
expect(validationResult.valid).toBeTrue();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('Route Helpers - createApiRoute', async () => {
|
tap.test('Route Helpers - createApiRoute', async () => {
|
||||||
const route = createApiRoute('api.example.com', '/v1', { host: 'localhost', port: 3000 }, {
|
const route = createApiRoute('api.example.com', '/v1', { host: 'localhost', port: 3000 }, {
|
||||||
@ -874,34 +834,8 @@ tap.test('Route Patterns - createApiGatewayRoute', async () => {
|
|||||||
expect(result.valid).toBeTrue();
|
expect(result.valid).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('Route Patterns - createStaticFileServerRoute', async () => {
|
// createStaticFileServerRoute has been removed - static file serving should be handled by
|
||||||
// Create static file server route
|
// external servers (nginx/apache) behind the proxy
|
||||||
const staticRoute = createStaticFileServerRoute(
|
|
||||||
'static.example.com',
|
|
||||||
'/var/www/html',
|
|
||||||
{
|
|
||||||
useTls: true,
|
|
||||||
cacheControl: 'public, max-age=7200'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Validate route configuration
|
|
||||||
expect(staticRoute.match.domains).toEqual('static.example.com');
|
|
||||||
expect(staticRoute.action.type).toEqual('static');
|
|
||||||
|
|
||||||
// Check static configuration
|
|
||||||
if (staticRoute.action.static) {
|
|
||||||
expect(staticRoute.action.static.root).toEqual('/var/www/html');
|
|
||||||
|
|
||||||
// Check cache control headers if they exist
|
|
||||||
if (staticRoute.action.static.headers) {
|
|
||||||
expect(staticRoute.action.static.headers['Cache-Control']).toEqual('public, max-age=7200');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = validateRouteConfig(staticRoute);
|
|
||||||
expect(result.valid).toBeTrue();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('Route Patterns - createWebSocketPattern', async () => {
|
tap.test('Route Patterns - createWebSocketPattern', async () => {
|
||||||
// Create WebSocket route pattern
|
// Create WebSocket route pattern
|
||||||
|
@ -53,7 +53,15 @@ export function mergeRouteConfigs(
|
|||||||
if (overrideRoute.action) {
|
if (overrideRoute.action) {
|
||||||
// If action types are different, replace the entire action
|
// If action types are different, replace the entire action
|
||||||
if (overrideRoute.action.type && overrideRoute.action.type !== mergedRoute.action.type) {
|
if (overrideRoute.action.type && overrideRoute.action.type !== mergedRoute.action.type) {
|
||||||
|
// Handle socket handler specially since it's a function
|
||||||
|
if (overrideRoute.action.type === 'socket-handler' && overrideRoute.action.socketHandler) {
|
||||||
|
mergedRoute.action = {
|
||||||
|
type: 'socket-handler',
|
||||||
|
socketHandler: overrideRoute.action.socketHandler
|
||||||
|
};
|
||||||
|
} else {
|
||||||
mergedRoute.action = JSON.parse(JSON.stringify(overrideRoute.action));
|
mergedRoute.action = JSON.parse(JSON.stringify(overrideRoute.action));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise merge the action properties
|
// Otherwise merge the action properties
|
||||||
mergedRoute.action = { ...mergedRoute.action };
|
mergedRoute.action = { ...mergedRoute.action };
|
||||||
@ -74,7 +82,10 @@ export function mergeRouteConfigs(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// No special merging needed for socket handlers - they are functions
|
// Handle socket handler update
|
||||||
|
if (overrideRoute.action.socketHandler) {
|
||||||
|
mergedRoute.action.socketHandler = overrideRoute.action.socketHandler;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ export function validateRouteAction(action: IRouteAction): { valid: boolean; err
|
|||||||
// Validate action type
|
// Validate action type
|
||||||
if (!action.type) {
|
if (!action.type) {
|
||||||
errors.push('Action type is required');
|
errors.push('Action type is required');
|
||||||
} else if (!['forward', 'redirect', 'static', 'block'].includes(action.type)) {
|
} else if (!['forward', 'socket-handler'].includes(action.type)) {
|
||||||
errors.push(`Invalid action type: ${action.type}`);
|
errors.push(`Invalid action type: ${action.type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user