dcrouter/test/suite/smtpserver_email-processing/test.ep-09.delivery-status-notifications.ts
2025-05-25 19:05:43 +00:00

486 lines
15 KiB
TypeScript

import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'
import type { ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525;
let testServer: ITestServer;
tap.test('setup - start test server', async () => {
testServer = await startTestServer({ port: TEST_PORT });
await new Promise(resolve => setTimeout(resolve, 1000));
});
tap.test('DSN - Extension advertised in EHLO', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (dataBuffer.includes('220 ') && !dataBuffer.includes('EHLO')) {
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (dataBuffer.includes('250')) {
// Check if DSN extension is advertised
const dsnSupported = dataBuffer.toLowerCase().includes('dsn');
console.log('DSN extension advertised:', dsnSupported);
// Parse extensions
const lines = dataBuffer.split('\r\n');
const extensions = lines
.filter(line => line.startsWith('250-') || (line.startsWith('250 ') && lines.indexOf(line) > 0))
.map(line => line.substring(4).split(' ')[0].toUpperCase());
console.log('Server extensions:', extensions);
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('DSN - Success notification request', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
let step = 'greeting';
let completed = false;
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (step === 'greeting' && dataBuffer.includes('220 ')) {
step = 'ehlo';
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
step = 'mail';
// MAIL FROM with DSN parameters
const envId = `dsn-success-${Date.now()}`;
socket.write(`MAIL FROM:<sender@example.com> RET=FULL ENVID=${envId}\r\n`);
dataBuffer = '';
} else if (step === 'mail') {
const accepted = dataBuffer.includes('250');
const notSupported = dataBuffer.includes('501') || dataBuffer.includes('555');
console.log(`MAIL FROM with DSN: ${accepted ? 'accepted' : notSupported ? 'not supported' : 'error'}`);
if (accepted || notSupported) {
step = 'rcpt';
// Plain MAIL FROM if DSN not supported
if (notSupported) {
socket.write('MAIL FROM:<sender@example.com>\r\n');
dataBuffer = '';
} else {
// RCPT TO with NOTIFY parameter
socket.write('RCPT TO:<recipient@example.com> NOTIFY=SUCCESS\r\n');
dataBuffer = '';
}
}
} else if (step === 'rcpt') {
const accepted = dataBuffer.includes('250');
const notSupported = dataBuffer.includes('501') || dataBuffer.includes('555');
if (notSupported) {
// DSN not supported, try plain RCPT TO
socket.write('RCPT TO:<recipient@example.com>\r\n');
step = 'rcpt_plain';
dataBuffer = '';
} else if (accepted) {
step = 'data';
socket.write('DATA\r\n');
dataBuffer = '';
}
} else if (step === 'rcpt_plain' && dataBuffer.includes('250')) {
step = 'data';
socket.write('DATA\r\n');
dataBuffer = '';
} else if (step === 'data' && dataBuffer.includes('354')) {
const email = [
`From: sender@example.com`,
`To: recipient@example.com`,
`Subject: DSN Test - Success Notification`,
`Date: ${new Date().toUTCString()}`,
`Message-ID: <dsn-success-${Date.now()}@example.com>`,
'',
'This email tests DSN success notification.',
'The server should send a success DSN if supported.',
'.',
''
].join('\r\n');
socket.write(email);
step = 'sent';
dataBuffer = '';
} else if (step === 'sent' && dataBuffer.includes('250 ') && dataBuffer.includes('message queued')) {
if (!completed) {
completed = true;
console.log('Email with DSN success request accepted');
expect(true).toEqual(true);
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('DSN - Multiple notification types', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
let step = 'greeting';
let completed = false;
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (step === 'greeting' && dataBuffer.includes('220 ')) {
step = 'ehlo';
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
step = 'mail';
socket.write('MAIL FROM:<sender@example.com>\r\n');
dataBuffer = '';
} else if (step === 'mail' && dataBuffer.includes('250')) {
step = 'rcpt';
// Request multiple notification types
socket.write('RCPT TO:<recipient@example.com> NOTIFY=SUCCESS,FAILURE,DELAY\r\n');
dataBuffer = '';
} else if (step === 'rcpt') {
const accepted = dataBuffer.includes('250');
const notSupported = dataBuffer.includes('501') || dataBuffer.includes('555');
console.log(`Multiple NOTIFY types: ${accepted ? 'accepted' : notSupported ? 'not supported' : 'error'}`);
if (notSupported) {
// Try plain RCPT TO
socket.write('RCPT TO:<recipient@example.com>\r\n');
step = 'rcpt_plain';
dataBuffer = '';
} else if (accepted) {
step = 'data';
socket.write('DATA\r\n');
dataBuffer = '';
}
} else if (step === 'rcpt_plain' && dataBuffer.includes('250')) {
step = 'data';
socket.write('DATA\r\n');
dataBuffer = '';
} else if (step === 'data' && dataBuffer.includes('354')) {
const email = [
`From: sender@example.com`,
`To: recipient@example.com`,
`Subject: DSN Test - Multiple Notifications`,
`Date: ${new Date().toUTCString()}`,
`Message-ID: <dsn-multi-${Date.now()}@example.com>`,
'',
'Testing multiple DSN notification types.',
'.',
''
].join('\r\n');
socket.write(email);
step = 'sent';
dataBuffer = '';
} else if (step === 'sent' && dataBuffer.includes('250 ') && dataBuffer.includes('message queued')) {
if (!completed) {
completed = true;
console.log('Email with multiple DSN types accepted');
expect(true).toEqual(true);
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('DSN - Never notify', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
let step = 'greeting';
let completed = false;
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (step === 'greeting' && dataBuffer.includes('220 ')) {
step = 'ehlo';
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
step = 'mail';
socket.write('MAIL FROM:<sender@example.com>\r\n');
dataBuffer = '';
} else if (step === 'mail' && dataBuffer.includes('250')) {
step = 'rcpt';
// Request no notifications
socket.write('RCPT TO:<recipient@example.com> NOTIFY=NEVER\r\n');
dataBuffer = '';
} else if (step === 'rcpt') {
const accepted = dataBuffer.includes('250');
const notSupported = dataBuffer.includes('501') || dataBuffer.includes('555');
console.log(`NOTIFY=NEVER: ${accepted ? 'accepted' : notSupported ? 'not supported' : 'error'}`);
expect(accepted || notSupported).toEqual(true);
if (notSupported) {
socket.write('RCPT TO:<recipient@example.com>\r\n');
step = 'rcpt_plain';
dataBuffer = '';
} else if (accepted) {
step = 'data';
socket.write('DATA\r\n');
dataBuffer = '';
}
} else if (step === 'rcpt_plain' && dataBuffer.includes('250')) {
step = 'data';
socket.write('DATA\r\n');
dataBuffer = '';
} else if (step === 'data' && dataBuffer.includes('354')) {
const email = [
`From: sender@example.com`,
`To: recipient@example.com`,
`Subject: DSN Test - Never Notify`,
`Date: ${new Date().toUTCString()}`,
`Message-ID: <dsn-never-${Date.now()}@example.com>`,
'',
'This email should not generate any DSN.',
'.',
''
].join('\r\n');
socket.write(email);
step = 'sent';
dataBuffer = '';
} else if (step === 'sent' && dataBuffer.includes('250 ') && dataBuffer.includes('message queued')) {
if (!completed) {
completed = true;
console.log('Email with NOTIFY=NEVER accepted');
expect(true).toEqual(true);
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('DSN - Original recipient tracking', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
let step = 'greeting';
let completed = false;
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (step === 'greeting' && dataBuffer.includes('220 ')) {
step = 'ehlo';
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
step = 'mail';
socket.write('MAIL FROM:<sender@example.com>\r\n');
dataBuffer = '';
} else if (step === 'mail' && dataBuffer.includes('250')) {
step = 'rcpt';
// Include original recipient for tracking
socket.write('RCPT TO:<recipient@example.com> NOTIFY=FAILURE ORCPT=rfc822;original@example.com\r\n');
dataBuffer = '';
} else if (step === 'rcpt') {
const accepted = dataBuffer.includes('250');
const notSupported = dataBuffer.includes('501') || dataBuffer.includes('555');
console.log(`ORCPT parameter: ${accepted ? 'accepted' : notSupported ? 'not supported' : 'error'}`);
if (notSupported) {
socket.write('RCPT TO:<recipient@example.com>\r\n');
step = 'rcpt_plain';
dataBuffer = '';
} else if (accepted) {
step = 'data';
socket.write('DATA\r\n');
dataBuffer = '';
}
} else if (step === 'rcpt_plain' && dataBuffer.includes('250')) {
step = 'data';
socket.write('DATA\r\n');
dataBuffer = '';
} else if (step === 'data' && dataBuffer.includes('354')) {
const email = [
`From: sender@example.com`,
`To: recipient@example.com`,
`Subject: DSN Test - Original Recipient`,
`Date: ${new Date().toUTCString()}`,
`Message-ID: <dsn-orcpt-${Date.now()}@example.com>`,
'',
'This email tests ORCPT parameter for tracking.',
'.',
''
].join('\r\n');
socket.write(email);
step = 'sent';
dataBuffer = '';
} else if (step === 'sent' && dataBuffer.includes('250 ') && dataBuffer.includes('message queued')) {
if (!completed) {
completed = true;
console.log('Email with ORCPT tracking accepted');
expect(true).toEqual(true);
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('DSN - Return parameter handling', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
let step = 'greeting';
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (step === 'greeting' && dataBuffer.includes('220 ')) {
step = 'ehlo';
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
step = 'mail_hdrs';
// Test RET=HDRS
socket.write('MAIL FROM:<sender@example.com> RET=HDRS\r\n');
dataBuffer = '';
} else if (step === 'mail_hdrs') {
const accepted = dataBuffer.includes('250');
const notSupported = dataBuffer.includes('501') || dataBuffer.includes('555');
console.log(`RET=HDRS: ${accepted ? 'accepted' : notSupported ? 'not supported' : 'error'}`);
if (accepted || notSupported) {
// Reset and test RET=FULL
socket.write('RSET\r\n');
step = 'reset';
dataBuffer = '';
}
} else if (step === 'reset' && dataBuffer.includes('250')) {
step = 'mail_full';
socket.write('MAIL FROM:<sender@example.com> RET=FULL\r\n');
dataBuffer = '';
} else if (step === 'mail_full') {
const accepted = dataBuffer.includes('250');
const notSupported = dataBuffer.includes('501') || dataBuffer.includes('555');
console.log(`RET=FULL: ${accepted ? 'accepted' : notSupported ? 'not supported' : 'error'}`);
expect(accepted || notSupported).toEqual(true);
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('cleanup - stop test server', async () => {
await stopTestServer(testServer);
});
export default tap.start();