update
This commit is contained in:
258
test/test.mta.ts
Normal file
258
test/test.mta.ts
Normal file
@ -0,0 +1,258 @@
|
||||
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<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 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();
|
258
test/test.smtp.client.ts
Normal file
258
test/test.smtp.client.ts
Normal file
@ -0,0 +1,258 @@
|
||||
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 SMTP client instance
|
||||
const smtpClient = new SmtpClient(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 = new SmtpClient(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 capabilities without connecting to a real server
|
||||
* It uses a simple mock that only checks method signatures and properties
|
||||
*/
|
||||
tap.test('verify SMTP client 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 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 = new SmtpClient(options);
|
||||
|
||||
// Mock the connect method
|
||||
smtpClient['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
|
||||
smtpClient['sendCommand'] = async function(command: string) {
|
||||
return Promise.resolve(mockServer.getResponse(command));
|
||||
};
|
||||
|
||||
// Mock the readResponse method
|
||||
smtpClient['readResponse'] = async function() {
|
||||
return Promise.resolve(mockServer.getResponse('connect'));
|
||||
};
|
||||
|
||||
// Test sending an email
|
||||
try {
|
||||
const result = await smtpClient.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 smtpClient.close();
|
||||
expect(smtpClient.isConnected()).toBeFalsy();
|
||||
});
|
||||
|
||||
tap.test('test SMTP client 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 SMTP client instance
|
||||
const smtpClient = new SmtpClient({
|
||||
host: 'smtp.example.com',
|
||||
port: 587,
|
||||
secure: false
|
||||
});
|
||||
|
||||
// Mock the connect method
|
||||
smtpClient['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
|
||||
smtpClient['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 smtpClient.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();
|
Reference in New Issue
Block a user