Remove obsolete test files for SenderReputationMonitor, DcRouter, and StorageManager

- Deleted tests for SenderReputationMonitor, IPWarmupManager, and related functionality.
- Removed tests for DcRouter including health status, server statistics, and configuration requests.
- Eliminated tests for protected endpoints and admin login functionality.
- Cleared out tests for socket handler integration and unit tests.
- Removed tests for StorageManager covering memory, filesystem, and custom function backends.
This commit is contained in:
2025-10-28 22:19:41 +00:00
parent 0ac598818f
commit f262f602a0
20 changed files with 0 additions and 3901 deletions

View File

@@ -1,65 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../ts/plugins.ts';
import * as paths from '../ts/paths.ts';
import { SenderReputationMonitor } from '../ts/deliverability/classes.senderreputationmonitor.ts';
import { IPWarmupManager } from '../ts/deliverability/classes.ipwarmupmanager.ts';
/**
* Basic test to check if our integrated classes work correctly
*/
tap.test('verify that SenderReputationMonitor and IPWarmupManager are functioning', async () => {
// Create instances of both classes
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: true,
domains: ['example.com']
});
const ipWarmupManager = IPWarmupManager.getInstance({
enabled: true,
ipAddresses: ['192.168.1.1', '192.168.1.2'],
targetDomains: ['example.com']
});
// Test SenderReputationMonitor
reputationMonitor.recordSendEvent('example.com', { type: 'sent', count: 100 });
reputationMonitor.recordSendEvent('example.com', { type: 'delivered', count: 95 });
const reputationData = reputationMonitor.getReputationData('example.com');
expect(reputationData).toBeTruthy();
const summary = reputationMonitor.getReputationSummary();
expect(summary.length).toBeGreaterThan(0);
// Add and remove domains
reputationMonitor.addDomain('test.com');
reputationMonitor.removeDomain('test.com');
// Test IPWarmupManager
ipWarmupManager.setActiveAllocationPolicy('balanced');
const bestIP = ipWarmupManager.getBestIPForSending({
from: 'test@example.com',
to: ['recipient@test.com'],
domain: 'example.com'
});
if (bestIP) {
ipWarmupManager.recordSend(bestIP);
const canSendMore = ipWarmupManager.canSendMoreToday(bestIP);
expect(typeof canSendMore).toEqual('boolean');
}
const stageCount = ipWarmupManager.getStageCount();
expect(stageCount).toBeGreaterThan(0);
});
// Final clean-up test
tap.test('clean up after tests', async () => {
// No-op - just to make sure everything is cleaned up properly
});
tap.test('stop', async () => {
await tap.stopForcefully();
});
export default tap.start();

View File

@@ -1,201 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../ts/plugins.ts';
import * as path from 'path';
import * as fs from 'fs';
import {
DcRouter,
type IDcRouterOptions,
type IEmailConfig,
type EmailProcessingMode,
type IDomainRule
} from '../ts/classes.dcrouter.ts';
tap.test('DcRouter class - Custom email port configuration', async () => {
// Define custom port mapping
const customPortMapping = {
25: 11025, // Custom SMTP port mapping
587: 11587, // Custom submission port mapping
465: 11465, // Custom SMTPS port mapping
2525: 12525 // Additional custom port
};
// Create a custom email configuration
const emailConfig: IEmailConfig = {
ports: [25, 587, 465, 2525], // Added a non-standard port
hostname: 'mail.example.com',
maxMessageSize: 50 * 1024 * 1024, // 50MB
defaultMode: 'forward' as EmailProcessingMode,
defaultServer: 'fallback-mail.example.com',
defaultPort: 25,
defaultTls: true,
domainRules: [
{
pattern: '*@example.com',
mode: 'forward' as EmailProcessingMode,
target: {
server: 'mail1.example.com',
port: 25,
useTls: true
}
},
{
pattern: '*@example.org',
mode: 'mta' as EmailProcessingMode,
mtaOptions: {
domain: 'example.org',
allowLocalDelivery: true
}
}
]
};
// Create custom email storage path
const customEmailsPath = path.join(process.cwd(), 'email');
// Ensure directory exists and is empty
if (fs.existsSync(customEmailsPath)) {
try {
fs.rmdirSync(customEmailsPath, { recursive: true });
} catch (e) {
console.warn('Could not remove test directory:', e);
}
}
fs.mkdirSync(customEmailsPath, { recursive: true });
// Create DcRouter options with custom email port configuration
const options: IDcRouterOptions = {
emailConfig,
emailPortConfig: {
portMapping: customPortMapping,
portSettings: {
2525: {
terminateTls: false,
routeName: 'custom-smtp-route'
}
},
receivedEmailsPath: customEmailsPath
},
tls: {
contactEmail: 'test@example.com'
}
};
// Create DcRouter instance
const router = new DcRouter(options);
// Verify the options are correctly set
expect(router.options.emailPortConfig).toBeTruthy();
expect(router.options.emailPortConfig.portMapping).toEqual(customPortMapping);
expect(router.options.emailPortConfig.receivedEmailsPath).toEqual(customEmailsPath);
// Test the generateEmailRoutes method
if (typeof router['generateEmailRoutes'] === 'function') {
const routes = router['generateEmailRoutes'](emailConfig);
// Verify that all ports are configured
expect(routes.length).toBeGreaterThan(0); // At least some routes are configured
// Check the custom port configuration
const customPortRoute = routes.find(r => {
const ports = r.match.ports;
return ports === 2525 || (Array.isArray(ports) && (ports as number[]).includes(2525));
});
expect(customPortRoute).toBeTruthy();
expect(customPortRoute?.name).toEqual('custom-smtp-route');
expect(customPortRoute?.action.target.port).toEqual(12525);
// Check standard port mappings
const smtpRoute = routes.find(r => {
const ports = r.match.ports;
return ports === 25 || (Array.isArray(ports) && (ports as number[]).includes(25));
});
expect(smtpRoute?.action.target.port).toEqual(11025);
const submissionRoute = routes.find(r => {
const ports = r.match.ports;
return ports === 587 || (Array.isArray(ports) && (ports as number[]).includes(587));
});
expect(submissionRoute?.action.target.port).toEqual(11587);
}
// Clean up
try {
fs.rmdirSync(customEmailsPath, { recursive: true });
} catch (e) {
console.warn('Could not remove test directory in cleanup:', e);
}
});
tap.test('DcRouter class - Custom email storage path', async () => {
// Create custom email storage path
const customEmailsPath = path.join(process.cwd(), 'email');
// Ensure directory exists and is empty
if (fs.existsSync(customEmailsPath)) {
try {
fs.rmdirSync(customEmailsPath, { recursive: true });
} catch (e) {
console.warn('Could not remove test directory:', e);
}
}
fs.mkdirSync(customEmailsPath, { recursive: true });
// Create a basic email configuration
const emailConfig: IEmailConfig = {
ports: [25],
hostname: 'mail.example.com',
defaultMode: 'mta' as EmailProcessingMode,
domainRules: []
};
// Create DcRouter options with custom email storage path
const options: IDcRouterOptions = {
emailConfig,
emailPortConfig: {
receivedEmailsPath: customEmailsPath
},
tls: {
contactEmail: 'test@example.com'
}
};
// Create DcRouter instance
const router = new DcRouter(options);
// Start the router to initialize email services
await router.start();
// Verify that the custom email storage path was configured
expect(router.options.emailPortConfig?.receivedEmailsPath).toEqual(customEmailsPath);
// Verify the directory exists
expect(fs.existsSync(customEmailsPath)).toEqual(true);
// Verify unified email server was initialized
expect(router.unifiedEmailServer).toBeTruthy();
// Stop the router
await router.stop();
// Clean up
try {
fs.rmdirSync(customEmailsPath, { recursive: true });
} catch (e) {
console.warn('Could not remove test directory in cleanup:', e);
}
});
// Final clean-up test
tap.test('clean up after tests', async () => {
// No-op - just to make sure everything is cleaned up properly
});
tap.test('stop', async () => {
await tap.stopForcefully();
});
// Export a function to run all tests
export default tap.start();

View File

@@ -1,55 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../ts/plugins.ts';
import * as paths from '../ts/paths.ts';
// Import the components we want to test
import { SenderReputationMonitor } from '../ts/deliverability/classes.senderreputationmonitor.ts';
import { IPWarmupManager } from '../ts/deliverability/classes.ipwarmupmanager.ts';
// Ensure test directories exist
paths.ensureDirectories();
// Test SenderReputationMonitor functionality
tap.test('SenderReputationMonitor should track sending events', async () => {
// Initialize monitor with test domain
const monitor = SenderReputationMonitor.getInstance({
enabled: true,
domains: ['test-domain.com']
});
// Record some events
monitor.recordSendEvent('test-domain.com', { type: 'sent', count: 100 });
monitor.recordSendEvent('test-domain.com', { type: 'delivered', count: 95 });
// Get domain metrics
const metrics = monitor.getReputationData('test-domain.com');
// Verify metrics were recorded
if (metrics) {
expect(metrics.volume.sent).toEqual(100);
expect(metrics.volume.delivered).toEqual(95);
}
});
// Test IPWarmupManager functionality
tap.test('IPWarmupManager should handle IP allocation policies', async () => {
// Initialize warmup manager
const manager = IPWarmupManager.getInstance({
enabled: true,
ipAddresses: ['192.168.1.1', '192.168.1.2'],
targetDomains: ['test-domain.com']
});
// Set allocation policy
manager.setActiveAllocationPolicy('balanced');
// Verify allocation methods work
const canSend = manager.canSendMoreToday('192.168.1.1');
expect(typeof canSend).toEqual('boolean');
});
tap.test('stop', async () => {
await tap.stopForcefully();
});
export default tap.start();

View File

@@ -1,141 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../ts/plugins.ts';
import * as paths from '../ts/paths.ts';
import { DnsManager } from '../ts/mail/routing/classes.dns.manager.ts';
import { DKIMCreator } from '../ts/mail/security/classes.dkimcreator.ts';
import { StorageManager } from '../ts/storage/classes.storagemanager.ts';
import type { IEmailDomainConfig } from '../ts/mail/routing/interfaces.ts';
// 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();

View File

@@ -1,257 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../ts/plugins.ts';
import * as paths from '../ts/paths.ts';
import { StorageManager } from '../ts/storage/classes.storagemanager.ts';
import { DnsManager } from '../ts/mail/routing/classes.dns.manager.ts';
import { DomainRegistry } from '../ts/mail/routing/classes.domain.registry.ts';
import { DKIMCreator } from '../ts/mail/security/classes.dkimcreator.ts';
import type { IEmailDomainConfig } from '../ts/mail/routing/interfaces.ts';
// 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();

View File

@@ -1,169 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import { DcRouter } from '../ts/classes.dcrouter.ts';
import * as plugins from '../ts/plugins.ts';
let dcRouter: DcRouter;
tap.test('should NOT instantiate DNS server when dnsDomain is not set', async () => {
dcRouter = new DcRouter({
smartProxyConfig: {
routes: []
}
});
await dcRouter.start();
// Check that DNS server is not created
expect((dcRouter as any).dnsServer).toBeUndefined();
await dcRouter.stop();
});
tap.test('should instantiate DNS server when dnsDomain is set', async () => {
// Use a non-standard port to avoid conflicts
const testPort = 8443;
dcRouter = new DcRouter({
dnsDomain: 'dns.test.local',
smartProxyConfig: {
routes: [],
portMappings: {
443: testPort // Map port 443 to test port
}
} as any
});
try {
await dcRouter.start();
} catch (error) {
// If start fails due to port conflict, that's OK for this test
// We're mainly testing the route generation logic
}
// Check that DNS server is created
expect((dcRouter as any).dnsServer).toBeDefined();
// Check routes were generated (even if SmartProxy failed to start)
const generatedRoutes = (dcRouter as any).generateDnsRoutes();
expect(generatedRoutes.length).toEqual(2); // /dns-query and /resolve
// Check that routes have socket-handler action
generatedRoutes.forEach((route: any) => {
expect(route.action.type).toEqual('socket-handler');
expect(route.action.socketHandler).toBeDefined();
});
try {
await dcRouter.stop();
} catch (error) {
// Ignore stop errors
}
});
tap.test('should create DNS routes with correct configuration', async () => {
dcRouter = new DcRouter({
dnsDomain: 'dns.example.com',
smartProxyConfig: {
routes: []
}
});
// Access the private method to generate routes
const dnsRoutes = (dcRouter as any).generateDnsRoutes();
expect(dnsRoutes.length).toEqual(2);
// Check first route (dns-query)
const dnsQueryRoute = dnsRoutes.find((r: any) => r.name === 'dns-over-https-dns-query');
expect(dnsQueryRoute).toBeDefined();
expect(dnsQueryRoute.match.ports).toContain(443);
expect(dnsQueryRoute.match.domains).toContain('dns.example.com');
expect(dnsQueryRoute.match.path).toEqual('/dns-query');
// Check second route (resolve)
const resolveRoute = dnsRoutes.find((r: any) => r.name === 'dns-over-https-resolve');
expect(resolveRoute).toBeDefined();
expect(resolveRoute.match.ports).toContain(443);
expect(resolveRoute.match.domains).toContain('dns.example.com');
expect(resolveRoute.match.path).toEqual('/resolve');
});
tap.test('DNS socket handler should handle sockets correctly', async () => {
dcRouter = new DcRouter({
dnsDomain: 'dns.test.local',
smartProxyConfig: {
routes: [],
portMappings: { 443: 8444 } // Use different test port
} as any
});
try {
await dcRouter.start();
} catch (error) {
// Ignore start errors for this test
}
// Create a mock socket
const mockSocket = new plugins.net.Socket();
let socketEnded = false;
let socketDestroyed = false;
mockSocket.end = () => {
socketEnded = true;
};
mockSocket.destroy = () => {
socketDestroyed = true;
};
// Get the socket handler
const socketHandler = (dcRouter as any).createDnsSocketHandler();
expect(socketHandler).toBeDefined();
expect(typeof socketHandler).toEqual('function');
// Test with DNS server initialized
try {
await socketHandler(mockSocket);
} catch (error) {
// Expected - mock socket won't work properly
}
// Socket should be handled by DNS server (even if it errors)
expect(socketHandler).toBeDefined();
try {
await dcRouter.stop();
} catch (error) {
// Ignore stop errors
}
});
tap.test('DNS server should have manual HTTPS mode enabled', async () => {
dcRouter = new DcRouter({
dnsDomain: 'dns.test.local'
});
// Don't actually start it to avoid port conflicts
// Instead, directly call the setup method
try {
await (dcRouter as any).setupDnsWithSocketHandler();
} catch (error) {
// May fail but that's OK
}
// Check that DNS server was created with correct options
const dnsServer = (dcRouter as any).dnsServer;
expect(dnsServer).toBeDefined();
// The important thing is that the DNS routes are created correctly
// and that the socket handler is set up
const socketHandler = (dcRouter as any).createDnsSocketHandler();
expect(socketHandler).toBeDefined();
expect(typeof socketHandler).toEqual('function');
});
tap.test('stop', async () => {
await tap.stopForcefully();
});
export default tap.start();

View File

@@ -1,283 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../ts/plugins.ts';
import * as paths from '../ts/paths.ts';
import { DnsManager } from '../ts/mail/routing/classes.dns.manager.ts';
import { DomainRegistry } from '../ts/mail/routing/classes.domain.registry.ts';
import { StorageManager } from '../ts/storage/classes.storagemanager.ts';
import { DKIMCreator } from '../ts/mail/security/classes.dkimcreator.ts';
import type { IEmailDomainConfig } from '../ts/mail/routing/interfaces.ts';
// 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<string, string[]> = new Map();
private mockTxtRecords: Map<string, string[][]> = new Map();
private mockMxRecords: Map<string, any[]> = 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<string[]> {
return this.mockNsRecords.get(domain) || [];
}
protected async resolveTxt(domain: string): Promise<string[][]> {
return this.mockTxtRecords.get(domain) || [];
}
protected async resolveMx(domain: string): Promise<any[]> {
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();

View File

@@ -1,228 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import { DcRouter } from '../ts/classes.dcrouter.ts';
import * as plugins from '../ts/plugins.ts';
let dcRouter: DcRouter;
tap.test('should use traditional port forwarding when useSocketHandler is false', async () => {
dcRouter = new DcRouter({
emailConfig: {
ports: [25, 587, 465],
hostname: 'mail.test.local',
domains: ['test.local'],
routes: [],
useSocketHandler: false // Traditional mode
},
smartProxyConfig: {
routes: []
}
});
await dcRouter.start();
// Check that email server is created and listening on ports
const emailServer = (dcRouter as any).emailServer;
expect(emailServer).toBeDefined();
// Check SmartProxy routes are forward type
const smartProxy = (dcRouter as any).smartProxy;
const routes = smartProxy?.options?.routes || [];
const emailRoutes = routes.filter((route: any) =>
route.name?.includes('-route')
);
emailRoutes.forEach((route: any) => {
expect(route.action.type).toEqual('forward');
expect(route.action.target).toBeDefined();
expect(route.action.target.host).toEqual('localhost');
});
await dcRouter.stop();
});
tap.test('should use socket-handler mode when useSocketHandler is true', async () => {
dcRouter = new DcRouter({
emailConfig: {
ports: [25, 587, 465],
hostname: 'mail.test.local',
domains: ['test.local'],
routes: [],
useSocketHandler: true // Socket-handler mode
},
smartProxyConfig: {
routes: []
}
});
await dcRouter.start();
// Check that email server is created
const emailServer = (dcRouter as any).emailServer;
expect(emailServer).toBeDefined();
// Check SmartProxy routes are socket-handler type
const smartProxy = (dcRouter as any).smartProxy;
const routes = smartProxy?.options?.routes || [];
const emailRoutes = routes.filter((route: any) =>
route.name?.includes('-route')
);
emailRoutes.forEach((route: any) => {
expect(route.action.type).toEqual('socket-handler');
expect(route.action.socketHandler).toBeDefined();
expect(typeof route.action.socketHandler).toEqual('function');
});
await dcRouter.stop();
});
tap.test('should generate correct email routes for each port', async () => {
const emailConfig = {
ports: [25, 587, 465],
hostname: 'mail.test.local',
domains: ['test.local'],
routes: [],
useSocketHandler: true
};
dcRouter = new DcRouter({ emailConfig });
// Access the private method to generate routes
const emailRoutes = (dcRouter as any).generateEmailRoutes(emailConfig);
expect(emailRoutes.length).toEqual(3);
// Check SMTP route (port 25)
const smtpRoute = emailRoutes.find((r: any) => r.name === 'smtp-route');
expect(smtpRoute).toBeDefined();
expect(smtpRoute.match.ports).toContain(25);
expect(smtpRoute.action.type).toEqual('socket-handler');
// Check Submission route (port 587)
const submissionRoute = emailRoutes.find((r: any) => r.name === 'submission-route');
expect(submissionRoute).toBeDefined();
expect(submissionRoute.match.ports).toContain(587);
expect(submissionRoute.action.type).toEqual('socket-handler');
// Check SMTPS route (port 465)
const smtpsRoute = emailRoutes.find((r: any) => r.name === 'smtps-route');
expect(smtpsRoute).toBeDefined();
expect(smtpsRoute.match.ports).toContain(465);
expect(smtpsRoute.action.type).toEqual('socket-handler');
});
tap.test('email socket handler should handle different ports correctly', async () => {
dcRouter = new DcRouter({
emailConfig: {
ports: [25, 587, 465],
hostname: 'mail.test.local',
domains: ['test.local'],
routes: [],
useSocketHandler: true
}
});
await dcRouter.start();
// Test port 25 handler (plain SMTP)
const port25Handler = (dcRouter as any).createMailSocketHandler(25);
expect(port25Handler).toBeDefined();
expect(typeof port25Handler).toEqual('function');
// Test port 465 handler (SMTPS - should wrap in TLS)
const port465Handler = (dcRouter as any).createMailSocketHandler(465);
expect(port465Handler).toBeDefined();
expect(typeof port465Handler).toEqual('function');
await dcRouter.stop();
});
tap.test('email server handleSocket method should work', async () => {
dcRouter = new DcRouter({
emailConfig: {
ports: [25],
hostname: 'mail.test.local',
domains: ['test.local'],
routes: [],
useSocketHandler: true
}
});
await dcRouter.start();
const emailServer = (dcRouter as any).emailServer;
expect(emailServer).toBeDefined();
expect(emailServer.handleSocket).toBeDefined();
expect(typeof emailServer.handleSocket).toEqual('function');
// Create a mock socket
const mockSocket = new plugins.net.Socket();
let socketDestroyed = false;
mockSocket.destroy = () => {
socketDestroyed = true;
};
// Test handleSocket
try {
await emailServer.handleSocket(mockSocket, 25);
// It will fail because we don't have a real socket, but it should handle it gracefully
} catch (error) {
// Expected to error with mock socket
}
await dcRouter.stop();
});
tap.test('should not create SMTP servers when useSocketHandler is true', async () => {
dcRouter = new DcRouter({
emailConfig: {
ports: [25, 587, 465],
hostname: 'mail.test.local',
domains: ['test.local'],
routes: [],
useSocketHandler: true
}
});
await dcRouter.start();
// The email server should not have any SMTP server instances
const emailServer = (dcRouter as any).emailServer;
expect(emailServer).toBeDefined();
// The servers array should be empty (no port binding)
expect(emailServer.servers).toBeDefined();
expect(emailServer.servers.length).toEqual(0);
await dcRouter.stop();
});
tap.test('TLS handling should differ between ports', async () => {
const emailConfig = {
ports: [25, 465],
hostname: 'mail.test.local',
domains: ['test.local'],
routes: [],
useSocketHandler: false // Use traditional mode to check TLS config
};
dcRouter = new DcRouter({ emailConfig });
const emailRoutes = (dcRouter as any).generateEmailRoutes(emailConfig);
// Port 25 should use passthrough
const smtpRoute = emailRoutes.find((r: any) => r.match.ports[0] === 25);
expect(smtpRoute.action.tls.mode).toEqual('passthrough');
// Port 465 should use terminate
const smtpsRoute = emailRoutes.find((r: any) => r.match.ports[0] === 465);
expect(smtpsRoute.action.tls.mode).toEqual('terminate');
expect(smtpsRoute.action.tls.certificate).toEqual('auto');
});
tap.test('stop', async () => {
await tap.stopForcefully();
});
export default tap.start();

View File

@@ -1,408 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as errors from '../ts/errors/index.ts';
import {
PlatformError,
ValidationError,
NetworkError,
ResourceError,
OperationError
} from '../ts/errors/base.errors.ts';
import {
ErrorSeverity,
ErrorCategory,
ErrorRecoverability
} from '../ts/errors/error.codes.ts';
import {
EmailServiceError,
EmailTemplateError,
EmailValidationError,
EmailSendError,
EmailReceiveError
} from '../ts/errors/email.errors.ts';
import {
MtaConnectionError,
MtaAuthenticationError,
MtaDeliveryError,
MtaConfigurationError
} from '../ts/errors/mta.errors.ts';
import {
ErrorHandler
} from '../ts/errors/error-handler.ts';
// Test base error classes
tap.test('Base error classes should set properties correctly', async () => {
const message = 'Test error message';
const code = 'TEST_ERROR_CODE';
const context = {
component: 'TestComponent',
operation: 'testOperation',
data: { foo: 'bar' }
};
// Test PlatformError
const platformError = new PlatformError(
message,
code,
ErrorSeverity.MEDIUM,
ErrorCategory.OPERATION,
ErrorRecoverability.MAYBE_RECOVERABLE,
context
);
expect(platformError.message).toEqual(message);
expect(platformError.code).toEqual(code);
expect(platformError.severity).toEqual(ErrorSeverity.MEDIUM);
expect(platformError.category).toEqual(ErrorCategory.OPERATION);
expect(platformError.recoverability).toEqual(ErrorRecoverability.MAYBE_RECOVERABLE);
expect(platformError.context?.component).toEqual(context.component);
expect(platformError.context?.operation).toEqual(context.operation);
expect(platformError.context?.data?.foo).toEqual('bar');
expect(platformError.name).toEqual('PlatformError');
// Test ValidationError
const validationError = new ValidationError(message, code, context);
expect(validationError.category).toEqual(ErrorCategory.VALIDATION);
expect(validationError.severity).toEqual(ErrorSeverity.LOW);
// Test NetworkError
const networkError = new NetworkError(message, code, context);
expect(networkError.category).toEqual(ErrorCategory.CONNECTIVITY);
expect(networkError.severity).toEqual(ErrorSeverity.MEDIUM);
expect(networkError.recoverability).toEqual(ErrorRecoverability.MAYBE_RECOVERABLE);
// Test ResourceError
const resourceError = new ResourceError(message, code, context);
expect(resourceError.category).toEqual(ErrorCategory.RESOURCE);
});
// Test 7: Error withRetry() method
tap.test('PlatformError withRetry creates new instance with retry info', async () => {
const originalError = new EmailSendError('Send failed', {
data: { someData: true }
});
const retryError = originalError.withRetry(3, 1, 1000);
// Verify it's a new instance
expect(retryError === originalError).toEqual(false);
expect(retryError).toBeInstanceOf(EmailSendError);
// Verify original data is preserved
expect(retryError.context?.data?.someData).toEqual(true);
// Verify retry info is added
expect(retryError.context?.retry?.maxRetries).toEqual(3);
expect(retryError.context?.retry?.currentRetry).toEqual(1);
expect(retryError.context?.retry?.retryDelay).toEqual(1000);
expect(retryError.context?.retry?.nextRetryAt).toBeTypeofNumber();
});
// Test email error classes
tap.test('Email error classes should be properly constructed', async () => {
try {
// Test EmailServiceError
const emailServiceError = new EmailServiceError('Email service error', {
component: 'EmailService',
operation: 'sendEmail'
});
expect(emailServiceError.code).toEqual('EMAIL_SERVICE_ERROR');
expect(emailServiceError.name).toEqual('EmailServiceError');
// Test EmailTemplateError
const templateError = new EmailTemplateError('Template not found: welcome_email', {
data: { templateId: 'welcome_email' }
});
expect(templateError.code).toEqual('EMAIL_TEMPLATE_ERROR');
expect(templateError.context.data?.templateId).toEqual('welcome_email');
// Test EmailSendError with permanent flag
const permanentError = EmailSendError.permanent(
'Invalid recipient: user@example.com',
{ data: { details: 'DNS not found', recipient: 'user@example.com' } }
);
expect(permanentError.code).toEqual('EMAIL_SEND_ERROR');
expect(permanentError.isPermanent()).toEqual(true);
expect(permanentError.context.data?.permanent).toEqual(true);
// Test EmailSendError with temporary flag and retry
const tempError = EmailSendError.temporary(
'Server busy',
3,
0,
1000,
{ data: { server: 'smtp.example.com' } }
);
expect(tempError.isPermanent()).toEqual(false);
expect(tempError.context.data?.permanent).toEqual(false);
expect(tempError.context.retry?.maxRetries).toEqual(3);
expect(tempError.shouldRetry()).toEqual(true);
} catch (error) {
console.error('Test failed with error:', error);
throw error;
}
});
// Test MTA error classes
tap.test('MTA error classes should be properly constructed', async () => {
try {
// Test MtaConnectionError
const dnsError = MtaConnectionError.dnsError('mail.example.com', new Error('DNS lookup failed'));
expect(dnsError.code).toEqual('MTA_CONNECTION_ERROR');
expect(dnsError.category).toEqual(ErrorCategory.CONNECTIVITY);
expect(dnsError.context.data?.hostname).toEqual('mail.example.com');
// Test MtaTimeoutError via MtaConnectionError.timeout
const timeoutError = MtaConnectionError.timeout('mail.example.com', 25, 30000);
expect(timeoutError.code).toEqual('MTA_CONNECTION_ERROR');
expect(timeoutError.context.data?.timeout).toEqual(30000);
// Test MtaAuthenticationError
const authError = MtaAuthenticationError.invalidCredentials('mail.example.com', 'user@example.com');
expect(authError.code).toEqual('MTA_AUTHENTICATION_ERROR');
expect(authError.category).toEqual(ErrorCategory.AUTHENTICATION);
expect(authError.context.data?.username).toEqual('user@example.com');
// Test MtaDeliveryError
const permDeliveryError = MtaDeliveryError.permanent(
'User unknown',
'nonexistent@example.com',
'550',
'550 5.1.1 User unknown',
{}
);
expect(permDeliveryError.code).toEqual('MTA_DELIVERY_ERROR');
expect(permDeliveryError.isPermanent()).toEqual(true);
expect(permDeliveryError.getRecipientAddress()).toEqual('nonexistent@example.com');
expect(permDeliveryError.getStatusCode()).toEqual('550');
// Test temporary delivery error with retry
const tempDeliveryError = MtaDeliveryError.temporary(
'Mailbox temporarily unavailable',
'user@example.com',
'450',
'450 4.2.1 Mailbox temporarily unavailable',
3,
1,
5000
);
expect(tempDeliveryError.isPermanent()).toEqual(false);
expect(tempDeliveryError.shouldRetry()).toEqual(true);
expect(tempDeliveryError.context.retry?.currentRetry).toEqual(1);
expect(tempDeliveryError.context.retry?.maxRetries).toEqual(3);
} catch (error) {
console.error('MTA test failed with error:', error);
throw error;
}
});
// Test error handler utility
tap.test('ErrorHandler should properly handle and format errors', async () => {
// Configure error handler
ErrorHandler.configure({
logErrors: false, // Disable for testing
includeStacksInProd: false,
retry: {
maxAttempts: 5,
baseDelay: 100,
maxDelay: 1000,
backoffFactor: 2
}
});
// Test converting regular Error to PlatformError
const regularError = new Error('Something went wrong');
const platformError = ErrorHandler.toPlatformError(
regularError,
'PLATFORM_OPERATION_ERROR',
{ component: 'TestHandler' }
);
expect(platformError).toBeInstanceOf(PlatformError);
expect(platformError.code).toEqual('PLATFORM_OPERATION_ERROR');
expect(platformError.context?.component).toEqual('TestHandler');
// Test formatting error for API response
const formattedError = ErrorHandler.formatErrorForResponse(platformError, true);
expect(formattedError.code).toEqual('PLATFORM_OPERATION_ERROR');
expect(formattedError.message).toEqual('An unexpected error occurred.');
expect(formattedError.details?.rawMessage).toEqual('Something went wrong');
// Test executing a function with error handling
let executed = false;
try {
await ErrorHandler.execute(async () => {
executed = true;
throw new Error('Execution failed');
}, 'TEST_EXECUTION_ERROR', { operation: 'testExecution' });
} catch (error) {
expect(error).toBeInstanceOf(PlatformError);
expect(error.code).toEqual('TEST_EXECUTION_ERROR');
expect(error.context.operation).toEqual('testExecution');
}
expect(executed).toEqual(true);
// Test executeWithRetry successful after retries
let attempts = 0;
const result = await ErrorHandler.executeWithRetry(
async () => {
attempts++;
if (attempts < 3) {
throw new Error('Temporary failure');
}
return 'success';
},
'TEST_RETRY_ERROR',
{
maxAttempts: 5,
baseDelay: 10, // Use small delay for tests
retryableErrorPatterns: [/Temporary failure/], // Add pattern to make error retryable
onRetry: (error, attempt, delay) => {
expect(error).toBeInstanceOf(PlatformError);
expect(attempt).toBeGreaterThan(0);
expect(delay).toBeGreaterThan(0);
}
}
);
expect(result).toEqual('success');
expect(attempts).toEqual(3);
// Test executeWithRetry that fails after max attempts
attempts = 0;
try {
await ErrorHandler.executeWithRetry(
async () => {
attempts++;
throw new Error('Persistent failure');
},
'TEST_RETRY_ERROR',
{
maxAttempts: 3,
baseDelay: 10,
retryableErrorPatterns: [/Persistent failure/] // Make error retryable so it tries all attempts
}
);
} catch (error) {
expect(error).toBeInstanceOf(PlatformError);
expect(attempts).toEqual(3);
}
});
// Test retry utilities
tap.test('Error retry utilities should work correctly', async () => {
let attempts = 0;
const start = Date.now();
try {
await errors.retry(
async () => {
attempts++;
if (attempts < 3) {
throw new Error('Temporary error');
}
return 'success';
},
{
maxRetries: 5,
initialDelay: 20,
backoffFactor: 1.5,
retryableErrors: [/Temporary/]
}
);
} catch (e) {
// Should not reach here
expect(false).toEqual(true);
}
expect(attempts).toEqual(3);
// Test retry with non-retryable error
attempts = 0;
try {
await errors.retry(
async () => {
attempts++;
throw new Error('Critical error');
},
{
maxRetries: 3,
initialDelay: 10,
retryableErrors: [/Temporary/] // Won't match "Critical"
}
);
} catch (error) {
expect(error.message).toEqual('Critical error');
expect(attempts).toEqual(1); // Should only attempt once
}
});
// Helper function that will reject first n times, then resolve
interface FlakyFunction {
(failTimes: number, result?: any): Promise<any>;
counter: number;
reset: () => void;
}
const flaky: FlakyFunction = Object.assign(
async function (failTimes: number, result: any = 'success'): Promise<any> {
if (flaky.counter < failTimes) {
flaky.counter++;
throw new Error(`Flaky failure ${flaky.counter}`);
}
return result;
},
{
counter: 0,
reset: () => { flaky.counter = 0; }
}
);
// Test error wrapping and retry combination
tap.test('Error handling can be combined with retry for robust operations', async () => {
// Reset counter for the test
flaky.reset();
// Create a wrapped version of the flaky function
const wrapped = errors.withErrorHandling(
() => flaky(2, 'wrapped success'),
'TEST_WRAPPED_ERROR',
{ component: 'TestComponent' }
);
// Execute with retry
const result = await errors.retry(
wrapped,
{
maxRetries: 3,
initialDelay: 10,
retryableErrors: [/Flaky failure/]
}
);
expect(result).toEqual('wrapped success');
expect(flaky.counter).toEqual(2);
// Reset and test failure case
flaky.reset();
try {
await errors.retry(
() => flaky(5, 'never reached'),
{
maxRetries: 2, // Only retry twice, but we need 5 attempts to succeed
initialDelay: 10,
retryableErrors: [/Flaky failure/] // Add pattern to make it retry
}
);
// Should not reach here
expect(false).toEqual(true);
} catch (error) {
expect(error.message).toContain('Flaky failure');
expect(flaky.counter).toEqual(3); // Initial + 2 retries = 3 attempts
}
});
tap.test('stop', async () => {
await tap.stopForcefully();
});
export default tap.start();

View File

@@ -1,313 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../ts/plugins.ts';
import * as paths from '../ts/paths.ts';
import { StorageManager } from '../ts/storage/classes.storagemanager.ts';
import { DKIMCreator } from '../ts/mail/security/classes.dkimcreator.ts';
import { BounceManager } from '../ts/mail/core/classes.bouncemanager.ts';
import { EmailRouter } from '../ts/mail/routing/classes.email.router.ts';
import type { IEmailRoute } from '../ts/mail/routing/interfaces.ts';
tap.test('Storage Persistence Across Restarts', async () => {
const testDir = plugins.path.join(paths.dataDir, '.test-integration-persistence');
// Phase 1: Create storage and write data
{
const storage = new StorageManager({ fsPath: testDir });
// Write some test data
await storage.set('/test/key1', 'value1');
await storage.setJSON('/test/json', { data: 'test', count: 42 });
await storage.set('/other/key2', 'value2');
}
// Phase 2: Create new instance and verify data persists
{
const storage = new StorageManager({ fsPath: testDir });
// Verify data persists
const value1 = await storage.get('/test/key1');
expect(value1).toEqual('value1');
const jsonData = await storage.getJSON('/test/json');
expect(jsonData).toEqual({ data: 'test', count: 42 });
const value2 = await storage.get('/other/key2');
expect(value2).toEqual('value2');
// Test list operation
const testKeys = await storage.list('/test');
expect(testKeys.length).toEqual(2);
expect(testKeys).toContain('/test/key1');
expect(testKeys).toContain('/test/json');
}
// Clean up
await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {});
});
tap.test('DKIM Storage Integration', async () => {
const testDir = plugins.path.join(paths.dataDir, '.test-integration-dkim');
const keysDir = plugins.path.join(testDir, 'keys');
// Phase 1: Generate DKIM keys with storage
{
const storage = new StorageManager({ fsPath: testDir });
const dkimCreator = new DKIMCreator(keysDir, storage);
await dkimCreator.handleDKIMKeysForDomain('storage.example.com');
// Verify keys exist
const keys = await dkimCreator.readDKIMKeys('storage.example.com');
expect(keys.privateKey).toBeTruthy();
expect(keys.publicKey).toBeTruthy();
}
// Phase 2: New instance should find keys in storage
{
const storage = new StorageManager({ fsPath: testDir });
const dkimCreator = new DKIMCreator(keysDir, storage);
// Keys should be loaded from storage
const keys = await dkimCreator.readDKIMKeys('storage.example.com');
expect(keys.privateKey).toBeTruthy();
expect(keys.publicKey).toBeTruthy();
expect(keys.privateKey).toContain('BEGIN RSA PRIVATE KEY');
}
// Clean up
await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {});
});
tap.test('Bounce Manager Storage Integration', async () => {
const testDir = plugins.path.join(paths.dataDir, '.test-integration-bounce');
// Phase 1: Add to suppression list with storage
{
const storage = new StorageManager({ fsPath: testDir });
const bounceManager = new BounceManager({
storageManager: storage
});
// Add emails to suppression list
bounceManager.addToSuppressionList('bounce1@example.com', 'Hard bounce: invalid_recipient');
bounceManager.addToSuppressionList('bounce2@example.com', 'Soft bounce: temporary', Date.now() + 3600000);
// Verify suppression
expect(bounceManager.isEmailSuppressed('bounce1@example.com')).toEqual(true);
expect(bounceManager.isEmailSuppressed('bounce2@example.com')).toEqual(true);
}
// Wait a moment to ensure async save completes
await new Promise(resolve => setTimeout(resolve, 100));
// Phase 2: New instance should load suppression list from storage
{
const storage = new StorageManager({ fsPath: testDir });
const bounceManager = new BounceManager({
storageManager: storage
});
// Wait for async load
await new Promise(resolve => setTimeout(resolve, 100));
// Verify persistence
expect(bounceManager.isEmailSuppressed('bounce1@example.com')).toEqual(true);
expect(bounceManager.isEmailSuppressed('bounce2@example.com')).toEqual(true);
expect(bounceManager.isEmailSuppressed('notbounced@example.com')).toEqual(false);
// Check suppression info
const info1 = bounceManager.getSuppressionInfo('bounce1@example.com');
expect(info1).toBeTruthy();
expect(info1?.reason).toContain('Hard bounce');
expect(info1?.expiresAt).toBeUndefined(); // Permanent
const info2 = bounceManager.getSuppressionInfo('bounce2@example.com');
expect(info2).toBeTruthy();
expect(info2?.reason).toContain('Soft bounce');
expect(info2?.expiresAt).toBeGreaterThan(Date.now());
}
// Clean up
await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {});
});
tap.test('Email Router Storage Integration', async () => {
const testDir = plugins.path.join(paths.dataDir, '.test-integration-router');
const testRoutes: IEmailRoute[] = [
{
name: 'test-route-1',
match: { recipients: '*@test.com' },
action: { type: 'forward', forward: { host: 'test.server.com', port: 25 } },
priority: 100
},
{
name: 'test-route-2',
match: { senders: '*@internal.com' },
action: { type: 'process', process: { scan: true, dkim: true } },
priority: 50
}
];
// Phase 1: Save routes with storage
{
const storage = new StorageManager({ fsPath: testDir });
const router = new EmailRouter([], {
storageManager: storage,
persistChanges: true
});
// Add routes
await router.addRoute(testRoutes[0]);
await router.addRoute(testRoutes[1]);
// Verify routes
const routes = router.getRoutes();
expect(routes.length).toEqual(2);
expect(routes[0].name).toEqual('test-route-1'); // Higher priority first
expect(routes[1].name).toEqual('test-route-2');
}
// Phase 2: New instance should load routes from storage
{
const storage = new StorageManager({ fsPath: testDir });
const router = new EmailRouter([], {
storageManager: storage,
persistChanges: true
});
// Wait for async load
await new Promise(resolve => setTimeout(resolve, 100));
// Manually load routes (since constructor load is fire-and-forget)
await router.loadRoutes({ replace: true });
// Verify persistence
const routes = router.getRoutes();
expect(routes.length).toEqual(2);
expect(routes[0].name).toEqual('test-route-1');
expect(routes[0].priority).toEqual(100);
expect(routes[1].name).toEqual('test-route-2');
expect(routes[1].priority).toEqual(50);
// Test route retrieval
const route1 = router.getRoute('test-route-1');
expect(route1).toBeTruthy();
expect(route1?.match.recipients).toEqual('*@test.com');
}
// Clean up
await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {});
});
tap.test('Storage Backend Switching', async () => {
const testDir = plugins.path.join(paths.dataDir, '.test-integration-switching');
const testData = { key: 'value', nested: { data: true } };
// Phase 1: Start with memory storage
const memoryStore = new Map<string, string>();
{
const storage = new StorageManager(); // Memory backend
await storage.setJSON('/switch/test', testData);
// Verify it's in memory
expect(storage.getBackend()).toEqual('memory');
}
// Phase 2: Switch to custom backend
{
const storage = new StorageManager({
readFunction: async (key) => memoryStore.get(key) || null,
writeFunction: async (key, value) => { memoryStore.set(key, value); }
});
// Write data
await storage.setJSON('/switch/test', testData);
// Verify backend
expect(storage.getBackend()).toEqual('custom');
expect(memoryStore.has('/switch/test')).toEqual(true);
}
// Phase 3: Switch to filesystem
{
const storage = new StorageManager({ fsPath: testDir });
// Migrate data from custom backend
const dataStr = memoryStore.get('/switch/test');
if (dataStr) {
await storage.set('/switch/test', dataStr);
}
// Verify data migrated
const data = await storage.getJSON('/switch/test');
expect(data).toEqual(testData);
expect(storage.getBackend()).toEqual('filesystem'); // fsPath is now properly reported as filesystem
}
// Clean up
await plugins.fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {});
});
tap.test('Data Migration Between Backends', async () => {
const testDir1 = plugins.path.join(paths.dataDir, '.test-migration-source');
const testDir2 = plugins.path.join(paths.dataDir, '.test-migration-dest');
// Create test data structure
const testData = {
'/config/app': JSON.stringify({ name: 'test-app', version: '1.0.0' }),
'/config/database': JSON.stringify({ host: 'localhost', port: 5432 }),
'/data/users/1': JSON.stringify({ id: 1, name: 'User One' }),
'/data/users/2': JSON.stringify({ id: 2, name: 'User Two' }),
'/logs/app.log': 'Log entry 1\nLog entry 2\nLog entry 3'
};
// Phase 1: Populate source storage
{
const source = new StorageManager({ fsPath: testDir1 });
for (const [key, value] of Object.entries(testData)) {
await source.set(key, value);
}
// Verify data written
const keys = await source.list('/');
expect(keys.length).toBeGreaterThanOrEqual(5);
}
// Phase 2: Migrate to destination
{
const source = new StorageManager({ fsPath: testDir1 });
const dest = new StorageManager({ fsPath: testDir2 });
// List all keys from source
const allKeys = await source.list('/');
// Migrate each key
for (const key of allKeys) {
const value = await source.get(key);
if (value !== null) {
await dest.set(key, value);
}
}
// Verify migration
for (const [key, expectedValue] of Object.entries(testData)) {
const value = await dest.get(key);
expect(value).toEqual(expectedValue);
}
// Verify structure preserved
const configKeys = await dest.list('/config');
expect(configKeys.length).toEqual(2);
const userKeys = await dest.list('/data/users');
expect(userKeys.length).toEqual(2);
}
// Clean up
await plugins.fs.promises.rm(testDir1, { recursive: true, force: true }).catch(() => {});
await plugins.fs.promises.rm(testDir2, { recursive: true, force: true }).catch(() => {});
});
export default tap.start();

View File

@@ -1,75 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../ts/plugins.ts';
// SzPlatformService doesn't exist in codebase - using DcRouter instead for integration tests
import DcRouter from '../ts/classes.dcrouter.ts';
import { BounceManager } from '../ts/mail/core/classes.bouncemanager.ts';
import { smtpClientMod } from '../ts/mail/delivery/index.ts';
import { SmtpServer } from '../ts/mail/delivery/smtpserver/smtp-server.ts';
// Test the new integration architecture
tap.test('should be able to create an SMTP server', async (tools) => {
// Create an SMTP server
const smtpServer = new SmtpServer({
options: {
port: 10025,
hostname: 'test.example.com',
key: '',
cert: ''
}
});
// Verify it was created properly
expect(smtpServer).toBeTruthy();
expect(smtpServer.options.port).toEqual(10025);
expect(smtpServer.options.hostname).toEqual('test.example.com');
});
tap.test('DcRouter should support email configuration', async (tools) => {
// Create a DcRouter with email config
const dcRouter = new DcRouter({
emailConfig: {
useEmail: true,
domainRules: [{
// name: 'test-rule', // not part of IDomainRule
match: {
senderPattern: '.*@test.com',
},
actions: []
}]
}
});
// Verify it was created properly
expect(dcRouter).toBeTruthy();
});
tap.test('SMTP client should be able to connect to SMTP server', async (tools) => {
// Create an SMTP client
const options = {
host: 'smtp.test.com',
port: 587,
secure: false,
auth: {
user: 'test@example.com',
pass: 'testpass'
},
connectionTimeout: 5000,
socketTimeout: 5000
};
const smtpClient = smtpClientMod.createSmtpClient(options);
// Verify it was created properly
expect(smtpClient).toBeTruthy();
// Since options are not exposed, just verify the client was created
expect(typeof smtpClient.sendMail).toEqual('function');
expect(typeof smtpClient.getPoolStatus).toEqual('function');
});
tap.test('stop', async () => {
await tap.stopForcefully();
});
// Export for tapbundle execution
export default tap.start();

View File

@@ -1,323 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../ts/plugins.ts';
import * as paths from '../ts/paths.ts';
import { IPWarmupManager } from '../ts/deliverability/classes.ipwarmupmanager.ts';
// Cleanup any temporary test data
const cleanupTestData = () => {
const warmupDataPath = plugins.path.join(paths.dataDir, 'warmup');
if (plugins.fs.existsSync(warmupDataPath)) {
// Remove the directory recursively using fs instead of smartfile
plugins.fs.rmSync(warmupDataPath, { recursive: true, force: true });
}
};
// Helper to reset the singleton instance between tests
const resetSingleton = () => {
// @ts-ignore - accessing private static field for testing
IPWarmupManager.instance = null;
};
// Before running any tests
tap.test('setup', async () => {
cleanupTestData();
});
// Test initialization of IPWarmupManager
tap.test('should initialize IPWarmupManager with default settings', async () => {
resetSingleton();
const ipWarmupManager = IPWarmupManager.getInstance();
expect(ipWarmupManager).toBeTruthy();
expect(typeof ipWarmupManager.getBestIPForSending).toEqual('function');
expect(typeof ipWarmupManager.canSendMoreToday).toEqual('function');
expect(typeof ipWarmupManager.getStageCount).toEqual('function');
expect(typeof ipWarmupManager.setActiveAllocationPolicy).toEqual('function');
});
// Test initialization with custom settings
tap.test('should initialize IPWarmupManager with custom settings', async () => {
resetSingleton();
const ipWarmupManager = IPWarmupManager.getInstance({
enabled: true,
ipAddresses: ['192.168.1.1', '192.168.1.2'],
targetDomains: ['example.com', 'test.com'],
fallbackPercentage: 75
});
// Test setting allocation policy
ipWarmupManager.setActiveAllocationPolicy('roundRobin');
// Get best IP for sending
const bestIP = ipWarmupManager.getBestIPForSending({
from: 'test@example.com',
to: ['recipient@test.com'],
domain: 'example.com'
});
// Check if we can send more today
const canSendMore = ipWarmupManager.canSendMoreToday('192.168.1.1');
// Check stage count
const stageCount = ipWarmupManager.getStageCount();
expect(typeof stageCount).toEqual('number');
});
// Test IP allocation policies
tap.test('should allocate IPs using balanced policy', async () => {
resetSingleton();
const ipWarmupManager = IPWarmupManager.getInstance({
enabled: true,
ipAddresses: ['192.168.1.1', '192.168.1.2', '192.168.1.3'],
targetDomains: ['example.com', 'test.com']
// Remove allocationPolicy which is not in the interface
});
ipWarmupManager.setActiveAllocationPolicy('balanced');
// Use getBestIPForSending multiple times and check if all IPs are used
const usedIPs = new Set();
for (let i = 0; i < 30; i++) {
const ip = ipWarmupManager.getBestIPForSending({
from: 'test@example.com',
to: ['recipient@test.com'],
domain: 'example.com'
});
if (ip) usedIPs.add(ip);
}
// We should use at least 2 different IPs with balanced policy
expect(usedIPs.size >= 2).toEqual(true);
});
// Test round robin allocation policy
tap.test('should allocate IPs using round robin policy', async () => {
resetSingleton();
const ipWarmupManager = IPWarmupManager.getInstance({
enabled: true,
ipAddresses: ['192.168.1.1', '192.168.1.2', '192.168.1.3'],
targetDomains: ['example.com', 'test.com']
// Remove allocationPolicy which is not in the interface
});
ipWarmupManager.setActiveAllocationPolicy('roundRobin');
// First few IPs should rotate through the available IPs
const firstIP = ipWarmupManager.getBestIPForSending({
from: 'test@example.com',
to: ['recipient@test.com'],
domain: 'example.com'
});
const secondIP = ipWarmupManager.getBestIPForSending({
from: 'test@example.com',
to: ['recipient@test.com'],
domain: 'example.com'
});
const thirdIP = ipWarmupManager.getBestIPForSending({
from: 'test@example.com',
to: ['recipient@test.com'],
domain: 'example.com'
});
// Round robin should give us different IPs for consecutive calls
expect(firstIP !== secondIP).toEqual(true);
// With 3 IPs, the fourth call should cycle back to one of the IPs
const fourthIP = ipWarmupManager.getBestIPForSending({
from: 'test@example.com',
to: ['recipient@test.com'],
domain: 'example.com'
});
// Check that the fourth IP is one of the 3 valid IPs
expect(['192.168.1.1', '192.168.1.2', '192.168.1.3'].includes(fourthIP)).toEqual(true);
});
// Test dedicated domain allocation policy
tap.test('should allocate IPs using dedicated domain policy', async () => {
resetSingleton();
const ipWarmupManager = IPWarmupManager.getInstance({
enabled: true,
ipAddresses: ['192.168.1.1', '192.168.1.2', '192.168.1.3'],
targetDomains: ['example.com', 'test.com', 'other.com']
// Remove allocationPolicy which is not in the interface
});
ipWarmupManager.setActiveAllocationPolicy('dedicated');
// Instead of mapDomainToIP which doesn't exist, we'll simulate domain mapping
// by making dedicated calls per domain - we can't call the internal method directly
// Each domain should get its dedicated IP
const exampleIP = ipWarmupManager.getBestIPForSending({
from: 'test@example.com',
to: ['recipient@gmail.com'],
domain: 'example.com'
});
const testIP = ipWarmupManager.getBestIPForSending({
from: 'test@test.com',
to: ['recipient@gmail.com'],
domain: 'test.com'
});
const otherIP = ipWarmupManager.getBestIPForSending({
from: 'test@other.com',
to: ['recipient@gmail.com'],
domain: 'other.com'
});
// Since we're not actually mapping domains to IPs, we can only test if they return valid IPs
// The original assertions have been modified since we can't guarantee which IP will be returned
expect(exampleIP).toBeTruthy();
expect(testIP).toBeTruthy();
expect(otherIP).toBeTruthy();
});
// Test daily sending limits
tap.test('should enforce daily sending limits', async () => {
resetSingleton();
const ipWarmupManager = IPWarmupManager.getInstance({
enabled: true,
ipAddresses: ['192.168.1.1'],
targetDomains: ['example.com']
// Remove allocationPolicy which is not in the interface
});
// Override the warmup stage for testing
// @ts-ignore - accessing private method for testing
ipWarmupManager.warmupStatuses.set('192.168.1.1', {
ipAddress: '192.168.1.1',
isActive: true,
currentStage: 1,
startDate: new Date(),
currentStageStartDate: new Date(),
targetCompletionDate: new Date(),
currentDailyAllocation: 5,
sentInCurrentStage: 0,
totalSent: 0,
dailyStats: [],
metrics: {
openRate: 0,
bounceRate: 0,
complaintRate: 0
}
});
// Set a very low daily limit for testing
// @ts-ignore - accessing private method for testing
ipWarmupManager.config.stages = [
{ stage: 1, maxDailyVolume: 5, durationDays: 5, targetMetrics: { maxBounceRate: 8, minOpenRate: 15 } }
];
// First pass: should be able to get an IP
const ip = ipWarmupManager.getBestIPForSending({
from: 'test@example.com',
to: ['recipient@test.com'],
domain: 'example.com'
});
expect(ip === '192.168.1.1').toEqual(true);
// Record 5 sends to reach the daily limit
for (let i = 0; i < 5; i++) {
ipWarmupManager.recordSend('192.168.1.1');
}
// Check if we can send more today
const canSendMore = ipWarmupManager.canSendMoreToday('192.168.1.1');
expect(canSendMore).toEqual(false);
// After reaching limit, getBestIPForSending should return null
// since there are no available IPs
const sixthIP = ipWarmupManager.getBestIPForSending({
from: 'test@example.com',
to: ['recipient@test.com'],
domain: 'example.com'
});
expect(sixthIP === null).toEqual(true);
});
// Test recording sends
tap.test('should record send events correctly', async () => {
resetSingleton();
const ipWarmupManager = IPWarmupManager.getInstance({
enabled: true,
ipAddresses: ['192.168.1.1', '192.168.1.2'],
targetDomains: ['example.com'],
});
// Set allocation policy
ipWarmupManager.setActiveAllocationPolicy('balanced');
// Get an IP for sending
const ip = ipWarmupManager.getBestIPForSending({
from: 'test@example.com',
to: ['recipient@test.com'],
domain: 'example.com'
});
// If we got an IP, record some sends
if (ip) {
// Record a few sends
for (let i = 0; i < 5; i++) {
ipWarmupManager.recordSend(ip);
}
// Check if we can still send more
const canSendMore = ipWarmupManager.canSendMoreToday(ip);
expect(typeof canSendMore).toEqual('boolean');
}
});
// Test that DedicatedDomainPolicy assigns IPs correctly
tap.test('should assign IPs using dedicated domain policy', async () => {
resetSingleton();
const ipWarmupManager = IPWarmupManager.getInstance({
enabled: true,
ipAddresses: ['192.168.1.1', '192.168.1.2', '192.168.1.3'],
targetDomains: ['example.com', 'test.com', 'other.com']
});
// Set allocation policy to dedicated domains
ipWarmupManager.setActiveAllocationPolicy('dedicated');
// Check allocation by querying for different domains
const ip1 = ipWarmupManager.getBestIPForSending({
from: 'test@example.com',
to: ['recipient@test.com'],
domain: 'example.com'
});
const ip2 = ipWarmupManager.getBestIPForSending({
from: 'test@test.com',
to: ['recipient@test.com'],
domain: 'test.com'
});
// If we got IPs, they should be consistently assigned
if (ip1 && ip2) {
// Requesting the same domain again should return the same IP
const ip1again = ipWarmupManager.getBestIPForSending({
from: 'another@example.com',
to: ['recipient@test.com'],
domain: 'example.com'
});
expect(ip1again === ip1).toEqual(true);
}
});
// After all tests, clean up
tap.test('cleanup', async () => {
cleanupTestData();
});
tap.test('stop', async () => {
await tap.stopForcefully();
});
export default tap.start();

View File

@@ -1,130 +0,0 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DcRouter } from '../ts/index.ts';
import { TypedRequest } from '@api.global/typedrequest';
import * as interfaces from '../ts_interfaces/index.ts';
let testDcRouter: DcRouter;
let identity: interfaces.data.IIdentity;
tap.test('should start DCRouter with OpsServer', async () => {
testDcRouter = new DcRouter({
// Minimal config for testing
});
await testDcRouter.start();
expect(testDcRouter.opsServer).toBeInstanceOf(Object);
});
tap.test('should login with admin credentials and receive JWT', async () => {
const loginRequest = new TypedRequest<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
'http://localhost:3000/typedrequest',
'adminLoginWithUsernameAndPassword'
);
const response = await loginRequest.fire({
username: 'admin',
password: 'admin'
});
expect(response).toHaveProperty('identity');
expect(response.identity).toHaveProperty('jwt');
expect(response.identity).toHaveProperty('userId');
expect(response.identity).toHaveProperty('name');
expect(response.identity).toHaveProperty('expiresAt');
expect(response.identity).toHaveProperty('role');
expect(response.identity.role).toEqual('admin');
identity = response.identity;
console.log('JWT:', identity.jwt);
});
tap.test('should verify valid JWT identity', async () => {
const verifyRequest = new TypedRequest<interfaces.requests.IReq_VerifyIdentity>(
'http://localhost:3000/typedrequest',
'verifyIdentity'
);
const response = await verifyRequest.fire({
identity
});
expect(response).toHaveProperty('valid');
expect(response.valid).toBeTrue();
expect(response).toHaveProperty('identity');
expect(response.identity.userId).toEqual(identity.userId);
});
tap.test('should reject invalid JWT', async () => {
const verifyRequest = new TypedRequest<interfaces.requests.IReq_VerifyIdentity>(
'http://localhost:3000/typedrequest',
'verifyIdentity'
);
const response = await verifyRequest.fire({
identity: {
...identity,
jwt: 'invalid.jwt.token'
}
});
expect(response).toHaveProperty('valid');
expect(response.valid).toBeFalse();
});
tap.test('should verify JWT matches identity data', async () => {
const verifyRequest = new TypedRequest<interfaces.requests.IReq_VerifyIdentity>(
'http://localhost:3000/typedrequest',
'verifyIdentity'
);
// The response should contain the same identity data as the JWT
const response = await verifyRequest.fire({
identity
});
expect(response).toHaveProperty('valid');
expect(response.valid).toBeTrue();
expect(response.identity.expiresAt).toEqual(identity.expiresAt);
expect(response.identity.userId).toEqual(identity.userId);
});
tap.test('should handle logout', async () => {
const logoutRequest = new TypedRequest<interfaces.requests.IReq_AdminLogout>(
'http://localhost:3000/typedrequest',
'adminLogout'
);
const response = await logoutRequest.fire({
identity
});
expect(response).toHaveProperty('success');
expect(response.success).toBeTrue();
});
tap.test('should reject wrong credentials', async () => {
const loginRequest = new TypedRequest<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
'http://localhost:3000/typedrequest',
'adminLoginWithUsernameAndPassword'
);
let errorOccurred = false;
try {
await loginRequest.fire({
username: 'admin',
password: 'wrongpassword'
});
} catch (error) {
errorOccurred = true;
// TypedResponseError is thrown
expect(error).toBeTruthy();
}
expect(errorOccurred).toBeTrue();
});
tap.test('should stop DCRouter', async () => {
await testDcRouter.stop();
});
export default tap.start();

View File

@@ -1,66 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../ts/plugins.ts';
import * as paths from '../ts/paths.ts';
import { SenderReputationMonitor } from '../ts/deliverability/classes.senderreputationmonitor.ts';
import { IPWarmupManager } from '../ts/deliverability/classes.ipwarmupmanager.ts';
/**
* Basic test to check if our integrated classes work correctly
*/
tap.test('verify that SenderReputationMonitor and IPWarmupManager are functioning', async (tools) => {
// Create instances of both classes
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: true,
domains: ['example.com']
});
const ipWarmupManager = IPWarmupManager.getInstance({
enabled: true,
ipAddresses: ['192.168.1.1', '192.168.1.2'],
targetDomains: ['example.com']
});
// Test SenderReputationMonitor
reputationMonitor.recordSendEvent('example.com', { type: 'sent', count: 100 });
reputationMonitor.recordSendEvent('example.com', { type: 'delivered', count: 95 });
const reputationData = reputationMonitor.getReputationData('example.com');
const summary = reputationMonitor.getReputationSummary();
// Basic checks
expect(reputationData).toBeTruthy();
expect(summary.length).toBeGreaterThan(0);
// Add and remove domains
reputationMonitor.addDomain('test.com');
reputationMonitor.removeDomain('test.com');
// Test IPWarmupManager
ipWarmupManager.setActiveAllocationPolicy('balanced');
const bestIP = ipWarmupManager.getBestIPForSending({
from: 'test@example.com',
to: ['recipient@test.com'],
domain: 'example.com'
});
if (bestIP) {
ipWarmupManager.recordSend(bestIP);
const canSendMore = ipWarmupManager.canSendMoreToday(bestIP);
expect(canSendMore !== undefined).toEqual(true);
}
const stageCount = ipWarmupManager.getStageCount();
expect(stageCount).toBeGreaterThan(0);
});
// Final clean-up test
tap.test('clean up after tests', async () => {
// No-op - just to make sure everything is cleaned up properly
});
tap.test('stop', async () => {
await tap.stopForcefully();
});
export default tap.start();

View File

@@ -1,83 +0,0 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DcRouter } from '../ts/index.ts';
import { TypedRequest } from '@api.global/typedrequest';
import * as interfaces from '../ts_interfaces/index.ts';
let testDcRouter: DcRouter;
tap.test('should start DCRouter with OpsServer', async () => {
testDcRouter = new DcRouter({
// Minimal config for testing
});
await testDcRouter.start();
expect(testDcRouter.opsServer).toBeInstanceOf(Object);
});
tap.test('should respond to health status request', async () => {
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
'http://localhost:3000/typedrequest',
'getHealthStatus'
);
const response = await healthRequest.fire({
detailed: false
});
expect(response).toHaveProperty('health');
expect(response.health.healthy).toBeTrue();
expect(response.health.services).toHaveProperty('OpsServer');
});
tap.test('should respond to server statistics request', async () => {
const statsRequest = new TypedRequest<interfaces.requests.IReq_GetServerStatistics>(
'http://localhost:3000/typedrequest',
'getServerStatistics'
);
const response = await statsRequest.fire({
includeHistory: false
});
expect(response).toHaveProperty('stats');
expect(response.stats).toHaveProperty('uptime');
expect(response.stats).toHaveProperty('cpuUsage');
expect(response.stats).toHaveProperty('memoryUsage');
});
tap.test('should respond to configuration request', async () => {
const configRequest = new TypedRequest<interfaces.requests.IReq_GetConfiguration>(
'http://localhost:3000/typedrequest',
'getConfiguration'
);
const response = await configRequest.fire({});
expect(response).toHaveProperty('config');
expect(response.config).toHaveProperty('email');
expect(response.config).toHaveProperty('dns');
expect(response.config).toHaveProperty('proxy');
expect(response.config).toHaveProperty('security');
});
tap.test('should handle log retrieval request', async () => {
const logsRequest = new TypedRequest<interfaces.requests.IReq_GetRecentLogs>(
'http://localhost:3000/typedrequest',
'getRecentLogs'
);
const response = await logsRequest.fire({
limit: 10
});
expect(response).toHaveProperty('logs');
expect(response).toHaveProperty('total');
expect(response).toHaveProperty('hasMore');
expect(response.logs).toBeArray();
});
tap.test('should stop DCRouter', async () => {
await testDcRouter.stop();
});
export default tap.start();

View File

@@ -1,115 +0,0 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DcRouter } from '../ts/index.ts';
import { TypedRequest } from '@api.global/typedrequest';
import * as interfaces from '../ts_interfaces/index.ts';
let testDcRouter: DcRouter;
let adminIdentity: interfaces.data.IIdentity;
tap.test('should start DCRouter with OpsServer', async () => {
testDcRouter = new DcRouter({
// Minimal config for testing
});
await testDcRouter.start();
expect(testDcRouter.opsServer).toBeInstanceOf(Object);
});
tap.test('should login as admin', async () => {
const loginRequest = new TypedRequest<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
'http://localhost:3000/typedrequest',
'adminLoginWithUsernameAndPassword'
);
const response = await loginRequest.fire({
username: 'admin',
password: 'admin'
});
expect(response).toHaveProperty('identity');
adminIdentity = response.identity;
console.log('Admin logged in with JWT');
});
tap.test('should allow admin to update configuration', async () => {
const updateRequest = new TypedRequest<interfaces.requests.IReq_UpdateConfiguration>(
'http://localhost:3000/typedrequest',
'updateConfiguration'
);
const response = await updateRequest.fire({
identity: adminIdentity,
section: 'security',
config: {
rateLimit: true,
spamDetection: true
}
});
expect(response).toHaveProperty('updated');
expect(response.updated).toBeTrue();
});
tap.test('should reject configuration update without identity', async () => {
const updateRequest = new TypedRequest<interfaces.requests.IReq_UpdateConfiguration>(
'http://localhost:3000/typedrequest',
'updateConfiguration'
);
try {
await updateRequest.fire({
section: 'security',
config: {
rateLimit: false
}
});
expect(true).toBeFalse(); // Should not reach here
} catch (error) {
expect(error).toBeTruthy();
console.log('Successfully rejected request without identity');
}
});
tap.test('should reject configuration update with invalid JWT', async () => {
const updateRequest = new TypedRequest<interfaces.requests.IReq_UpdateConfiguration>(
'http://localhost:3000/typedrequest',
'updateConfiguration'
);
try {
await updateRequest.fire({
identity: {
...adminIdentity,
jwt: 'invalid.jwt.token'
},
section: 'security',
config: {
rateLimit: false
}
});
expect(true).toBeFalse(); // Should not reach here
} catch (error) {
expect(error).toBeTruthy();
console.log('Successfully rejected request with invalid JWT');
}
});
tap.test('should allow access to public endpoints without auth', async () => {
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
'http://localhost:3000/typedrequest',
'getHealthStatus'
);
// No identity provided
const response = await healthRequest.fire({});
expect(response).toHaveProperty('health');
expect(response.health.healthy).toBeTrue();
console.log('Public endpoint accessible without auth');
});
tap.test('should stop DCRouter', async () => {
await testDcRouter.stop();
});
export default tap.start();

View File

@@ -1,262 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../ts/plugins.ts';
import * as paths from '../ts/paths.ts';
import { SenderReputationMonitor } from '../ts/deliverability/classes.senderreputationmonitor.ts';
// Set NODE_ENV to test to prevent loading persisted data
process.env.NODE_ENV = 'test';
// Cleanup any temporary test data
const cleanupTestData = () => {
const reputationDataPath = plugins.path.join(paths.dataDir, 'reputation');
if (plugins.fs.existsSync(reputationDataPath)) {
// Remove the directory recursively using fs instead of smartfile
plugins.fs.rmSync(reputationDataPath, { recursive: true, force: true });
}
};
// Helper to reset the singleton instance between tests
const resetSingleton = () => {
// @ts-ignore - accessing private static field for testing
SenderReputationMonitor.instance = null;
// Clean up any timeout to prevent race conditions
const activeSendReputationMonitors = Array.from(Object.values(global))
.filter((item: any) => item && typeof item === 'object' && item._idleTimeout)
.filter((item: any) =>
item._onTimeout &&
item._onTimeout.toString &&
item._onTimeout.toString().includes('updateAllDomainMetrics'));
// Clear any active timeouts to prevent race conditions
activeSendReputationMonitors.forEach((timer: any) => {
clearTimeout(timer);
});
};
// Before running any tests
tap.test('setup', async () => {
resetSingleton();
cleanupTestData();
});
// Test initialization of SenderReputationMonitor
tap.test('should initialize SenderReputationMonitor with default settings', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance();
expect(reputationMonitor).toBeTruthy();
// Check if the object has the expected methods
expect(typeof reputationMonitor.recordSendEvent).toEqual('function');
expect(typeof reputationMonitor.getReputationData).toEqual('function');
expect(typeof reputationMonitor.getReputationSummary).toEqual('function');
});
// Test initialization with custom settings
tap.test('should initialize SenderReputationMonitor with custom settings', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: false, // Disable automatic updates to prevent race conditions
domains: ['example.com', 'test.com'],
updateFrequency: 12 * 60 * 60 * 1000, // 12 hours
alertThresholds: {
minReputationScore: 80,
maxComplaintRate: 0.05
}
});
// Test adding domains
reputationMonitor.addDomain('newdomain.com');
// Test retrieving reputation data
const data = reputationMonitor.getReputationData('example.com');
expect(data).toBeTruthy();
expect(data.domain).toEqual('example.com');
});
// Test recording and tracking send events
tap.test('should record send events and update metrics', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: false, // Disable automatic updates to prevent race conditions
domains: ['example.com']
});
// Record a series of events
reputationMonitor.recordSendEvent('example.com', { type: 'sent', count: 100 });
reputationMonitor.recordSendEvent('example.com', { type: 'delivered', count: 95 });
reputationMonitor.recordSendEvent('example.com', { type: 'bounce', hardBounce: true, count: 3 });
reputationMonitor.recordSendEvent('example.com', { type: 'bounce', hardBounce: false, count: 2 });
reputationMonitor.recordSendEvent('example.com', { type: 'complaint', count: 1 });
// Check metrics
const metrics = reputationMonitor.getReputationData('example.com');
expect(metrics).toBeTruthy();
expect(metrics.volume.sent).toEqual(100);
expect(metrics.volume.delivered).toEqual(95);
expect(metrics.volume.hardBounces).toEqual(3);
expect(metrics.volume.softBounces).toEqual(2);
expect(metrics.complaints.total).toEqual(1);
});
// Test reputation score calculation
tap.test('should calculate reputation scores correctly', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: false, // Disable automatic updates to prevent race conditions
domains: ['high.com', 'medium.com', 'low.com']
});
// Record events for different domains
reputationMonitor.recordSendEvent('high.com', { type: 'sent', count: 1000 });
reputationMonitor.recordSendEvent('high.com', { type: 'delivered', count: 990 });
reputationMonitor.recordSendEvent('high.com', { type: 'open', count: 500 });
reputationMonitor.recordSendEvent('medium.com', { type: 'sent', count: 1000 });
reputationMonitor.recordSendEvent('medium.com', { type: 'delivered', count: 950 });
reputationMonitor.recordSendEvent('medium.com', { type: 'open', count: 300 });
reputationMonitor.recordSendEvent('low.com', { type: 'sent', count: 1000 });
reputationMonitor.recordSendEvent('low.com', { type: 'delivered', count: 850 });
reputationMonitor.recordSendEvent('low.com', { type: 'open', count: 100 });
// Get reputation summary
const summary = reputationMonitor.getReputationSummary();
expect(Array.isArray(summary)).toEqual(true);
expect(summary.length >= 3).toEqual(true);
// Check that domains are included in the summary
const domains = summary.map(item => item.domain);
expect(domains.includes('high.com')).toEqual(true);
expect(domains.includes('medium.com')).toEqual(true);
expect(domains.includes('low.com')).toEqual(true);
});
// Test adding and removing domains
tap.test('should add and remove domains for monitoring', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: false, // Disable automatic updates to prevent race conditions
domains: ['example.com']
});
// Add a new domain
reputationMonitor.addDomain('newdomain.com');
// Record data for the new domain
reputationMonitor.recordSendEvent('newdomain.com', { type: 'sent', count: 50 });
// Check that data was recorded for the new domain
const metrics = reputationMonitor.getReputationData('newdomain.com');
expect(metrics).toBeTruthy();
expect(metrics.volume.sent).toEqual(50);
// Remove a domain
reputationMonitor.removeDomain('newdomain.com');
// Check that data is no longer available
const removedMetrics = reputationMonitor.getReputationData('newdomain.com');
expect(removedMetrics === null).toEqual(true);
});
// Test handling open and click events
tap.test('should track engagement metrics correctly', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: false, // Disable automatic updates to prevent race conditions
domains: ['example.com']
});
// Record basic sending metrics
reputationMonitor.recordSendEvent('example.com', { type: 'sent', count: 1000 });
reputationMonitor.recordSendEvent('example.com', { type: 'delivered', count: 950 });
// Record engagement events
reputationMonitor.recordSendEvent('example.com', { type: 'open', count: 500 });
reputationMonitor.recordSendEvent('example.com', { type: 'click', count: 250 });
// Check engagement metrics
const metrics = reputationMonitor.getReputationData('example.com');
expect(metrics).toBeTruthy();
expect(metrics.engagement.opens).toEqual(500);
expect(metrics.engagement.clicks).toEqual(250);
expect(typeof metrics.engagement.openRate).toEqual('number');
expect(typeof metrics.engagement.clickRate).toEqual('number');
});
// Test historical data tracking
tap.test('should store historical reputation data', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: false, // Disable automatic updates to prevent race conditions
domains: ['example.com']
});
// Record events over multiple days
const today = new Date();
const todayStr = today.toISOString().split('T')[0];
// Record data
reputationMonitor.recordSendEvent('example.com', { type: 'sent', count: 1000 });
reputationMonitor.recordSendEvent('example.com', { type: 'delivered', count: 950 });
// Get metrics data
const metrics = reputationMonitor.getReputationData('example.com');
// Check that historical data exists
expect(metrics.historical).toBeTruthy();
expect(metrics.historical.reputationScores).toBeTruthy();
// Check that daily send volume is tracked
expect(metrics.volume.dailySendVolume).toBeTruthy();
expect(metrics.volume.dailySendVolume[todayStr]).toEqual(1000);
});
// Test event recording for different event types
tap.test('should correctly handle different event types', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: false, // Disable automatic updates to prevent race conditions
domains: ['example.com']
});
// Record different types of events
reputationMonitor.recordSendEvent('example.com', { type: 'sent', count: 100 });
reputationMonitor.recordSendEvent('example.com', { type: 'delivered', count: 95 });
reputationMonitor.recordSendEvent('example.com', { type: 'bounce', hardBounce: true, count: 3 });
reputationMonitor.recordSendEvent('example.com', { type: 'bounce', hardBounce: false, count: 2 });
reputationMonitor.recordSendEvent('example.com', { type: 'complaint', receivingDomain: 'gmail.com', count: 1 });
reputationMonitor.recordSendEvent('example.com', { type: 'open', count: 50 });
reputationMonitor.recordSendEvent('example.com', { type: 'click', count: 25 });
// Check metrics for different event types
const metrics = reputationMonitor.getReputationData('example.com');
// Check volume metrics
expect(metrics.volume.sent).toEqual(100);
expect(metrics.volume.delivered).toEqual(95);
expect(metrics.volume.hardBounces).toEqual(3);
expect(metrics.volume.softBounces).toEqual(2);
// Check complaint metrics
expect(metrics.complaints.total).toEqual(1);
expect(metrics.complaints.topDomains[0].domain).toEqual('gmail.com');
// Check engagement metrics
expect(metrics.engagement.opens).toEqual(50);
expect(metrics.engagement.clicks).toEqual(25);
});
// After all tests, clean up
tap.test('cleanup', async () => {
resetSingleton();
cleanupTestData();
});
tap.test('stop', async () => {
await tap.stopForcefully();
});
export default tap.start();

View File

@@ -1,240 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import { DcRouter } from '../ts/classes.dcrouter.ts';
import * as plugins from '../ts/plugins.ts';
let dcRouter: DcRouter;
tap.test('should run both DNS and email with socket-handlers simultaneously', async () => {
dcRouter = new DcRouter({
dnsDomain: 'dns.integration.test',
emailConfig: {
ports: [25, 587, 465],
hostname: 'mail.integration.test',
domains: ['integration.test'],
routes: [],
useSocketHandler: true
},
smartProxyConfig: {
routes: []
}
});
await dcRouter.start();
// Verify both services are running
const dnsServer = (dcRouter as any).dnsServer;
const emailServer = (dcRouter as any).emailServer;
expect(dnsServer).toBeDefined();
expect(emailServer).toBeDefined();
// Verify SmartProxy has routes for both services
const smartProxy = (dcRouter as any).smartProxy;
const routes = smartProxy?.options?.routes || [];
// Count DNS routes
const dnsRoutes = routes.filter((route: any) =>
route.name?.includes('dns-over-https')
);
expect(dnsRoutes.length).toEqual(2);
// Count email routes
const emailRoutes = routes.filter((route: any) =>
route.name?.includes('-route') && !route.name?.includes('dns')
);
expect(emailRoutes.length).toEqual(3);
// All routes should be socket-handler type
[...dnsRoutes, ...emailRoutes].forEach((route: any) => {
expect(route.action.type).toEqual('socket-handler');
expect(route.action.socketHandler).toBeDefined();
});
await dcRouter.stop();
});
tap.test('should handle mixed configuration (DNS socket-handler, email traditional)', async () => {
dcRouter = new DcRouter({
dnsDomain: 'dns.mixed.test',
emailConfig: {
ports: [25, 587],
hostname: 'mail.mixed.test',
domains: ['mixed.test'],
routes: [],
useSocketHandler: false // Traditional mode
},
smartProxyConfig: {
routes: []
}
});
await dcRouter.start();
const smartProxy = (dcRouter as any).smartProxy;
const routes = smartProxy?.options?.routes || [];
// DNS routes should be socket-handler
const dnsRoutes = routes.filter((route: any) =>
route.name?.includes('dns-over-https')
);
dnsRoutes.forEach((route: any) => {
expect(route.action.type).toEqual('socket-handler');
});
// Email routes should be forward
const emailRoutes = routes.filter((route: any) =>
route.name?.includes('-route') && !route.name?.includes('dns')
);
emailRoutes.forEach((route: any) => {
expect(route.action.type).toEqual('forward');
expect(route.action.target.port).toBeGreaterThan(10000); // Internal port
});
await dcRouter.stop();
});
tap.test('should properly clean up resources on stop', async () => {
dcRouter = new DcRouter({
dnsDomain: 'dns.cleanup.test',
emailConfig: {
ports: [25],
hostname: 'mail.cleanup.test',
domains: ['cleanup.test'],
routes: [],
useSocketHandler: true
}
});
await dcRouter.start();
// Services should be running
expect((dcRouter as any).dnsServer).toBeDefined();
expect((dcRouter as any).emailServer).toBeDefined();
expect((dcRouter as any).smartProxy).toBeDefined();
await dcRouter.stop();
// After stop, services should still be defined but stopped
// (The stop method doesn't null out the properties, just stops the services)
expect((dcRouter as any).dnsServer).toBeDefined();
expect((dcRouter as any).emailServer).toBeDefined();
});
tap.test('should handle configuration updates correctly', async () => {
// Start with minimal config
dcRouter = new DcRouter({
smartProxyConfig: {
routes: []
}
});
await dcRouter.start();
// Initially no DNS or email
expect((dcRouter as any).dnsServer).toBeUndefined();
expect((dcRouter as any).emailServer).toBeUndefined();
// Update to add email config
await dcRouter.updateEmailConfig({
ports: [25],
hostname: 'mail.update.test',
domains: ['update.test'],
routes: [],
useSocketHandler: true
});
// Now email should be running
expect((dcRouter as any).emailServer).toBeDefined();
await dcRouter.stop();
});
tap.test('performance: socket-handler should not create internal listeners', async () => {
dcRouter = new DcRouter({
dnsDomain: 'dns.perf.test',
emailConfig: {
ports: [25, 587, 465],
hostname: 'mail.perf.test',
domains: ['perf.test'],
routes: [],
useSocketHandler: true
}
});
await dcRouter.start();
// Get the number of listeners before creating handlers
const eventCounts: { [key: string]: number } = {};
// DNS server should not have HTTPS listeners
const dnsServer = (dcRouter as any).dnsServer;
// The DNS server should exist but not bind to HTTPS port
expect(dnsServer).toBeDefined();
// Email server should not have any server listeners
const emailServer = (dcRouter as any).emailServer;
expect(emailServer.servers.length).toEqual(0);
await dcRouter.stop();
});
tap.test('should handle errors gracefully', async () => {
dcRouter = new DcRouter({
dnsDomain: 'dns.error.test',
emailConfig: {
ports: [25],
hostname: 'mail.error.test',
domains: ['error.test'],
routes: [],
useSocketHandler: true
}
});
await dcRouter.start();
// Test DNS error handling
const dnsHandler = (dcRouter as any).createDnsSocketHandler();
const errorSocket = new plugins.net.Socket();
let errorThrown = false;
try {
// This should handle the error gracefully
await dnsHandler(errorSocket);
} catch (error) {
errorThrown = true;
}
// Should not throw, should handle gracefully
expect(errorThrown).toBeFalsy();
await dcRouter.stop();
});
tap.test('should correctly identify secure connections', async () => {
dcRouter = new DcRouter({
emailConfig: {
ports: [465],
hostname: 'mail.secure.test',
domains: ['secure.test'],
routes: [],
useSocketHandler: true
}
});
await dcRouter.start();
// The email socket handler for port 465 should handle TLS
const handler = (dcRouter as any).createMailSocketHandler(465);
expect(handler).toBeDefined();
// Port 465 requires immediate TLS, which is handled in the socket handler
// This is different from ports 25/587 which use STARTTLS
await dcRouter.stop();
});
tap.test('stop', async () => {
await tap.stopForcefully();
});
export default tap.start();

View File

@@ -1,198 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import { DcRouter } from '../ts/classes.dcrouter.ts';
/**
* Unit tests for socket-handler functionality
* These tests focus on the configuration and route generation logic
* without actually starting services on real ports
*/
let dcRouter: DcRouter;
tap.test('DNS route generation with dnsDomain', async () => {
dcRouter = new DcRouter({
dnsDomain: 'dns.unit.test'
});
// Test the route generation directly
const dnsRoutes = (dcRouter as any).generateDnsRoutes();
expect(dnsRoutes).toBeDefined();
expect(dnsRoutes.length).toEqual(2);
// Check /dns-query route
const dnsQueryRoute = dnsRoutes[0];
expect(dnsQueryRoute.name).toEqual('dns-over-https-dns-query');
expect(dnsQueryRoute.match.ports).toEqual([443]);
expect(dnsQueryRoute.match.domains).toEqual(['dns.unit.test']);
expect(dnsQueryRoute.match.path).toEqual('/dns-query');
expect(dnsQueryRoute.action.type).toEqual('socket-handler');
expect(dnsQueryRoute.action.socketHandler).toBeDefined();
// Check /resolve route
const resolveRoute = dnsRoutes[1];
expect(resolveRoute.name).toEqual('dns-over-https-resolve');
expect(resolveRoute.match.ports).toEqual([443]);
expect(resolveRoute.match.domains).toEqual(['dns.unit.test']);
expect(resolveRoute.match.path).toEqual('/resolve');
expect(resolveRoute.action.type).toEqual('socket-handler');
expect(resolveRoute.action.socketHandler).toBeDefined();
});
tap.test('DNS route generation without dnsDomain', async () => {
dcRouter = new DcRouter({
// No dnsDomain set
});
const dnsRoutes = (dcRouter as any).generateDnsRoutes();
expect(dnsRoutes).toBeDefined();
expect(dnsRoutes.length).toEqual(0); // No routes generated
});
tap.test('Email route generation with socket-handler', async () => {
const emailConfig = {
ports: [25, 587, 465],
hostname: 'mail.unit.test',
domains: ['unit.test'],
routes: [],
useSocketHandler: true
};
dcRouter = new DcRouter({ emailConfig });
const emailRoutes = (dcRouter as any).generateEmailRoutes(emailConfig);
expect(emailRoutes).toBeDefined();
expect(emailRoutes.length).toEqual(3);
// Check all routes use socket-handler
emailRoutes.forEach((route: any) => {
expect(route.action.type).toEqual('socket-handler');
expect(route.action.socketHandler).toBeDefined();
expect(typeof route.action.socketHandler).toEqual('function');
});
// Check specific ports
const port25Route = emailRoutes.find((r: any) => r.match.ports[0] === 25);
expect(port25Route.name).toEqual('smtp-route');
const port587Route = emailRoutes.find((r: any) => r.match.ports[0] === 587);
expect(port587Route.name).toEqual('submission-route');
const port465Route = emailRoutes.find((r: any) => r.match.ports[0] === 465);
expect(port465Route.name).toEqual('smtps-route');
});
tap.test('Email route generation with traditional forwarding', async () => {
const emailConfig = {
ports: [25, 587],
hostname: 'mail.unit.test',
domains: ['unit.test'],
routes: [],
useSocketHandler: false // Traditional mode
};
dcRouter = new DcRouter({ emailConfig });
const emailRoutes = (dcRouter as any).generateEmailRoutes(emailConfig);
expect(emailRoutes).toBeDefined();
expect(emailRoutes.length).toEqual(2);
// Check all routes use forward action
emailRoutes.forEach((route: any) => {
expect(route.action.type).toEqual('forward');
expect(route.action.target).toBeDefined();
expect(route.action.target.host).toEqual('localhost');
expect(route.action.target.port).toBeGreaterThan(10000); // Internal port
});
});
tap.test('Email TLS modes are set correctly', async () => {
const emailConfig = {
ports: [25, 465],
hostname: 'mail.unit.test',
domains: ['unit.test'],
routes: [],
useSocketHandler: false
};
dcRouter = new DcRouter({ emailConfig });
const emailRoutes = (dcRouter as any).generateEmailRoutes(emailConfig);
// Port 25 should use passthrough (STARTTLS)
const port25Route = emailRoutes.find((r: any) => r.match.ports[0] === 25);
expect(port25Route.action.tls.mode).toEqual('passthrough');
// Port 465 should use terminate (implicit TLS)
const port465Route = emailRoutes.find((r: any) => r.match.ports[0] === 465);
expect(port465Route.action.tls.mode).toEqual('terminate');
expect(port465Route.action.tls.certificate).toEqual('auto');
});
tap.test('Combined DNS and email configuration', async () => {
dcRouter = new DcRouter({
dnsDomain: 'dns.combined.test',
emailConfig: {
ports: [25],
hostname: 'mail.combined.test',
domains: ['combined.test'],
routes: [],
useSocketHandler: true
}
});
// Generate both types of routes
const dnsRoutes = (dcRouter as any).generateDnsRoutes();
const emailRoutes = (dcRouter as any).generateEmailRoutes(dcRouter.options.emailConfig);
// Check DNS routes
expect(dnsRoutes.length).toEqual(2);
dnsRoutes.forEach((route: any) => {
expect(route.action.type).toEqual('socket-handler');
expect(route.match.domains).toEqual(['dns.combined.test']);
});
// Check email routes
expect(emailRoutes.length).toEqual(1);
expect(emailRoutes[0].action.type).toEqual('socket-handler');
expect(emailRoutes[0].match.ports).toEqual([25]);
});
tap.test('Socket handler functions are created correctly', async () => {
dcRouter = new DcRouter({
dnsDomain: 'dns.handler.test',
emailConfig: {
ports: [25, 465],
hostname: 'mail.handler.test',
domains: ['handler.test'],
routes: [],
useSocketHandler: true
}
});
// Test DNS socket handler creation
const dnsHandler = (dcRouter as any).createDnsSocketHandler();
expect(dnsHandler).toBeDefined();
expect(typeof dnsHandler).toEqual('function');
// Test email socket handler creation for different ports
const smtp25Handler = (dcRouter as any).createMailSocketHandler(25);
expect(smtp25Handler).toBeDefined();
expect(typeof smtp25Handler).toEqual('function');
const smtp465Handler = (dcRouter as any).createMailSocketHandler(465);
expect(smtp465Handler).toBeDefined();
expect(typeof smtp465Handler).toEqual('function');
// Handlers should be different functions
expect(smtp25Handler).not.toEqual(smtp465Handler);
});
tap.test('stop', async () => {
await tap.stopForcefully();
});
export default tap.start();

View File

@@ -1,289 +0,0 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../ts/plugins.ts';
import * as paths from '../ts/paths.ts';
import { StorageManager } from '../ts/storage/classes.storagemanager.ts';
import { promises as fs } from 'fs';
import * as path from 'path';
// Test data
const testData = {
string: 'Hello, World!',
json: { name: 'test', value: 42, nested: { data: true } },
largeString: 'x'.repeat(10000)
};
tap.test('Storage Manager - Memory Backend', async () => {
// Create StorageManager without config (defaults to memory)
const storage = new StorageManager();
// Test basic get/set
await storage.set('/test/key', testData.string);
const value = await storage.get('/test/key');
expect(value).toEqual(testData.string);
// Test JSON helpers
await storage.setJSON('/test/json', testData.json);
const jsonValue = await storage.getJSON('/test/json');
expect(jsonValue).toEqual(testData.json);
// Test exists
expect(await storage.exists('/test/key')).toEqual(true);
expect(await storage.exists('/nonexistent')).toEqual(false);
// Test delete
await storage.delete('/test/key');
expect(await storage.exists('/test/key')).toEqual(false);
// Test list
await storage.set('/items/1', 'one');
await storage.set('/items/2', 'two');
await storage.set('/other/3', 'three');
const items = await storage.list('/items');
expect(items.length).toEqual(2);
expect(items).toContain('/items/1');
expect(items).toContain('/items/2');
// Verify memory backend
expect(storage.getBackend()).toEqual('memory');
});
tap.test('Storage Manager - Filesystem Backend', async () => {
const testDir = path.join(paths.dataDir, '.test-storage');
// Clean up test directory if it exists
try {
await fs.rm(testDir, { recursive: true, force: true });
} catch {}
// Create StorageManager with filesystem path
const storage = new StorageManager({ fsPath: testDir });
// Test basic operations
await storage.set('/test/file', testData.string);
const value = await storage.get('/test/file');
expect(value).toEqual(testData.string);
// Verify file exists on disk
const filePath = path.join(testDir, 'test', 'file');
const fileExists = await fs.access(filePath).then(() => true).catch(() => false);
expect(fileExists).toEqual(true);
// Test atomic writes (temp file should not exist)
const tempPath = filePath + '.tmp';
const tempExists = await fs.access(tempPath).then(() => true).catch(() => false);
expect(tempExists).toEqual(false);
// Test nested paths
await storage.set('/deeply/nested/path/to/file', testData.largeString);
const nestedValue = await storage.get('/deeply/nested/path/to/file');
expect(nestedValue).toEqual(testData.largeString);
// Test list with filesystem
await storage.set('/fs/items/a', 'alpha');
await storage.set('/fs/items/b', 'beta');
await storage.set('/fs/other/c', 'gamma');
// Filesystem backend now properly supports list
const fsItems = await storage.list('/fs/items');
expect(fsItems.length).toEqual(2); // Should find both items
// Clean up
await fs.rm(testDir, { recursive: true, force: true });
});
tap.test('Storage Manager - Custom Function Backend', async () => {
// Create in-memory storage for custom functions
const customStore = new Map<string, string>();
const storage = new StorageManager({
readFunction: async (key: string) => {
return customStore.get(key) || null;
},
writeFunction: async (key: string, value: string) => {
customStore.set(key, value);
}
});
// Test basic operations
await storage.set('/custom/key', testData.string);
expect(customStore.has('/custom/key')).toEqual(true);
const value = await storage.get('/custom/key');
expect(value).toEqual(testData.string);
// Test that delete sets empty value (as per implementation)
await storage.delete('/custom/key');
expect(customStore.get('/custom/key')).toEqual('');
// Verify custom backend (filesystem is implemented as custom backend internally)
expect(storage.getBackend()).toEqual('custom');
});
tap.test('Storage Manager - Key Validation', async () => {
const storage = new StorageManager();
// Test key normalization
await storage.set('test/key', 'value1'); // Missing leading slash
const value1 = await storage.get('/test/key');
expect(value1).toEqual('value1');
// Test dangerous path elements are removed
await storage.set('/test/../danger/key', 'value2');
const value2 = await storage.get('/test/danger/key'); // .. is removed, not the whole path segment
expect(value2).toEqual('value2');
// Test multiple slashes are normalized
await storage.set('/test///multiple////slashes', 'value3');
const value3 = await storage.get('/test/multiple/slashes');
expect(value3).toEqual('value3');
// Test invalid keys throw errors
let emptyKeyError: Error | null = null;
try {
await storage.set('', 'value');
} catch (error) {
emptyKeyError = error as Error;
}
expect(emptyKeyError).toBeTruthy();
expect(emptyKeyError?.message).toEqual('Storage key must be a non-empty string');
let nullKeyError: Error | null = null;
try {
await storage.set(null as any, 'value');
} catch (error) {
nullKeyError = error as Error;
}
expect(nullKeyError).toBeTruthy();
expect(nullKeyError?.message).toEqual('Storage key must be a non-empty string');
});
tap.test('Storage Manager - Concurrent Access', async () => {
const storage = new StorageManager();
const promises: Promise<void>[] = [];
// Simulate concurrent writes
for (let i = 0; i < 100; i++) {
promises.push(storage.set(`/concurrent/key${i}`, `value${i}`));
}
await Promise.all(promises);
// Verify all writes succeeded
for (let i = 0; i < 100; i++) {
const value = await storage.get(`/concurrent/key${i}`);
expect(value).toEqual(`value${i}`);
}
// Test concurrent reads
const readPromises: Promise<string | null>[] = [];
for (let i = 0; i < 100; i++) {
readPromises.push(storage.get(`/concurrent/key${i}`));
}
const results = await Promise.all(readPromises);
for (let i = 0; i < 100; i++) {
expect(results[i]).toEqual(`value${i}`);
}
});
tap.test('Storage Manager - Backend Priority', async () => {
const testDir = path.join(paths.dataDir, '.test-storage-priority');
// Test that custom functions take priority over fsPath
let warningLogged = false;
const originalWarn = console.warn;
console.warn = (message: string) => {
if (message.includes('Using custom read/write functions')) {
warningLogged = true;
}
};
const storage = new StorageManager({
fsPath: testDir,
readFunction: async () => 'custom-value',
writeFunction: async () => {}
});
console.warn = originalWarn;
expect(warningLogged).toEqual(true);
expect(storage.getBackend()).toEqual('custom'); // Custom functions take priority
// Clean up
try {
await fs.rm(testDir, { recursive: true, force: true });
} catch {}
});
tap.test('Storage Manager - Error Handling', async () => {
// Test filesystem errors
const storage = new StorageManager({
readFunction: async () => {
throw new Error('Read error');
},
writeFunction: async () => {
throw new Error('Write error');
}
});
// Read errors should return null
const value = await storage.get('/error/key');
expect(value).toEqual(null);
// Write errors should propagate
let writeError: Error | null = null;
try {
await storage.set('/error/key', 'value');
} catch (error) {
writeError = error as Error;
}
expect(writeError).toBeTruthy();
expect(writeError?.message).toEqual('Write error');
// Test JSON parse errors
const jsonStorage = new StorageManager({
readFunction: async () => 'invalid json',
writeFunction: async () => {}
});
// Test JSON parse errors
let jsonError: Error | null = null;
try {
await jsonStorage.getJSON('/invalid/json');
} catch (error) {
jsonError = error as Error;
}
expect(jsonError).toBeTruthy();
expect(jsonError?.message).toContain('JSON');
});
tap.test('Storage Manager - List Operations', async () => {
const storage = new StorageManager();
// Populate storage with hierarchical data
await storage.set('/app/config/database', 'db-config');
await storage.set('/app/config/cache', 'cache-config');
await storage.set('/app/data/users/1', 'user1');
await storage.set('/app/data/users/2', 'user2');
await storage.set('/app/logs/error.log', 'errors');
// List root
const rootItems = await storage.list('/');
expect(rootItems.length).toBeGreaterThanOrEqual(5);
// List specific paths
const configItems = await storage.list('/app/config');
expect(configItems.length).toEqual(2);
expect(configItems).toContain('/app/config/database');
expect(configItems).toContain('/app/config/cache');
const userItems = await storage.list('/app/data/users');
expect(userItems.length).toEqual(2);
// List non-existent path
const emptyList = await storage.list('/nonexistent/path');
expect(emptyList.length).toEqual(0);
});
export default tap.start();