BREAKING CHANGE(smtp-client): Replace the legacy TypeScript SMTP client with a new Rust-based SMTP client and IPC bridge for outbound delivery
This commit is contained in:
@@ -1,209 +0,0 @@
|
||||
import { smtpClientMod } from '../../ts/mail/delivery/index.js';
|
||||
import type { ISmtpClientOptions, SmtpClient } from '../../ts/mail/delivery/smtpclient/index.js';
|
||||
import { Email } from '../../ts/mail/core/classes.email.js';
|
||||
|
||||
/**
|
||||
* Create a test SMTP client
|
||||
*/
|
||||
export function createTestSmtpClient(options: Partial<ISmtpClientOptions> = {}): SmtpClient {
|
||||
const defaultOptions: ISmtpClientOptions = {
|
||||
host: options.host || 'localhost',
|
||||
port: options.port || 2525,
|
||||
secure: options.secure || false,
|
||||
auth: options.auth,
|
||||
connectionTimeout: options.connectionTimeout || 5000,
|
||||
socketTimeout: options.socketTimeout || 5000,
|
||||
maxConnections: options.maxConnections || 5,
|
||||
maxMessages: options.maxMessages || 100,
|
||||
debug: options.debug || false,
|
||||
tls: options.tls || {
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
};
|
||||
|
||||
return smtpClientMod.createSmtpClient(defaultOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send test email using SMTP client
|
||||
*/
|
||||
export async function sendTestEmail(
|
||||
client: SmtpClient,
|
||||
options: {
|
||||
from?: string;
|
||||
to?: string | string[];
|
||||
subject?: string;
|
||||
text?: string;
|
||||
html?: string;
|
||||
} = {}
|
||||
): Promise<any> {
|
||||
const mailOptions = {
|
||||
from: options.from || 'test@example.com',
|
||||
to: options.to || 'recipient@example.com',
|
||||
subject: options.subject || 'Test Email',
|
||||
text: options.text || 'This is a test email',
|
||||
html: options.html
|
||||
};
|
||||
|
||||
const email = new Email({
|
||||
from: mailOptions.from,
|
||||
to: mailOptions.to,
|
||||
subject: mailOptions.subject,
|
||||
text: mailOptions.text,
|
||||
html: mailOptions.html
|
||||
});
|
||||
return client.sendMail(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test SMTP client connection
|
||||
*/
|
||||
export async function testClientConnection(
|
||||
host: string,
|
||||
port: number,
|
||||
timeout: number = 5000
|
||||
): Promise<boolean> {
|
||||
const client = createTestSmtpClient({
|
||||
host,
|
||||
port,
|
||||
connectionTimeout: timeout
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await client.verify();
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} finally {
|
||||
if (client.close) {
|
||||
await client.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create authenticated SMTP client
|
||||
*/
|
||||
export function createAuthenticatedClient(
|
||||
host: string,
|
||||
port: number,
|
||||
username: string,
|
||||
password: string,
|
||||
authMethod: 'PLAIN' | 'LOGIN' = 'PLAIN'
|
||||
): SmtpClient {
|
||||
return createTestSmtpClient({
|
||||
host,
|
||||
port,
|
||||
auth: {
|
||||
user: username,
|
||||
pass: password,
|
||||
method: authMethod
|
||||
},
|
||||
secure: false
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create TLS-enabled SMTP client
|
||||
*/
|
||||
export function createTlsClient(
|
||||
host: string,
|
||||
port: number,
|
||||
options: {
|
||||
secure?: boolean;
|
||||
rejectUnauthorized?: boolean;
|
||||
} = {}
|
||||
): SmtpClient {
|
||||
return createTestSmtpClient({
|
||||
host,
|
||||
port,
|
||||
secure: options.secure || false,
|
||||
tls: {
|
||||
rejectUnauthorized: options.rejectUnauthorized || false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test client pool status
|
||||
*/
|
||||
export async function testClientPoolStatus(client: SmtpClient): Promise<any> {
|
||||
if (typeof client.getPoolStatus === 'function') {
|
||||
return client.getPoolStatus();
|
||||
}
|
||||
|
||||
// Fallback for clients without pool status
|
||||
return {
|
||||
size: 1,
|
||||
available: 1,
|
||||
pending: 0,
|
||||
connecting: 0,
|
||||
active: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Send multiple emails concurrently
|
||||
*/
|
||||
export async function sendConcurrentEmails(
|
||||
client: SmtpClient,
|
||||
count: number,
|
||||
emailOptions: {
|
||||
from?: string;
|
||||
to?: string;
|
||||
subject?: string;
|
||||
text?: string;
|
||||
} = {}
|
||||
): Promise<any[]> {
|
||||
const promises = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
promises.push(
|
||||
sendTestEmail(client, {
|
||||
...emailOptions,
|
||||
subject: `${emailOptions.subject || 'Test Email'} ${i + 1}`
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure client throughput
|
||||
*/
|
||||
export async function measureClientThroughput(
|
||||
client: SmtpClient,
|
||||
duration: number = 10000,
|
||||
emailOptions: {
|
||||
from?: string;
|
||||
to?: string;
|
||||
subject?: string;
|
||||
text?: string;
|
||||
} = {}
|
||||
): Promise<{ totalSent: number; successCount: number; errorCount: number; throughput: number }> {
|
||||
const startTime = Date.now();
|
||||
let totalSent = 0;
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
while (Date.now() - startTime < duration) {
|
||||
try {
|
||||
await sendTestEmail(client, emailOptions);
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
errorCount++;
|
||||
}
|
||||
totalSent++;
|
||||
}
|
||||
|
||||
const actualDuration = (Date.now() - startTime) / 1000; // in seconds
|
||||
const throughput = totalSent / actualDuration;
|
||||
|
||||
return {
|
||||
totalSent,
|
||||
successCount,
|
||||
errorCount,
|
||||
throughput
|
||||
};
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { smtpClientMod } from '../ts/mail/delivery/index.js';
|
||||
import type { ISmtpClientOptions, SmtpClient } from '../ts/mail/delivery/smtpclient/index.js';
|
||||
import { Email } from '../ts/mail/core/classes.email.js';
|
||||
|
||||
/**
|
||||
* Compatibility tests for the legacy SMTP client facade
|
||||
*/
|
||||
|
||||
tap.test('verify backward compatibility - client creation', async () => {
|
||||
// Create test configuration
|
||||
const options: ISmtpClientOptions = {
|
||||
host: 'smtp.example.com',
|
||||
port: 587,
|
||||
secure: false,
|
||||
connectionTimeout: 10000,
|
||||
domain: 'test.example.com'
|
||||
};
|
||||
|
||||
// Create SMTP client instance using legacy constructor
|
||||
const smtpClient = smtpClientMod.createSmtpClient(options);
|
||||
|
||||
// Verify instance was created correctly
|
||||
expect(smtpClient).toBeTruthy();
|
||||
expect(smtpClient.isConnected()).toBeFalsy(); // Should start disconnected
|
||||
});
|
||||
|
||||
tap.test('verify backward compatibility - methods exist', async () => {
|
||||
const options: ISmtpClientOptions = {
|
||||
host: 'smtp.example.com',
|
||||
port: 587,
|
||||
secure: false
|
||||
};
|
||||
|
||||
const smtpClient = smtpClientMod.createSmtpClient(options);
|
||||
|
||||
// Verify all expected methods exist
|
||||
expect(typeof smtpClient.sendMail === 'function').toBeTruthy();
|
||||
expect(typeof smtpClient.verify === 'function').toBeTruthy();
|
||||
expect(typeof smtpClient.isConnected === 'function').toBeTruthy();
|
||||
expect(typeof smtpClient.getPoolStatus === 'function').toBeTruthy();
|
||||
expect(typeof smtpClient.updateOptions === 'function').toBeTruthy();
|
||||
expect(typeof smtpClient.close === 'function').toBeTruthy();
|
||||
expect(typeof smtpClient.on === 'function').toBeTruthy();
|
||||
expect(typeof smtpClient.off === 'function').toBeTruthy();
|
||||
expect(typeof smtpClient.emit === 'function').toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('verify backward compatibility - options update', async () => {
|
||||
const options: ISmtpClientOptions = {
|
||||
host: 'smtp.example.com',
|
||||
port: 587,
|
||||
secure: false
|
||||
};
|
||||
|
||||
const smtpClient = smtpClientMod.createSmtpClient(options);
|
||||
|
||||
// Test option updates don't throw
|
||||
expect(() => smtpClient.updateOptions({
|
||||
host: 'new-smtp.example.com',
|
||||
port: 465,
|
||||
secure: true
|
||||
})).not.toThrow();
|
||||
|
||||
expect(() => smtpClient.updateOptions({
|
||||
debug: true,
|
||||
connectionTimeout: 5000
|
||||
})).not.toThrow();
|
||||
});
|
||||
|
||||
tap.test('verify backward compatibility - connection failure handling', async () => {
|
||||
const options: ISmtpClientOptions = {
|
||||
host: 'nonexistent.invalid.domain',
|
||||
port: 587,
|
||||
secure: false,
|
||||
connectionTimeout: 1000 // Short timeout for faster test
|
||||
};
|
||||
|
||||
const smtpClient = smtpClientMod.createSmtpClient(options);
|
||||
|
||||
// verify() should return false for invalid hosts
|
||||
const isValid = await smtpClient.verify();
|
||||
expect(isValid).toBeFalsy();
|
||||
|
||||
// sendMail should fail gracefully for invalid hosts
|
||||
const email = new Email({
|
||||
from: 'test@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Test Email',
|
||||
text: 'This is a test email'
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeFalsy();
|
||||
expect(result.error).toBeTruthy();
|
||||
} catch (error) {
|
||||
// Connection errors are expected for invalid domains
|
||||
expect(error).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('verify backward compatibility - pool status', async () => {
|
||||
const options: ISmtpClientOptions = {
|
||||
host: 'smtp.example.com',
|
||||
port: 587,
|
||||
secure: false,
|
||||
pool: true,
|
||||
maxConnections: 5
|
||||
};
|
||||
|
||||
const smtpClient = smtpClientMod.createSmtpClient(options);
|
||||
|
||||
// Get pool status
|
||||
const status = smtpClient.getPoolStatus();
|
||||
expect(status).toBeTruthy();
|
||||
expect(typeof status.total === 'number').toBeTruthy();
|
||||
expect(typeof status.active === 'number').toBeTruthy();
|
||||
expect(typeof status.idle === 'number').toBeTruthy();
|
||||
expect(typeof status.pending === 'number').toBeTruthy();
|
||||
|
||||
// Initially should have no connections
|
||||
expect(status.total).toEqual(0);
|
||||
expect(status.active).toEqual(0);
|
||||
expect(status.idle).toEqual(0);
|
||||
expect(status.pending).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('verify backward compatibility - event handling', async () => {
|
||||
const options: ISmtpClientOptions = {
|
||||
host: 'smtp.example.com',
|
||||
port: 587,
|
||||
secure: false
|
||||
};
|
||||
|
||||
const smtpClient = smtpClientMod.createSmtpClient(options);
|
||||
|
||||
// Test event listener methods don't throw
|
||||
const testListener = () => {};
|
||||
|
||||
expect(() => smtpClient.on('test', testListener)).not.toThrow();
|
||||
expect(() => smtpClient.off('test', testListener)).not.toThrow();
|
||||
expect(() => smtpClient.emit('test')).not.toThrow();
|
||||
});
|
||||
|
||||
tap.test('clean up after compatibility tests', async () => {
|
||||
// No-op - just to make sure everything is cleaned up properly
|
||||
});
|
||||
|
||||
tap.test('stop', async () => {
|
||||
await tap.stopForcefully();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
107
test/test.smtp.client.rust.node.ts
Normal file
107
test/test.smtp.client.rust.node.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { RustSecurityBridge, BridgeState } from '../ts/security/classes.rustsecuritybridge.js';
|
||||
import type { ISmtpSendOptions, ISmtpPoolStatus } from '../ts/security/classes.rustsecuritybridge.js';
|
||||
|
||||
let bridge: RustSecurityBridge;
|
||||
|
||||
tap.test('Rust SMTP Client - should start bridge', async () => {
|
||||
RustSecurityBridge.resetInstance();
|
||||
bridge = RustSecurityBridge.getInstance();
|
||||
const ok = await bridge.start();
|
||||
if (!ok) {
|
||||
console.log('WARNING: Rust binary not available — skipping Rust SMTP client tests');
|
||||
console.log('Build it with: cd rust && cargo build --release');
|
||||
}
|
||||
expect(typeof ok).toEqual('boolean');
|
||||
});
|
||||
|
||||
tap.test('Rust SMTP Client - getSmtpPoolStatus returns valid structure', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
const status: ISmtpPoolStatus = await bridge.getSmtpPoolStatus();
|
||||
expect(status).toBeTruthy();
|
||||
expect(status.pools).toBeTruthy();
|
||||
expect(typeof status.pools).toEqual('object');
|
||||
});
|
||||
|
||||
tap.test('Rust SMTP Client - verifySmtpConnection with unreachable host', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Use a non-routable IP to test connection failure handling
|
||||
const result = await bridge.verifySmtpConnection({
|
||||
host: '192.0.2.1', // TEST-NET-1 (RFC 5737) - guaranteed unreachable
|
||||
port: 25,
|
||||
secure: false,
|
||||
domain: 'test.example.com',
|
||||
});
|
||||
// If it returns rather than throwing, reachable should be false
|
||||
expect(result.reachable).toBeFalse();
|
||||
} catch (err) {
|
||||
// Connection errors are expected for unreachable hosts
|
||||
expect(err).toBeTruthy();
|
||||
expect(err.message || String(err)).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('Rust SMTP Client - sendEmail with connection refused error', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
const opts: ISmtpSendOptions = {
|
||||
host: '127.0.0.1',
|
||||
port: 52599, // random high port - should be refused
|
||||
secure: false,
|
||||
domain: 'test.example.com',
|
||||
email: {
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Test email',
|
||||
text: 'This is a test.',
|
||||
},
|
||||
connectionTimeoutSecs: 5,
|
||||
socketTimeoutSecs: 5,
|
||||
};
|
||||
|
||||
try {
|
||||
await bridge.sendOutboundEmail(opts);
|
||||
// Should not succeed — no server is listening
|
||||
throw new Error('Expected sendEmail to fail on connection refused');
|
||||
} catch (err) {
|
||||
// We expect a connection error
|
||||
expect(err).toBeTruthy();
|
||||
const msg = err.message || String(err);
|
||||
expect(msg.length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('Rust SMTP Client - closeSmtpPool cleans up', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
// Should not throw, even when no pools exist
|
||||
await bridge.closeSmtpPool();
|
||||
|
||||
// Verify pool status is empty after close
|
||||
const status = await bridge.getSmtpPoolStatus();
|
||||
expect(status.pools).toBeTruthy();
|
||||
const poolKeys = Object.keys(status.pools);
|
||||
expect(poolKeys.length).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('Rust SMTP Client - stop bridge', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
await bridge.stop();
|
||||
expect(bridge.state).toEqual(BridgeState.Stopped);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -1,191 +0,0 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as plugins from '../ts/plugins.js';
|
||||
import * as paths from '../ts/paths.js';
|
||||
import { smtpClientMod } from '../ts/mail/delivery/index.js';
|
||||
import type { ISmtpClientOptions, SmtpClient } from '../ts/mail/delivery/smtpclient/index.js';
|
||||
import { Email } from '../ts/mail/core/classes.email.js';
|
||||
|
||||
/**
|
||||
* Tests for the SMTP client class
|
||||
*/
|
||||
tap.test('verify SMTP client initialization', async () => {
|
||||
// Create test configuration
|
||||
const options: ISmtpClientOptions = {
|
||||
host: 'smtp.example.com',
|
||||
port: 587,
|
||||
secure: false,
|
||||
connectionTimeout: 10000,
|
||||
domain: 'test.example.com'
|
||||
};
|
||||
|
||||
// Create SMTP client instance
|
||||
const smtpClient = smtpClientMod.createSmtpClient(options);
|
||||
|
||||
// Verify instance was created correctly
|
||||
expect(smtpClient).toBeTruthy();
|
||||
expect(smtpClient.isConnected()).toBeFalsy(); // Should start disconnected
|
||||
});
|
||||
|
||||
tap.test('test SMTP client configuration update', async () => {
|
||||
// Create test configuration
|
||||
const options: ISmtpClientOptions = {
|
||||
host: 'smtp.example.com',
|
||||
port: 587,
|
||||
secure: false
|
||||
};
|
||||
|
||||
// Create SMTP client instance
|
||||
const smtpClient = smtpClientMod.createSmtpClient(options);
|
||||
|
||||
// Update configuration
|
||||
smtpClient.updateOptions({
|
||||
host: 'new-smtp.example.com',
|
||||
port: 465,
|
||||
secure: true
|
||||
});
|
||||
|
||||
// Can't directly test private fields, but we can verify it doesn't throw
|
||||
expect(() => smtpClient.updateOptions({
|
||||
tls: {
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
})).not.toThrow();
|
||||
});
|
||||
|
||||
// Mocked SMTP server for testing
|
||||
class MockSmtpServer {
|
||||
private responses: Map<string, string>;
|
||||
|
||||
constructor() {
|
||||
this.responses = new Map();
|
||||
|
||||
// Default responses
|
||||
this.responses.set('connect', '220 smtp.example.com ESMTP ready');
|
||||
this.responses.set('EHLO', '250-smtp.example.com\r\n250-PIPELINING\r\n250-SIZE 10240000\r\n250-STARTTLS\r\n250-AUTH PLAIN LOGIN\r\n250 HELP');
|
||||
this.responses.set('MAIL FROM', '250 OK');
|
||||
this.responses.set('RCPT TO', '250 OK');
|
||||
this.responses.set('DATA', '354 Start mail input; end with <CRLF>.<CRLF>');
|
||||
this.responses.set('data content', '250 OK: message accepted');
|
||||
this.responses.set('QUIT', '221 Bye');
|
||||
}
|
||||
|
||||
public setResponse(command: string, response: string): void {
|
||||
this.responses.set(command, response);
|
||||
}
|
||||
|
||||
public getResponse(command: string): string {
|
||||
if (command.startsWith('MAIL FROM')) {
|
||||
return this.responses.get('MAIL FROM') || '250 OK';
|
||||
} else if (command.startsWith('RCPT TO')) {
|
||||
return this.responses.get('RCPT TO') || '250 OK';
|
||||
} else if (command.startsWith('EHLO') || command.startsWith('HELO')) {
|
||||
return this.responses.get('EHLO') || '250 OK';
|
||||
} else if (command === 'DATA') {
|
||||
return this.responses.get('DATA') || '354 Start mail input; end with <CRLF>.<CRLF>';
|
||||
} else if (command.includes('Content-Type')) {
|
||||
return this.responses.get('data content') || '250 OK: message accepted';
|
||||
} else if (command === 'QUIT') {
|
||||
return this.responses.get('QUIT') || '221 Bye';
|
||||
}
|
||||
|
||||
return this.responses.get(command) || '250 OK';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This test validates the SMTP client public interface
|
||||
*/
|
||||
tap.test('verify SMTP client email delivery functionality with mock', async () => {
|
||||
// Create a test email
|
||||
const testEmail = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Test Email',
|
||||
text: 'This is a test email'
|
||||
});
|
||||
|
||||
// Create SMTP client options
|
||||
const options: ISmtpClientOptions = {
|
||||
host: 'smtp.example.com',
|
||||
port: 587,
|
||||
secure: false,
|
||||
domain: 'test.example.com',
|
||||
auth: {
|
||||
user: 'testuser',
|
||||
pass: 'testpass'
|
||||
}
|
||||
};
|
||||
|
||||
// Create SMTP client instance
|
||||
const smtpClient = smtpClientMod.createSmtpClient(options);
|
||||
|
||||
// Test public methods exist and have correct signatures
|
||||
expect(typeof smtpClient.sendMail).toEqual('function');
|
||||
expect(typeof smtpClient.verify).toEqual('function');
|
||||
expect(typeof smtpClient.isConnected).toEqual('function');
|
||||
expect(typeof smtpClient.getPoolStatus).toEqual('function');
|
||||
expect(typeof smtpClient.updateOptions).toEqual('function');
|
||||
expect(typeof smtpClient.close).toEqual('function');
|
||||
|
||||
// Test connection status before any operation
|
||||
expect(smtpClient.isConnected()).toBeFalsy();
|
||||
|
||||
// Test pool status
|
||||
const poolStatus = smtpClient.getPoolStatus();
|
||||
expect(poolStatus).toBeTruthy();
|
||||
expect(typeof poolStatus.active).toEqual('number');
|
||||
expect(typeof poolStatus.idle).toEqual('number');
|
||||
expect(typeof poolStatus.total).toEqual('number');
|
||||
|
||||
// Since we can't connect to a real server, we'll skip the actual send test
|
||||
// and just verify the client was created correctly
|
||||
expect(smtpClient).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('test SMTP client error handling with mock', async () => {
|
||||
// Create SMTP client instance
|
||||
const smtpClient = smtpClientMod.createSmtpClient({
|
||||
host: 'smtp.example.com',
|
||||
port: 587,
|
||||
secure: false
|
||||
});
|
||||
|
||||
// Test with valid email (Email class might allow any string)
|
||||
const testEmail = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Test Email',
|
||||
text: 'This is a test email'
|
||||
});
|
||||
|
||||
// Test event listener methods
|
||||
const mockListener = () => {};
|
||||
smtpClient.on('test-event', mockListener);
|
||||
smtpClient.off('test-event', mockListener);
|
||||
|
||||
// Test update options
|
||||
smtpClient.updateOptions({
|
||||
auth: {
|
||||
user: 'newuser',
|
||||
pass: 'newpass'
|
||||
}
|
||||
});
|
||||
|
||||
// Verify client is still functional
|
||||
expect(smtpClient.isConnected()).toBeFalsy();
|
||||
|
||||
// Test close on a non-connected client
|
||||
await smtpClient.close();
|
||||
expect(smtpClient.isConnected()).toBeFalsy();
|
||||
});
|
||||
|
||||
// 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();
|
||||
Reference in New Issue
Block a user