656 lines
23 KiB
TypeScript
656 lines
23 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import { createTestServer } from '../../helpers/server.loader.js';
|
|
import { createTestSmtpClient } from '../../helpers/smtp.client.js';
|
|
import { Email } from '../../../ts/index.js';
|
|
|
|
tap.test('CRFC-08: should handle SMTP extensions correctly (Various RFCs)', async (tools) => {
|
|
const testId = 'CRFC-08-smtp-extensions';
|
|
console.log(`\n${testId}: Testing SMTP extensions compliance...`);
|
|
|
|
let scenarioCount = 0;
|
|
|
|
// Scenario 1: CHUNKING extension (RFC 3030)
|
|
await (async () => {
|
|
scenarioCount++;
|
|
console.log(`\nScenario ${scenarioCount}: Testing CHUNKING extension (RFC 3030)`);
|
|
|
|
const testServer = await createTestServer({
|
|
onConnection: async (socket) => {
|
|
console.log(' [Server] Client connected');
|
|
socket.write('220 chunking.example.com ESMTP\r\n');
|
|
|
|
let chunkingMode = false;
|
|
let totalChunks = 0;
|
|
let totalBytes = 0;
|
|
|
|
socket.on('data', (data) => {
|
|
const text = data.toString();
|
|
|
|
if (chunkingMode) {
|
|
// In chunking mode, all data is message content
|
|
totalBytes += data.length;
|
|
console.log(` [Server] Received chunk: ${data.length} bytes`);
|
|
return;
|
|
}
|
|
|
|
const command = text.trim();
|
|
console.log(` [Server] Received: ${command}`);
|
|
|
|
if (command.startsWith('EHLO')) {
|
|
socket.write('250-chunking.example.com\r\n');
|
|
socket.write('250-CHUNKING\r\n');
|
|
socket.write('250-8BITMIME\r\n');
|
|
socket.write('250-BINARYMIME\r\n');
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('MAIL FROM:')) {
|
|
if (command.includes('BODY=BINARYMIME')) {
|
|
console.log(' [Server] Binary MIME body declared');
|
|
}
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('RCPT TO:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('BDAT ')) {
|
|
// BDAT command format: BDAT <size> [LAST]
|
|
const parts = command.split(' ');
|
|
const chunkSize = parseInt(parts[1]);
|
|
const isLast = parts.includes('LAST');
|
|
|
|
totalChunks++;
|
|
console.log(` [Server] BDAT chunk ${totalChunks}: ${chunkSize} bytes${isLast ? ' (LAST)' : ''}`);
|
|
|
|
if (isLast) {
|
|
socket.write(`250 OK: Message accepted, ${totalChunks} chunks, ${totalBytes} total bytes\r\n`);
|
|
chunkingMode = false;
|
|
totalChunks = 0;
|
|
totalBytes = 0;
|
|
} else {
|
|
socket.write('250 OK: Chunk accepted\r\n');
|
|
chunkingMode = true;
|
|
}
|
|
} else if (command === 'DATA') {
|
|
// DATA not allowed when CHUNKING is available
|
|
socket.write('503 5.5.1 Use BDAT instead of DATA\r\n');
|
|
} else if (command === 'QUIT') {
|
|
socket.write('221 Bye\r\n');
|
|
socket.end();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
const smtpClient = createTestSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false
|
|
});
|
|
|
|
// Test with binary content that would benefit from chunking
|
|
const binaryContent = Buffer.alloc(1024);
|
|
for (let i = 0; i < binaryContent.length; i++) {
|
|
binaryContent[i] = i % 256;
|
|
}
|
|
|
|
const email = new Email({
|
|
from: 'sender@example.com',
|
|
to: ['recipient@example.com'],
|
|
subject: 'CHUNKING test',
|
|
text: 'Testing CHUNKING extension with binary data',
|
|
attachments: [{
|
|
filename: 'binary-data.bin',
|
|
content: binaryContent
|
|
}]
|
|
});
|
|
|
|
const result = await smtpClient.sendMail(email);
|
|
console.log(' CHUNKING extension handled (if supported by client)');
|
|
expect(result).toBeDefined();
|
|
expect(result.messageId).toBeDefined();
|
|
|
|
await testServer.server.close();
|
|
})();
|
|
|
|
// Scenario 2: DELIVERBY extension (RFC 2852)
|
|
await (async () => {
|
|
scenarioCount++;
|
|
console.log(`\nScenario ${scenarioCount}: Testing DELIVERBY extension (RFC 2852)`);
|
|
|
|
const testServer = await createTestServer({
|
|
onConnection: async (socket) => {
|
|
console.log(' [Server] Client connected');
|
|
socket.write('220 deliverby.example.com ESMTP\r\n');
|
|
|
|
socket.on('data', (data) => {
|
|
const command = data.toString().trim();
|
|
console.log(` [Server] Received: ${command}`);
|
|
|
|
if (command.startsWith('EHLO')) {
|
|
socket.write('250-deliverby.example.com\r\n');
|
|
socket.write('250-DELIVERBY 86400\r\n'); // 24 hours max
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('MAIL FROM:')) {
|
|
// Check for DELIVERBY parameter
|
|
const deliverByMatch = command.match(/DELIVERBY=(\d+)([RN]?)/i);
|
|
if (deliverByMatch) {
|
|
const seconds = parseInt(deliverByMatch[1]);
|
|
const mode = deliverByMatch[2] || 'R'; // R=return, N=notify
|
|
|
|
console.log(` [Server] DELIVERBY: ${seconds} seconds, mode: ${mode}`);
|
|
|
|
if (seconds > 86400) {
|
|
socket.write('501 5.5.4 DELIVERBY time exceeds maximum\r\n');
|
|
} else if (seconds < 0) {
|
|
socket.write('501 5.5.4 Invalid DELIVERBY time\r\n');
|
|
} else {
|
|
socket.write('250 OK: Delivery deadline accepted\r\n');
|
|
}
|
|
} else {
|
|
socket.write('250 OK\r\n');
|
|
}
|
|
} else if (command.startsWith('RCPT TO:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'DATA') {
|
|
socket.write('354 Start mail input\r\n');
|
|
} else if (command === '.') {
|
|
socket.write('250 OK: Message queued with delivery deadline\r\n');
|
|
} else if (command === 'QUIT') {
|
|
socket.write('221 Bye\r\n');
|
|
socket.end();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
const smtpClient = createTestSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false
|
|
});
|
|
|
|
// Test with delivery deadline
|
|
const email = new Email({
|
|
from: 'sender@example.com',
|
|
to: ['urgent@example.com'],
|
|
subject: 'Urgent delivery test',
|
|
text: 'This message has a delivery deadline',
|
|
// Note: Most SMTP clients don't expose DELIVERBY directly
|
|
// but we can test server handling
|
|
});
|
|
|
|
const result = await smtpClient.sendMail(email);
|
|
console.log(' DELIVERBY extension supported by server');
|
|
expect(result).toBeDefined();
|
|
|
|
await testServer.server.close();
|
|
})();
|
|
|
|
// Scenario 3: ETRN extension (RFC 1985)
|
|
await (async () => {
|
|
scenarioCount++;
|
|
console.log(`\nScenario ${scenarioCount}: Testing ETRN extension (RFC 1985)`);
|
|
|
|
const testServer = await createTestServer({
|
|
onConnection: async (socket) => {
|
|
console.log(' [Server] Client connected');
|
|
socket.write('220 etrn.example.com ESMTP\r\n');
|
|
|
|
socket.on('data', (data) => {
|
|
const command = data.toString().trim();
|
|
console.log(` [Server] Received: ${command}`);
|
|
|
|
if (command.startsWith('EHLO')) {
|
|
socket.write('250-etrn.example.com\r\n');
|
|
socket.write('250-ETRN\r\n');
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('ETRN ')) {
|
|
const domain = command.substring(5);
|
|
console.log(` [Server] ETRN request for domain: ${domain}`);
|
|
|
|
if (domain === '@example.com') {
|
|
socket.write('250 OK: Queue processing started for example.com\r\n');
|
|
} else if (domain === '#urgent') {
|
|
socket.write('250 OK: Urgent queue processing started\r\n');
|
|
} else if (domain.includes('unknown')) {
|
|
socket.write('458 Unable to queue messages for node\r\n');
|
|
} else {
|
|
socket.write('250 OK: Queue processing started\r\n');
|
|
}
|
|
} else if (command.startsWith('MAIL FROM:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('RCPT TO:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'DATA') {
|
|
socket.write('354 Start mail input\r\n');
|
|
} else if (command === '.') {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'QUIT') {
|
|
socket.write('221 Bye\r\n');
|
|
socket.end();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// ETRN is typically used by mail servers, not clients
|
|
// We'll test the server's ETRN capability manually
|
|
const net = await import('net');
|
|
const client = net.createConnection(testServer.port, testServer.hostname);
|
|
|
|
const commands = [
|
|
'EHLO client.example.com',
|
|
'ETRN @example.com', // Request queue processing for domain
|
|
'ETRN #urgent', // Request urgent queue processing
|
|
'ETRN unknown.domain.com', // Test error handling
|
|
'QUIT'
|
|
];
|
|
|
|
let commandIndex = 0;
|
|
|
|
client.on('data', (data) => {
|
|
const response = data.toString().trim();
|
|
console.log(` [Client] Response: ${response}`);
|
|
|
|
if (commandIndex < commands.length) {
|
|
setTimeout(() => {
|
|
const command = commands[commandIndex];
|
|
console.log(` [Client] Sending: ${command}`);
|
|
client.write(command + '\r\n');
|
|
commandIndex++;
|
|
}, 100);
|
|
} else {
|
|
client.end();
|
|
}
|
|
});
|
|
|
|
await new Promise((resolve, reject) => {
|
|
client.on('end', () => {
|
|
console.log(' ETRN extension testing completed');
|
|
resolve(void 0);
|
|
});
|
|
client.on('error', reject);
|
|
});
|
|
|
|
await testServer.server.close();
|
|
})();
|
|
|
|
// Scenario 4: VRFY and EXPN extensions (RFC 5321)
|
|
await (async () => {
|
|
scenarioCount++;
|
|
console.log(`\nScenario ${scenarioCount}: Testing VRFY and EXPN extensions`);
|
|
|
|
const testServer = await createTestServer({
|
|
onConnection: async (socket) => {
|
|
console.log(' [Server] Client connected');
|
|
socket.write('220 verify.example.com ESMTP\r\n');
|
|
|
|
// Simulated user database
|
|
const users = new Map([
|
|
['admin', { email: 'admin@example.com', fullName: 'Administrator' }],
|
|
['john', { email: 'john.doe@example.com', fullName: 'John Doe' }],
|
|
['support', { email: 'support@example.com', fullName: 'Support Team' }]
|
|
]);
|
|
|
|
const mailingLists = new Map([
|
|
['staff', ['admin@example.com', 'john.doe@example.com']],
|
|
['support-team', ['support@example.com', 'admin@example.com']]
|
|
]);
|
|
|
|
socket.on('data', (data) => {
|
|
const command = data.toString().trim();
|
|
console.log(` [Server] Received: ${command}`);
|
|
|
|
if (command.startsWith('EHLO')) {
|
|
socket.write('250-verify.example.com\r\n');
|
|
socket.write('250-VRFY\r\n');
|
|
socket.write('250-EXPN\r\n');
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('VRFY ')) {
|
|
const query = command.substring(5);
|
|
console.log(` [Server] VRFY query: ${query}`);
|
|
|
|
// Look up user
|
|
const user = users.get(query.toLowerCase());
|
|
if (user) {
|
|
socket.write(`250 ${user.fullName} <${user.email}>\r\n`);
|
|
} else {
|
|
// Check if it's an email address
|
|
const emailMatch = Array.from(users.values()).find(u =>
|
|
u.email.toLowerCase() === query.toLowerCase()
|
|
);
|
|
if (emailMatch) {
|
|
socket.write(`250 ${emailMatch.fullName} <${emailMatch.email}>\r\n`);
|
|
} else {
|
|
socket.write('550 5.1.1 User unknown\r\n');
|
|
}
|
|
}
|
|
} else if (command.startsWith('EXPN ')) {
|
|
const listName = command.substring(5);
|
|
console.log(` [Server] EXPN query: ${listName}`);
|
|
|
|
const list = mailingLists.get(listName.toLowerCase());
|
|
if (list) {
|
|
socket.write(`250-Mailing list ${listName}:\r\n`);
|
|
list.forEach((email, index) => {
|
|
const prefix = index < list.length - 1 ? '250-' : '250 ';
|
|
socket.write(`${prefix}${email}\r\n`);
|
|
});
|
|
} else {
|
|
socket.write('550 5.1.1 Mailing list not found\r\n');
|
|
}
|
|
} else if (command.startsWith('MAIL FROM:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('RCPT TO:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'DATA') {
|
|
socket.write('354 Start mail input\r\n');
|
|
} else if (command === '.') {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'QUIT') {
|
|
socket.write('221 Bye\r\n');
|
|
socket.end();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Test VRFY and EXPN commands
|
|
const net = await import('net');
|
|
const client = net.createConnection(testServer.port, testServer.hostname);
|
|
|
|
const commands = [
|
|
'EHLO client.example.com',
|
|
'VRFY admin', // Verify user by username
|
|
'VRFY john.doe@example.com', // Verify user by email
|
|
'VRFY nonexistent', // Test unknown user
|
|
'EXPN staff', // Expand mailing list
|
|
'EXPN nonexistent-list', // Test unknown list
|
|
'QUIT'
|
|
];
|
|
|
|
let commandIndex = 0;
|
|
|
|
client.on('data', (data) => {
|
|
const response = data.toString().trim();
|
|
console.log(` [Client] Response: ${response}`);
|
|
|
|
if (commandIndex < commands.length) {
|
|
setTimeout(() => {
|
|
const command = commands[commandIndex];
|
|
console.log(` [Client] Sending: ${command}`);
|
|
client.write(command + '\r\n');
|
|
commandIndex++;
|
|
}, 200);
|
|
} else {
|
|
client.end();
|
|
}
|
|
});
|
|
|
|
await new Promise((resolve, reject) => {
|
|
client.on('end', () => {
|
|
console.log(' VRFY and EXPN testing completed');
|
|
resolve(void 0);
|
|
});
|
|
client.on('error', reject);
|
|
});
|
|
|
|
await testServer.server.close();
|
|
})();
|
|
|
|
// Scenario 5: HELP extension
|
|
await (async () => {
|
|
scenarioCount++;
|
|
console.log(`\nScenario ${scenarioCount}: Testing HELP extension`);
|
|
|
|
const testServer = await createTestServer({
|
|
onConnection: async (socket) => {
|
|
console.log(' [Server] Client connected');
|
|
socket.write('220 help.example.com ESMTP\r\n');
|
|
|
|
const helpTopics = new Map([
|
|
['commands', [
|
|
'Available commands:',
|
|
'EHLO <domain> - Extended HELLO',
|
|
'MAIL FROM:<addr> - Specify sender',
|
|
'RCPT TO:<addr> - Specify recipient',
|
|
'DATA - Start message text',
|
|
'QUIT - Close connection'
|
|
]],
|
|
['extensions', [
|
|
'Supported extensions:',
|
|
'SIZE - Message size declaration',
|
|
'8BITMIME - 8-bit MIME transport',
|
|
'STARTTLS - Start TLS negotiation',
|
|
'AUTH - SMTP Authentication',
|
|
'DSN - Delivery Status Notifications'
|
|
]],
|
|
['syntax', [
|
|
'Command syntax:',
|
|
'Commands are case-insensitive',
|
|
'Lines end with CRLF',
|
|
'Email addresses must be in <> brackets',
|
|
'Parameters are space-separated'
|
|
]]
|
|
]);
|
|
|
|
socket.on('data', (data) => {
|
|
const command = data.toString().trim();
|
|
console.log(` [Server] Received: ${command}`);
|
|
|
|
if (command.startsWith('EHLO')) {
|
|
socket.write('250-help.example.com\r\n');
|
|
socket.write('250-HELP\r\n');
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'HELP' || command === 'HELP HELP') {
|
|
socket.write('214-This server provides HELP for the following topics:\r\n');
|
|
socket.write('214-COMMANDS - List of available commands\r\n');
|
|
socket.write('214-EXTENSIONS - List of supported extensions\r\n');
|
|
socket.write('214-SYNTAX - Command syntax rules\r\n');
|
|
socket.write('214 Use HELP <topic> for specific information\r\n');
|
|
} else if (command.startsWith('HELP ')) {
|
|
const topic = command.substring(5).toLowerCase();
|
|
const helpText = helpTopics.get(topic);
|
|
|
|
if (helpText) {
|
|
helpText.forEach((line, index) => {
|
|
const prefix = index < helpText.length - 1 ? '214-' : '214 ';
|
|
socket.write(`${prefix}${line}\r\n`);
|
|
});
|
|
} else {
|
|
socket.write('504 5.3.0 HELP topic not available\r\n');
|
|
}
|
|
} else if (command.startsWith('MAIL FROM:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('RCPT TO:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'DATA') {
|
|
socket.write('354 Start mail input\r\n');
|
|
} else if (command === '.') {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'QUIT') {
|
|
socket.write('221 Bye\r\n');
|
|
socket.end();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Test HELP command
|
|
const net = await import('net');
|
|
const client = net.createConnection(testServer.port, testServer.hostname);
|
|
|
|
const commands = [
|
|
'EHLO client.example.com',
|
|
'HELP', // General help
|
|
'HELP COMMANDS', // Specific topic
|
|
'HELP EXTENSIONS', // Another topic
|
|
'HELP NONEXISTENT', // Unknown topic
|
|
'QUIT'
|
|
];
|
|
|
|
let commandIndex = 0;
|
|
|
|
client.on('data', (data) => {
|
|
const response = data.toString().trim();
|
|
console.log(` [Client] Response: ${response}`);
|
|
|
|
if (commandIndex < commands.length) {
|
|
setTimeout(() => {
|
|
const command = commands[commandIndex];
|
|
console.log(` [Client] Sending: ${command}`);
|
|
client.write(command + '\r\n');
|
|
commandIndex++;
|
|
}, 200);
|
|
} else {
|
|
client.end();
|
|
}
|
|
});
|
|
|
|
await new Promise((resolve, reject) => {
|
|
client.on('end', () => {
|
|
console.log(' HELP extension testing completed');
|
|
resolve(void 0);
|
|
});
|
|
client.on('error', reject);
|
|
});
|
|
|
|
await testServer.server.close();
|
|
})();
|
|
|
|
// Scenario 6: Extension combination and interaction
|
|
await (async () => {
|
|
scenarioCount++;
|
|
console.log(`\nScenario ${scenarioCount}: Testing extension combinations`);
|
|
|
|
const testServer = await createTestServer({
|
|
onConnection: async (socket) => {
|
|
console.log(' [Server] Client connected');
|
|
socket.write('220 combined.example.com ESMTP\r\n');
|
|
|
|
let activeExtensions: string[] = [];
|
|
|
|
socket.on('data', (data) => {
|
|
const command = data.toString().trim();
|
|
console.log(` [Server] Received: ${command}`);
|
|
|
|
if (command.startsWith('EHLO')) {
|
|
socket.write('250-combined.example.com\r\n');
|
|
|
|
// Announce multiple extensions
|
|
const extensions = [
|
|
'SIZE 52428800',
|
|
'8BITMIME',
|
|
'SMTPUTF8',
|
|
'ENHANCEDSTATUSCODES',
|
|
'PIPELINING',
|
|
'DSN',
|
|
'DELIVERBY 86400',
|
|
'CHUNKING',
|
|
'BINARYMIME',
|
|
'HELP'
|
|
];
|
|
|
|
extensions.forEach(ext => {
|
|
socket.write(`250-${ext}\r\n`);
|
|
activeExtensions.push(ext.split(' ')[0]);
|
|
});
|
|
|
|
socket.write('250 OK\r\n');
|
|
console.log(` [Server] Active extensions: ${activeExtensions.join(', ')}`);
|
|
} else if (command.startsWith('MAIL FROM:')) {
|
|
// Check for multiple extension parameters
|
|
const params = [];
|
|
|
|
if (command.includes('SIZE=')) {
|
|
const sizeMatch = command.match(/SIZE=(\d+)/);
|
|
if (sizeMatch) params.push(`SIZE=${sizeMatch[1]}`);
|
|
}
|
|
|
|
if (command.includes('BODY=')) {
|
|
const bodyMatch = command.match(/BODY=(\w+)/);
|
|
if (bodyMatch) params.push(`BODY=${bodyMatch[1]}`);
|
|
}
|
|
|
|
if (command.includes('SMTPUTF8')) {
|
|
params.push('SMTPUTF8');
|
|
}
|
|
|
|
if (command.includes('DELIVERBY=')) {
|
|
const deliverByMatch = command.match(/DELIVERBY=(\d+)/);
|
|
if (deliverByMatch) params.push(`DELIVERBY=${deliverByMatch[1]}`);
|
|
}
|
|
|
|
if (params.length > 0) {
|
|
console.log(` [Server] Extension parameters: ${params.join(', ')}`);
|
|
}
|
|
|
|
socket.write('250 2.1.0 Sender OK\r\n');
|
|
} else if (command.startsWith('RCPT TO:')) {
|
|
// Check for DSN parameters
|
|
if (command.includes('NOTIFY=')) {
|
|
const notifyMatch = command.match(/NOTIFY=([^,\s]+)/);
|
|
if (notifyMatch) {
|
|
console.log(` [Server] DSN NOTIFY: ${notifyMatch[1]}`);
|
|
}
|
|
}
|
|
|
|
socket.write('250 2.1.5 Recipient OK\r\n');
|
|
} else if (command === 'DATA') {
|
|
if (activeExtensions.includes('CHUNKING')) {
|
|
socket.write('503 5.5.1 Use BDAT when CHUNKING is available\r\n');
|
|
} else {
|
|
socket.write('354 Start mail input\r\n');
|
|
}
|
|
} else if (command.startsWith('BDAT ')) {
|
|
if (activeExtensions.includes('CHUNKING')) {
|
|
const parts = command.split(' ');
|
|
const size = parts[1];
|
|
const isLast = parts.includes('LAST');
|
|
console.log(` [Server] BDAT chunk: ${size} bytes${isLast ? ' (LAST)' : ''}`);
|
|
|
|
if (isLast) {
|
|
socket.write('250 2.0.0 Message accepted\r\n');
|
|
} else {
|
|
socket.write('250 2.0.0 Chunk accepted\r\n');
|
|
}
|
|
} else {
|
|
socket.write('500 5.5.1 CHUNKING not available\r\n');
|
|
}
|
|
} else if (command === '.') {
|
|
socket.write('250 2.0.0 Message accepted\r\n');
|
|
} else if (command === 'QUIT') {
|
|
socket.write('221 2.0.0 Bye\r\n');
|
|
socket.end();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
const smtpClient = createTestSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false
|
|
});
|
|
|
|
// Test email that could use multiple extensions
|
|
const email = new Email({
|
|
from: 'sender@example.com',
|
|
to: ['recipient@example.com'],
|
|
subject: 'Extension combination test with UTF-8: 测试',
|
|
text: 'Testing multiple SMTP extensions together',
|
|
dsn: {
|
|
notify: ['SUCCESS', 'FAILURE'],
|
|
envid: 'multi-ext-test-123'
|
|
}
|
|
});
|
|
|
|
const result = await smtpClient.sendMail(email);
|
|
console.log(' Multiple extension combination handled');
|
|
expect(result).toBeDefined();
|
|
expect(result.messageId).toBeDefined();
|
|
|
|
await testServer.server.close();
|
|
})();
|
|
|
|
console.log(`\n${testId}: All ${scenarioCount} SMTP extension scenarios tested ✓`);
|
|
});
|
|
|
|
tap.start(); |