257 lines
8.3 KiB
TypeScript
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 { DnsValidator } from '../ts/mail/routing/classes.dns.validator.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();
|