test(dns): add comprehensive tests for DNS record creation

- Add test.dns-manager-creation.ts to verify DNS record creation
- Test MX, SPF, DMARC, and DKIM record registration
- Verify records are stored in StorageManager
- Update readme.hints.md with DNS architecture refactoring notes
This commit is contained in:
Philipp Kunz 2025-05-30 09:29:03 +00:00
parent 37e1ecefd2
commit ad0ab6c103
2 changed files with 186 additions and 1 deletions

View File

@ -835,4 +835,48 @@ Rate limiting is now fully integrated into SMTP server handlers:
The only remaining item is implementing hour/day rate limits in the UnifiedRateLimiter, which would require:
1. Additional counters for hourly and daily windows
2. Separate tracking for these longer time periods
3. Cleanup logic for expired hourly/daily counters
3. Cleanup logic for expired hourly/daily counters
## DNS Architecture Refactoring (2025-05-30) - COMPLETED
### Overview
The DNS functionality has been refactored from UnifiedEmailServer to a dedicated DnsManager class for better discoverability and separation of concerns.
### Key Changes
1. **Renamed DnsValidator to DnsManager:**
- Extended functionality to handle both validation and creation of DNS records
- Added `ensureDnsRecords()` as the main entry point
- Moved DNS record creation logic from UnifiedEmailServer
2. **DnsManager Responsibilities:**
- Validate DNS configuration for all modes (forward, internal-dns, external-dns)
- Create DNS records for internal-dns domains
- Create DKIM records for all domains (when DKIMCreator is provided)
- Store DNS records in StorageManager for persistence
3. **DNS Record Creation Flow:**
```typescript
// In UnifiedEmailServer
const dnsManager = new DnsManager(this.dcRouter);
await dnsManager.ensureDnsRecords(domainConfigs, this.dkimCreator);
```
4. **Testing Pattern for DNS:**
- Mock the DNS server in tests by providing a mock `registerHandler` function
- Store handlers in a Map with key format: `${domain}:${types.join(',')}`
- Retrieve handlers with key format: `${domain}:${type}`
- Example mock implementation:
```typescript
this.dnsServer = {
registerHandler: (name: string, types: string[], handler: () => any) => {
const key = `${name}:${types.join(',')}`;
this.dnsHandlers.set(key, handler);
}
};
```
### Benefits
- DNS functionality is now easily discoverable in DnsManager
- Clear separation between DNS management and email server logic
- UnifiedEmailServer is simpler and more focused
- All DNS-related tests pass successfully

View File

@ -0,0 +1,141 @@
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 { DKIMCreator } from '../ts/mail/security/classes.dkimcreator.js';
import { StorageManager } from '../ts/storage/classes.storagemanager.js';
import type { IEmailDomainConfig } from '../ts/mail/routing/interfaces.js';
// Mock DcRouter with DNS server
class MockDcRouter {
public storageManager: StorageManager;
public dnsServer: any;
public options: any;
private dnsHandlers: Map<string, any> = new Map();
constructor(testDir: string, dnsDomain?: string) {
this.storageManager = new StorageManager({ fsPath: testDir });
this.options = { dnsDomain };
// Mock DNS server
this.dnsServer = {
registerHandler: (name: string, types: string[], handler: () => any) => {
const key = `${name}:${types.join(',')}`;
this.dnsHandlers.set(key, handler);
}
};
}
getDnsHandler(name: string, type: string): any {
const key = `${name}:${type}`;
return this.dnsHandlers.get(key);
}
}
tap.test('DnsManager - Create Internal DNS Records', async () => {
const testDir = plugins.path.join(paths.dataDir, '.test-dns-manager-creation');
const mockRouter = new MockDcRouter(testDir, 'ns.test.com') as any;
const dnsManager = new DnsManager(mockRouter);
const domainConfigs: IEmailDomainConfig[] = [
{
domain: 'test.example.com',
dnsMode: 'internal-dns',
dns: {
internal: {
mxPriority: 15,
ttl: 7200
}
},
dkim: {
selector: 'test2024',
keySize: 2048
}
}
];
// Create DNS records
await dnsManager.ensureDnsRecords(domainConfigs);
// Verify MX record was registered
const mxHandler = mockRouter.getDnsHandler('test.example.com', 'MX');
expect(mxHandler).toBeTruthy();
const mxRecord = mxHandler();
expect(mxRecord.type).toEqual('MX');
expect(mxRecord.data.priority).toEqual(15);
expect(mxRecord.data.exchange).toEqual('test.example.com');
expect(mxRecord.ttl).toEqual(7200);
// Verify SPF record was registered
const txtHandler = mockRouter.getDnsHandler('test.example.com', 'TXT');
expect(txtHandler).toBeTruthy();
const spfRecord = txtHandler();
expect(spfRecord.type).toEqual('TXT');
expect(spfRecord.data).toEqual('v=spf1 a mx ~all');
// Verify DMARC record was registered
const dmarcHandler = mockRouter.getDnsHandler('_dmarc.test.example.com', 'TXT');
expect(dmarcHandler).toBeTruthy();
const dmarcRecord = dmarcHandler();
expect(dmarcRecord.type).toEqual('TXT');
expect(dmarcRecord.data).toContain('v=DMARC1');
expect(dmarcRecord.data).toContain('p=none');
// Verify records were stored in StorageManager
const mxStored = await mockRouter.storageManager.getJSON('/email/dns/test.example.com/mx');
expect(mxStored).toBeTruthy();
expect(mxStored.priority).toEqual(15);
const spfStored = await mockRouter.storageManager.getJSON('/email/dns/test.example.com/spf');
expect(spfStored).toBeTruthy();
expect(spfStored.data).toEqual('v=spf1 a mx ~all');
// Clean up
await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {});
});
tap.test('DnsManager - Create DKIM Records', async () => {
const testDir = plugins.path.join(paths.dataDir, '.test-dns-manager-dkim');
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 dnsManager = new DnsManager(mockRouter);
const dkimCreator = new DKIMCreator(keysDir, mockRouter.storageManager);
const domainConfigs: IEmailDomainConfig[] = [
{
domain: 'dkim.example.com',
dnsMode: 'internal-dns',
dkim: {
selector: 'mail2024',
keySize: 2048
}
}
];
// Generate DKIM keys first
await dkimCreator.handleDKIMKeysForDomain('dkim.example.com');
// Create DNS records including DKIM
await dnsManager.ensureDnsRecords(domainConfigs, dkimCreator);
// Verify DKIM record was registered
const dkimHandler = mockRouter.getDnsHandler('mail2024._domainkey.dkim.example.com', 'TXT');
expect(dkimHandler).toBeTruthy();
const dkimRecord = dkimHandler();
expect(dkimRecord.type).toEqual('TXT');
expect(dkimRecord.data).toContain('v=DKIM1');
expect(dkimRecord.data).toContain('k=rsa');
expect(dkimRecord.data).toContain('p=');
// Verify DKIM record was stored
const dkimStored = await mockRouter.storageManager.getJSON('/email/dns/dkim.example.com/dkim');
expect(dkimStored).toBeTruthy();
expect(dkimStored.name).toEqual('mail2024._domainkey.dkim.example.com');
// Clean up
await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {});
});
export default tap.start();