update
This commit is contained in:
@ -1,513 +1,315 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestSmtpServer } from '../../helpers/server.loader.js';
|
||||
import { createSmtpClient } from '../../helpers/smtp.client.js';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.js';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.js';
|
||||
import * as net from 'net';
|
||||
|
||||
let testServer: any;
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup test SMTP server', async () => {
|
||||
testServer = await startTestSmtpServer();
|
||||
expect(testServer).toBeTruthy();
|
||||
expect(testServer.port).toBeGreaterThan(0);
|
||||
tap.test('setup - start SMTP server for invalid recipient tests', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2568,
|
||||
tlsEnabled: false,
|
||||
authRequired: false
|
||||
});
|
||||
|
||||
expect(testServer.port).toEqual(2568);
|
||||
});
|
||||
|
||||
tap.test('CERR-06: Invalid email address formats', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
validateEmails: true,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Test various invalid email formats
|
||||
// Test various invalid email formats that should be caught by Email validation
|
||||
const invalidEmails = [
|
||||
{ email: 'notanemail', error: 'Missing @ symbol' },
|
||||
{ email: '@example.com', error: 'Missing local part' },
|
||||
{ email: 'user@', error: 'Missing domain' },
|
||||
{ email: 'user name@example.com', error: 'Space in local part' },
|
||||
{ email: 'user@domain with spaces.com', error: 'Space in domain' },
|
||||
{ email: 'user@@example.com', error: 'Double @ symbol' },
|
||||
{ email: 'user@.com', error: 'Domain starts with dot' },
|
||||
{ email: 'user@domain.', error: 'Domain ends with dot' },
|
||||
{ email: 'user..name@example.com', error: 'Consecutive dots' },
|
||||
{ email: '.user@example.com', error: 'Starts with dot' },
|
||||
{ email: 'user.@example.com', error: 'Ends with dot' },
|
||||
{ email: 'user@domain..com', error: 'Consecutive dots in domain' },
|
||||
{ email: 'user<>@example.com', error: 'Invalid characters' },
|
||||
{ email: 'user@domain>.com', error: 'Invalid domain characters' }
|
||||
'notanemail',
|
||||
'@example.com',
|
||||
'user@',
|
||||
'user@@example.com',
|
||||
'user@domain..com'
|
||||
];
|
||||
|
||||
console.log('Testing invalid email formats:');
|
||||
|
||||
for (const test of invalidEmails) {
|
||||
console.log(`\nTesting: ${test.email} (${test.error})`);
|
||||
for (const invalidEmail of invalidEmails) {
|
||||
console.log(`Testing: ${invalidEmail}`);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: [test.email],
|
||||
subject: 'Invalid recipient test',
|
||||
text: 'Testing invalid email handling'
|
||||
});
|
||||
|
||||
try {
|
||||
await smtpClient.sendMail(email);
|
||||
console.log(' Unexpected success - email was accepted');
|
||||
} catch (error) {
|
||||
console.log(` Expected error: ${error.message}`);
|
||||
expect(error.message).toMatch(/invalid|syntax|format|address/i);
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: invalidEmail,
|
||||
subject: 'Invalid Recipient Test',
|
||||
text: 'Testing invalid email format'
|
||||
});
|
||||
|
||||
console.log('✗ Should have thrown validation error');
|
||||
} catch (error: any) {
|
||||
console.log(`✅ Validation error caught: ${error.message}`);
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
}
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CERR-06: Non-existent recipients', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
tap.test('CERR-06: SMTP 550 Invalid recipient', async () => {
|
||||
// Create server that rejects certain recipients
|
||||
const rejectServer = net.createServer((socket) => {
|
||||
socket.write('220 Reject Server\r\n');
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Test non-existent recipients
|
||||
const nonExistentRecipients = [
|
||||
'doesnotexist@example.com',
|
||||
'nosuchuser@example.com',
|
||||
'randomuser12345@example.com',
|
||||
'deleted-account@example.com'
|
||||
];
|
||||
|
||||
for (const recipient of nonExistentRecipients) {
|
||||
console.log(`\nTesting non-existent recipient: ${recipient}`);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: [recipient],
|
||||
subject: 'Non-existent recipient test',
|
||||
text: 'Testing non-existent recipient handling'
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO')) {
|
||||
if (command.includes('invalid@')) {
|
||||
socket.write('550 5.1.1 Invalid recipient\r\n');
|
||||
} else if (command.includes('unknown@')) {
|
||||
socket.write('550 5.1.1 User unknown\r\n');
|
||||
} else {
|
||||
socket.write('250 OK\r\n');
|
||||
}
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
|
||||
// Monitor RCPT TO response
|
||||
let rcptResponse = '';
|
||||
const originalSendCommand = smtpClient.sendCommand.bind(smtpClient);
|
||||
|
||||
smtpClient.sendCommand = async (command: string) => {
|
||||
const response = await originalSendCommand(command);
|
||||
if (command.startsWith('RCPT TO')) {
|
||||
rcptResponse = response;
|
||||
}
|
||||
return response;
|
||||
};
|
||||
|
||||
try {
|
||||
await smtpClient.sendMail(email);
|
||||
console.log(' Email accepted (may bounce later)');
|
||||
} catch (error) {
|
||||
console.log(` Rejected: ${error.message}`);
|
||||
|
||||
// Common rejection codes
|
||||
const rejectionCodes = ['550', '551', '553', '554'];
|
||||
const hasRejectionCode = rejectionCodes.some(code => error.message.includes(code));
|
||||
|
||||
if (hasRejectionCode) {
|
||||
console.log(' Recipient rejected by server');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CERR-06: Mixed valid and invalid recipients', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
continueOnRecipientError: true, // Continue even if some recipients fail
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
await new Promise<void>((resolve) => {
|
||||
rejectServer.listen(2569, () => resolve());
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: 2569,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: [
|
||||
'valid1@example.com',
|
||||
'invalid@format',
|
||||
'valid2@example.com',
|
||||
'nonexistent@example.com',
|
||||
'valid3@example.com'
|
||||
],
|
||||
subject: 'Mixed recipients test',
|
||||
text: 'Testing mixed valid/invalid recipients'
|
||||
to: 'invalid@example.com',
|
||||
subject: 'Invalid Recipient Test',
|
||||
text: 'Testing invalid recipient'
|
||||
});
|
||||
|
||||
// Track recipient results
|
||||
const recipientResults: { [email: string]: { accepted: boolean; error?: string } } = {};
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
smtpClient.on('recipient-result', (result) => {
|
||||
recipientResults[result.email] = {
|
||||
accepted: result.accepted,
|
||||
error: result.error
|
||||
};
|
||||
});
|
||||
|
||||
console.log('\nSending to mixed valid/invalid recipients...');
|
||||
|
||||
try {
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
console.log('\nResults:');
|
||||
console.log(` Accepted: ${result.accepted?.length || 0}`);
|
||||
console.log(` Rejected: ${result.rejected?.length || 0}`);
|
||||
|
||||
if (result.accepted && result.accepted.length > 0) {
|
||||
console.log('\nAccepted recipients:');
|
||||
result.accepted.forEach(email => console.log(` ✓ ${email}`));
|
||||
}
|
||||
|
||||
if (result.rejected && result.rejected.length > 0) {
|
||||
console.log('\nRejected recipients:');
|
||||
result.rejected.forEach(rejection => {
|
||||
console.log(` ✗ ${rejection.email}: ${rejection.reason}`);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Complete failure:', error.message);
|
||||
}
|
||||
expect(result.success).toBeFalse();
|
||||
console.log('Actual error:', result.error?.message);
|
||||
expect(result.error?.message).toMatch(/550|invalid|recipient/i);
|
||||
console.log('✅ 550 invalid recipient error handled');
|
||||
|
||||
await smtpClient.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
rejectServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CERR-06: Recipient validation methods', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
tap.test('CERR-06: SMTP 550 User unknown', async () => {
|
||||
// Create server that responds with user unknown
|
||||
const unknownServer = net.createServer((socket) => {
|
||||
socket.write('220 Unknown Server\r\n');
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Test VRFY command (if supported)
|
||||
console.log('\nTesting recipient validation methods:');
|
||||
|
||||
// 1. VRFY command
|
||||
try {
|
||||
const vrfyResponse = await smtpClient.sendCommand('VRFY user@example.com');
|
||||
console.log('VRFY response:', vrfyResponse.trim());
|
||||
|
||||
if (vrfyResponse.startsWith('252')) {
|
||||
console.log(' Server cannot verify but will accept');
|
||||
} else if (vrfyResponse.startsWith('250') || vrfyResponse.startsWith('251')) {
|
||||
console.log(' Address verified');
|
||||
} else if (vrfyResponse.startsWith('550')) {
|
||||
console.log(' Address not found');
|
||||
} else if (vrfyResponse.startsWith('502')) {
|
||||
console.log(' VRFY command not implemented');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('VRFY error:', error.message);
|
||||
}
|
||||
|
||||
// 2. EXPN command (if supported)
|
||||
try {
|
||||
const expnResponse = await smtpClient.sendCommand('EXPN postmaster');
|
||||
console.log('\nEXPN response:', expnResponse.trim());
|
||||
} catch (error) {
|
||||
console.log('EXPN error:', error.message);
|
||||
}
|
||||
|
||||
// 3. Null sender probe (common validation technique)
|
||||
console.log('\nTesting null sender probe:');
|
||||
|
||||
const probeEmail = new Email({
|
||||
from: '', // Null sender
|
||||
to: ['test@example.com'],
|
||||
subject: 'Address verification probe',
|
||||
text: 'This is an address verification probe'
|
||||
});
|
||||
|
||||
try {
|
||||
// Just test RCPT TO, don't actually send
|
||||
await smtpClient.sendCommand('MAIL FROM:<>');
|
||||
const rcptResponse = await smtpClient.sendCommand('RCPT TO:<test@example.com>');
|
||||
console.log('Null sender probe result:', rcptResponse.trim());
|
||||
await smtpClient.sendCommand('RSET');
|
||||
} catch (error) {
|
||||
console.log('Null sender probe failed:', error.message);
|
||||
}
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CERR-06: International email addresses', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
supportInternational: true,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Check for SMTPUTF8 support
|
||||
const ehloResponse = await smtpClient.sendCommand('EHLO testclient.example.com');
|
||||
const supportsSmtpUtf8 = ehloResponse.includes('SMTPUTF8');
|
||||
|
||||
console.log(`\nSMTPUTF8 support: ${supportsSmtpUtf8}`);
|
||||
|
||||
// Test international email addresses
|
||||
const internationalEmails = [
|
||||
'user@例え.jp',
|
||||
'користувач@приклад.укр',
|
||||
'usuario@ejemplo.españ',
|
||||
'用户@例子.中国',
|
||||
'user@tëst.com'
|
||||
];
|
||||
|
||||
for (const recipient of internationalEmails) {
|
||||
console.log(`\nTesting international address: ${recipient}`);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: [recipient],
|
||||
subject: 'International recipient test',
|
||||
text: 'Testing international email addresses'
|
||||
});
|
||||
|
||||
try {
|
||||
await smtpClient.sendMail(email);
|
||||
console.log(' Accepted (SMTPUTF8 working)');
|
||||
} catch (error) {
|
||||
if (!supportsSmtpUtf8) {
|
||||
console.log(' Expected rejection - no SMTPUTF8 support');
|
||||
} else {
|
||||
console.log(` Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CERR-06: Recipient limits', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 10000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Test recipient limits
|
||||
const recipientCounts = [10, 50, 100, 200, 500, 1000];
|
||||
|
||||
for (const count of recipientCounts) {
|
||||
console.log(`\nTesting ${count} recipients...`);
|
||||
|
||||
// Generate recipients
|
||||
const recipients = Array.from({ length: count }, (_, i) => `user${i}@example.com`);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: recipients,
|
||||
subject: `Testing ${count} recipients`,
|
||||
text: 'Testing recipient limits'
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
let acceptedCount = 0;
|
||||
let rejectedCount = 0;
|
||||
|
||||
// Monitor RCPT TO responses
|
||||
const originalSendCommand = smtpClient.sendCommand.bind(smtpClient);
|
||||
|
||||
smtpClient.sendCommand = async (command: string) => {
|
||||
const response = await originalSendCommand(command);
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
|
||||
if (command.startsWith('RCPT TO')) {
|
||||
if (response.startsWith('250')) {
|
||||
acceptedCount++;
|
||||
} else if (response.match(/^[45]/)) {
|
||||
rejectedCount++;
|
||||
|
||||
// Check for recipient limit errors
|
||||
if (response.match(/too many|limit|maximum/i)) {
|
||||
console.log(` Recipient limit reached at ${acceptedCount} recipients`);
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO')) {
|
||||
socket.write('550 5.1.1 User unknown\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
unknownServer.listen(2570, () => resolve());
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: 2570,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'unknown@example.com',
|
||||
subject: 'Unknown User Test',
|
||||
text: 'Testing unknown user'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeFalse();
|
||||
console.log('Actual error:', result.error?.message);
|
||||
expect(result.error?.message).toMatch(/550|unknown|recipient/i);
|
||||
console.log('✅ 550 user unknown error handled');
|
||||
|
||||
await smtpClient.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
unknownServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CERR-06: Mixed valid and invalid recipients', async () => {
|
||||
// Create server that accepts some recipients and rejects others
|
||||
const mixedServer = net.createServer((socket) => {
|
||||
socket.write('220 Mixed Server\r\n');
|
||||
let inData = false;
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const lines = data.toString().split('\r\n');
|
||||
|
||||
lines.forEach(line => {
|
||||
if (!line && lines[lines.length - 1] === '') return;
|
||||
|
||||
if (inData) {
|
||||
// We're in DATA mode - look for the terminating dot
|
||||
if (line === '.') {
|
||||
socket.write('250 OK\r\n');
|
||||
inData = false;
|
||||
}
|
||||
// Otherwise, just consume the data
|
||||
} else {
|
||||
// We're in command mode
|
||||
if (line.startsWith('EHLO')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (line.startsWith('MAIL FROM')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (line.startsWith('RCPT TO')) {
|
||||
if (line.includes('valid@')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else {
|
||||
socket.write('550 5.1.1 Recipient rejected\r\n');
|
||||
}
|
||||
} else if (line === 'DATA') {
|
||||
socket.write('354 Send data\r\n');
|
||||
inData = true;
|
||||
} else if (line === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
try {
|
||||
await smtpClient.sendMail(email);
|
||||
const elapsed = Date.now() - startTime;
|
||||
|
||||
console.log(` Success: ${acceptedCount} accepted, ${rejectedCount} rejected`);
|
||||
console.log(` Time: ${elapsed}ms (${(elapsed/count).toFixed(2)}ms per recipient)`);
|
||||
} catch (error) {
|
||||
console.log(` Error after ${acceptedCount} recipients: ${error.message}`);
|
||||
|
||||
if (error.message.match(/too many|limit/i)) {
|
||||
console.log(' Server recipient limit exceeded');
|
||||
break; // Don't test higher counts
|
||||
}
|
||||
}
|
||||
|
||||
// Reset for next test
|
||||
await smtpClient.sendCommand('RSET');
|
||||
}
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CERR-06: Recipient error codes', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Common recipient error codes and their meanings
|
||||
const errorCodes = [
|
||||
{ code: '550 5.1.1', meaning: 'User unknown', permanent: true },
|
||||
{ code: '551 5.1.6', meaning: 'User has moved', permanent: true },
|
||||
{ code: '552 5.2.2', meaning: 'Mailbox full', permanent: true },
|
||||
{ code: '553 5.1.3', meaning: 'Invalid address syntax', permanent: true },
|
||||
{ code: '554 5.7.1', meaning: 'Relay access denied', permanent: true },
|
||||
{ code: '450 4.1.1', meaning: 'Temporary user lookup failure', permanent: false },
|
||||
{ code: '451 4.1.8', meaning: 'Sender address rejected', permanent: false },
|
||||
{ code: '452 4.2.2', meaning: 'Mailbox full (temporary)', permanent: false }
|
||||
];
|
||||
|
||||
console.log('\nRecipient error code reference:');
|
||||
|
||||
errorCodes.forEach(error => {
|
||||
console.log(`\n${error.code}: ${error.meaning}`);
|
||||
console.log(` Type: ${error.permanent ? 'Permanent failure' : 'Temporary failure'}`);
|
||||
console.log(` Action: ${error.permanent ? 'Bounce immediately' : 'Queue and retry'}`);
|
||||
});
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CERR-06: Catch-all and wildcard handling', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Test catch-all and wildcard addresses
|
||||
const wildcardTests = [
|
||||
'*@example.com',
|
||||
'catchall@*',
|
||||
'*@*.com',
|
||||
'user+*@example.com',
|
||||
'sales-*@example.com'
|
||||
];
|
||||
|
||||
console.log('\nTesting wildcard/catch-all addresses:');
|
||||
|
||||
for (const recipient of wildcardTests) {
|
||||
console.log(`\nTesting: ${recipient}`);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: [recipient],
|
||||
subject: 'Wildcard test',
|
||||
text: 'Testing wildcard recipient'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
await smtpClient.sendMail(email);
|
||||
console.log(' Accepted (server may expand wildcard)');
|
||||
} catch (error) {
|
||||
console.log(` Rejected: ${error.message}`);
|
||||
|
||||
// Wildcards typically rejected as invalid syntax
|
||||
expect(error.message).toMatch(/invalid|syntax|format/i);
|
||||
}
|
||||
}
|
||||
await new Promise<void>((resolve) => {
|
||||
mixedServer.listen(2571, () => resolve());
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: 2571,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['valid@example.com', 'invalid@example.com'],
|
||||
subject: 'Mixed Recipients Test',
|
||||
text: 'Testing mixed valid and invalid recipients'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
// Should fail when any recipient is rejected
|
||||
expect(result.success).toBeFalse();
|
||||
console.log('Actual error:', result.error?.message);
|
||||
expect(result.error?.message).toMatch(/550|reject|recipient|timeout|transmission/i);
|
||||
console.log('✅ Mixed recipients error handled');
|
||||
|
||||
await smtpClient.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
mixedServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CERR-06: Recipient validation timing', async () => {
|
||||
tap.test('CERR-06: Domain not found - 550', async () => {
|
||||
// Create server that rejects due to domain issues
|
||||
const domainServer = net.createServer((socket) => {
|
||||
socket.write('220 Domain Server\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO')) {
|
||||
socket.write('550 5.1.2 Domain not found\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
domainServer.listen(2572, () => resolve());
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: 2572,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'user@nonexistent.domain',
|
||||
subject: 'Domain Not Found Test',
|
||||
text: 'Testing domain not found'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeFalse();
|
||||
console.log('Actual error:', result.error?.message);
|
||||
expect(result.error?.message).toMatch(/550|domain|recipient/i);
|
||||
console.log('✅ 550 domain not found error handled');
|
||||
|
||||
await smtpClient.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
domainServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CERR-06: Valid recipient succeeds', async () => {
|
||||
// Test successful email send with working server
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
recipientValidationTimeout: 3000,
|
||||
debug: true
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'valid@example.com',
|
||||
subject: 'Valid Recipient Test',
|
||||
text: 'Testing valid recipient'
|
||||
});
|
||||
|
||||
// Test validation timing for different scenarios
|
||||
const timingTests = [
|
||||
{ email: 'quick@example.com', expectedTime: 'fast' },
|
||||
{ email: 'slow.lookup@remote-domain.com', expectedTime: 'slow' },
|
||||
{ email: 'timeout@unresponsive-server.com', expectedTime: 'timeout' }
|
||||
];
|
||||
|
||||
console.log('\nTesting recipient validation timing:');
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
for (const test of timingTests) {
|
||||
console.log(`\nValidating: ${test.email}`);
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
await smtpClient.sendCommand(`RCPT TO:<${test.email}>`);
|
||||
const elapsed = Date.now() - startTime;
|
||||
|
||||
console.log(` Response time: ${elapsed}ms`);
|
||||
console.log(` Expected: ${test.expectedTime}`);
|
||||
|
||||
if (test.expectedTime === 'timeout' && elapsed >= 3000) {
|
||||
console.log(' Validation timed out as expected');
|
||||
}
|
||||
} catch (error) {
|
||||
const elapsed = Date.now() - startTime;
|
||||
console.log(` Error after ${elapsed}ms: ${error.message}`);
|
||||
}
|
||||
|
||||
await smtpClient.sendCommand('RSET');
|
||||
}
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Valid recipient email sent successfully');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await testServer.stop();
|
||||
}
|
||||
tap.test('cleanup - stop SMTP server', async () => {
|
||||
await stopTestServer(testServer);
|
||||
});
|
||||
|
||||
export default tap.start();
|
Reference in New Issue
Block a user