feat(storage): add comprehensive tests for StorageManager with memory, filesystem, and custom function backends
feat(email): implement EmailSendJob class for robust email delivery with retry logic and MX record resolution feat(mail): restructure mail module exports for simplified access to core and delivery functionalities
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.ts';
|
||||
import type { SmtpClient } from '../../../ts/mail/delivery/smtpclient/smtp-client.ts';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.ts';
|
||||
|
||||
let testServer: ITestServer;
|
||||
let smtpClient: SmtpClient;
|
||||
|
||||
tap.test('setup - start SMTP server for email composition tests', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2570,
|
||||
tlsEnabled: false,
|
||||
authRequired: false
|
||||
});
|
||||
|
||||
expect(testServer.port).toEqual(2570);
|
||||
});
|
||||
|
||||
tap.test('setup - create SMTP client', async () => {
|
||||
smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
const isConnected = await smtpClient.verify();
|
||||
expect(isConnected).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('CEP-01: Basic Headers - should send email with required headers', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Test Email with Basic Headers',
|
||||
text: 'This is the plain text body'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
expect(result.acceptedRecipients).toContain('recipient@example.com');
|
||||
expect(result.messageId).toBeTypeofString();
|
||||
|
||||
console.log('✅ Basic email headers sent successfully');
|
||||
console.log('📧 Message ID:', result.messageId);
|
||||
});
|
||||
|
||||
tap.test('CEP-01: Basic Headers - should handle multiple recipients', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient1@example.com', 'recipient2@example.com', 'recipient3@example.com'],
|
||||
subject: 'Email to Multiple Recipients',
|
||||
text: 'This email has multiple recipients'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
expect(result.acceptedRecipients).toContain('recipient1@example.com');
|
||||
expect(result.acceptedRecipients).toContain('recipient2@example.com');
|
||||
expect(result.acceptedRecipients).toContain('recipient3@example.com');
|
||||
|
||||
console.log(`✅ Sent to ${result.acceptedRecipients.length} recipients`);
|
||||
});
|
||||
|
||||
tap.test('CEP-01: Basic Headers - should support CC and BCC recipients', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'primary@example.com',
|
||||
cc: ['cc1@example.com', 'cc2@example.com'],
|
||||
bcc: ['bcc1@example.com', 'bcc2@example.com'],
|
||||
subject: 'Email with CC and BCC',
|
||||
text: 'Testing CC and BCC functionality'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
// All recipients should be accepted
|
||||
expect(result.acceptedRecipients.length).toEqual(5);
|
||||
|
||||
console.log('✅ CC and BCC recipients handled correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-01: Basic Headers - should add custom headers', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Email with Custom Headers',
|
||||
text: 'This email contains custom headers',
|
||||
headers: {
|
||||
'X-Custom-Header': 'custom-value',
|
||||
'X-Priority': '1',
|
||||
'X-Mailer': 'DCRouter Test Suite',
|
||||
'Reply-To': 'replies@example.com'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Custom headers added to email');
|
||||
});
|
||||
|
||||
tap.test('CEP-01: Basic Headers - should set email priority', async () => {
|
||||
// Test high priority
|
||||
const highPriorityEmail = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'High Priority Email',
|
||||
text: 'This is a high priority message',
|
||||
priority: 'high'
|
||||
});
|
||||
|
||||
const highResult = await smtpClient.sendMail(highPriorityEmail);
|
||||
expect(highResult.success).toBeTrue();
|
||||
|
||||
// Test normal priority
|
||||
const normalPriorityEmail = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Normal Priority Email',
|
||||
text: 'This is a normal priority message',
|
||||
priority: 'normal'
|
||||
});
|
||||
|
||||
const normalResult = await smtpClient.sendMail(normalPriorityEmail);
|
||||
expect(normalResult.success).toBeTrue();
|
||||
|
||||
// Test low priority
|
||||
const lowPriorityEmail = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Low Priority Email',
|
||||
text: 'This is a low priority message',
|
||||
priority: 'low'
|
||||
});
|
||||
|
||||
const lowResult = await smtpClient.sendMail(lowPriorityEmail);
|
||||
expect(lowResult.success).toBeTrue();
|
||||
|
||||
console.log('✅ All priority levels handled correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-01: Basic Headers - should handle sender with display name', async () => {
|
||||
const email = new Email({
|
||||
from: 'John Doe <john.doe@example.com>',
|
||||
to: 'Jane Smith <jane.smith@example.com>',
|
||||
subject: 'Email with Display Names',
|
||||
text: 'Testing display names in email addresses'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
expect(result.envelope?.from).toContain('john.doe@example.com');
|
||||
|
||||
console.log('✅ Display names in addresses handled correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-01: Basic Headers - should generate proper Message-ID', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Message-ID Test',
|
||||
text: 'Testing Message-ID generation'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
expect(result.messageId).toBeTypeofString();
|
||||
|
||||
// Message-ID should contain id@domain format (without angle brackets)
|
||||
expect(result.messageId).toMatch(/^.+@.+$/);
|
||||
|
||||
console.log('✅ Valid Message-ID generated:', result.messageId);
|
||||
});
|
||||
|
||||
tap.test('CEP-01: Basic Headers - should handle long subject lines', async () => {
|
||||
const longSubject = 'This is a very long subject line that exceeds the typical length and might need to be wrapped according to RFC specifications for email headers';
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: longSubject,
|
||||
text: 'Email with long subject line'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Long subject line handled correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-01: Basic Headers - should sanitize header values', async () => {
|
||||
// Test with potentially problematic characters
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Subject with\nnewline and\rcarriage return',
|
||||
text: 'Testing header sanitization',
|
||||
headers: {
|
||||
'X-Test-Header': 'Value with\nnewline'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Header values sanitized correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-01: Basic Headers - should include Date header', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Date Header Test',
|
||||
text: 'Testing automatic Date header'
|
||||
});
|
||||
|
||||
const beforeSend = new Date();
|
||||
const result = await smtpClient.sendMail(email);
|
||||
const afterSend = new Date();
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
|
||||
// The email should have been sent between beforeSend and afterSend
|
||||
console.log('✅ Date header automatically included');
|
||||
});
|
||||
|
||||
tap.test('cleanup - close SMTP client', async () => {
|
||||
if (smtpClient && smtpClient.isConnected()) {
|
||||
await smtpClient.close();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('cleanup - stop SMTP server', async () => {
|
||||
await stopTestServer(testServer);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,321 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.ts';
|
||||
import type { SmtpClient } from '../../../ts/mail/delivery/smtpclient/smtp-client.ts';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.ts';
|
||||
|
||||
let testServer: ITestServer;
|
||||
let smtpClient: SmtpClient;
|
||||
|
||||
tap.test('setup - start SMTP server for MIME tests', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2571,
|
||||
tlsEnabled: false,
|
||||
authRequired: false,
|
||||
size: 25 * 1024 * 1024 // 25MB for attachment tests
|
||||
});
|
||||
|
||||
expect(testServer.port).toEqual(2571);
|
||||
});
|
||||
|
||||
tap.test('setup - create SMTP client', async () => {
|
||||
smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
socketTimeout: 60000, // Longer timeout for large attachments
|
||||
debug: true
|
||||
});
|
||||
|
||||
const isConnected = await smtpClient.verify();
|
||||
expect(isConnected).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('CEP-02: MIME Multipart - should send multipart/alternative (text + HTML)', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Multipart Alternative Test',
|
||||
text: 'This is the plain text version of the email.',
|
||||
html: '<html><body><h1>HTML Version</h1><p>This is the <strong>HTML version</strong> of the email.</p></body></html>'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Multipart/alternative email sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-02: MIME Multipart - should send multipart/mixed with attachments', async () => {
|
||||
const textAttachment = Buffer.from('This is a text file attachment content.');
|
||||
const csvData = 'Name,Email,Score\nJohn Doe,john@example.com,95\nJane Smith,jane@example.com,87';
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Multipart Mixed with Attachments',
|
||||
text: 'This email contains attachments.',
|
||||
html: '<p>This email contains <strong>attachments</strong>.</p>',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'document.txt',
|
||||
content: textAttachment,
|
||||
contentType: 'text/plain'
|
||||
},
|
||||
{
|
||||
filename: 'data.csv',
|
||||
content: Buffer.from(csvData),
|
||||
contentType: 'text/csv'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Multipart/mixed with attachments sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-02: MIME Multipart - should handle inline images', async () => {
|
||||
// Create a small test image (1x1 red pixel PNG)
|
||||
const redPixelPng = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==',
|
||||
'base64'
|
||||
);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Inline Image Test',
|
||||
text: 'This email contains an inline image.',
|
||||
html: '<p>Here is an inline image: <img src="cid:red-pixel" alt="Red Pixel"></p>',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'red-pixel.png',
|
||||
content: redPixelPng,
|
||||
contentType: 'image/png',
|
||||
contentId: 'red-pixel' // Content-ID for inline reference
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Email with inline image sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-02: MIME Multipart - should handle multiple attachment types', async () => {
|
||||
const attachments = [
|
||||
{
|
||||
filename: 'text.txt',
|
||||
content: Buffer.from('Plain text file'),
|
||||
contentType: 'text/plain'
|
||||
},
|
||||
{
|
||||
filename: 'data.json',
|
||||
content: Buffer.from(JSON.stringify({ test: 'data', value: 123 })),
|
||||
contentType: 'application/json'
|
||||
},
|
||||
{
|
||||
filename: 'binary.bin',
|
||||
content: Buffer.from([0x00, 0x01, 0x02, 0x03, 0xFF, 0xFE, 0xFD]),
|
||||
contentType: 'application/octet-stream'
|
||||
},
|
||||
{
|
||||
filename: 'document.pdf',
|
||||
content: Buffer.from('%PDF-1.4\n%fake pdf content for testing'),
|
||||
contentType: 'application/pdf'
|
||||
}
|
||||
];
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Multiple Attachment Types',
|
||||
text: 'Testing various attachment types',
|
||||
attachments
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Multiple attachment types handled correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-02: MIME Multipart - should encode binary attachments with base64', async () => {
|
||||
// Create binary data with all byte values
|
||||
const binaryData = Buffer.alloc(256);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
binaryData[i] = i;
|
||||
}
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Binary Attachment Encoding Test',
|
||||
text: 'This email contains binary data that must be base64 encoded',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'binary-data.bin',
|
||||
content: binaryData,
|
||||
contentType: 'application/octet-stream',
|
||||
encoding: 'base64' // Explicitly specify encoding
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Binary attachment base64 encoded correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-02: MIME Multipart - should handle large attachments', async () => {
|
||||
// Create a 5MB attachment
|
||||
const largeData = Buffer.alloc(5 * 1024 * 1024);
|
||||
for (let i = 0; i < largeData.length; i++) {
|
||||
largeData[i] = i % 256;
|
||||
}
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Large Attachment Test',
|
||||
text: 'This email contains a large attachment',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'large-file.dat',
|
||||
content: largeData,
|
||||
contentType: 'application/octet-stream'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = await smtpClient.sendMail(email);
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log(`✅ Large attachment (5MB) sent in ${duration}ms`);
|
||||
});
|
||||
|
||||
tap.test('CEP-02: MIME Multipart - should handle nested multipart structures', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Complex Multipart Structure',
|
||||
text: 'Plain text version',
|
||||
html: '<p>HTML version with <img src="cid:logo" alt="Logo"></p>',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'logo.png',
|
||||
content: Buffer.from('fake png data'),
|
||||
contentType: 'image/png',
|
||||
contentId: 'logo' // Inline image
|
||||
},
|
||||
{
|
||||
filename: 'attachment.txt',
|
||||
content: Buffer.from('Regular attachment'),
|
||||
contentType: 'text/plain'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Nested multipart structure (mixed + related + alternative) handled');
|
||||
});
|
||||
|
||||
tap.test('CEP-02: MIME Multipart - should handle attachment filenames with special characters', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Special Filename Test',
|
||||
text: 'Testing attachments with special filenames',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'file with spaces.txt',
|
||||
content: Buffer.from('Content 1'),
|
||||
contentType: 'text/plain'
|
||||
},
|
||||
{
|
||||
filename: 'файл.txt', // Cyrillic
|
||||
content: Buffer.from('Content 2'),
|
||||
contentType: 'text/plain'
|
||||
},
|
||||
{
|
||||
filename: '文件.txt', // Chinese
|
||||
content: Buffer.from('Content 3'),
|
||||
contentType: 'text/plain'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Special characters in filenames handled correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-02: MIME Multipart - should handle empty attachments', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Empty Attachment Test',
|
||||
text: 'This email has an empty attachment',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'empty.txt',
|
||||
content: Buffer.from(''), // Empty content
|
||||
contentType: 'text/plain'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Empty attachment handled correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-02: MIME Multipart - should respect content-type parameters', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Content-Type Parameters Test',
|
||||
text: 'Testing content-type with charset',
|
||||
html: '<p>HTML with specific charset</p>',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'utf8-text.txt',
|
||||
content: Buffer.from('UTF-8 text: 你好世界'),
|
||||
contentType: 'text/plain; charset=utf-8'
|
||||
},
|
||||
{
|
||||
filename: 'data.xml',
|
||||
content: Buffer.from('<?xml version="1.0" encoding="UTF-8"?><root>Test</root>'),
|
||||
contentType: 'application/xml; charset=utf-8'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Content-type parameters preserved correctly');
|
||||
});
|
||||
|
||||
tap.test('cleanup - close SMTP client', async () => {
|
||||
if (smtpClient && smtpClient.isConnected()) {
|
||||
await smtpClient.close();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('cleanup - stop SMTP server', async () => {
|
||||
await stopTestServer(testServer);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,334 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.ts';
|
||||
import type { SmtpClient } from '../../../ts/mail/delivery/smtpclient/smtp-client.ts';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.ts';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
let testServer: ITestServer;
|
||||
let smtpClient: SmtpClient;
|
||||
|
||||
tap.test('setup - start SMTP server for attachment encoding tests', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2572,
|
||||
tlsEnabled: false,
|
||||
authRequired: false,
|
||||
size: 50 * 1024 * 1024 // 50MB for large attachment tests
|
||||
});
|
||||
|
||||
expect(testServer.port).toEqual(2572);
|
||||
});
|
||||
|
||||
tap.test('setup - create SMTP client', async () => {
|
||||
smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
socketTimeout: 120000, // 2 minutes for large attachments
|
||||
debug: true
|
||||
});
|
||||
|
||||
const isConnected = await smtpClient.verify();
|
||||
expect(isConnected).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('CEP-03: Attachment Encoding - should encode text attachment with base64', async () => {
|
||||
const textContent = 'This is a test text file.\nIt contains multiple lines.\nAnd some special characters: © ® ™';
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Text Attachment Base64 Test',
|
||||
text: 'Email with text attachment',
|
||||
attachments: [{
|
||||
filename: 'test.txt',
|
||||
content: Buffer.from(textContent),
|
||||
contentType: 'text/plain',
|
||||
encoding: 'base64'
|
||||
}]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Text attachment encoded with base64');
|
||||
});
|
||||
|
||||
tap.test('CEP-03: Attachment Encoding - should encode binary data correctly', async () => {
|
||||
// Create binary data with all possible byte values
|
||||
const binaryData = Buffer.alloc(256);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
binaryData[i] = i;
|
||||
}
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Binary Attachment Test',
|
||||
text: 'Email with binary attachment',
|
||||
attachments: [{
|
||||
filename: 'binary.dat',
|
||||
content: binaryData,
|
||||
contentType: 'application/octet-stream'
|
||||
}]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Binary data encoded correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-03: Attachment Encoding - should handle various file types', async () => {
|
||||
const attachments = [
|
||||
{
|
||||
filename: 'image.jpg',
|
||||
content: Buffer.from('/9j/4AAQSkZJRgABAQEASABIAAD/2wBD', 'base64'), // Partial JPEG header
|
||||
contentType: 'image/jpeg'
|
||||
},
|
||||
{
|
||||
filename: 'document.pdf',
|
||||
content: Buffer.from('%PDF-1.4\n%âÃÏÓ\n', 'utf8'),
|
||||
contentType: 'application/pdf'
|
||||
},
|
||||
{
|
||||
filename: 'archive.zip',
|
||||
content: Buffer.from('PK\x03\x04'), // ZIP magic number
|
||||
contentType: 'application/zip'
|
||||
},
|
||||
{
|
||||
filename: 'audio.mp3',
|
||||
content: Buffer.from('ID3'), // MP3 ID3 tag
|
||||
contentType: 'audio/mpeg'
|
||||
}
|
||||
];
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Multiple File Types Test',
|
||||
text: 'Testing various attachment types',
|
||||
attachments
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Various file types encoded correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-03: Attachment Encoding - should handle quoted-printable encoding', async () => {
|
||||
const textWithSpecialChars = 'This line has special chars: café, naïve, résumé\r\nThis line is very long and might need soft line breaks when encoded with quoted-printable encoding method\r\n=This line starts with equals sign';
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Quoted-Printable Test',
|
||||
text: 'Email with quoted-printable attachment',
|
||||
attachments: [{
|
||||
filename: 'special-chars.txt',
|
||||
content: Buffer.from(textWithSpecialChars, 'utf8'),
|
||||
contentType: 'text/plain; charset=utf-8',
|
||||
encoding: 'quoted-printable'
|
||||
}]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Quoted-printable encoding handled correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-03: Attachment Encoding - should handle content-disposition', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Content-Disposition Test',
|
||||
text: 'Testing attachment vs inline disposition',
|
||||
html: '<p>Image below: <img src="cid:inline-image"></p>',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'attachment.txt',
|
||||
content: Buffer.from('This is an attachment'),
|
||||
contentType: 'text/plain'
|
||||
// Default disposition is 'attachment'
|
||||
},
|
||||
{
|
||||
filename: 'inline-image.png',
|
||||
content: Buffer.from('fake png data'),
|
||||
contentType: 'image/png',
|
||||
contentId: 'inline-image' // Makes it inline
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Content-disposition handled correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-03: Attachment Encoding - should handle large attachments efficiently', async () => {
|
||||
// Create a 10MB attachment
|
||||
const largeSize = 10 * 1024 * 1024;
|
||||
const largeData = crypto.randomBytes(largeSize);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Large Attachment Test',
|
||||
text: 'Email with large attachment',
|
||||
attachments: [{
|
||||
filename: 'large-file.bin',
|
||||
content: largeData,
|
||||
contentType: 'application/octet-stream'
|
||||
}]
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = await smtpClient.sendMail(email);
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log(`✅ Large attachment (${largeSize / 1024 / 1024}MB) sent in ${duration}ms`);
|
||||
console.log(` Throughput: ${(largeSize / 1024 / 1024 / (duration / 1000)).toFixed(2)} MB/s`);
|
||||
});
|
||||
|
||||
tap.test('CEP-03: Attachment Encoding - should handle Unicode filenames', async () => {
|
||||
const unicodeAttachments = [
|
||||
{
|
||||
filename: '文档.txt', // Chinese
|
||||
content: Buffer.from('Chinese filename test'),
|
||||
contentType: 'text/plain'
|
||||
},
|
||||
{
|
||||
filename: 'файл.txt', // Russian
|
||||
content: Buffer.from('Russian filename test'),
|
||||
contentType: 'text/plain'
|
||||
},
|
||||
{
|
||||
filename: 'ファイル.txt', // Japanese
|
||||
content: Buffer.from('Japanese filename test'),
|
||||
contentType: 'text/plain'
|
||||
},
|
||||
{
|
||||
filename: '🎉emoji🎊.txt', // Emoji
|
||||
content: Buffer.from('Emoji filename test'),
|
||||
contentType: 'text/plain'
|
||||
}
|
||||
];
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Unicode Filenames Test',
|
||||
text: 'Testing Unicode characters in filenames',
|
||||
attachments: unicodeAttachments
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Unicode filenames encoded correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-03: Attachment Encoding - should handle special MIME headers', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'MIME Headers Test',
|
||||
text: 'Testing special MIME headers',
|
||||
attachments: [{
|
||||
filename: 'report.xml',
|
||||
content: Buffer.from('<?xml version="1.0"?><root>test</root>'),
|
||||
contentType: 'application/xml; charset=utf-8',
|
||||
encoding: 'base64',
|
||||
headers: {
|
||||
'Content-Description': 'Monthly Report',
|
||||
'Content-Transfer-Encoding': 'base64',
|
||||
'Content-ID': '<report-2024-01@example.com>'
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Special MIME headers handled correctly');
|
||||
});
|
||||
|
||||
tap.test('CEP-03: Attachment Encoding - should handle attachment size limits', async () => {
|
||||
// Test with attachment near server limit
|
||||
const nearLimitSize = 45 * 1024 * 1024; // 45MB (near 50MB limit)
|
||||
const nearLimitData = Buffer.alloc(nearLimitSize);
|
||||
|
||||
// Fill with some pattern to avoid compression benefits
|
||||
for (let i = 0; i < nearLimitSize; i++) {
|
||||
nearLimitData[i] = i % 256;
|
||||
}
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Near Size Limit Test',
|
||||
text: 'Testing attachment near size limit',
|
||||
attachments: [{
|
||||
filename: 'near-limit.bin',
|
||||
content: nearLimitData,
|
||||
contentType: 'application/octet-stream'
|
||||
}]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log(`✅ Attachment near size limit (${nearLimitSize / 1024 / 1024}MB) accepted`);
|
||||
});
|
||||
|
||||
tap.test('CEP-03: Attachment Encoding - should handle mixed encoding types', async () => {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Mixed Encoding Test',
|
||||
text: 'Plain text body',
|
||||
html: '<p>HTML body with special chars: café</p>',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'base64.bin',
|
||||
content: crypto.randomBytes(1024),
|
||||
contentType: 'application/octet-stream',
|
||||
encoding: 'base64'
|
||||
},
|
||||
{
|
||||
filename: 'quoted.txt',
|
||||
content: Buffer.from('Text with special chars: naïve café résumé'),
|
||||
contentType: 'text/plain; charset=utf-8',
|
||||
encoding: 'quoted-printable'
|
||||
},
|
||||
{
|
||||
filename: '7bit.txt',
|
||||
content: Buffer.from('Simple ASCII text only'),
|
||||
contentType: 'text/plain',
|
||||
encoding: '7bit'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Mixed encoding types handled correctly');
|
||||
});
|
||||
|
||||
tap.test('cleanup - close SMTP client', async () => {
|
||||
if (smtpClient && smtpClient.isConnected()) {
|
||||
await smtpClient.close();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('cleanup - stop SMTP server', async () => {
|
||||
await stopTestServer(testServer);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,187 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.ts';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.ts';
|
||||
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup test SMTP server', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2577,
|
||||
tlsEnabled: false,
|
||||
authRequired: false
|
||||
});
|
||||
expect(testServer).toBeTruthy();
|
||||
expect(testServer.port).toEqual(2577);
|
||||
});
|
||||
|
||||
tap.test('CEP-04: Basic BCC handling', async () => {
|
||||
console.log('Testing basic BCC handling');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Create email with BCC recipients
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['visible@example.com'],
|
||||
bcc: ['hidden1@example.com', 'hidden2@example.com'],
|
||||
subject: 'BCC Test Email',
|
||||
text: 'This email tests BCC functionality'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with BCC recipients');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-04: Multiple BCC recipients', async () => {
|
||||
console.log('Testing multiple BCC recipients');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Create email with many BCC recipients
|
||||
const bccRecipients = Array.from({ length: 10 },
|
||||
(_, i) => `bcc${i + 1}@example.com`
|
||||
);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['primary@example.com'],
|
||||
bcc: bccRecipients,
|
||||
subject: 'Multiple BCC Test',
|
||||
text: 'Testing with multiple BCC recipients'
|
||||
});
|
||||
|
||||
console.log(`Sending email with ${bccRecipients.length} BCC recipients...`);
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = await smtpClient.sendMail(email);
|
||||
const elapsed = Date.now() - startTime;
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log(`Processed ${bccRecipients.length} BCC recipients in ${elapsed}ms`);
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-04: BCC-only email', async () => {
|
||||
console.log('Testing BCC-only email');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Create email with only BCC recipients (no TO or CC)
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
bcc: ['hidden1@example.com', 'hidden2@example.com', 'hidden3@example.com'],
|
||||
subject: 'BCC-Only Email',
|
||||
text: 'This email has only BCC recipients'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent BCC-only email');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-04: Mixed recipient types', async () => {
|
||||
console.log('Testing mixed recipient types');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Create email with all recipient types
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['to1@example.com', 'to2@example.com'],
|
||||
cc: ['cc1@example.com', 'cc2@example.com'],
|
||||
bcc: ['bcc1@example.com', 'bcc2@example.com'],
|
||||
subject: 'Mixed Recipients Test',
|
||||
text: 'Testing all recipient types together'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Recipient breakdown:');
|
||||
console.log(` TO: ${email.to?.length || 0} recipients`);
|
||||
console.log(` CC: ${email.cc?.length || 0} recipients`);
|
||||
console.log(` BCC: ${email.bcc?.length || 0} recipients`);
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-04: BCC with special characters in addresses', async () => {
|
||||
console.log('Testing BCC with special characters');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// BCC addresses with special characters
|
||||
const specialBccAddresses = [
|
||||
'user+tag@example.com',
|
||||
'first.last@example.com',
|
||||
'user_name@example.com'
|
||||
];
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['visible@example.com'],
|
||||
bcc: specialBccAddresses,
|
||||
subject: 'BCC Special Characters Test',
|
||||
text: 'Testing BCC with special character addresses'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully processed BCC addresses with special characters');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await stopTestServer(testServer);
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,277 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.ts';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.ts';
|
||||
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup test SMTP server', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2578,
|
||||
tlsEnabled: false,
|
||||
authRequired: false
|
||||
});
|
||||
expect(testServer).toBeTruthy();
|
||||
expect(testServer.port).toEqual(2578);
|
||||
});
|
||||
|
||||
tap.test('CEP-05: Basic Reply-To header', async () => {
|
||||
console.log('Testing basic Reply-To header');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Create email with Reply-To header
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
replyTo: 'replies@example.com',
|
||||
subject: 'Reply-To Test',
|
||||
text: 'This email tests Reply-To header functionality'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with Reply-To header');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-05: Multiple Reply-To addresses', async () => {
|
||||
console.log('Testing multiple Reply-To addresses');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Create email with multiple Reply-To addresses
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
replyTo: ['reply1@example.com', 'reply2@example.com'],
|
||||
subject: 'Multiple Reply-To Test',
|
||||
text: 'This email tests multiple Reply-To addresses'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with multiple Reply-To addresses');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-05: Reply-To with display names', async () => {
|
||||
console.log('Testing Reply-To with display names');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Create email with Reply-To containing display names
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
replyTo: 'Support Team <support@example.com>',
|
||||
subject: 'Reply-To Display Name Test',
|
||||
text: 'This email tests Reply-To with display names'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with Reply-To display name');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-05: Return-Path header', async () => {
|
||||
console.log('Testing Return-Path header');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Create email with custom Return-Path
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Return-Path Test',
|
||||
text: 'This email tests Return-Path functionality',
|
||||
headers: {
|
||||
'Return-Path': '<bounces@example.com>'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with Return-Path header');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-05: Different From and Return-Path', async () => {
|
||||
console.log('Testing different From and Return-Path addresses');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Create email with different From and Return-Path
|
||||
const email = new Email({
|
||||
from: 'noreply@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Different Return-Path Test',
|
||||
text: 'This email has different From and Return-Path addresses',
|
||||
headers: {
|
||||
'Return-Path': '<bounces+tracking@example.com>'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with different From and Return-Path');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-05: Reply-To and Return-Path together', async () => {
|
||||
console.log('Testing Reply-To and Return-Path together');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Create email with both Reply-To and Return-Path
|
||||
const email = new Email({
|
||||
from: 'notifications@example.com',
|
||||
to: ['user@example.com'],
|
||||
replyTo: 'support@example.com',
|
||||
subject: 'Reply-To and Return-Path Test',
|
||||
text: 'This email tests both Reply-To and Return-Path headers',
|
||||
headers: {
|
||||
'Return-Path': '<bounces@example.com>'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with both Reply-To and Return-Path');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-05: International characters in Reply-To', async () => {
|
||||
console.log('Testing international characters in Reply-To');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Create email with international characters in Reply-To
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
replyTo: 'Suppört Téam <support@example.com>',
|
||||
subject: 'International Reply-To Test',
|
||||
text: 'This email tests international characters in Reply-To'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with international Reply-To');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-05: Empty and invalid Reply-To handling', async () => {
|
||||
console.log('Testing empty and invalid Reply-To handling');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Test with empty Reply-To (should work)
|
||||
const email1 = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'No Reply-To Test',
|
||||
text: 'This email has no Reply-To header'
|
||||
});
|
||||
|
||||
const result1 = await smtpClient.sendMail(email1);
|
||||
expect(result1).toBeDefined();
|
||||
expect(result1.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email without Reply-To');
|
||||
|
||||
// Test with empty string Reply-To
|
||||
const email2 = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
replyTo: '',
|
||||
subject: 'Empty Reply-To Test',
|
||||
text: 'This email has empty Reply-To'
|
||||
});
|
||||
|
||||
const result2 = await smtpClient.sendMail(email2);
|
||||
expect(result2).toBeDefined();
|
||||
expect(result2.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with empty Reply-To');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await stopTestServer(testServer);
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,235 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.ts';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.ts';
|
||||
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup test SMTP server', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2579,
|
||||
tlsEnabled: false,
|
||||
authRequired: false
|
||||
});
|
||||
expect(testServer).toBeTruthy();
|
||||
expect(testServer.port).toEqual(2579);
|
||||
});
|
||||
|
||||
tap.test('CEP-06: Basic UTF-8 characters', async () => {
|
||||
console.log('Testing basic UTF-8 characters');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Email with basic UTF-8 characters
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'UTF-8 Test: café, naïve, résumé',
|
||||
text: 'This email contains UTF-8 characters: café, naïve, résumé, piñata',
|
||||
html: '<p>HTML with UTF-8: <strong>café</strong>, <em>naïve</em>, résumé, piñata</p>'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with basic UTF-8 characters');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-06: European characters', async () => {
|
||||
console.log('Testing European characters');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Email with European characters
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'European: ñ, ü, ø, å, ß, æ',
|
||||
text: [
|
||||
'German: Müller, Größe, Weiß',
|
||||
'Spanish: niño, señor, España',
|
||||
'French: français, crème, être',
|
||||
'Nordic: København, Göteborg, Ålesund',
|
||||
'Polish: Kraków, Gdańsk, Wrocław'
|
||||
].join('\n'),
|
||||
html: `
|
||||
<h1>European Characters Test</h1>
|
||||
<ul>
|
||||
<li>German: Müller, Größe, Weiß</li>
|
||||
<li>Spanish: niño, señor, España</li>
|
||||
<li>French: français, crème, être</li>
|
||||
<li>Nordic: København, Göteborg, Ålesund</li>
|
||||
<li>Polish: Kraków, Gdańsk, Wrocław</li>
|
||||
</ul>
|
||||
`
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with European characters');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-06: Asian characters', async () => {
|
||||
console.log('Testing Asian characters');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Email with Asian characters
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Asian: 你好, こんにちは, 안녕하세요',
|
||||
text: [
|
||||
'Chinese (Simplified): 你好世界',
|
||||
'Chinese (Traditional): 你好世界',
|
||||
'Japanese: こんにちは世界',
|
||||
'Korean: 안녕하세요 세계',
|
||||
'Thai: สวัสดีโลก',
|
||||
'Hindi: नमस्ते संसार'
|
||||
].join('\n'),
|
||||
html: `
|
||||
<h1>Asian Characters Test</h1>
|
||||
<table>
|
||||
<tr><td>Chinese (Simplified):</td><td>你好世界</td></tr>
|
||||
<tr><td>Chinese (Traditional):</td><td>你好世界</td></tr>
|
||||
<tr><td>Japanese:</td><td>こんにちは世界</td></tr>
|
||||
<tr><td>Korean:</td><td>안녕하세요 세계</td></tr>
|
||||
<tr><td>Thai:</td><td>สวัสดีโลก</td></tr>
|
||||
<tr><td>Hindi:</td><td>नमस्ते संसार</td></tr>
|
||||
</table>
|
||||
`
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with Asian characters');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-06: Emojis and symbols', async () => {
|
||||
console.log('Testing emojis and symbols');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Email with emojis and symbols
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Emojis: 🎉 🚀 ✨ 🌈',
|
||||
text: [
|
||||
'Faces: 😀 😃 😄 😁 😆 😅 😂',
|
||||
'Objects: 🎉 🚀 ✨ 🌈 ⭐ 🔥 💎',
|
||||
'Animals: 🐶 🐱 🐭 🐹 🐰 🦊 🐻',
|
||||
'Food: 🍎 🍌 🍇 🍓 🥝 🍅 🥑',
|
||||
'Symbols: ✓ ✗ ⚠ ♠ ♣ ♥ ♦',
|
||||
'Math: ∑ ∏ ∫ ∞ ± × ÷ ≠ ≤ ≥'
|
||||
].join('\n'),
|
||||
html: `
|
||||
<h1>Emojis and Symbols Test 🎉</h1>
|
||||
<p>Faces: 😀 😃 😄 😁 😆 😅 😂</p>
|
||||
<p>Objects: 🎉 🚀 ✨ 🌈 ⭐ 🔥 💎</p>
|
||||
<p>Animals: 🐶 🐱 🐭 🐹 🐰 🦊 🐻</p>
|
||||
<p>Food: 🍎 🍌 🍇 🍓 🥝 🍅 🥑</p>
|
||||
<p>Symbols: ✓ ✗ ⚠ ♠ ♣ ♥ ♦</p>
|
||||
<p>Math: ∑ ∏ ∫ ∞ ± × ÷ ≠ ≤ ≥</p>
|
||||
`
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with emojis and symbols');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CEP-06: Mixed international content', async () => {
|
||||
console.log('Testing mixed international content');
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Email with mixed international content
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Mixed: Hello 你好 مرحبا こんにちは 🌍',
|
||||
text: [
|
||||
'English: Hello World!',
|
||||
'Chinese: 你好世界!',
|
||||
'Arabic: مرحبا بالعالم!',
|
||||
'Japanese: こんにちは世界!',
|
||||
'Russian: Привет мир!',
|
||||
'Greek: Γεια σας κόσμε!',
|
||||
'Mixed: Hello 世界 🌍 مرحبا こんにちは!'
|
||||
].join('\n'),
|
||||
html: `
|
||||
<h1>International Mix 🌍</h1>
|
||||
<div style="font-family: Arial, sans-serif;">
|
||||
<p><strong>English:</strong> Hello World!</p>
|
||||
<p><strong>Chinese:</strong> 你好世界!</p>
|
||||
<p><strong>Arabic:</strong> مرحبا بالعالم!</p>
|
||||
<p><strong>Japanese:</strong> こんにちは世界!</p>
|
||||
<p><strong>Russian:</strong> Привет мир!</p>
|
||||
<p><strong>Greek:</strong> Γεια σας κόσμε!</p>
|
||||
<p><strong>Mixed:</strong> Hello 世界 🌍 مرحبا こんにちは!</p>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
console.log('Successfully sent email with mixed international content');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await stopTestServer(testServer);
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,489 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.ts';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.ts';
|
||||
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup test SMTP server', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2567,
|
||||
tlsEnabled: false,
|
||||
authRequired: false
|
||||
});
|
||||
expect(testServer).toBeTruthy();
|
||||
expect(testServer.port).toEqual(2567);
|
||||
});
|
||||
|
||||
tap.test('CEP-07: Basic HTML email', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Create HTML email
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'HTML Email Test',
|
||||
html: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; }
|
||||
.header { color: #333; background: #f0f0f0; padding: 20px; }
|
||||
.content { padding: 20px; }
|
||||
.footer { color: #666; font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Welcome!</h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>This is an <strong>HTML email</strong> with <em>formatting</em>.</p>
|
||||
<ul>
|
||||
<li>Feature 1</li>
|
||||
<li>Feature 2</li>
|
||||
<li>Feature 3</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>© 2024 Example Corp</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
text: 'Welcome! This is an HTML email with formatting. Features: 1, 2, 3. © 2024 Example Corp'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Basic HTML email sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-07: HTML email with inline images', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 10000
|
||||
});
|
||||
|
||||
// Create a simple 1x1 red pixel PNG
|
||||
const redPixelBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==';
|
||||
|
||||
// Create HTML email with inline image
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Email with Inline Images',
|
||||
html: `
|
||||
<html>
|
||||
<body>
|
||||
<h1>Email with Inline Images</h1>
|
||||
<p>Here's an inline image:</p>
|
||||
<img src="cid:image001" alt="Red pixel" width="100" height="100">
|
||||
<p>And here's another one:</p>
|
||||
<img src="cid:logo" alt="Company logo">
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
attachments: [
|
||||
{
|
||||
filename: 'red-pixel.png',
|
||||
content: Buffer.from(redPixelBase64, 'base64'),
|
||||
contentType: 'image/png',
|
||||
cid: 'image001' // Content-ID for inline reference
|
||||
},
|
||||
{
|
||||
filename: 'logo.png',
|
||||
content: Buffer.from(redPixelBase64, 'base64'), // Reuse for demo
|
||||
contentType: 'image/png',
|
||||
cid: 'logo'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('HTML email with inline images sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-07: Complex HTML with multiple inline resources', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 10000
|
||||
});
|
||||
|
||||
// Create email with multiple inline resources
|
||||
const email = new Email({
|
||||
from: 'newsletter@example.com',
|
||||
to: 'subscriber@example.com',
|
||||
subject: 'Newsletter with Rich Content',
|
||||
html: `
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 0; padding: 0; }
|
||||
.header { background: url('cid:header-bg') center/cover; height: 200px; }
|
||||
.logo { width: 150px; }
|
||||
.product { display: inline-block; margin: 10px; }
|
||||
.product img { width: 100px; height: 100px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<img src="cid:logo" alt="Company Logo" class="logo">
|
||||
</div>
|
||||
<h1>Monthly Newsletter</h1>
|
||||
<div class="products">
|
||||
<div class="product">
|
||||
<img src="cid:product1" alt="Product 1">
|
||||
<p>Product 1</p>
|
||||
</div>
|
||||
<div class="product">
|
||||
<img src="cid:product2" alt="Product 2">
|
||||
<p>Product 2</p>
|
||||
</div>
|
||||
<div class="product">
|
||||
<img src="cid:product3" alt="Product 3">
|
||||
<p>Product 3</p>
|
||||
</div>
|
||||
</div>
|
||||
<img src="cid:footer-divider" alt="" style="width: 100%; height: 2px;">
|
||||
<p>© 2024 Example Corp</p>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
text: 'Monthly Newsletter - View in HTML for best experience',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'header-bg.jpg',
|
||||
content: Buffer.from('fake-image-data'),
|
||||
contentType: 'image/jpeg',
|
||||
cid: 'header-bg'
|
||||
},
|
||||
{
|
||||
filename: 'logo.png',
|
||||
content: Buffer.from('fake-logo-data'),
|
||||
contentType: 'image/png',
|
||||
cid: 'logo'
|
||||
},
|
||||
{
|
||||
filename: 'product1.jpg',
|
||||
content: Buffer.from('fake-product1-data'),
|
||||
contentType: 'image/jpeg',
|
||||
cid: 'product1'
|
||||
},
|
||||
{
|
||||
filename: 'product2.jpg',
|
||||
content: Buffer.from('fake-product2-data'),
|
||||
contentType: 'image/jpeg',
|
||||
cid: 'product2'
|
||||
},
|
||||
{
|
||||
filename: 'product3.jpg',
|
||||
content: Buffer.from('fake-product3-data'),
|
||||
contentType: 'image/jpeg',
|
||||
cid: 'product3'
|
||||
},
|
||||
{
|
||||
filename: 'divider.gif',
|
||||
content: Buffer.from('fake-divider-data'),
|
||||
contentType: 'image/gif',
|
||||
cid: 'footer-divider'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Complex HTML with multiple inline resources sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-07: HTML with external and inline images mixed', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Mix of inline and external images
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Mixed Image Sources',
|
||||
html: `
|
||||
<html>
|
||||
<body>
|
||||
<h1>Mixed Image Sources</h1>
|
||||
<h2>Inline Image:</h2>
|
||||
<img src="cid:inline-logo" alt="Inline Logo" width="100">
|
||||
<h2>External Images:</h2>
|
||||
<img src="https://via.placeholder.com/150" alt="External Image 1">
|
||||
<img src="http://example.com/image.jpg" alt="External Image 2">
|
||||
<h2>Data URI Image:</h2>
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==" alt="Data URI">
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
attachments: [
|
||||
{
|
||||
filename: 'logo.png',
|
||||
content: Buffer.from('logo-data'),
|
||||
contentType: 'image/png',
|
||||
cid: 'inline-logo'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Successfully sent email with mixed image sources');
|
||||
});
|
||||
|
||||
tap.test('CEP-07: HTML email responsive design', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Responsive HTML email
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Responsive HTML Email',
|
||||
html: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
@media screen and (max-width: 600px) {
|
||||
.container { width: 100% !important; }
|
||||
.column { width: 100% !important; display: block !important; }
|
||||
.mobile-hide { display: none !important; }
|
||||
}
|
||||
.container { width: 600px; margin: 0 auto; }
|
||||
.column { width: 48%; display: inline-block; vertical-align: top; }
|
||||
img { max-width: 100%; height: auto; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Responsive Design Test</h1>
|
||||
<div class="column">
|
||||
<img src="cid:left-image" alt="Left Column">
|
||||
<p>Left column content</p>
|
||||
</div>
|
||||
<div class="column">
|
||||
<img src="cid:right-image" alt="Right Column">
|
||||
<p>Right column content</p>
|
||||
</div>
|
||||
<p class="mobile-hide">This text is hidden on mobile devices</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
text: 'Responsive Design Test - View in HTML',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'left.jpg',
|
||||
content: Buffer.from('left-image-data'),
|
||||
contentType: 'image/jpeg',
|
||||
cid: 'left-image'
|
||||
},
|
||||
{
|
||||
filename: 'right.jpg',
|
||||
content: Buffer.from('right-image-data'),
|
||||
contentType: 'image/jpeg',
|
||||
cid: 'right-image'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Successfully sent responsive HTML email');
|
||||
});
|
||||
|
||||
tap.test('CEP-07: HTML sanitization and security', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Email with potentially dangerous HTML
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'HTML Security Test',
|
||||
html: `
|
||||
<html>
|
||||
<body>
|
||||
<h1>Security Test</h1>
|
||||
<!-- Scripts should be handled safely -->
|
||||
<script>alert('This should not execute');</script>
|
||||
<img src="x" onerror="alert('XSS')">
|
||||
<a href="javascript:alert('Click')">Dangerous Link</a>
|
||||
<iframe src="https://evil.com"></iframe>
|
||||
<form action="https://evil.com/steal">
|
||||
<input type="text" name="data">
|
||||
</form>
|
||||
<!-- Safe content -->
|
||||
<p>This is safe text content.</p>
|
||||
<img src="cid:safe-image" alt="Safe Image">
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
text: 'Security Test - Plain text version',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'safe.png',
|
||||
content: Buffer.from('safe-image-data'),
|
||||
contentType: 'image/png',
|
||||
cid: 'safe-image'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('HTML security test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-07: Large HTML email with many inline images', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 30000
|
||||
});
|
||||
|
||||
// Create email with many inline images
|
||||
const imageCount = 10; // Reduced for testing
|
||||
const attachments: any[] = [];
|
||||
let htmlContent = '<html><body><h1>Performance Test</h1>';
|
||||
|
||||
for (let i = 0; i < imageCount; i++) {
|
||||
const cid = `image${i}`;
|
||||
htmlContent += `<img src="cid:${cid}" alt="Image ${i}" width="50" height="50">`;
|
||||
|
||||
attachments.push({
|
||||
filename: `image${i}.png`,
|
||||
content: Buffer.from(`fake-image-data-${i}`),
|
||||
contentType: 'image/png',
|
||||
cid: cid
|
||||
});
|
||||
}
|
||||
|
||||
htmlContent += '</body></html>';
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: `Email with ${imageCount} inline images`,
|
||||
html: htmlContent,
|
||||
attachments: attachments
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log(`Performance test with ${imageCount} inline images sent successfully`);
|
||||
});
|
||||
|
||||
tap.test('CEP-07: Alternative content for non-HTML clients', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Email with rich HTML and good plain text alternative
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Newsletter - March 2024',
|
||||
html: `
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif;">
|
||||
<div style="background: #f0f0f0; padding: 20px;">
|
||||
<img src="cid:header" alt="Company Newsletter" style="width: 100%; max-width: 600px;">
|
||||
</div>
|
||||
<div style="padding: 20px;">
|
||||
<h1 style="color: #333;">March Newsletter</h1>
|
||||
<h2 style="color: #666;">Featured Articles</h2>
|
||||
<ul>
|
||||
<li><a href="https://example.com/article1">10 Tips for Spring Cleaning</a></li>
|
||||
<li><a href="https://example.com/article2">New Product Launch</a></li>
|
||||
<li><a href="https://example.com/article3">Customer Success Story</a></li>
|
||||
</ul>
|
||||
<div style="background: #e0e0e0; padding: 15px; margin: 20px 0;">
|
||||
<h3>Special Offer!</h3>
|
||||
<p>Get 20% off with code: <strong>SPRING20</strong></p>
|
||||
<img src="cid:offer" alt="Special Offer" style="width: 100%; max-width: 400px;">
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: #333; color: #fff; padding: 20px; text-align: center;">
|
||||
<p>© 2024 Example Corp | <a href="https://example.com/unsubscribe" style="color: #fff;">Unsubscribe</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
text: `COMPANY NEWSLETTER
|
||||
March 2024
|
||||
|
||||
FEATURED ARTICLES
|
||||
* 10 Tips for Spring Cleaning
|
||||
https://example.com/article1
|
||||
* New Product Launch
|
||||
https://example.com/article2
|
||||
* Customer Success Story
|
||||
https://example.com/article3
|
||||
|
||||
SPECIAL OFFER!
|
||||
Get 20% off with code: SPRING20
|
||||
|
||||
---
|
||||
© 2024 Example Corp
|
||||
Unsubscribe: https://example.com/unsubscribe`,
|
||||
attachments: [
|
||||
{
|
||||
filename: 'header.jpg',
|
||||
content: Buffer.from('header-image'),
|
||||
contentType: 'image/jpeg',
|
||||
cid: 'header'
|
||||
},
|
||||
{
|
||||
filename: 'offer.jpg',
|
||||
content: Buffer.from('offer-image'),
|
||||
contentType: 'image/jpeg',
|
||||
cid: 'offer'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Newsletter with alternative content sent successfully');
|
||||
});
|
||||
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await stopTestServer(testServer);
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,293 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.ts';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.ts';
|
||||
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup test SMTP server', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2568,
|
||||
tlsEnabled: false,
|
||||
authRequired: false
|
||||
});
|
||||
expect(testServer).toBeTruthy();
|
||||
expect(testServer.port).toEqual(2568);
|
||||
});
|
||||
|
||||
tap.test('CEP-08: Basic custom headers', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Create email with custom headers
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Custom Headers Test',
|
||||
text: 'Testing custom headers',
|
||||
headers: {
|
||||
'X-Custom-Header': 'Custom Value',
|
||||
'X-Campaign-ID': 'CAMP-2024-03',
|
||||
'X-Priority': 'High',
|
||||
'X-Mailer': 'Custom SMTP Client v1.0'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Basic custom headers test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-08: Standard headers override protection', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Try to override standard headers via custom headers
|
||||
const email = new Email({
|
||||
from: 'real-sender@example.com',
|
||||
to: 'real-recipient@example.com',
|
||||
subject: 'Real Subject',
|
||||
text: 'Testing header override protection',
|
||||
headers: {
|
||||
'From': 'fake-sender@example.com', // Should not override
|
||||
'To': 'fake-recipient@example.com', // Should not override
|
||||
'Subject': 'Fake Subject', // Should not override
|
||||
'Date': 'Mon, 1 Jan 2000 00:00:00 +0000', // Might be allowed
|
||||
'Message-ID': '<fake@example.com>', // Might be allowed
|
||||
'X-Original-From': 'tracking@example.com' // Custom header, should work
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Header override protection test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-08: Tracking and analytics headers', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Common tracking headers
|
||||
const email = new Email({
|
||||
from: 'marketing@example.com',
|
||||
to: 'customer@example.com',
|
||||
subject: 'Special Offer Inside!',
|
||||
text: 'Check out our special offers',
|
||||
headers: {
|
||||
'X-Campaign-ID': 'SPRING-2024-SALE',
|
||||
'X-Customer-ID': 'CUST-12345',
|
||||
'X-Segment': 'high-value-customers',
|
||||
'X-AB-Test': 'variant-b',
|
||||
'X-Send-Time': new Date().toISOString(),
|
||||
'X-Template-Version': '2.1.0',
|
||||
'List-Unsubscribe': '<https://example.com/unsubscribe?id=12345>',
|
||||
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
|
||||
'Precedence': 'bulk'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Tracking and analytics headers test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-08: MIME extension headers', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// MIME-related custom headers
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'MIME Extensions Test',
|
||||
html: '<p>HTML content</p>',
|
||||
text: 'Plain text content',
|
||||
headers: {
|
||||
'MIME-Version': '1.0', // Usually auto-added
|
||||
'X-Accept-Language': 'en-US, en;q=0.9, fr;q=0.8',
|
||||
'X-Auto-Response-Suppress': 'DR, RN, NRN, OOF',
|
||||
'Importance': 'high',
|
||||
'X-Priority': '1',
|
||||
'X-MSMail-Priority': 'High',
|
||||
'Sensitivity': 'Company-Confidential'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('MIME extension headers test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-08: Email threading headers', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Simulate email thread
|
||||
const messageId = `<${Date.now()}.${Math.random()}@example.com>`;
|
||||
const inReplyTo = '<original-message@example.com>';
|
||||
const references = '<thread-start@example.com> <second-message@example.com>';
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Re: Email Threading Test',
|
||||
text: 'This is a reply in the thread',
|
||||
headers: {
|
||||
'Message-ID': messageId,
|
||||
'In-Reply-To': inReplyTo,
|
||||
'References': references,
|
||||
'Thread-Topic': 'Email Threading Test',
|
||||
'Thread-Index': Buffer.from('thread-data').toString('base64')
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Email threading headers test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-08: Security and authentication headers', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Security-related headers
|
||||
const email = new Email({
|
||||
from: 'secure@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Security Headers Test',
|
||||
text: 'Testing security headers',
|
||||
headers: {
|
||||
'X-Originating-IP': '[192.168.1.100]',
|
||||
'X-Auth-Result': 'PASS',
|
||||
'X-Spam-Score': '0.1',
|
||||
'X-Spam-Status': 'No, score=0.1',
|
||||
'X-Virus-Scanned': 'ClamAV using ClamSMTP',
|
||||
'Authentication-Results': 'example.com; spf=pass smtp.mailfrom=sender@example.com',
|
||||
'ARC-Seal': 'i=1; cv=none; d=example.com; s=arc-20240315; t=1710500000;',
|
||||
'ARC-Message-Signature': 'i=1; a=rsa-sha256; c=relaxed/relaxed;',
|
||||
'ARC-Authentication-Results': 'i=1; example.com; spf=pass'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Security and authentication headers test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-08: Header folding for long values', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Create headers with long values that need folding
|
||||
const longValue = 'This is a very long header value that exceeds the recommended 78 character limit per line and should be folded according to RFC 5322 specifications for proper email transmission';
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Header Folding Test with a very long subject line that should be properly folded',
|
||||
text: 'Testing header folding',
|
||||
headers: {
|
||||
'X-Long-Header': longValue,
|
||||
'X-Multiple-Values': 'value1@example.com, value2@example.com, value3@example.com, value4@example.com, value5@example.com, value6@example.com',
|
||||
'References': '<msg1@example.com> <msg2@example.com> <msg3@example.com> <msg4@example.com> <msg5@example.com> <msg6@example.com> <msg7@example.com>'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Header folding test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-08: Custom headers with special characters', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Headers with special characters
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Special Characters in Headers',
|
||||
text: 'Testing special characters',
|
||||
headers: {
|
||||
'X-Special-Chars': 'Value with special: !@#$%^&*()',
|
||||
'X-Quoted-String': '"This is a quoted string"',
|
||||
'X-Unicode': 'Unicode: café, naïve, 你好',
|
||||
'X-Control-Chars': 'No\ttabs\nor\rnewlines', // Should be sanitized
|
||||
'X-Empty': '',
|
||||
'X-Spaces': ' trimmed ',
|
||||
'X-Semicolon': 'part1; part2; part3'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Special characters test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-08: Duplicate header handling', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Some headers can appear multiple times
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Duplicate Headers Test',
|
||||
text: 'Testing duplicate headers',
|
||||
headers: {
|
||||
'Received': 'from server1.example.com',
|
||||
'X-Received': 'from server2.example.com', // Workaround for multiple
|
||||
'Comments': 'First comment',
|
||||
'X-Comments': 'Second comment', // Workaround for multiple
|
||||
'X-Tag': 'tag1, tag2, tag3' // String instead of array
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Duplicate header handling test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await stopTestServer(testServer);
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,314 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.ts';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.ts';
|
||||
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup test SMTP server', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2569,
|
||||
tlsEnabled: false,
|
||||
authRequired: false
|
||||
});
|
||||
expect(testServer).toBeTruthy();
|
||||
expect(testServer.port).toEqual(2569);
|
||||
});
|
||||
|
||||
tap.test('CEP-09: Basic priority headers', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Test different priority levels
|
||||
const priorityLevels = [
|
||||
{ priority: 'high', headers: { 'X-Priority': '1', 'Importance': 'high' } },
|
||||
{ priority: 'normal', headers: { 'X-Priority': '3', 'Importance': 'normal' } },
|
||||
{ priority: 'low', headers: { 'X-Priority': '5', 'Importance': 'low' } }
|
||||
];
|
||||
|
||||
for (const level of priorityLevels) {
|
||||
console.log(`Testing ${level.priority} priority email...`);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: `${level.priority.toUpperCase()} Priority Test`,
|
||||
text: `This is a ${level.priority} priority message`,
|
||||
priority: level.priority as 'high' | 'normal' | 'low'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
}
|
||||
|
||||
console.log('Basic priority headers test completed successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-09: Multiple priority header formats', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Test various priority header combinations
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Multiple Priority Headers Test',
|
||||
text: 'Testing various priority header formats',
|
||||
headers: {
|
||||
'X-Priority': '1 (Highest)',
|
||||
'X-MSMail-Priority': 'High',
|
||||
'Importance': 'high',
|
||||
'Priority': 'urgent',
|
||||
'X-Message-Flag': 'Follow up'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Multiple priority header formats test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-09: Client-specific priority mappings', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Send test email with comprehensive priority headers
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Cross-client Priority Test',
|
||||
text: 'This should appear as high priority in all clients',
|
||||
priority: 'high'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Client-specific priority mappings test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-09: Sensitivity and confidentiality headers', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Test sensitivity levels
|
||||
const sensitivityLevels = [
|
||||
{ level: 'Personal', description: 'Personal information' },
|
||||
{ level: 'Private', description: 'Private communication' },
|
||||
{ level: 'Company-Confidential', description: 'Internal use only' },
|
||||
{ level: 'Normal', description: 'No special handling' }
|
||||
];
|
||||
|
||||
for (const sensitivity of sensitivityLevels) {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: `${sensitivity.level} Message`,
|
||||
text: sensitivity.description,
|
||||
headers: {
|
||||
'Sensitivity': sensitivity.level,
|
||||
'X-Sensitivity': sensitivity.level
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
}
|
||||
|
||||
console.log('Sensitivity and confidentiality headers test completed successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-09: Auto-response suppression headers', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Headers to suppress auto-responses (vacation messages, etc.)
|
||||
const email = new Email({
|
||||
from: 'noreply@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Automated Notification',
|
||||
text: 'This is an automated message. Please do not reply.',
|
||||
headers: {
|
||||
'X-Auto-Response-Suppress': 'All', // Microsoft
|
||||
'Auto-Submitted': 'auto-generated', // RFC 3834
|
||||
'Precedence': 'bulk', // Traditional
|
||||
'X-Autoreply': 'no',
|
||||
'X-Autorespond': 'no',
|
||||
'List-Id': '<notifications.example.com>', // Mailing list header
|
||||
'List-Unsubscribe': '<mailto:unsubscribe@example.com>'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Auto-response suppression headers test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-09: Expiration and retention headers', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Set expiration date for the email
|
||||
const expirationDate = new Date();
|
||||
expirationDate.setDate(expirationDate.getDate() + 7); // Expires in 7 days
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Time-sensitive Information',
|
||||
text: 'This information expires in 7 days',
|
||||
headers: {
|
||||
'Expiry-Date': expirationDate.toUTCString(),
|
||||
'X-Message-TTL': '604800', // 7 days in seconds
|
||||
'X-Auto-Delete-After': expirationDate.toISOString(),
|
||||
'X-Retention-Date': expirationDate.toISOString()
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Expiration and retention headers test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-09: Message flags and categories', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Test various message flags and categories
|
||||
const flaggedEmails = [
|
||||
{
|
||||
flag: 'Follow up',
|
||||
category: 'Action Required',
|
||||
color: 'red'
|
||||
},
|
||||
{
|
||||
flag: 'For Your Information',
|
||||
category: 'Informational',
|
||||
color: 'blue'
|
||||
},
|
||||
{
|
||||
flag: 'Review',
|
||||
category: 'Pending Review',
|
||||
color: 'yellow'
|
||||
}
|
||||
];
|
||||
|
||||
for (const flaggedEmail of flaggedEmails) {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: `${flaggedEmail.flag}: Important Document`,
|
||||
text: `This email is flagged as: ${flaggedEmail.flag}`,
|
||||
headers: {
|
||||
'X-Message-Flag': flaggedEmail.flag,
|
||||
'X-Category': flaggedEmail.category,
|
||||
'X-Color-Label': flaggedEmail.color,
|
||||
'Keywords': flaggedEmail.flag.replace(' ', '-')
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
}
|
||||
|
||||
console.log('Message flags and categories test completed successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-09: Priority with delivery timing', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Test deferred delivery with priority
|
||||
const futureDate = new Date();
|
||||
futureDate.setHours(futureDate.getHours() + 2); // Deliver in 2 hours
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Scheduled High Priority Message',
|
||||
text: 'This high priority message should be delivered at a specific time',
|
||||
priority: 'high',
|
||||
headers: {
|
||||
'Deferred-Delivery': futureDate.toUTCString(),
|
||||
'X-Delay-Until': futureDate.toISOString(),
|
||||
'X-Priority': '1',
|
||||
'Importance': 'High'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Priority with delivery timing test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-09: Priority impact on routing', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Test batch of emails with different priorities
|
||||
const emails = [
|
||||
{ priority: 'high', subject: 'URGENT: Server Down' },
|
||||
{ priority: 'high', subject: 'Critical Security Update' },
|
||||
{ priority: 'normal', subject: 'Weekly Report' },
|
||||
{ priority: 'low', subject: 'Newsletter' },
|
||||
{ priority: 'low', subject: 'Promotional Offer' }
|
||||
];
|
||||
|
||||
for (const emailData of emails) {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: emailData.subject,
|
||||
text: `Priority: ${emailData.priority}`,
|
||||
priority: emailData.priority as 'high' | 'normal' | 'low'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
}
|
||||
|
||||
console.log('Priority impact on routing test completed successfully');
|
||||
});
|
||||
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await stopTestServer(testServer);
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,411 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.ts';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.ts';
|
||||
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup test SMTP server', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2570,
|
||||
tlsEnabled: false,
|
||||
authRequired: false
|
||||
});
|
||||
expect(testServer).toBeTruthy();
|
||||
expect(testServer.port).toEqual(2570);
|
||||
});
|
||||
|
||||
tap.test('CEP-10: Read receipt headers', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Create email requesting read receipt
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Important: Please confirm receipt',
|
||||
text: 'Please confirm you have read this message',
|
||||
headers: {
|
||||
'Disposition-Notification-To': 'sender@example.com',
|
||||
'Return-Receipt-To': 'sender@example.com',
|
||||
'X-Confirm-Reading-To': 'sender@example.com',
|
||||
'X-MS-Receipt-Request': 'sender@example.com'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Read receipt headers test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-10: DSN (Delivery Status Notification) requests', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Create email with DSN options
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'DSN Test Email',
|
||||
text: 'Testing delivery status notifications',
|
||||
headers: {
|
||||
'X-DSN-Options': 'notify=SUCCESS,FAILURE,DELAY;return=HEADERS',
|
||||
'X-Envelope-ID': `msg-${Date.now()}`
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('DSN requests test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-10: DSN notify options', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Test different DSN notify combinations
|
||||
const notifyOptions = [
|
||||
{ notify: ['SUCCESS'], description: 'Notify on successful delivery only' },
|
||||
{ notify: ['FAILURE'], description: 'Notify on failure only' },
|
||||
{ notify: ['DELAY'], description: 'Notify on delays only' },
|
||||
{ notify: ['SUCCESS', 'FAILURE'], description: 'Notify on success and failure' },
|
||||
{ notify: ['NEVER'], description: 'Never send notifications' }
|
||||
];
|
||||
|
||||
for (const option of notifyOptions) {
|
||||
console.log(`Testing DSN: ${option.description}`);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: `DSN Test: ${option.description}`,
|
||||
text: 'Testing DSN notify options',
|
||||
headers: {
|
||||
'X-DSN-Notify': option.notify.join(','),
|
||||
'X-DSN-Return': 'HEADERS'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
}
|
||||
|
||||
console.log('DSN notify options test completed successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-10: DSN return types', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Test different return types
|
||||
const returnTypes = [
|
||||
{ type: 'FULL', description: 'Return full message on failure' },
|
||||
{ type: 'HEADERS', description: 'Return headers only' }
|
||||
];
|
||||
|
||||
for (const returnType of returnTypes) {
|
||||
console.log(`Testing DSN return type: ${returnType.description}`);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: `DSN Return Type: ${returnType.type}`,
|
||||
text: 'Testing DSN return types',
|
||||
headers: {
|
||||
'X-DSN-Notify': 'FAILURE',
|
||||
'X-DSN-Return': returnType.type
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
}
|
||||
|
||||
console.log('DSN return types test completed successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-10: MDN (Message Disposition Notification)', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Create MDN request email
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Please confirm reading',
|
||||
text: 'This message requests a read receipt',
|
||||
headers: {
|
||||
'Disposition-Notification-To': 'sender@example.com',
|
||||
'Disposition-Notification-Options': 'signed-receipt-protocol=optional,pkcs7-signature',
|
||||
'Original-Message-ID': `<${Date.now()}@example.com>`
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
|
||||
// Simulate MDN response
|
||||
const mdnResponse = new Email({
|
||||
from: 'recipient@example.com',
|
||||
to: 'sender@example.com',
|
||||
subject: 'Read: Please confirm reading',
|
||||
headers: {
|
||||
'Content-Type': 'multipart/report; report-type=disposition-notification',
|
||||
'In-Reply-To': `<${Date.now()}@example.com>`,
|
||||
'References': `<${Date.now()}@example.com>`,
|
||||
'Auto-Submitted': 'auto-replied'
|
||||
},
|
||||
text: 'The message was displayed to the recipient',
|
||||
attachments: [{
|
||||
filename: 'disposition-notification.txt',
|
||||
content: Buffer.from(`Reporting-UA: mail.example.com; MailClient/1.0
|
||||
Original-Recipient: rfc822;recipient@example.com
|
||||
Final-Recipient: rfc822;recipient@example.com
|
||||
Original-Message-ID: <${Date.now()}@example.com>
|
||||
Disposition: automatic-action/MDN-sent-automatically; displayed`),
|
||||
contentType: 'message/disposition-notification'
|
||||
}]
|
||||
});
|
||||
|
||||
const mdnResult = await smtpClient.sendMail(mdnResponse);
|
||||
expect(mdnResult.success).toBeTruthy();
|
||||
console.log('MDN test completed successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-10: Multiple recipients with different DSN', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Email with multiple recipients
|
||||
const emails = [
|
||||
{
|
||||
to: 'important@example.com',
|
||||
dsn: 'SUCCESS,FAILURE,DELAY'
|
||||
},
|
||||
{
|
||||
to: 'normal@example.com',
|
||||
dsn: 'FAILURE'
|
||||
},
|
||||
{
|
||||
to: 'optional@example.com',
|
||||
dsn: 'NEVER'
|
||||
}
|
||||
];
|
||||
|
||||
for (const emailData of emails) {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: emailData.to,
|
||||
subject: 'Multi-recipient DSN Test',
|
||||
text: 'Testing per-recipient DSN options',
|
||||
headers: {
|
||||
'X-DSN-Notify': emailData.dsn,
|
||||
'X-DSN-Return': 'HEADERS'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
}
|
||||
|
||||
console.log('Multiple recipients DSN test completed successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-10: DSN with ORCPT', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Test ORCPT (Original Recipient) parameter
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'forwarded@example.com',
|
||||
subject: 'DSN with ORCPT Test',
|
||||
text: 'Testing original recipient tracking',
|
||||
headers: {
|
||||
'X-DSN-Notify': 'SUCCESS,FAILURE',
|
||||
'X-DSN-Return': 'HEADERS',
|
||||
'X-Original-Recipient': 'rfc822;original@example.com'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('DSN with ORCPT test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-10: Receipt request formats', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Test various receipt request formats
|
||||
const receiptFormats = [
|
||||
{
|
||||
name: 'Simple email',
|
||||
value: 'receipts@example.com'
|
||||
},
|
||||
{
|
||||
name: 'With display name',
|
||||
value: '"Receipt Handler" <receipts@example.com>'
|
||||
},
|
||||
{
|
||||
name: 'Multiple addresses',
|
||||
value: 'receipts@example.com, backup@example.com'
|
||||
},
|
||||
{
|
||||
name: 'With comment',
|
||||
value: 'receipts@example.com (Automated System)'
|
||||
}
|
||||
];
|
||||
|
||||
for (const format of receiptFormats) {
|
||||
console.log(`Testing receipt format: ${format.name}`);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: `Receipt Format: ${format.name}`,
|
||||
text: 'Testing receipt address formats',
|
||||
headers: {
|
||||
'Disposition-Notification-To': format.value
|
||||
}
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTruthy();
|
||||
}
|
||||
|
||||
console.log('Receipt request formats test completed successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-10: Non-delivery reports', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Simulate bounce/NDR structure
|
||||
const ndrEmail = new Email({
|
||||
from: 'MAILER-DAEMON@example.com',
|
||||
to: 'original-sender@example.com',
|
||||
subject: 'Undelivered Mail Returned to Sender',
|
||||
headers: {
|
||||
'Auto-Submitted': 'auto-replied',
|
||||
'Content-Type': 'multipart/report; report-type=delivery-status',
|
||||
'X-Failed-Recipients': 'nonexistent@example.com'
|
||||
},
|
||||
text: 'This is the mail delivery agent at example.com.\n\n' +
|
||||
'I was unable to deliver your message to the following addresses:\n\n' +
|
||||
'<nonexistent@example.com>: User unknown',
|
||||
attachments: [
|
||||
{
|
||||
filename: 'delivery-status.txt',
|
||||
content: Buffer.from(`Reporting-MTA: dns; mail.example.com
|
||||
X-Queue-ID: 123456789
|
||||
Arrival-Date: ${new Date().toUTCString()}
|
||||
|
||||
Final-Recipient: rfc822;nonexistent@example.com
|
||||
Original-Recipient: rfc822;nonexistent@example.com
|
||||
Action: failed
|
||||
Status: 5.1.1
|
||||
Diagnostic-Code: smtp; 550 5.1.1 User unknown`),
|
||||
contentType: 'message/delivery-status'
|
||||
},
|
||||
{
|
||||
filename: 'original-message.eml',
|
||||
content: Buffer.from('From: original-sender@example.com\r\n' +
|
||||
'To: nonexistent@example.com\r\n' +
|
||||
'Subject: Original Subject\r\n\r\n' +
|
||||
'Original message content'),
|
||||
contentType: 'message/rfc822'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(ndrEmail);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Non-delivery report test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('CEP-10: Delivery delay notifications', async () => {
|
||||
const smtpClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Simulate delayed delivery notification
|
||||
const delayNotification = new Email({
|
||||
from: 'postmaster@example.com',
|
||||
to: 'sender@example.com',
|
||||
subject: 'Delivery Status: Delayed',
|
||||
headers: {
|
||||
'Auto-Submitted': 'auto-replied',
|
||||
'Content-Type': 'multipart/report; report-type=delivery-status',
|
||||
'X-Delay-Reason': 'Remote server temporarily unavailable'
|
||||
},
|
||||
text: 'This is an automatically generated Delivery Delay Notification.\n\n' +
|
||||
'Your message has not been delivered to the following recipients yet:\n\n' +
|
||||
' recipient@remote-server.com\n\n' +
|
||||
'The server will continue trying to deliver your message for 48 hours.',
|
||||
attachments: [{
|
||||
filename: 'delay-status.txt',
|
||||
content: Buffer.from(`Reporting-MTA: dns; mail.example.com
|
||||
Arrival-Date: ${new Date(Date.now() - 3600000).toUTCString()}
|
||||
Last-Attempt-Date: ${new Date().toUTCString()}
|
||||
|
||||
Final-Recipient: rfc822;recipient@remote-server.com
|
||||
Action: delayed
|
||||
Status: 4.4.1
|
||||
Will-Retry-Until: ${new Date(Date.now() + 172800000).toUTCString()}
|
||||
Diagnostic-Code: smtp; 421 4.4.1 Remote server temporarily unavailable`),
|
||||
contentType: 'message/delivery-status'
|
||||
}]
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(delayNotification);
|
||||
expect(result.success).toBeTruthy();
|
||||
console.log('Delivery delay notification test sent successfully');
|
||||
});
|
||||
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await stopTestServer(testServer);
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user