dcrouter/test/test.dns-mode-switching.ts
Philipp Kunz 37e1ecefd2 refactor(dns): extend DnsValidator to DnsManager with DNS record creation
- Rename DnsValidator to DnsManager to better reflect its expanded responsibilities
- Move DNS record creation logic from UnifiedEmailServer to DnsManager
- Add ensureDnsRecords() method that handles both validation and creation
- Consolidate internal DNS record creation (MX, SPF, DMARC) in one place
- Keep DKIM key generation in UnifiedEmailServer but move DNS registration to DnsManager
- Update all imports and tests to use DnsManager instead of DnsValidator
- Improve code organization and discoverability of DNS functionality
2025-05-30 08:52:07 +00:00

257 lines
8.3 KiB
TypeScript

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<IEmailDomainConfig>('/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<IEmailDomainConfig>('/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();