import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as net from 'net'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; const TEST_PORT = 2525; let testServer: SmtpServer; tap.test('setup - start test server', async () => { testServer = 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: 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:\r\n'); dataBuffer = ''; } else { // RCPT TO with NOTIFY parameter socket.write('RCPT TO: 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:\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: `, '', '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:\r\n'); dataBuffer = ''; } else if (step === 'mail' && dataBuffer.includes('250')) { step = 'rcpt'; // Request multiple notification types socket.write('RCPT TO: 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:\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: `, '', '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:\r\n'); dataBuffer = ''; } else if (step === 'mail' && dataBuffer.includes('250')) { step = 'rcpt'; // Request no notifications socket.write('RCPT TO: 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:\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: `, '', '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:\r\n'); dataBuffer = ''; } else if (step === 'mail' && dataBuffer.includes('250')) { step = 'rcpt'; // Include original recipient for tracking socket.write('RCPT TO: 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:\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: `, '', '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: 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: 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); }); tap.start();