import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as plugins from '../ts/plugins.js'; import * as paths from '../ts/paths.js'; import { DnsManager } from '../ts/mail/routing/classes.dns.manager.js'; import { DomainRegistry } from '../ts/mail/routing/classes.domain.registry.js'; import { StorageManager } from '../ts/storage/classes.storagemanager.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 }; } } // Mock DNS resolver for testing class MockDnsManager extends DnsManager { private mockNsRecords: Map = new Map(); private mockTxtRecords: Map = new Map(); private mockMxRecords: Map = new Map(); setNsRecords(domain: string, records: string[]) { this.mockNsRecords.set(domain, records); } setTxtRecords(domain: string, records: string[][]) { this.mockTxtRecords.set(domain, records); } setMxRecords(domain: string, records: any[]) { this.mockMxRecords.set(domain, records); } protected async resolveNs(domain: string): Promise { return this.mockNsRecords.get(domain) || []; } protected async resolveTxt(domain: string): Promise { return this.mockTxtRecords.get(domain) || []; } protected async resolveMx(domain: string): Promise { return this.mockMxRecords.get(domain) || []; } } tap.test('DNS Validator - Forward Mode', async () => { const testDir = plugins.path.join(paths.dataDir, '.test-dns-forward'); const mockRouter = new MockDcRouter(testDir) as any; const validator = new DnsManager(mockRouter); const config: IEmailDomainConfig = { domain: 'forward.example.com', dnsMode: 'forward', dns: { forward: { skipDnsValidation: true } } }; const result = await validator.validateDomain(config); expect(result.valid).toEqual(true); expect(result.errors.length).toEqual(0); expect(result.warnings.length).toBeGreaterThan(0); // Should have warning about forward mode // Clean up await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {}); }); tap.test('DNS Validator - Internal DNS Mode', async () => { const testDir = plugins.path.join(paths.dataDir, '.test-dns-internal'); const mockRouter = new MockDcRouter(testDir, 'ns.myservice.com') as any; const validator = new MockDnsManager(mockRouter); // Setup NS delegation validator.setNsRecords('mail.example.com', ['ns.myservice.com']); const config: IEmailDomainConfig = { domain: 'mail.example.com', dnsMode: 'internal-dns', dns: { internal: { mxPriority: 10, ttl: 3600 } } }; const result = await validator.validateDomain(config); expect(result.valid).toEqual(true); expect(result.errors.length).toEqual(0); // Test without NS delegation validator.setNsRecords('mail2.example.com', ['other.nameserver.com']); const config2: IEmailDomainConfig = { domain: 'mail2.example.com', dnsMode: 'internal-dns' }; const result2 = await validator.validateDomain(config2); // Should have warnings but still be valid (warnings don't make it invalid) expect(result2.valid).toEqual(true); expect(result2.warnings.length).toBeGreaterThan(0); expect(result2.requiredChanges.length).toBeGreaterThan(0); // Clean up await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {}); }); tap.test('DNS Validator - External DNS Mode', async () => { const testDir = plugins.path.join(paths.dataDir, '.test-dns-external'); const mockRouter = new MockDcRouter(testDir) as any; const validator = new MockDnsManager(mockRouter); // Setup mock DNS records validator.setMxRecords('example.com', [ { priority: 10, exchange: 'mail.example.com' } ]); validator.setTxtRecords('example.com', [ ['v=spf1 mx ~all'] ]); validator.setTxtRecords('default._domainkey.example.com', [ ['v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ...'] ]); validator.setTxtRecords('_dmarc.example.com', [ ['v=DMARC1; p=none; rua=mailto:dmarc@example.com'] ]); const config: IEmailDomainConfig = { domain: 'example.com', dnsMode: 'external-dns', dns: { external: { requiredRecords: ['MX', 'SPF', 'DKIM', 'DMARC'] } } }; const result = await validator.validateDomain(config); // External DNS validation checks if records exist and provides instructions expect(result.valid).toEqual(true); expect(result.errors.length).toEqual(0); // Clean up await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {}); }); tap.test('DKIM Key Generation', async () => { const testDir = plugins.path.join(paths.dataDir, '.test-dkim-generation'); const storage = new StorageManager({ fsPath: testDir }); // Ensure keys directory exists const keysDir = plugins.path.join(testDir, 'keys'); await plugins.fs.promises.mkdir(keysDir, { recursive: true }); const dkimCreator = new DKIMCreator(keysDir, storage); // Generate DKIM keys await dkimCreator.handleDKIMKeysForDomain('test.example.com'); // Verify keys were created const keys = await dkimCreator.readDKIMKeys('test.example.com'); expect(keys.privateKey).toBeTruthy(); expect(keys.publicKey).toBeTruthy(); expect(keys.privateKey).toContain('BEGIN RSA PRIVATE KEY'); expect(keys.publicKey).toContain('BEGIN PUBLIC KEY'); // Get DNS record const dnsRecord = await dkimCreator.getDNSRecordForDomain('test.example.com'); expect(dnsRecord.name).toEqual('mta._domainkey.test.example.com'); expect(dnsRecord.type).toEqual('TXT'); expect(dnsRecord.value).toContain('v=DKIM1'); expect(dnsRecord.value).toContain('k=rsa'); expect(dnsRecord.value).toContain('p='); // Test key rotation const needsRotation = await dkimCreator.needsRotation('test.example.com', 'default', 0); // 0 days = always rotate expect(needsRotation).toEqual(true); // Clean up await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {}); }); tap.test('Domain Registry', async () => { // Test domain configurations const domains: IEmailDomainConfig[] = [ { domain: 'simple.example.com', dnsMode: 'internal-dns' }, { domain: 'configured.example.com', dnsMode: 'external-dns', dkim: { selector: 'custom', keySize: 4096 }, rateLimits: { outbound: { messagesPerMinute: 100 } } } ]; const defaults = { dnsMode: 'internal-dns' as const, dkim: { selector: 'default', keySize: 2048 } }; const registry = new DomainRegistry(domains, defaults); // Test simple domain (uses defaults) const simpleConfig = registry.getDomainConfig('simple.example.com'); expect(simpleConfig).toBeTruthy(); expect(simpleConfig?.dnsMode).toEqual('internal-dns'); expect(simpleConfig?.dkim?.selector).toEqual('default'); expect(simpleConfig?.dkim?.keySize).toEqual(2048); // Test configured domain const configuredConfig = registry.getDomainConfig('configured.example.com'); expect(configuredConfig).toBeTruthy(); expect(configuredConfig?.dnsMode).toEqual('external-dns'); expect(configuredConfig?.dkim?.selector).toEqual('custom'); expect(configuredConfig?.dkim?.keySize).toEqual(4096); expect(configuredConfig?.rateLimits?.outbound?.messagesPerMinute).toEqual(100); // Test non-existent domain const nonExistent = registry.getDomainConfig('nonexistent.example.com'); expect(nonExistent).toEqual(undefined); // Returns undefined, not null // Test getting all domains const allDomains = registry.getAllDomains(); expect(allDomains.length).toEqual(2); expect(allDomains).toContain('simple.example.com'); expect(allDomains).toContain('configured.example.com'); }); tap.test('DNS Record Generation', async () => { const testDir = plugins.path.join(paths.dataDir, '.test-dns-records'); const storage = new StorageManager({ fsPath: testDir }); // Ensure keys directory exists const keysDir = plugins.path.join(testDir, 'keys'); await plugins.fs.promises.mkdir(keysDir, { recursive: true }); const dkimCreator = new DKIMCreator(keysDir, storage); // Generate DKIM keys first await dkimCreator.handleDKIMKeysForDomain('records.example.com'); // Test DNS record for domain const dkimRecord = await dkimCreator.getDNSRecordForDomain('records.example.com'); // Check DKIM record expect(dkimRecord).toBeTruthy(); expect(dkimRecord.name).toContain('_domainkey.records.example.com'); expect(dkimRecord.value).toContain('v=DKIM1'); // Note: The DnsManager doesn't have a generateDnsRecords method exposed // DNS records are handled internally or by the DNS server component // Clean up await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {}); }); export default tap.start();