feat(RouteManager): Add getAllRoutes API to RouteManager and update test environment to improve timeouts, logging, and cleanup; remove deprecated test files and adjust devDependencies accordingly
This commit is contained in:
parent
61ab1482e3
commit
41f7d09c52
@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-05-18 - 19.1.0 - feat(RouteManager)
|
||||||
|
Add getAllRoutes API to RouteManager and update test environment to improve timeouts, logging, and cleanup; remove deprecated test files and adjust devDependencies accordingly
|
||||||
|
|
||||||
|
- Removed @push.rocks/tapbundle from devDependencies in package.json
|
||||||
|
- Deleted deprecated test.certprovisioner.unit.ts file
|
||||||
|
- Improved timeout handling and cleanup logic in test.networkproxy.function-targets.ts
|
||||||
|
- Added getAllRoutes public method to RouteManager to retrieve all routes
|
||||||
|
- Minor adjustments in SmartAcme integration tests with updated certificate fixture format
|
||||||
|
|
||||||
## 2025-05-18 - 19.0.0 - BREAKING CHANGE(certificates)
|
## 2025-05-18 - 19.0.0 - BREAKING CHANGE(certificates)
|
||||||
Remove legacy certificate modules and Port80Handler; update documentation and route configurations to use SmartCertManager for certificate management.
|
Remove legacy certificate modules and Port80Handler; update documentation and route configurations to use SmartCertManager for certificate management.
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
"@git.zone/tsbuild": "^2.5.1",
|
"@git.zone/tsbuild": "^2.5.1",
|
||||||
"@git.zone/tsrun": "^1.2.44",
|
"@git.zone/tsrun": "^1.2.44",
|
||||||
"@git.zone/tstest": "^1.9.0",
|
"@git.zone/tstest": "^1.9.0",
|
||||||
"@push.rocks/tapbundle": "^6.0.3",
|
|
||||||
"@types/node": "^22.15.18",
|
"@types/node": "^22.15.18",
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
|
@ -45,8 +45,8 @@ tap.test('should handle static certificates', async () => {
|
|||||||
tls: {
|
tls: {
|
||||||
mode: 'terminate',
|
mode: 'terminate',
|
||||||
certificate: {
|
certificate: {
|
||||||
certFile: './test/fixtures/cert.pem',
|
cert: '-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----',
|
||||||
keyFile: './test/fixtures/key.pem'
|
key: '-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,211 +0,0 @@
|
|||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
|
||||||
import * as plugins from '../ts/plugins.js';
|
|
||||||
import { CertProvisioner } from '../ts/certificate/providers/cert-provisioner.js';
|
|
||||||
import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js';
|
|
||||||
import type { ICertificateData } from '../ts/certificate/models/certificate-types.js';
|
|
||||||
import type { TCertProvisionObject } from '../ts/certificate/providers/cert-provisioner.js';
|
|
||||||
|
|
||||||
// Fake Port80Handler stub
|
|
||||||
class FakePort80Handler extends plugins.EventEmitter {
|
|
||||||
public domainsAdded: string[] = [];
|
|
||||||
public renewCalled: string[] = [];
|
|
||||||
addDomain(opts: { domainName: string; sslRedirect: boolean; acmeMaintenance: boolean }) {
|
|
||||||
this.domainsAdded.push(opts.domainName);
|
|
||||||
}
|
|
||||||
async renewCertificate(domain: string): Promise<void> {
|
|
||||||
this.renewCalled.push(domain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fake NetworkProxyBridge stub
|
|
||||||
class FakeNetworkProxyBridge {
|
|
||||||
public appliedCerts: ICertificateData[] = [];
|
|
||||||
applyExternalCertificate(cert: ICertificateData) {
|
|
||||||
this.appliedCerts.push(cert);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tap.test('CertProvisioner handles static provisioning', async () => {
|
|
||||||
const domain = 'static.com';
|
|
||||||
// Create route-based configuration for testing
|
|
||||||
const routeConfigs: IRouteConfig[] = [{
|
|
||||||
name: 'Static Route',
|
|
||||||
match: {
|
|
||||||
ports: 443,
|
|
||||||
domains: [domain]
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: 'forward',
|
|
||||||
target: { host: 'localhost', port: 443 },
|
|
||||||
tls: {
|
|
||||||
mode: 'terminate-and-reencrypt',
|
|
||||||
certificate: 'auto'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
const fakePort80 = new FakePort80Handler();
|
|
||||||
const fakeBridge = new FakeNetworkProxyBridge();
|
|
||||||
// certProvider returns static certificate
|
|
||||||
const certProvider = async (d: string): Promise<TCertProvisionObject> => {
|
|
||||||
expect(d).toEqual(domain);
|
|
||||||
return {
|
|
||||||
domainName: domain,
|
|
||||||
publicKey: 'CERT',
|
|
||||||
privateKey: 'KEY',
|
|
||||||
validUntil: Date.now() + 3600 * 1000,
|
|
||||||
created: Date.now(),
|
|
||||||
csr: 'CSR',
|
|
||||||
id: 'ID',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const prov = new CertProvisioner(
|
|
||||||
routeConfigs,
|
|
||||||
fakePort80 as any,
|
|
||||||
fakeBridge as any,
|
|
||||||
certProvider,
|
|
||||||
1, // low renew threshold
|
|
||||||
1, // short interval
|
|
||||||
false // disable auto renew for unit test
|
|
||||||
);
|
|
||||||
const events: any[] = [];
|
|
||||||
prov.on('certificate', (data) => events.push(data));
|
|
||||||
await prov.start();
|
|
||||||
// Static flow: no addDomain, certificate applied via bridge
|
|
||||||
expect(fakePort80.domainsAdded.length).toEqual(0);
|
|
||||||
expect(fakeBridge.appliedCerts.length).toEqual(1);
|
|
||||||
expect(events.length).toEqual(1);
|
|
||||||
const evt = events[0];
|
|
||||||
expect(evt.domain).toEqual(domain);
|
|
||||||
expect(evt.certificate).toEqual('CERT');
|
|
||||||
expect(evt.privateKey).toEqual('KEY');
|
|
||||||
expect(evt.isRenewal).toEqual(false);
|
|
||||||
expect(evt.source).toEqual('static');
|
|
||||||
expect(evt.routeReference).toBeTruthy();
|
|
||||||
expect(evt.routeReference.routeName).toEqual('Static Route');
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('CertProvisioner handles http01 provisioning', async () => {
|
|
||||||
const domain = 'http01.com';
|
|
||||||
// Create route-based configuration for testing
|
|
||||||
const routeConfigs: IRouteConfig[] = [{
|
|
||||||
name: 'HTTP01 Route',
|
|
||||||
match: {
|
|
||||||
ports: 443,
|
|
||||||
domains: [domain]
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: 'forward',
|
|
||||||
target: { host: 'localhost', port: 80 },
|
|
||||||
tls: {
|
|
||||||
mode: 'terminate',
|
|
||||||
certificate: 'auto'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
const fakePort80 = new FakePort80Handler();
|
|
||||||
const fakeBridge = new FakeNetworkProxyBridge();
|
|
||||||
// certProvider returns http01 directive
|
|
||||||
const certProvider = async (): Promise<TCertProvisionObject> => 'http01';
|
|
||||||
const prov = new CertProvisioner(
|
|
||||||
routeConfigs,
|
|
||||||
fakePort80 as any,
|
|
||||||
fakeBridge as any,
|
|
||||||
certProvider,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
const events: any[] = [];
|
|
||||||
prov.on('certificate', (data) => events.push(data));
|
|
||||||
await prov.start();
|
|
||||||
// HTTP-01 flow: addDomain called, no static cert applied
|
|
||||||
expect(fakePort80.domainsAdded).toEqual([domain]);
|
|
||||||
expect(fakeBridge.appliedCerts.length).toEqual(0);
|
|
||||||
expect(events.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('CertProvisioner on-demand http01 renewal', async () => {
|
|
||||||
const domain = 'renew.com';
|
|
||||||
// Create route-based configuration for testing
|
|
||||||
const routeConfigs: IRouteConfig[] = [{
|
|
||||||
name: 'Renewal Route',
|
|
||||||
match: {
|
|
||||||
ports: 443,
|
|
||||||
domains: [domain]
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: 'forward',
|
|
||||||
target: { host: 'localhost', port: 80 },
|
|
||||||
tls: {
|
|
||||||
mode: 'terminate',
|
|
||||||
certificate: 'auto'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
const fakePort80 = new FakePort80Handler();
|
|
||||||
const fakeBridge = new FakeNetworkProxyBridge();
|
|
||||||
const certProvider = async (): Promise<TCertProvisionObject> => 'http01';
|
|
||||||
const prov = new CertProvisioner(
|
|
||||||
routeConfigs,
|
|
||||||
fakePort80 as any,
|
|
||||||
fakeBridge as any,
|
|
||||||
certProvider,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
// requestCertificate should call renewCertificate
|
|
||||||
await prov.requestCertificate(domain);
|
|
||||||
expect(fakePort80.renewCalled).toEqual([domain]);
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('CertProvisioner on-demand static provisioning', async () => {
|
|
||||||
const domain = 'ondemand.com';
|
|
||||||
// Create route-based configuration for testing
|
|
||||||
const routeConfigs: IRouteConfig[] = [{
|
|
||||||
name: 'On-Demand Route',
|
|
||||||
match: {
|
|
||||||
ports: 443,
|
|
||||||
domains: [domain]
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: 'forward',
|
|
||||||
target: { host: 'localhost', port: 443 },
|
|
||||||
tls: {
|
|
||||||
mode: 'terminate-and-reencrypt',
|
|
||||||
certificate: 'auto'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
const fakePort80 = new FakePort80Handler();
|
|
||||||
const fakeBridge = new FakeNetworkProxyBridge();
|
|
||||||
const certProvider = async (): Promise<TCertProvisionObject> => ({
|
|
||||||
domainName: domain,
|
|
||||||
publicKey: 'PKEY',
|
|
||||||
privateKey: 'PRIV',
|
|
||||||
validUntil: Date.now() + 1000,
|
|
||||||
created: Date.now(),
|
|
||||||
csr: 'CSR',
|
|
||||||
id: 'ID',
|
|
||||||
});
|
|
||||||
const prov = new CertProvisioner(
|
|
||||||
routeConfigs,
|
|
||||||
fakePort80 as any,
|
|
||||||
fakeBridge as any,
|
|
||||||
certProvider,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
const events: any[] = [];
|
|
||||||
prov.on('certificate', (data) => events.push(data));
|
|
||||||
await prov.requestCertificate(domain);
|
|
||||||
expect(fakeBridge.appliedCerts.length).toEqual(1);
|
|
||||||
expect(events.length).toEqual(1);
|
|
||||||
expect(events[0].domain).toEqual(domain);
|
|
||||||
expect(events[0].source).toEqual('static');
|
|
||||||
expect(events[0].routeReference).toBeTruthy();
|
|
||||||
expect(events[0].routeReference.routeName).toEqual('On-Demand Route');
|
|
||||||
});
|
|
||||||
|
|
||||||
export default tap.start();
|
|
@ -4,8 +4,6 @@ import { NetworkProxy } from '../ts/proxies/network-proxy/index.js';
|
|||||||
import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js';
|
import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js';
|
||||||
import type { IRouteContext } from '../ts/core/models/route-context.js';
|
import type { IRouteContext } from '../ts/core/models/route-context.js';
|
||||||
|
|
||||||
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
|
|
||||||
// Declare variables for tests
|
// Declare variables for tests
|
||||||
let networkProxy: NetworkProxy;
|
let networkProxy: NetworkProxy;
|
||||||
let testServer: plugins.http.Server;
|
let testServer: plugins.http.Server;
|
||||||
@ -14,7 +12,9 @@ let serverPort: number;
|
|||||||
let serverPortHttp2: number;
|
let serverPortHttp2: number;
|
||||||
|
|
||||||
// Setup test environment
|
// Setup test environment
|
||||||
tap.test('setup NetworkProxy function-based targets test environment', async () => {
|
tap.test('setup NetworkProxy function-based targets test environment', async (tools) => {
|
||||||
|
// Set a reasonable timeout for the test
|
||||||
|
tools.timeout = 30000; // 30 seconds
|
||||||
// Create simple HTTP server to respond to requests
|
// Create simple HTTP server to respond to requests
|
||||||
testServer = plugins.http.createServer((req, res) => {
|
testServer = plugins.http.createServer((req, res) => {
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
@ -41,6 +41,11 @@ tap.test('setup NetworkProxy function-based targets test environment', async ()
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle HTTP/2 errors
|
||||||
|
testServerHttp2.on('error', (err) => {
|
||||||
|
console.error('HTTP/2 server error:', err);
|
||||||
|
});
|
||||||
|
|
||||||
// Start the servers
|
// Start the servers
|
||||||
await new Promise<void>(resolve => {
|
await new Promise<void>(resolve => {
|
||||||
testServer.listen(0, () => {
|
testServer.listen(0, () => {
|
||||||
@ -318,21 +323,57 @@ tap.test('should support context-based routing with path', async () => {
|
|||||||
|
|
||||||
// Cleanup test environment
|
// Cleanup test environment
|
||||||
tap.test('cleanup NetworkProxy function-based targets test environment', async () => {
|
tap.test('cleanup NetworkProxy function-based targets test environment', async () => {
|
||||||
if (networkProxy) {
|
// Skip cleanup if setup failed
|
||||||
await networkProxy.stop();
|
if (!networkProxy && !testServer && !testServerHttp2) {
|
||||||
|
console.log('Skipping cleanup - setup failed');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop test servers first
|
||||||
if (testServer) {
|
if (testServer) {
|
||||||
await new Promise<void>(resolve => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
testServer.close(() => resolve());
|
testServer.close((err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error closing test server:', err);
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
console.log('Test server closed successfully');
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (testServerHttp2) {
|
if (testServerHttp2) {
|
||||||
await new Promise<void>(resolve => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
testServerHttp2.close(() => resolve());
|
testServerHttp2.close((err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error closing HTTP/2 test server:', err);
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
console.log('HTTP/2 test server closed successfully');
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop NetworkProxy last
|
||||||
|
if (networkProxy) {
|
||||||
|
console.log('Stopping NetworkProxy...');
|
||||||
|
await networkProxy.stop();
|
||||||
|
console.log('NetworkProxy stopped successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force exit after a short delay to ensure cleanup
|
||||||
|
const cleanupTimeout = setTimeout(() => {
|
||||||
|
console.log('Cleanup completed, exiting');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// Don't keep the process alive just for this timeout
|
||||||
|
if (cleanupTimeout.unref) {
|
||||||
|
cleanupTimeout.unref();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper function to make HTTPS requests with self-signed certificate support
|
// Helper function to make HTTPS requests with self-signed certificate support
|
||||||
@ -365,5 +406,8 @@ async function makeRequest(options: plugins.http.RequestOptions): Promise<{ stat
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export the test runner to start tests
|
// Start the tests
|
||||||
export default tap.start();
|
tap.start().then(() => {
|
||||||
|
// Ensure process exits after tests complete
|
||||||
|
process.exit(0);
|
||||||
|
});
|
@ -1,5 +1,5 @@
|
|||||||
import * as plugins from '../ts/plugins.js';
|
import * as plugins from '../ts/plugins.js';
|
||||||
import { tap } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
import { SmartCertManager } from '../ts/proxies/smart-proxy/certificate-manager.js';
|
import { SmartCertManager } from '../ts/proxies/smart-proxy/certificate-manager.js';
|
||||||
import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js';
|
import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js';
|
||||||
|
|
||||||
@ -10,17 +10,21 @@ tap.test('should create a SmartCertManager instance', async () => {
|
|||||||
{
|
{
|
||||||
name: 'test-acme-route',
|
name: 'test-acme-route',
|
||||||
match: {
|
match: {
|
||||||
domains: ['test.example.com']
|
domains: ['test.example.com'],
|
||||||
|
ports: []
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
type: 'proxy',
|
type: 'forward',
|
||||||
target: 'http://localhost:3000',
|
target: {
|
||||||
|
host: 'localhost',
|
||||||
|
port: 3000
|
||||||
|
},
|
||||||
tls: {
|
tls: {
|
||||||
mode: 'terminate',
|
mode: 'terminate',
|
||||||
certificate: 'auto'
|
certificate: 'auto',
|
||||||
},
|
acme: {
|
||||||
acme: {
|
email: 'test@example.com'
|
||||||
email: 'test@example.com'
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
name: '@push.rocks/smartproxy',
|
||||||
version: '19.0.0',
|
version: '19.1.0',
|
||||||
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
|
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
|
||||||
}
|
}
|
||||||
|
@ -173,6 +173,13 @@ export class RouteManager extends plugins.EventEmitter {
|
|||||||
return this.portMap.get(port) || [];
|
return this.portMap.get(port) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all routes
|
||||||
|
*/
|
||||||
|
public getAllRoutes(): IRouteConfig[] {
|
||||||
|
return [...this.routes];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if a pattern matches a domain using glob matching
|
* Test if a pattern matches a domain using glob matching
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user