139 lines
3.5 KiB
TypeScript
139 lines
3.5 KiB
TypeScript
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||
|
import { SmartProxy } from '../ts/index.js';
|
||
|
|
||
|
/**
|
||
|
* Test that verifies ACME challenge routes are properly created
|
||
|
*/
|
||
|
tap.test('should create ACME challenge route with high ports', async (tools) => {
|
||
|
tools.timeout(5000);
|
||
|
|
||
|
const capturedRoutes: any[] = [];
|
||
|
|
||
|
const settings = {
|
||
|
routes: [
|
||
|
{
|
||
|
name: 'secure-route',
|
||
|
match: {
|
||
|
ports: [18443], // High port to avoid permission issues
|
||
|
domains: 'test.local'
|
||
|
},
|
||
|
action: {
|
||
|
type: 'forward' as const,
|
||
|
target: { host: 'localhost', port: 8080 },
|
||
|
tls: {
|
||
|
mode: 'terminate' as const,
|
||
|
certificate: 'auto'
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
],
|
||
|
acme: {
|
||
|
email: 'test@test.local',
|
||
|
port: 18080 // High port for ACME challenges
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const proxy = new SmartProxy(settings);
|
||
|
|
||
|
// Capture route updates
|
||
|
const originalUpdateRoutes = (proxy as any).updateRoutesInternal.bind(proxy);
|
||
|
(proxy as any).updateRoutesInternal = async function(routes: any[]) {
|
||
|
capturedRoutes.push([...routes]);
|
||
|
return originalUpdateRoutes(routes);
|
||
|
};
|
||
|
|
||
|
await proxy.start();
|
||
|
|
||
|
// Check that ACME challenge route was added
|
||
|
const finalRoutes = capturedRoutes[capturedRoutes.length - 1];
|
||
|
const challengeRoute = finalRoutes.find((r: any) => r.name === 'acme-challenge');
|
||
|
|
||
|
expect(challengeRoute).toBeDefined();
|
||
|
expect(challengeRoute.match.path).toEqual('/.well-known/acme-challenge/*');
|
||
|
expect(challengeRoute.match.ports).toEqual(18080);
|
||
|
expect(challengeRoute.action.type).toEqual('static');
|
||
|
expect(challengeRoute.priority).toEqual(1000);
|
||
|
|
||
|
await proxy.stop();
|
||
|
});
|
||
|
|
||
|
tap.test('should handle HTTP request parsing correctly', async (tools) => {
|
||
|
tools.timeout(5000);
|
||
|
|
||
|
let handlerCalled = false;
|
||
|
let receivedContext: any;
|
||
|
|
||
|
const settings = {
|
||
|
routes: [
|
||
|
{
|
||
|
name: 'test-static',
|
||
|
match: {
|
||
|
ports: [18090],
|
||
|
path: '/test/*'
|
||
|
},
|
||
|
action: {
|
||
|
type: 'static' as const,
|
||
|
handler: async (context) => {
|
||
|
handlerCalled = true;
|
||
|
receivedContext = context;
|
||
|
return {
|
||
|
status: 200,
|
||
|
headers: { 'Content-Type': 'text/plain' },
|
||
|
body: 'OK'
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
]
|
||
|
};
|
||
|
|
||
|
const proxy = new SmartProxy(settings);
|
||
|
|
||
|
// Mock NFTables manager
|
||
|
(proxy as any).nftablesManager = {
|
||
|
ensureNFTablesSetup: async () => {},
|
||
|
stop: async () => {}
|
||
|
};
|
||
|
|
||
|
await proxy.start();
|
||
|
|
||
|
// Create a simple HTTP request
|
||
|
const client = new plugins.net.Socket();
|
||
|
|
||
|
await new Promise<void>((resolve, reject) => {
|
||
|
client.connect(18090, 'localhost', () => {
|
||
|
// Send HTTP request
|
||
|
const request = [
|
||
|
'GET /test/example HTTP/1.1',
|
||
|
'Host: localhost:18090',
|
||
|
'User-Agent: test-client',
|
||
|
'',
|
||
|
''
|
||
|
].join('\r\n');
|
||
|
|
||
|
client.write(request);
|
||
|
|
||
|
// Wait for response
|
||
|
client.on('data', (data) => {
|
||
|
const response = data.toString();
|
||
|
expect(response).toContain('HTTP/1.1 200');
|
||
|
expect(response).toContain('OK');
|
||
|
client.end();
|
||
|
resolve();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
client.on('error', reject);
|
||
|
});
|
||
|
|
||
|
// Verify handler was called
|
||
|
expect(handlerCalled).toBeTrue();
|
||
|
expect(receivedContext).toBeDefined();
|
||
|
expect(receivedContext.path).toEqual('/test/example');
|
||
|
expect(receivedContext.method).toEqual('GET');
|
||
|
expect(receivedContext.headers.host).toEqual('localhost:18090');
|
||
|
|
||
|
await proxy.stop();
|
||
|
});
|
||
|
|
||
|
tap.start();
|