204 lines
6.7 KiB
TypeScript
204 lines
6.7 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import { SmartProxy } from '../ts/index.js';
|
|
import * as net from 'net';
|
|
|
|
// Test that certificate provisioning waits for ports to be ready
|
|
tap.test('should defer certificate provisioning until after ports are listening', async (tapTest) => {
|
|
// Track the order of operations
|
|
const operationLog: string[] = [];
|
|
|
|
// Create a mock server to verify ports are listening
|
|
let port80Listening = false;
|
|
|
|
// Try to use port 8080 instead of 80 to avoid permission issues in testing
|
|
const acmePort = 8080;
|
|
|
|
// Create proxy with ACME certificate requirement
|
|
const proxy = new SmartProxy({
|
|
useHttpProxy: [acmePort],
|
|
httpProxyPort: 8845, // Use different port to avoid conflicts
|
|
acme: {
|
|
email: 'test@test.local',
|
|
useProduction: false,
|
|
port: acmePort
|
|
},
|
|
routes: [{
|
|
name: 'test-acme-route',
|
|
match: {
|
|
ports: 8443,
|
|
domains: ['test.local']
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: { host: 'localhost', port: 8181 },
|
|
tls: {
|
|
mode: 'terminate',
|
|
certificate: 'auto',
|
|
acme: {
|
|
email: 'test@test.local',
|
|
useProduction: false
|
|
}
|
|
}
|
|
}
|
|
}]
|
|
});
|
|
|
|
// Mock some internal methods to track operation order
|
|
const originalAddPorts = proxy['portManager'].addPorts;
|
|
proxy['portManager'].addPorts = async function(ports: number[]) {
|
|
operationLog.push('Starting port listeners');
|
|
const result = await originalAddPorts.call(this, ports);
|
|
operationLog.push('Port listeners started');
|
|
port80Listening = true;
|
|
return result;
|
|
};
|
|
|
|
// Track that we created a certificate manager and SmartProxy will call provisionAllCertificates
|
|
let certManagerCreated = false;
|
|
|
|
// Override createCertificateManager to set up our tracking
|
|
const originalCreateCertManager = (proxy as any).createCertificateManager;
|
|
(proxy as any).certManagerCreated = false;
|
|
|
|
// Mock certificate manager to avoid real ACME initialization
|
|
(proxy as any).createCertificateManager = async function() {
|
|
operationLog.push('Creating certificate manager');
|
|
const mockCertManager = {
|
|
setUpdateRoutesCallback: () => {},
|
|
setHttpProxy: () => {},
|
|
setGlobalAcmeDefaults: () => {},
|
|
setAcmeStateManager: () => {},
|
|
initialize: async () => {
|
|
operationLog.push('Certificate manager initialized');
|
|
},
|
|
provisionAllCertificates: async () => {
|
|
operationLog.push('Starting certificate provisioning');
|
|
if (!port80Listening) {
|
|
operationLog.push('ERROR: Certificate provisioning started before ports ready');
|
|
}
|
|
operationLog.push('Certificate provisioning completed');
|
|
},
|
|
stop: async () => {},
|
|
getAcmeOptions: () => ({ email: 'test@test.local', useProduction: false }),
|
|
getState: () => ({ challengeRouteActive: false })
|
|
};
|
|
certManagerCreated = true;
|
|
(proxy as any).certManager = mockCertManager;
|
|
return mockCertManager;
|
|
};
|
|
|
|
// Start the proxy
|
|
await proxy.start();
|
|
|
|
// Verify the order of operations
|
|
expect(operationLog).toContain('Starting port listeners');
|
|
expect(operationLog).toContain('Port listeners started');
|
|
expect(operationLog).toContain('Starting certificate provisioning');
|
|
|
|
// Ensure port listeners started before certificate provisioning
|
|
const portStartIndex = operationLog.indexOf('Port listeners started');
|
|
const certStartIndex = operationLog.indexOf('Starting certificate provisioning');
|
|
|
|
expect(portStartIndex).toBeLessThan(certStartIndex);
|
|
expect(operationLog).not.toContain('ERROR: Certificate provisioning started before ports ready');
|
|
|
|
await proxy.stop();
|
|
});
|
|
|
|
// Test that ACME challenge route is available when certificate is requested
|
|
tap.test('should have ACME challenge route ready before certificate provisioning', async (tapTest) => {
|
|
let challengeRouteActive = false;
|
|
let certificateProvisioningStarted = false;
|
|
|
|
const proxy = new SmartProxy({
|
|
useHttpProxy: [8080],
|
|
httpProxyPort: 8846, // Use different port to avoid conflicts
|
|
acme: {
|
|
email: 'test@test.local',
|
|
useProduction: false,
|
|
port: 8080
|
|
},
|
|
routes: [{
|
|
name: 'test-route',
|
|
match: {
|
|
ports: 8443,
|
|
domains: ['test.example.com']
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: { host: 'localhost', port: 8181 },
|
|
tls: {
|
|
mode: 'terminate',
|
|
certificate: 'auto'
|
|
}
|
|
}
|
|
}]
|
|
});
|
|
|
|
// Mock the certificate manager to track operations
|
|
const originalInitialize = proxy['certManager'] ?
|
|
proxy['certManager'].initialize : null;
|
|
|
|
if (proxy['certManager']) {
|
|
const certManager = proxy['certManager'];
|
|
|
|
// Track when challenge route is added
|
|
const originalAddChallenge = certManager['addChallengeRoute'];
|
|
certManager['addChallengeRoute'] = async function() {
|
|
await originalAddChallenge.call(this);
|
|
challengeRouteActive = true;
|
|
};
|
|
|
|
// Track when certificate provisioning starts
|
|
const originalProvisionAcme = certManager['provisionAcmeCertificate'];
|
|
certManager['provisionAcmeCertificate'] = async function(...args: any[]) {
|
|
certificateProvisioningStarted = true;
|
|
// Verify challenge route is active
|
|
expect(challengeRouteActive).toEqual(true);
|
|
// Don't actually provision in test
|
|
return;
|
|
};
|
|
}
|
|
|
|
// Mock certificate manager to avoid real ACME initialization
|
|
(proxy as any).createCertificateManager = async function() {
|
|
const mockCertManager = {
|
|
setUpdateRoutesCallback: () => {},
|
|
setHttpProxy: () => {},
|
|
setGlobalAcmeDefaults: () => {},
|
|
setAcmeStateManager: () => {},
|
|
initialize: async () => {
|
|
challengeRouteActive = true;
|
|
},
|
|
provisionAllCertificates: async () => {
|
|
certificateProvisioningStarted = true;
|
|
expect(challengeRouteActive).toEqual(true);
|
|
},
|
|
stop: async () => {},
|
|
getAcmeOptions: () => ({ email: 'test@test.local', useProduction: false }),
|
|
getState: () => ({ challengeRouteActive: false }),
|
|
addChallengeRoute: async () => {
|
|
challengeRouteActive = true;
|
|
},
|
|
provisionAcmeCertificate: async () => {
|
|
certificateProvisioningStarted = true;
|
|
expect(challengeRouteActive).toEqual(true);
|
|
}
|
|
};
|
|
// Call initialize like the real createCertificateManager does
|
|
await mockCertManager.initialize();
|
|
return mockCertManager;
|
|
};
|
|
|
|
await proxy.start();
|
|
|
|
// Give it a moment to complete initialization
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
// Verify challenge route was added before any certificate provisioning
|
|
expect(challengeRouteActive).toEqual(true);
|
|
|
|
await proxy.stop();
|
|
});
|
|
|
|
export default tap.start(); |