BREAKING CHANGE(certProvisioner): Refactor: Introduce unified CertProvisioner to centralize certificate provisioning and renewal; remove legacy ACME config from Port80Handler and update SmartProxy to delegate certificate lifecycle management.
This commit is contained in:
		
							
								
								
									
										140
									
								
								test/test.certprovisioner.unit.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								test/test.certprovisioner.unit.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| import { tap, expect } from '@push.rocks/tapbundle'; | ||||
| import * as plugins from '../ts/plugins.js'; | ||||
| import { CertProvisioner } from '../ts/smartproxy/classes.pp.certprovisioner.js'; | ||||
| import type { IDomainConfig, ISmartProxyCertProvisionObject } from '../ts/smartproxy/classes.pp.interfaces.js'; | ||||
| import type { ICertificateData } from '../ts/port80handler/classes.port80handler.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'; | ||||
|   const domainConfigs: IDomainConfig[] = [{ domains: [domain], allowedIPs: [] }]; | ||||
|   const fakePort80 = new FakePort80Handler(); | ||||
|   const fakeBridge = new FakeNetworkProxyBridge(); | ||||
|   // certProvider returns static certificate | ||||
|   const certProvider = async (d: string): Promise<ISmartProxyCertProvisionObject> => { | ||||
|     expect(d).toEqual(domain); | ||||
|     return { | ||||
|       domainName: domain, | ||||
|       publicKey: 'CERT', | ||||
|       privateKey: 'KEY', | ||||
|       validUntil: Date.now() + 3600 * 1000 | ||||
|     }; | ||||
|   }; | ||||
|   const prov = new CertProvisioner( | ||||
|     domainConfigs, | ||||
|     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'); | ||||
| }); | ||||
|  | ||||
| tap.test('CertProvisioner handles http01 provisioning', async () => { | ||||
|   const domain = 'http01.com'; | ||||
|   const domainConfigs: IDomainConfig[] = [{ domains: [domain], allowedIPs: [] }]; | ||||
|   const fakePort80 = new FakePort80Handler(); | ||||
|   const fakeBridge = new FakeNetworkProxyBridge(); | ||||
|   // certProvider returns http01 directive | ||||
|   const certProvider = async (): Promise<ISmartProxyCertProvisionObject> => 'http01'; | ||||
|   const prov = new CertProvisioner( | ||||
|     domainConfigs, | ||||
|     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'; | ||||
|   const domainConfigs: IDomainConfig[] = [{ domains: [domain], allowedIPs: [] }]; | ||||
|   const fakePort80 = new FakePort80Handler(); | ||||
|   const fakeBridge = new FakeNetworkProxyBridge(); | ||||
|   const certProvider = async (): Promise<ISmartProxyCertProvisionObject> => 'http01'; | ||||
|   const prov = new CertProvisioner( | ||||
|     domainConfigs, | ||||
|     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'; | ||||
|   const domainConfigs: IDomainConfig[] = [{ domains: [domain], allowedIPs: [] }]; | ||||
|   const fakePort80 = new FakePort80Handler(); | ||||
|   const fakeBridge = new FakeNetworkProxyBridge(); | ||||
|   const certProvider = async (): Promise<ISmartProxyCertProvisionObject> => ({ | ||||
|     domainName: domain, | ||||
|     publicKey: 'PKEY', | ||||
|     privateKey: 'PRIV', | ||||
|     validUntil: Date.now() + 1000 | ||||
|   }); | ||||
|   const prov = new CertProvisioner( | ||||
|     domainConfigs, | ||||
|     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'); | ||||
| }); | ||||
|  | ||||
| export default tap.start(); | ||||
		Reference in New Issue
	
	Block a user