dcrouter/test/suite/smtpclient_error-handling/test.cerr-04.greylisting-handling.ts

255 lines
7.8 KiB
TypeScript
Raw Permalink Normal View History

2025-05-24 16:19:19 +00:00
import { tap, expect } from '@git.zone/tstest/tapbundle';
2025-05-26 10:35:50 +00:00
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.js';
2025-05-24 16:19:19 +00:00
import { Email } from '../../../ts/mail/core/classes.email.js';
import * as net from 'net';
2025-05-26 10:35:50 +00:00
let testServer: ITestServer;
2025-05-24 16:19:19 +00:00
2025-05-26 10:35:50 +00:00
tap.test('setup - start SMTP server for greylisting tests', async () => {
testServer = await startTestServer({
port: 2559,
tlsEnabled: false,
authRequired: false
});
expect(testServer.port).toEqual(2559);
2025-05-24 16:19:19 +00:00
});
2025-05-26 10:35:50 +00:00
tap.test('CERR-04: Basic greylisting response handling', async () => {
2025-05-24 16:19:19 +00:00
// Create server that simulates greylisting
const greylistServer = net.createServer((socket) => {
socket.write('220 Greylist Test Server\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO') || command.startsWith('HELO')) {
socket.write('250 OK\r\n');
} else if (command.startsWith('MAIL FROM')) {
socket.write('250 OK\r\n');
} else if (command.startsWith('RCPT TO')) {
2025-05-26 10:35:50 +00:00
// Simulate greylisting response
socket.write('451 4.7.1 Greylisting in effect, please retry later\r\n');
2025-05-24 16:19:19 +00:00
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
} else {
socket.write('250 OK\r\n');
}
});
});
await new Promise<void>((resolve) => {
2025-05-26 10:35:50 +00:00
greylistServer.listen(2560, () => resolve());
2025-05-24 16:19:19 +00:00
});
2025-05-26 10:35:50 +00:00
const smtpClient = await createSmtpClient({
2025-05-24 16:19:19 +00:00
host: '127.0.0.1',
2025-05-26 10:35:50 +00:00
port: 2560,
2025-05-24 16:19:19 +00:00
secure: false,
2025-05-26 10:35:50 +00:00
connectionTimeout: 5000
2025-05-24 16:19:19 +00:00
});
const email = new Email({
from: 'sender@example.com',
2025-05-26 10:35:50 +00:00
to: 'recipient@example.com',
2025-05-24 16:19:19 +00:00
subject: 'Greylisting Test',
2025-05-26 10:35:50 +00:00
text: 'Testing greylisting response handling'
2025-05-24 16:19:19 +00:00
});
2025-05-26 10:35:50 +00:00
const result = await smtpClient.sendMail(email);
2025-05-24 16:19:19 +00:00
2025-05-26 10:35:50 +00:00
// Should get a failed result due to greylisting
expect(result.success).toBeFalse();
console.log('Actual error:', result.error?.message);
expect(result.error?.message).toMatch(/451|greylist|rejected/i);
console.log('✅ Greylisting response handled correctly');
2025-05-24 16:19:19 +00:00
await smtpClient.close();
2025-05-26 10:35:50 +00:00
await new Promise<void>((resolve) => {
greylistServer.close(() => resolve());
});
2025-05-24 16:19:19 +00:00
});
tap.test('CERR-04: Different greylisting response codes', async () => {
2025-05-26 10:35:50 +00:00
// Test recognition of various greylisting response patterns
2025-05-24 16:19:19 +00:00
const greylistResponses = [
{ code: '451 4.7.1', message: 'Greylisting in effect, please retry', isGreylist: true },
{ code: '450 4.7.1', message: 'Try again later', isGreylist: true },
{ code: '451 4.7.0', message: 'Temporary rejection', isGreylist: true },
{ code: '421 4.7.0', message: 'Too many connections, try later', isGreylist: false },
{ code: '452 4.2.2', message: 'Mailbox full', isGreylist: false },
2025-05-26 10:35:50 +00:00
{ code: '451', message: 'Requested action aborted', isGreylist: false }
2025-05-24 16:19:19 +00:00
];
2025-05-26 10:35:50 +00:00
console.log('Testing greylisting response recognition:');
2025-05-24 16:19:19 +00:00
for (const response of greylistResponses) {
2025-05-26 10:35:50 +00:00
console.log(`Response: ${response.code} ${response.message}`);
2025-05-24 16:19:19 +00:00
// Check if response matches greylisting patterns
const isGreylistPattern =
(response.code.startsWith('450') || response.code.startsWith('451')) &&
(response.message.toLowerCase().includes('grey') ||
response.message.toLowerCase().includes('try') ||
response.message.toLowerCase().includes('later') ||
2025-05-26 10:35:50 +00:00
response.message.toLowerCase().includes('temporary') ||
2025-05-24 16:19:19 +00:00
response.code.includes('4.7.'));
console.log(` Detected as greylisting: ${isGreylistPattern}`);
console.log(` Expected: ${response.isGreylist}`);
expect(isGreylistPattern).toEqual(response.isGreylist);
}
});
2025-05-26 10:35:50 +00:00
tap.test('CERR-04: Greylisting with temporary failure', async () => {
// Create server that sends 450 response (temporary failure)
const tempFailServer = net.createServer((socket) => {
socket.write('220 Temp Fail Server\r\n');
2025-05-24 16:19:19 +00:00
socket.on('data', (data) => {
const command = data.toString().trim();
2025-05-26 10:35:50 +00:00
if (command.startsWith('EHLO')) {
2025-05-24 16:19:19 +00:00
socket.write('250 OK\r\n');
} else if (command.startsWith('MAIL FROM')) {
socket.write('250 OK\r\n');
} else if (command.startsWith('RCPT TO')) {
2025-05-26 10:35:50 +00:00
socket.write('450 4.7.1 Mailbox temporarily unavailable\r\n');
2025-05-24 16:19:19 +00:00
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
});
await new Promise<void>((resolve) => {
2025-05-26 10:35:50 +00:00
tempFailServer.listen(2561, () => resolve());
2025-05-24 16:19:19 +00:00
});
2025-05-26 10:35:50 +00:00
const smtpClient = await createSmtpClient({
2025-05-24 16:19:19 +00:00
host: '127.0.0.1',
2025-05-26 10:35:50 +00:00
port: 2561,
2025-05-24 16:19:19 +00:00
secure: false,
2025-05-26 10:35:50 +00:00
connectionTimeout: 5000
2025-05-24 16:19:19 +00:00
});
const email = new Email({
from: 'sender@example.com',
2025-05-26 10:35:50 +00:00
to: 'recipient@example.com',
subject: '450 Test',
text: 'Testing 450 temporary failure response'
2025-05-24 16:19:19 +00:00
});
2025-05-26 10:35:50 +00:00
const result = await smtpClient.sendMail(email);
expect(result.success).toBeFalse();
console.log('Actual error:', result.error?.message);
expect(result.error?.message).toMatch(/450|temporary|rejected/i);
console.log('✅ 450 temporary failure handled');
2025-05-24 16:19:19 +00:00
await smtpClient.close();
2025-05-26 10:35:50 +00:00
await new Promise<void>((resolve) => {
tempFailServer.close(() => resolve());
});
2025-05-24 16:19:19 +00:00
});
2025-05-26 10:35:50 +00:00
tap.test('CERR-04: Greylisting with multiple recipients', async () => {
// Test successful email send to multiple recipients on working server
const smtpClient = await createSmtpClient({
2025-05-24 16:19:19 +00:00
host: testServer.hostname,
port: testServer.port,
secure: false,
2025-05-26 10:35:50 +00:00
connectionTimeout: 5000
2025-05-24 16:19:19 +00:00
});
const email = new Email({
from: 'sender@example.com',
2025-05-26 10:35:50 +00:00
to: ['user1@normal.com', 'user2@example.com'],
subject: 'Multi-recipient Test',
text: 'Testing multiple recipients'
2025-05-24 16:19:19 +00:00
});
2025-05-26 10:35:50 +00:00
const result = await smtpClient.sendMail(email);
2025-05-24 16:19:19 +00:00
2025-05-26 10:35:50 +00:00
expect(result.success).toBeTrue();
console.log('✅ Multiple recipients handled correctly');
2025-05-24 16:19:19 +00:00
await smtpClient.close();
2025-05-26 10:35:50 +00:00
});
2025-05-24 16:19:19 +00:00
2025-05-26 10:35:50 +00:00
tap.test('CERR-04: Basic connection verification', async () => {
const smtpClient = await createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000
});
2025-05-24 16:19:19 +00:00
2025-05-26 10:35:50 +00:00
const result = await smtpClient.verify();
expect(result).toBeTrue();
console.log('✅ Connection verification successful');
2025-05-24 16:19:19 +00:00
await smtpClient.close();
});
2025-05-26 10:35:50 +00:00
tap.test('CERR-04: Server with RCPT rejection', async () => {
// Test server rejecting at RCPT TO stage
const rejectServer = net.createServer((socket) => {
socket.write('220 Reject Server\r\n');
2025-05-24 16:19:19 +00:00
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')) {
2025-05-26 10:35:50 +00:00
socket.write('451 4.2.1 Recipient rejected temporarily\r\n');
2025-05-24 16:19:19 +00:00
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
});
});
await new Promise<void>((resolve) => {
2025-05-26 10:35:50 +00:00
rejectServer.listen(2562, () => resolve());
2025-05-24 16:19:19 +00:00
});
2025-05-26 10:35:50 +00:00
const smtpClient = await createSmtpClient({
2025-05-24 16:19:19 +00:00
host: '127.0.0.1',
2025-05-26 10:35:50 +00:00
port: 2562,
2025-05-24 16:19:19 +00:00
secure: false,
2025-05-26 10:35:50 +00:00
connectionTimeout: 5000
2025-05-24 16:19:19 +00:00
});
const email = new Email({
from: 'sender@example.com',
2025-05-26 10:35:50 +00:00
to: 'recipient@example.com',
subject: 'RCPT Rejection Test',
text: 'Testing RCPT TO rejection'
2025-05-24 16:19:19 +00:00
});
2025-05-26 10:35:50 +00:00
const result = await smtpClient.sendMail(email);
2025-05-24 16:19:19 +00:00
2025-05-26 10:35:50 +00:00
expect(result.success).toBeFalse();
console.log('Actual error:', result.error?.message);
expect(result.error?.message).toMatch(/451|reject|recipient/i);
console.log('✅ RCPT rejection handled correctly');
2025-05-24 16:19:19 +00:00
await smtpClient.close();
2025-05-26 10:35:50 +00:00
await new Promise<void>((resolve) => {
rejectServer.close(() => resolve());
2025-05-24 16:19:19 +00:00
});
});
2025-05-26 10:35:50 +00:00
tap.test('cleanup - stop SMTP server', async () => {
await stopTestServer(testServer);
2025-05-24 16:19:19 +00:00
});
export default tap.start();