import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as plugins from '../ts/plugins.js'; import * as paths from '../ts/paths.js'; import { StorageManager } from '../ts/storage/classes.storagemanager.js'; import { DnsManager } from '../ts/mail/routing/classes.dns.manager.js'; import { DomainRegistry } from '../ts/mail/routing/classes.domain.registry.js'; import { DKIMCreator } from '../ts/mail/security/classes.dkimcreator.js'; import type { IEmailDomainConfig } from '../ts/mail/routing/interfaces.js'; // Mock DcRouter for testing class MockDcRouter { public storageManager: StorageManager; public options: any; constructor(testDir: string, dnsDomain?: string) { this.storageManager = new StorageManager({ fsPath: testDir }); this.options = { dnsDomain }; } } tap.test('DNS Mode Switching - Forward to Internal', async () => { const testDir = plugins.path.join(paths.dataDir, '.test-dns-mode-switch-1'); const keysDir = plugins.path.join(testDir, 'keys'); await plugins.fs.promises.mkdir(keysDir, { recursive: true }); const mockRouter = new MockDcRouter(testDir, 'ns.test.com') as any; const dkimCreator = new DKIMCreator(keysDir, mockRouter.storageManager); // Phase 1: Start with forward mode let config: IEmailDomainConfig = { domain: 'switchtest1.com', dnsMode: 'forward', dns: { forward: { skipDnsValidation: true } } }; let registry = new DomainRegistry([config]); let domainConfig = registry.getDomainConfig('switchtest1.com'); expect(domainConfig?.dnsMode).toEqual('forward'); // DKIM keys should still be generated for consistency await dkimCreator.handleDKIMKeysForDomain('switchtest1.com'); const keys = await dkimCreator.readDKIMKeys('switchtest1.com'); expect(keys.privateKey).toBeTruthy(); // Phase 2: Switch to internal-dns mode config = { domain: 'switchtest1.com', dnsMode: 'internal-dns', dns: { internal: { mxPriority: 20, ttl: 7200 } } }; registry = new DomainRegistry([config]); domainConfig = registry.getDomainConfig('switchtest1.com'); expect(domainConfig?.dnsMode).toEqual('internal-dns'); expect(domainConfig?.dns?.internal?.mxPriority).toEqual(20); // DKIM keys should persist across mode switches const keysAfterSwitch = await dkimCreator.readDKIMKeys('switchtest1.com'); expect(keysAfterSwitch.privateKey).toEqual(keys.privateKey); // Clean up await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {}); }); tap.test('DNS Mode Switching - External to Forward', async () => { const testDir = plugins.path.join(paths.dataDir, '.test-dns-mode-switch-2'); const keysDir = plugins.path.join(testDir, 'keys'); await plugins.fs.promises.mkdir(keysDir, { recursive: true }); const mockRouter = new MockDcRouter(testDir) as any; const dkimCreator = new DKIMCreator(keysDir, mockRouter.storageManager); // Phase 1: Start with external-dns mode let config: IEmailDomainConfig = { domain: 'switchtest2.com', dnsMode: 'external-dns', dns: { external: { requiredRecords: ['MX', 'SPF', 'DKIM'] } }, dkim: { selector: 'custom2024', keySize: 4096 } }; let registry = new DomainRegistry([config]); let domainConfig = registry.getDomainConfig('switchtest2.com'); expect(domainConfig?.dnsMode).toEqual('external-dns'); expect(domainConfig?.dkim?.selector).toEqual('custom2024'); expect(domainConfig?.dkim?.keySize).toEqual(4096); // Generate DKIM keys (always uses default selector initially) await dkimCreator.handleDKIMKeysForDomain('switchtest2.com'); // For custom selector, we would need to implement key rotation const dnsRecord = await dkimCreator.getDNSRecordForDomain('switchtest2.com'); expect(dnsRecord.name).toContain('mta._domainkey'); // Phase 2: Switch to forward mode config = { domain: 'switchtest2.com', dnsMode: 'forward', dns: { forward: { targetDomain: 'mail.forward.com' } } }; registry = new DomainRegistry([config]); domainConfig = registry.getDomainConfig('switchtest2.com'); expect(domainConfig?.dnsMode).toEqual('forward'); expect(domainConfig?.dns?.forward?.targetDomain).toEqual('mail.forward.com'); // DKIM configuration should revert to defaults expect(domainConfig?.dkim?.selector).toEqual('default'); expect(domainConfig?.dkim?.keySize).toEqual(2048); // Clean up await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {}); }); tap.test('DNS Mode Switching - Multiple Domains Different Modes', async () => { const testDir = plugins.path.join(paths.dataDir, '.test-dns-mode-switch-3'); const mockRouter = new MockDcRouter(testDir, 'ns.multi.com') as any; // Configure multiple domains with different modes const domains: IEmailDomainConfig[] = [ { domain: 'forward.multi.com', dnsMode: 'forward' }, { domain: 'internal.multi.com', dnsMode: 'internal-dns', dns: { internal: { mxPriority: 5 } } }, { domain: 'external.multi.com', dnsMode: 'external-dns', rateLimits: { inbound: { messagesPerMinute: 50 } } } ]; const registry = new DomainRegistry(domains); // Verify each domain has correct mode expect(registry.getDomainConfig('forward.multi.com')?.dnsMode).toEqual('forward'); expect(registry.getDomainConfig('internal.multi.com')?.dnsMode).toEqual('internal-dns'); expect(registry.getDomainConfig('external.multi.com')?.dnsMode).toEqual('external-dns'); // Verify mode-specific configurations expect(registry.getDomainConfig('internal.multi.com')?.dns?.internal?.mxPriority).toEqual(5); expect(registry.getDomainConfig('external.multi.com')?.rateLimits?.inbound?.messagesPerMinute).toEqual(50); // Get domains by mode const forwardDomains = registry.getDomainsByMode('forward'); const internalDomains = registry.getDomainsByMode('internal-dns'); const externalDomains = registry.getDomainsByMode('external-dns'); expect(forwardDomains.length).toEqual(1); expect(forwardDomains[0].domain).toEqual('forward.multi.com'); expect(internalDomains.length).toEqual(1); expect(internalDomains[0].domain).toEqual('internal.multi.com'); expect(externalDomains.length).toEqual(1); expect(externalDomains[0].domain).toEqual('external.multi.com'); // Clean up await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {}); }); tap.test('DNS Mode Switching - Configuration Persistence', async () => { const testDir = plugins.path.join(paths.dataDir, '.test-dns-mode-switch-4'); const storage = new StorageManager({ fsPath: testDir }); // Save domain configuration const config: IEmailDomainConfig = { domain: 'persist.test.com', dnsMode: 'internal-dns', dns: { internal: { mxPriority: 15, ttl: 1800 } }, dkim: { selector: 'persist2024', rotateKeys: true, rotationInterval: 30 }, rateLimits: { outbound: { messagesPerHour: 1000 } } }; // Save to storage await storage.setJSON('/email/domains/persist.test.com', config); // Simulate restart - load from storage const loadedConfig = await storage.getJSON('/email/domains/persist.test.com'); expect(loadedConfig).toBeTruthy(); expect(loadedConfig?.dnsMode).toEqual('internal-dns'); expect(loadedConfig?.dns?.internal?.mxPriority).toEqual(15); expect(loadedConfig?.dkim?.selector).toEqual('persist2024'); expect(loadedConfig?.dkim?.rotateKeys).toEqual(true); expect(loadedConfig?.rateLimits?.outbound?.messagesPerHour).toEqual(1000); // Update DNS mode if (loadedConfig) { loadedConfig.dnsMode = 'forward'; loadedConfig.dns = { forward: { skipDnsValidation: false } }; await storage.setJSON('/email/domains/persist.test.com', loadedConfig); } // Load updated config const updatedConfig = await storage.getJSON('/email/domains/persist.test.com'); expect(updatedConfig?.dnsMode).toEqual('forward'); expect(updatedConfig?.dns?.forward?.skipDnsValidation).toEqual(false); // Clean up await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {}); }); export default tap.start();