This commit is contained in:
2025-05-24 01:00:30 +00:00
parent cb52446f65
commit f2e9ff0a51
38 changed files with 223 additions and 175 deletions

View File

@@ -49,9 +49,9 @@ export async function startTestServer(config: ITestServerConfig): Promise<ITestS
} }
} as any; } as any;
// Load test certificates if TLS is enabled // Load test certificates
let key: string | undefined; let key: string;
let cert: string | undefined; let cert: string;
if (serverConfig.tlsEnabled) { if (serverConfig.tlsEnabled) {
try { try {
@@ -89,6 +89,34 @@ export async function startTestServer(config: ITestServerConfig): Promise<ITestS
cert = pki.certificateToPem(certificate); cert = pki.certificateToPem(certificate);
key = pki.privateKeyToPem(keys.privateKey); key = pki.privateKeyToPem(keys.privateKey);
} }
} else {
// Always provide a self-signed certificate for non-TLS servers
// This is required by the interface
const forge = await import('node-forge');
const pki = forge.pki;
// Generate key pair
const keys = pki.rsa.generateKeyPair(2048);
// Create certificate
const certificate = pki.createCertificate();
certificate.publicKey = keys.publicKey;
certificate.serialNumber = '01';
certificate.validity.notBefore = new Date();
certificate.validity.notAfter = new Date();
certificate.validity.notAfter.setFullYear(certificate.validity.notBefore.getFullYear() + 1);
const attrs = [{
name: 'commonName',
value: serverConfig.hostname
}];
certificate.setSubject(attrs);
certificate.setIssuer(attrs);
certificate.sign(keys.privateKey);
// Convert to PEM
cert = pki.certificateToPem(certificate);
key = pki.privateKeyToPem(keys.privateKey);
} }
// SMTP server options // SMTP server options
@@ -103,7 +131,10 @@ export async function startTestServer(config: ITestServerConfig): Promise<ITestS
socketTimeout: serverConfig.timeout, socketTimeout: serverConfig.timeout,
connectionTimeout: serverConfig.timeout * 2, connectionTimeout: serverConfig.timeout * 2,
cleanupInterval: 300000, cleanupInterval: 300000,
auth: serverConfig.authRequired auth: serverConfig.authRequired ? {
required: true,
methods: ['PLAIN', 'LOGIN'] as ('PLAIN' | 'LOGIN' | 'OAUTH2')[]
} : undefined
}; };
// Create SMTP server // Create SMTP server

View File

@@ -1,5 +1,6 @@
import { SmtpClient } from '../../ts/mail/delivery/classes.smtp.client.js'; import { SmtpClient } from '../../ts/mail/delivery/classes.smtp.client.js';
import type { ISmtpClientOptions } from '../../ts/mail/delivery/smtpclient/interfaces.js'; import type { ISmtpClientOptions } from '../../ts/mail/delivery/smtpclient/interfaces.js';
import { Email } from '../../ts/mail/core/classes.email.js';
/** /**
* Create a test SMTP client * Create a test SMTP client
@@ -10,18 +11,11 @@ export function createTestSmtpClient(options: Partial<ISmtpClientOptions> = {}):
port: options.port || 2525, port: options.port || 2525,
secure: options.secure || false, secure: options.secure || false,
auth: options.auth, auth: options.auth,
ignoreTLS: options.ignoreTLS || true,
requireTLS: options.requireTLS || false,
connectionTimeout: options.connectionTimeout || 5000, connectionTimeout: options.connectionTimeout || 5000,
socketTimeout: options.socketTimeout || 5000, socketTimeout: options.socketTimeout || 5000,
greetingTimeout: options.greetingTimeout || 5000,
maxConnections: options.maxConnections || 5, maxConnections: options.maxConnections || 5,
maxMessages: options.maxMessages || 100, maxMessages: options.maxMessages || 100,
rateDelta: options.rateDelta || 1000,
rateLimit: options.rateLimit || 5,
logger: options.logger || false,
debug: options.debug || false, debug: options.debug || false,
authMethod: options.authMethod || 'PLAIN',
tls: options.tls || { tls: options.tls || {
rejectUnauthorized: false rejectUnauthorized: false
} }
@@ -51,7 +45,14 @@ export async function sendTestEmail(
html: options.html html: options.html
}; };
return client.sendMail(mailOptions); const email = new Email({
from: mailOptions.from,
to: mailOptions.to,
subject: mailOptions.subject,
text: mailOptions.text,
html: mailOptions.html
});
return client.sendMail(email);
} }
/** /**
@@ -95,11 +96,10 @@ export function createAuthenticatedClient(
port, port,
auth: { auth: {
user: username, user: username,
pass: password pass: password,
method: authMethod
}, },
authMethod, secure: false
secure: false,
ignoreTLS: true
}); });
} }
@@ -111,7 +111,6 @@ export function createTlsClient(
port: number, port: number,
options: { options: {
secure?: boolean; secure?: boolean;
requireTLS?: boolean;
rejectUnauthorized?: boolean; rejectUnauthorized?: boolean;
} = {} } = {}
): SmtpClient { ): SmtpClient {
@@ -119,8 +118,6 @@ export function createTlsClient(
host, host,
port, port,
secure: options.secure || false, secure: options.secure || false,
requireTLS: options.requireTLS || false,
ignoreTLS: false,
tls: { tls: {
rejectUnauthorized: options.rejectUnauthorized || false rejectUnauthorized: options.rejectUnauthorized || false
} }

View File

@@ -1,15 +1,14 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
const TEST_TIMEOUT = 30000; const TEST_TIMEOUT = 30000;
let testServer: SmtpServer; let testServer: ITestServer;
tap.test('setup - start SMTP server for abrupt disconnection tests', async () => { tap.test('setup - start SMTP server for abrupt disconnection tests', async () => {
testServer = testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
}); });

View File

@@ -1,17 +1,16 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; import type { ITestServer } from '../../helpers/server.loader.js';
// Test configuration // Test configuration
const TEST_PORT = 2525; const TEST_PORT = 2525;
const TEST_TIMEOUT = 5000; const TEST_TIMEOUT = 5000;
let testServer: SmtpServer; let testServer: ITestServer;
// Setup // Setup
tap.test('setup - start SMTP server', async () => { tap.test('setup - start SMTP server', async () => {
testServer = testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
}); });

View File

@@ -1,15 +1,14 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
const TEST_TIMEOUT = 30000; const TEST_TIMEOUT = 30000;
let testServer: SmtpServer; let testServer: ITestServer;
tap.test('setup - start SMTP server for connection rejection tests', async () => { tap.test('setup - start SMTP server for connection rejection tests', async () => {
testServer = testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
}); });

View File

@@ -1,9 +1,8 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js';
import { createConcurrentConnections, performSmtpHandshake, closeSmtpConnection } from '../../helpers/utils.js'; import { createConcurrentConnections, performSmtpHandshake, closeSmtpConnection } from '../../helpers/utils.js';
let testServer: SmtpServer; let testServer: ITestServer;
const CONCURRENT_COUNT = 10; const CONCURRENT_COUNT = 10;
const TEST_PORT = 2527; const TEST_PORT = 2527;

View File

@@ -1,9 +1,8 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js';
import { connectToSmtp, performSmtpHandshake, closeSmtpConnection } from '../../helpers/utils.js'; import { connectToSmtp, performSmtpHandshake, closeSmtpConnection } from '../../helpers/utils.js';
let testServer: SmtpServer; let testServer: ITestServer;
tap.test('setup - start SMTP server with TLS support', async () => { tap.test('setup - start SMTP server with TLS support', async () => {
testServer = await startTestServer({ testServer = await startTestServer({

View File

@@ -1,10 +1,9 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; import type { ITestServer } from '../../helpers/server.loader.js';
let testServer: ITestServer;
let testServer: SmtpServer;
const TEST_PORT = 2525; const TEST_PORT = 2525;
tap.test('setup - start test server', async () => { tap.test('setup - start test server', async () => {

View File

@@ -2,13 +2,12 @@ import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net'; import * as net from 'net';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
const SAMPLE_FILES_DIR = path.join(process.cwd(), '.nogit', 'sample-files'); const SAMPLE_FILES_DIR = path.join(process.cwd(), '.nogit', 'sample-files');
let testServer: SmtpServer; let testServer: ITestServer;
// Helper function to read and encode files // Helper function to read and encode files
function readFileAsBase64(filePath: string): string { function readFileAsBase64(filePath: string): string {

View File

@@ -1,17 +1,16 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; import type { ITestServer } from '../../helpers/server.loader.js';
// Test configuration // Test configuration
const TEST_PORT = 2525; const TEST_PORT = 2525;
const TEST_TIMEOUT = 15000; const TEST_TIMEOUT = 15000;
let testServer: SmtpServer; let testServer: ITestServer;
// Setup // Setup
tap.test('setup - start SMTP server', async () => { tap.test('setup - start SMTP server', async () => {
testServer = testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
}); });

View File

@@ -1,14 +1,13 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
let testServer: SmtpServer; let testServer: ITestServer;
tap.test('setup - start test server', async () => { tap.test('setup - start test server', async () => {
testServer = testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
}); });

View File

@@ -1,14 +1,13 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
let testServer: SmtpServer; let testServer: ITestServer;
tap.test('setup - start test server', async () => { tap.test('setup - start test server', async () => {
testServer = testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
}); });

View File

@@ -1,17 +1,16 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; import type { ITestServer } from '../../helpers/server.loader.js';
// Test configuration // Test configuration
const TEST_PORT = 2525; const TEST_PORT = 2525;
const TEST_TIMEOUT = 20000; const TEST_TIMEOUT = 20000;
let testServer: SmtpServer; let testServer: ITestServer;
// Setup // Setup
tap.test('setup - start SMTP server', async () => { tap.test('setup - start SMTP server', async () => {
testServer = testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
}); });

View File

@@ -1,14 +1,13 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer;
tap.test('setup - start test server', async () => { tap.test('setup - start test server', async () => {
testServer = testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
}); });

View File

@@ -81,7 +81,7 @@ tap.test('Special Character Handling - Comprehensive Unicode test', async (tools
'=== CURRENCY & SYMBOLS ===', '=== CURRENCY & SYMBOLS ===',
'Currency: $€£¥¢₹₽₩₪₫₨₦₡₵₴₸₼₲₱', 'Currency: $€£¥¢₹₽₩₪₫₨₦₡₵₴₸₼₲₱',
'Symbols: ©®™§¶†‡•…‰‱°℃℉№', 'Symbols: ©®™§¶†‡•…‰‱°℃℉№',
'Punctuation: «»""''‚„‹›–—―‖‗''""‚„…‰′″‴‵‶‷‸‹›※‼‽⁇⁈⁉⁏⁐⁑⁒⁓⁔⁕⁖⁗⁘⁙⁚⁛⁜⁝⁞', `Punctuation: «»""''‚„‹›–—―‖‗''""‚„…‰′″‴‵‶‷‸‹›※‼‽⁇⁈⁉⁏⁐⁑⁒⁓⁔⁕⁖⁗⁘⁙⁚⁛⁜⁝⁞`,
'', '',
'=== EMOJI & SYMBOLS ===', '=== EMOJI & SYMBOLS ===',
'Common: ☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☔☕☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷', 'Common: ☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☔☕☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷',

View File

@@ -18,7 +18,7 @@ tap.test('setup - start SMTP server', async () => {
hostname: 'localhost' hostname: 'localhost'
}); });
expect(testServer).toBeTypeofObject(); expect(testServer).toBeDefined();
expect(testServer.port).toEqual(TEST_PORT); expect(testServer.port).toEqual(TEST_PORT);
}); });

View File

@@ -18,7 +18,7 @@ tap.test('setup - start SMTP server', async () => {
hostname: 'localhost' hostname: 'localhost'
}); });
expect(testServer).toBeTypeofObject(); expect(testServer).toBeDefined();
expect(testServer.port).toEqual(TEST_PORT); expect(testServer.port).toEqual(TEST_PORT);
}); });

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('RFC 3461 DSN - DSN extension advertised', async (tools) => { tap.test('RFC 3461 DSN - DSN extension advertised', async (tools) => {

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('RFC 5321 - Server greeting format', async (tools) => { tap.test('RFC 5321 - Server greeting format', async (tools) => {

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('RFC 5322 - Message format with required headers', async (tools) => { tap.test('RFC 5322 - Message format with required headers', async (tools) => {

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('RFC 6376 DKIM - Server accepts email with DKIM signature', async (tools) => { tap.test('RFC 6376 DKIM - Server accepts email with DKIM signature', async (tools) => {

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('RFC 7208 SPF - Server handles SPF checks', async (tools) => { tap.test('RFC 7208 SPF - Server handles SPF checks', async (tools) => {

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('RFC 7489 DMARC - Server handles DMARC policies', async (tools) => { tap.test('RFC 7489 DMARC - Server handles DMARC policies', async (tools) => {

View File

@@ -2,16 +2,15 @@ import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import * as tls from 'tls'; import * as tls from 'tls';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('RFC 8314 TLS - STARTTLS advertised in EHLO', async (tools) => { tap.test('RFC 8314 TLS - STARTTLS advertised in EHLO', async (tools) => {

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('Authorization - Valid sender domain', async (tools) => { tap.test('Authorization - Valid sender domain', async (tools) => {

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('Bounce Management - Invalid recipient domain', async (tools) => { tap.test('Bounce Management - Invalid recipient domain', async (tools) => {

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('Content Scanning - Suspicious content patterns', async (tools) => { tap.test('Content Scanning - Suspicious content patterns', async (tools) => {

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('DKIM Processing - Valid DKIM signature', async (tools) => { tap.test('DKIM Processing - Valid DKIM signature', async (tools) => {

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('DMARC Policy - Reject policy enforcement', async (tools) => { tap.test('DMARC Policy - Reject policy enforcement', async (tools) => {

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('Header Injection Prevention - CRLF injection in headers', async (tools) => { tap.test('Header Injection Prevention - CRLF injection in headers', async (tools) => {

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('IP Reputation - Suspicious hostname in EHLO', async (tools) => { tap.test('IP Reputation - Suspicious hostname in EHLO', async (tools) => {
@@ -215,7 +214,7 @@ tap.test('IP Reputation - Multiple connections from same IP', async (tools) => {
// Small delay between connections // Small delay between connections
if (i < totalConnections - 1) { if (i < totalConnections - 1) {
await plugins.smartdelay.delayFor(100); await tools.delayFor(100);
} }
} }

View File

@@ -1,4 +1,4 @@
import { tap, expect } from '@git.zone/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';
import type { ITestServer } from '../../helpers/server.loader.js'; import type { ITestServer } from '../../helpers/server.loader.js';

View File

@@ -1,16 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('SPF Checking - Authorized IP from local domain', async (tools) => { tap.test('SPF Checking - Authorized IP from local domain', async (tools) => {

View File

@@ -2,16 +2,15 @@ import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js'; import * as plugins from '../../../ts/plugins.js';
import * as net from 'net'; import * as net from 'net';
import * as tls from 'tls'; import * as tls from 'tls';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525; const TEST_PORT = 2525;
import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: ITestServer;
let testServer: SmtpServer; tap.test('setup - start test server', async (toolsArg) => {
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT }); testServer = await startTestServer({ port: TEST_PORT });
await plugins.smartdelay.delayFor(1000); await toolsArg.delayFor(1000);
}); });
tap.test('TLS Certificate Validation - STARTTLS certificate check', async (tools) => { tap.test('TLS Certificate Validation - STARTTLS certificate check', async (tools) => {

View File

@@ -99,16 +99,25 @@ tap.test('DcRouter class - Custom email port configuration', async () => {
expect(routes.length).toBeGreaterThan(0); // At least some routes are configured expect(routes.length).toBeGreaterThan(0); // At least some routes are configured
// Check the custom port configuration // Check the custom port configuration
const customPortRoute = routes.find(r => r.match.ports?.includes(2525)); const customPortRoute = routes.find(r => {
const ports = r.match.ports;
return ports === 2525 || (Array.isArray(ports) && ports.includes(2525));
});
expect(customPortRoute).toBeTruthy(); expect(customPortRoute).toBeTruthy();
expect(customPortRoute?.name).toEqual('custom-smtp-route'); expect(customPortRoute?.name).toEqual('custom-smtp-route');
expect(customPortRoute?.action.target.port).toEqual(12525); expect(customPortRoute?.action.target.port).toEqual(12525);
// Check standard port mappings // Check standard port mappings
const smtpRoute = routes.find(r => r.match.ports?.includes(25)); const smtpRoute = routes.find(r => {
const ports = r.match.ports;
return ports === 25 || (Array.isArray(ports) && ports.includes(25));
});
expect(smtpRoute?.action.target.port).toEqual(11025); expect(smtpRoute?.action.target.port).toEqual(11025);
const submissionRoute = routes.find(r => r.match.ports?.includes(587)); const submissionRoute = routes.find(r => {
const ports = r.match.ports;
return ports === 587 || (Array.isArray(ports) && ports.includes(587));
});
expect(submissionRoute?.action.target.port).toEqual(11587); expect(submissionRoute?.action.target.port).toEqual(11587);
} }

View File

@@ -5,6 +5,7 @@ import * as paths from './paths.js';
// Import the consolidated email config // Import the consolidated email config
import type { IEmailConfig, IDomainRule } from './mail/routing/classes.email.config.js'; import type { IEmailConfig, IDomainRule } from './mail/routing/classes.email.config.js';
import type { EmailProcessingMode } from './mail/delivery/interfaces.js';
import { DomainRouter } from './mail/routing/classes.domain.router.js'; import { DomainRouter } from './mail/routing/classes.domain.router.js';
import { UnifiedEmailServer } from './mail/routing/classes.unified.email.server.js'; import { UnifiedEmailServer } from './mail/routing/classes.unified.email.server.js';
import { UnifiedDeliveryQueue, type IQueueOptions } from './mail/delivery/classes.delivery.queue.js'; import { UnifiedDeliveryQueue, type IQueueOptions } from './mail/delivery/classes.delivery.queue.js';
@@ -726,4 +727,7 @@ export class DcRouter {
} }
} }
// Re-export types for convenience
export type { IEmailConfig, IDomainRule, EmailProcessingMode };
export default DcRouter; export default DcRouter;

View File

@@ -613,6 +613,12 @@ export class UnifiedDeliveryQueue extends EventEmitter {
// Stop processing // Stop processing
this.stopProcessing(); this.stopProcessing();
// Clear the check timer to prevent memory leaks
if (this.checkTimer) {
clearInterval(this.checkTimer);
this.checkTimer = undefined;
}
// If using disk storage, make sure all items are persisted // If using disk storage, make sure all items are persisted
if (this.options.storageType === 'disk') { if (this.options.storageType === 'disk') {
const pendingWrites: Promise<void>[] = []; const pendingWrites: Promise<void>[] = [];

View File

@@ -146,6 +146,35 @@ export class UnifiedRateLimiter extends EventEmitter {
} }
} }
/**
* Destroy the rate limiter and clean up all resources
*/
public destroy(): void {
// Stop the cleanup interval
this.stop();
// Clear all maps to free memory
this.counters.clear();
this.ipCounters.clear();
this.patternCounters.clear();
// Clear blocks
if (this.config.blocks) {
this.config.blocks = {};
}
// Clear statistics
this.stats = {
activeCounters: 0,
totalBlocked: 0,
currentlyBlocked: 0,
byPattern: {},
byIp: {}
};
logger.log('info', 'UnifiedRateLimiter destroyed');
}
/** /**
* Clean up expired counters and blocks * Clean up expired counters and blocks
*/ */