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:
2025-05-30 09:29:03 +00:00
parent 37e1ecefd2
commit ad0ab6c103
2 changed files with 186 additions and 1 deletions

View File

@@ -836,3 +836,47 @@ The only remaining item is implementing hour/day rate limits in the UnifiedRateL
1. Additional counters for hourly and daily windows 1. Additional counters for hourly and daily windows
2. Separate tracking for these longer time periods 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();