241 lines
6.9 KiB
TypeScript
241 lines
6.9 KiB
TypeScript
import { SmartProxy } from '../ts/proxies/smart-proxy/index.js';
|
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
|
|
const testProxy = new SmartProxy({
|
|
routes: [{
|
|
name: 'test-route',
|
|
match: { ports: 9443, domains: 'test.local' },
|
|
action: {
|
|
type: 'forward',
|
|
target: { host: 'localhost', port: 8080 },
|
|
tls: {
|
|
mode: 'terminate',
|
|
certificate: 'auto',
|
|
acme: {
|
|
email: 'test@test.local',
|
|
useProduction: false
|
|
}
|
|
}
|
|
}
|
|
}],
|
|
acme: {
|
|
port: 9080 // Use high port for ACME challenges
|
|
}
|
|
});
|
|
|
|
tap.test('should provision certificate automatically', async () => {
|
|
// Mock certificate manager to avoid real ACME initialization
|
|
const mockCertStatus = {
|
|
domain: 'test-route',
|
|
status: 'valid' as const,
|
|
source: 'acme' as const,
|
|
expiryDate: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),
|
|
issueDate: new Date()
|
|
};
|
|
|
|
(testProxy as any).createCertificateManager = async function() {
|
|
return {
|
|
setUpdateRoutesCallback: () => {},
|
|
setHttpProxy: () => {},
|
|
setGlobalAcmeDefaults: () => {},
|
|
setAcmeStateManager: () => {},
|
|
initialize: async () => {},
|
|
provisionAllCertificates: async () => {},
|
|
stop: async () => {},
|
|
getAcmeOptions: () => ({ email: 'test@test.local', useProduction: false }),
|
|
getState: () => ({ challengeRouteActive: false }),
|
|
getCertificateStatus: () => mockCertStatus
|
|
};
|
|
};
|
|
|
|
(testProxy as any).getCertificateStatus = () => mockCertStatus;
|
|
|
|
await testProxy.start();
|
|
|
|
const status = testProxy.getCertificateStatus('test-route');
|
|
expect(status).toBeDefined();
|
|
expect(status.status).toEqual('valid');
|
|
expect(status.source).toEqual('acme');
|
|
|
|
await testProxy.stop();
|
|
});
|
|
|
|
tap.test('should handle static certificates', async () => {
|
|
const proxy = new SmartProxy({
|
|
routes: [{
|
|
name: 'static-route',
|
|
match: { ports: 9444, domains: 'static.example.com' },
|
|
action: {
|
|
type: 'forward',
|
|
target: { host: 'localhost', port: 8080 },
|
|
tls: {
|
|
mode: 'terminate',
|
|
certificate: {
|
|
cert: '-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----',
|
|
key: '-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----'
|
|
}
|
|
}
|
|
}
|
|
}]
|
|
});
|
|
|
|
await proxy.start();
|
|
|
|
const status = proxy.getCertificateStatus('static-route');
|
|
expect(status).toBeDefined();
|
|
expect(status.status).toEqual('valid');
|
|
expect(status.source).toEqual('static');
|
|
|
|
await proxy.stop();
|
|
});
|
|
|
|
tap.test('should handle ACME challenge routes', async () => {
|
|
const proxy = new SmartProxy({
|
|
routes: [{
|
|
name: 'auto-cert-route',
|
|
match: { ports: 9445, domains: 'acme.local' },
|
|
action: {
|
|
type: 'forward',
|
|
target: { host: 'localhost', port: 8080 },
|
|
tls: {
|
|
mode: 'terminate',
|
|
certificate: 'auto',
|
|
acme: {
|
|
email: 'acme@test.local',
|
|
useProduction: false,
|
|
challengePort: 9081
|
|
}
|
|
}
|
|
}
|
|
}, {
|
|
name: 'port-9081-route',
|
|
match: { ports: 9081, domains: 'acme.local' },
|
|
action: {
|
|
type: 'forward',
|
|
target: { host: 'localhost', port: 8080 }
|
|
}
|
|
}],
|
|
acme: {
|
|
port: 9081 // Use high port for ACME challenges
|
|
}
|
|
});
|
|
|
|
// Mock certificate manager to avoid real ACME initialization
|
|
(proxy as any).createCertificateManager = async function() {
|
|
return {
|
|
setUpdateRoutesCallback: () => {},
|
|
setHttpProxy: () => {},
|
|
setGlobalAcmeDefaults: () => {},
|
|
setAcmeStateManager: () => {},
|
|
initialize: async () => {},
|
|
provisionAllCertificates: async () => {},
|
|
stop: async () => {},
|
|
getAcmeOptions: () => ({ email: 'acme@test.local', useProduction: false }),
|
|
getState: () => ({ challengeRouteActive: false })
|
|
};
|
|
};
|
|
|
|
await proxy.start();
|
|
|
|
// Verify the proxy is configured with routes including the necessary port
|
|
const routes = proxy.settings.routes;
|
|
|
|
// Check that we have a route listening on the ACME challenge port
|
|
const acmeChallengePort = 9081;
|
|
const routesOnChallengePort = routes.filter((r: any) => {
|
|
const ports = Array.isArray(r.match.ports) ? r.match.ports : [r.match.ports];
|
|
return ports.includes(acmeChallengePort);
|
|
});
|
|
|
|
expect(routesOnChallengePort.length).toBeGreaterThan(0);
|
|
expect(routesOnChallengePort[0].name).toEqual('port-9081-route');
|
|
|
|
// Verify the main route has ACME configuration
|
|
const mainRoute = routes.find((r: any) => r.name === 'auto-cert-route');
|
|
expect(mainRoute).toBeDefined();
|
|
expect(mainRoute?.action.tls?.certificate).toEqual('auto');
|
|
expect(mainRoute?.action.tls?.acme?.email).toEqual('acme@test.local');
|
|
expect(mainRoute?.action.tls?.acme?.challengePort).toEqual(9081);
|
|
|
|
await proxy.stop();
|
|
});
|
|
|
|
tap.test('should renew certificates', async () => {
|
|
const proxy = new SmartProxy({
|
|
routes: [{
|
|
name: 'renew-route',
|
|
match: { ports: 9446, domains: 'renew.local' },
|
|
action: {
|
|
type: 'forward',
|
|
target: { host: 'localhost', port: 8080 },
|
|
tls: {
|
|
mode: 'terminate',
|
|
certificate: 'auto',
|
|
acme: {
|
|
email: 'renew@test.local',
|
|
useProduction: false,
|
|
renewBeforeDays: 30
|
|
}
|
|
}
|
|
}
|
|
}],
|
|
acme: {
|
|
port: 9082 // Use high port for ACME challenges
|
|
}
|
|
});
|
|
|
|
// Mock certificate manager with renewal capability
|
|
let renewCalled = false;
|
|
const mockCertStatus = {
|
|
domain: 'renew-route',
|
|
status: 'valid' as const,
|
|
source: 'acme' as const,
|
|
expiryDate: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),
|
|
issueDate: new Date()
|
|
};
|
|
|
|
(proxy as any).certManager = {
|
|
renewCertificate: async (routeName: string) => {
|
|
renewCalled = true;
|
|
expect(routeName).toEqual('renew-route');
|
|
},
|
|
getCertificateStatus: () => mockCertStatus,
|
|
setUpdateRoutesCallback: () => {},
|
|
setHttpProxy: () => {},
|
|
setGlobalAcmeDefaults: () => {},
|
|
setAcmeStateManager: () => {},
|
|
initialize: async () => {},
|
|
provisionAllCertificates: async () => {},
|
|
stop: async () => {},
|
|
getAcmeOptions: () => ({ email: 'renew@test.local', useProduction: false }),
|
|
getState: () => ({ challengeRouteActive: false })
|
|
};
|
|
|
|
(proxy as any).createCertificateManager = async function() {
|
|
return this.certManager;
|
|
};
|
|
|
|
(proxy as any).getCertificateStatus = function(routeName: string) {
|
|
return this.certManager.getCertificateStatus(routeName);
|
|
};
|
|
|
|
(proxy as any).renewCertificate = async function(routeName: string) {
|
|
if (this.certManager) {
|
|
await this.certManager.renewCertificate(routeName);
|
|
}
|
|
};
|
|
|
|
await proxy.start();
|
|
|
|
// Force renewal
|
|
await proxy.renewCertificate('renew-route');
|
|
expect(renewCalled).toBeTrue();
|
|
|
|
const status = proxy.getCertificateStatus('renew-route');
|
|
expect(status).toBeDefined();
|
|
expect(status.status).toEqual('valid');
|
|
|
|
await proxy.stop();
|
|
});
|
|
|
|
tap.start(); |