import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as plugins from '../ts/plugins.js'; import * as paths from '../ts/paths.js'; import { SmtpClient } from '../ts/mail/delivery/classes.smtp.client.js'; import type { ISmtpClientOptions } from '../ts/mail/delivery/classes.smtp.client.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 MTA instance const mta = new MailTransferAgent(options); // Verify instance was created correctly expect(mta).toBeTruthy(); expect(mta.isConnected()).toBeFalsy(); // Should start disconnected }); tap.test('test MTA configuration update', async () => { // Create test configuration const options: IMtaConnectionOptions = { host: 'smtp.example.com', port: 587, secure: false }; // Create MTA instance const mta = new MailTransferAgent(options); // Update configuration mta.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(() => mta.updateOptions({ tls: { rejectUnauthorized: false } })).not.toThrow(); }); // Mocked SMTP server for testing class MockSmtpServer { private responses: Map; 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 .'); 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 .'; } 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 MTA capabilities without connecting to a real server * It uses a simple mock that only checks method signatures and properties */ tap.test('verify MTA email delivery functionality with mock', async () => { // Create a mock SMTP server const mockServer = new MockSmtpServer(); // 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 MTA options const options: IMtaConnectionOptions = { host: 'smtp.example.com', port: 587, secure: false, domain: 'test.example.com', auth: { user: 'testuser', pass: 'testpass' } }; // Create MTA instance const mta = new MailTransferAgent(options); // Mock the connect method mta['connect'] = async function() { // @ts-ignore: setting private property for testing this.connected = true; // @ts-ignore: setting private property for testing this.socket = { write: (data: string, callback: () => void) => { callback(); }, on: () => {}, once: () => {}, removeListener: () => {}, destroy: () => {}, setTimeout: () => {} }; // @ts-ignore: setting private property for testing this.supportedExtensions = new Set(['PIPELINING', 'SIZE', 'STARTTLS', 'AUTH']); return Promise.resolve(); }; // Mock the sendCommand method mta['sendCommand'] = async function(command: string) { return Promise.resolve(mockServer.getResponse(command)); }; // Mock the readResponse method mta['readResponse'] = async function() { return Promise.resolve(mockServer.getResponse('connect')); }; // Test sending an email try { const result = await mta.sendMail(testEmail); // Verify the result expect(result).toBeTruthy(); expect(result.success).toEqual(true); expect(result.acceptedRecipients).toEqual(['recipient@example.com']); expect(result.rejectedRecipients).toEqual([]); } catch (error) { // This should not happen expect(error).toBeUndefined(); } // Test closing the connection await mta.close(); expect(mta.isConnected()).toBeFalsy(); }); tap.test('test MTA error handling with mock', async () => { // Create a mock SMTP server const mockServer = new MockSmtpServer(); // Set error response for RCPT TO mockServer.setResponse('RCPT TO', '550 No such user here'); // Create a test email const testEmail = new Email({ from: 'sender@example.com', to: ['unknown@example.com'], subject: 'Test Email', text: 'This is a test email' }); // Create MTA instance const mta = new MailTransferAgent({ host: 'smtp.example.com', port: 587, secure: false }); // Mock the connect method mta['connect'] = async function() { // @ts-ignore: setting private property for testing this.connected = true; // @ts-ignore: setting private property for testing this.socket = { write: (data: string, callback: () => void) => { callback(); }, on: () => {}, once: () => {}, removeListener: () => {}, destroy: () => {}, setTimeout: () => {} }; // @ts-ignore: setting private property for testing this.supportedExtensions = new Set(['PIPELINING', 'SIZE', 'STARTTLS', 'AUTH']); return Promise.resolve(); }; // Mock the sendCommand method mta['sendCommand'] = async function(command: string) { const response = mockServer.getResponse(command); // Simulate an error response for RCPT TO if (command.startsWith('RCPT TO') && response.startsWith('550')) { const error = new Error(response); error['context'] = { data: { statusCode: '550' } }; throw error; } return Promise.resolve(response); }; // Test sending an email that will fail const result = await mta.sendMail(testEmail); // Verify the result shows failure expect(result).toBeTruthy(); expect(result.success).toEqual(false); expect(result.acceptedRecipients).toEqual([]); expect(result.rejectedRecipients).toEqual(['unknown@example.com']); expect(result.error).toBeTruthy(); }); // 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();