import { tap, expect } from '@push.rocks/tapbundle'; import * as net from 'net'; import { startTestServer, stopTestServer } from '../server.loader.js'; // Test configuration const TEST_PORT = 2525; const TEST_TIMEOUT = 15000; let testServer: any; // Setup tap.test('setup - start SMTP server', async () => { testServer = await startTestServer(); await new Promise(resolve => setTimeout(resolve, 1000)); }); // Test: Complete email sending flow tap.test('Basic Email Sending - should send email through complete SMTP flow', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); let receivedData = ''; let currentStep = 'connecting'; const fromAddress = 'sender@example.com'; const toAddress = 'recipient@example.com'; const emailContent = `Subject: Production Test Email\r\nFrom: ${fromAddress}\r\nTo: ${toAddress}\r\nDate: ${new Date().toUTCString()}\r\n\r\nThis is a test email sent during production testing.\r\nTest ID: EP-01\r\nTimestamp: ${Date.now()}\r\n`; const steps: string[] = []; socket.on('data', (data) => { receivedData += data.toString(); if (currentStep === 'connecting' && receivedData.includes('220')) { steps.push('CONNECT'); currentStep = 'ehlo'; socket.write('EHLO test.example.com\r\n'); } else if (currentStep === 'ehlo' && receivedData.includes('250')) { steps.push('EHLO'); currentStep = 'mail_from'; socket.write(`MAIL FROM:<${fromAddress}>\r\n`); } else if (currentStep === 'mail_from' && receivedData.includes('250')) { steps.push('MAIL FROM'); currentStep = 'rcpt_to'; socket.write(`RCPT TO:<${toAddress}>\r\n`); } else if (currentStep === 'rcpt_to' && receivedData.includes('250')) { steps.push('RCPT TO'); currentStep = 'data'; socket.write('DATA\r\n'); } else if (currentStep === 'data' && receivedData.includes('354')) { steps.push('DATA'); currentStep = 'email_content'; socket.write(emailContent); socket.write('\r\n.\r\n'); // End of data marker } else if (currentStep === 'email_content' && receivedData.includes('250')) { steps.push('CONTENT'); currentStep = 'quit'; socket.write('QUIT\r\n'); } else if (currentStep === 'quit' && receivedData.includes('221')) { steps.push('QUIT'); socket.destroy(); // Verify all steps completed expect(steps).toInclude('CONNECT'); expect(steps).toInclude('EHLO'); expect(steps).toInclude('MAIL FROM'); expect(steps).toInclude('RCPT TO'); expect(steps).toInclude('DATA'); expect(steps).toInclude('CONTENT'); expect(steps).toInclude('QUIT'); expect(steps.length).toEqual(7); done.resolve(); } else if (receivedData.match(/\r\n5\d{2}\s/)) { // Server error (5xx response codes) socket.destroy(); done.reject(new Error(`Email sending failed at step ${currentStep}: ${receivedData}`)); } }); socket.on('error', (error) => { done.reject(error); }); socket.on('timeout', () => { socket.destroy(); done.reject(new Error(`Connection timeout at step: ${currentStep}`)); }); await done.promise; }); // Test: Send email with attachments (MIME) tap.test('Basic Email Sending - should send email with MIME attachment', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); let receivedData = ''; let currentStep = 'connecting'; const fromAddress = 'sender@example.com'; const toAddress = 'recipient@example.com'; const boundary = '----=_Part_0_1234567890'; const emailContent = `Subject: Email with Attachment\r\nFrom: ${fromAddress}\r\nTo: ${toAddress}\r\nMIME-Version: 1.0\r\nContent-Type: multipart/mixed; boundary="${boundary}"\r\n\r\n--${boundary}\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\nThis email contains an attachment.\r\n\r\n--${boundary}\r\nContent-Type: text/plain; name="test.txt"\r\nContent-Disposition: attachment; filename="test.txt"\r\nContent-Transfer-Encoding: base64\r\n\r\nVGhpcyBpcyBhIHRlc3QgZmlsZS4=\r\n\r\n--${boundary}--\r\n`; socket.on('data', (data) => { receivedData += data.toString(); if (currentStep === 'connecting' && receivedData.includes('220')) { currentStep = 'ehlo'; socket.write('EHLO test.example.com\r\n'); } else if (currentStep === 'ehlo' && receivedData.includes('250')) { currentStep = 'mail_from'; socket.write(`MAIL FROM:<${fromAddress}>\r\n`); } else if (currentStep === 'mail_from' && receivedData.includes('250')) { currentStep = 'rcpt_to'; socket.write(`RCPT TO:<${toAddress}>\r\n`); } else if (currentStep === 'rcpt_to' && receivedData.includes('250')) { currentStep = 'data'; socket.write('DATA\r\n'); } else if (currentStep === 'data' && receivedData.includes('354')) { currentStep = 'email_content'; socket.write(emailContent); socket.write('\r\n.\r\n'); // End of data marker } else if (currentStep === 'email_content' && receivedData.includes('250')) { socket.write('QUIT\r\n'); setTimeout(() => { socket.destroy(); expect(receivedData).toInclude('250'); done.resolve(); }, 100); } }); socket.on('error', (error) => { done.reject(error); }); socket.on('timeout', () => { socket.destroy(); done.reject(new Error(`Connection timeout at step: ${currentStep}`)); }); await done.promise; }); // Test: Send HTML email tap.test('Basic Email Sending - should send HTML email', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); let receivedData = ''; let currentStep = 'connecting'; const fromAddress = 'sender@example.com'; const toAddress = 'recipient@example.com'; const boundary = '----=_Part_0_987654321'; const emailContent = `Subject: HTML Email Test\r\nFrom: ${fromAddress}\r\nTo: ${toAddress}\r\nMIME-Version: 1.0\r\nContent-Type: multipart/alternative; boundary="${boundary}"\r\n\r\n--${boundary}\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\nThis is the plain text version.\r\n\r\n--${boundary}\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n
This is the HTML version.
\r\n\r\n--${boundary}--\r\n`; socket.on('data', (data) => { receivedData += data.toString(); if (currentStep === 'connecting' && receivedData.includes('220')) { currentStep = 'ehlo'; socket.write('EHLO test.example.com\r\n'); } else if (currentStep === 'ehlo' && receivedData.includes('250')) { currentStep = 'mail_from'; socket.write(`MAIL FROM:<${fromAddress}>\r\n`); } else if (currentStep === 'mail_from' && receivedData.includes('250')) { currentStep = 'rcpt_to'; socket.write(`RCPT TO:<${toAddress}>\r\n`); } else if (currentStep === 'rcpt_to' && receivedData.includes('250')) { currentStep = 'data'; socket.write('DATA\r\n'); } else if (currentStep === 'data' && receivedData.includes('354')) { currentStep = 'email_content'; socket.write(emailContent); socket.write('\r\n.\r\n'); // End of data marker } else if (currentStep === 'email_content' && receivedData.includes('250')) { socket.write('QUIT\r\n'); setTimeout(() => { socket.destroy(); expect(receivedData).toInclude('250'); done.resolve(); }, 100); } }); socket.on('error', (error) => { done.reject(error); }); socket.on('timeout', () => { socket.destroy(); done.reject(new Error(`Connection timeout at step: ${currentStep}`)); }); await done.promise; }); // Test: Send email with custom headers tap.test('Basic Email Sending - should send email with custom headers', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); let receivedData = ''; let currentStep = 'connecting'; const fromAddress = 'sender@example.com'; const toAddress = 'recipient@example.com'; const emailContent = `Subject: Custom Headers Test\r\nFrom: ${fromAddress}\r\nTo: ${toAddress}\r\nX-Custom-Header: CustomValue\r\nX-Priority: 1\r\nX-Mailer: SMTP Test Suite\r\nReply-To: noreply@example.com\r\nOrganization: Test Organization\r\n\r\nThis email contains custom headers.\r\n`; socket.on('data', (data) => { receivedData += data.toString(); if (currentStep === 'connecting' && receivedData.includes('220')) { currentStep = 'ehlo'; socket.write('EHLO test.example.com\r\n'); } else if (currentStep === 'ehlo' && receivedData.includes('250')) { currentStep = 'mail_from'; socket.write(`MAIL FROM:<${fromAddress}>\r\n`); } else if (currentStep === 'mail_from' && receivedData.includes('250')) { currentStep = 'rcpt_to'; socket.write(`RCPT TO:<${toAddress}>\r\n`); } else if (currentStep === 'rcpt_to' && receivedData.includes('250')) { currentStep = 'data'; socket.write('DATA\r\n'); } else if (currentStep === 'data' && receivedData.includes('354')) { currentStep = 'email_content'; socket.write(emailContent); socket.write('\r\n.\r\n'); // End of data marker } else if (currentStep === 'email_content' && receivedData.includes('250')) { socket.write('QUIT\r\n'); setTimeout(() => { socket.destroy(); expect(receivedData).toInclude('250'); done.resolve(); }, 100); } }); socket.on('error', (error) => { done.reject(error); }); socket.on('timeout', () => { socket.destroy(); done.reject(new Error(`Connection timeout at step: ${currentStep}`)); }); await done.promise; }); // Test: Minimal email (only required headers) tap.test('Basic Email Sending - should send minimal email', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); let receivedData = ''; let currentStep = 'connecting'; const fromAddress = 'sender@example.com'; const toAddress = 'recipient@example.com'; // Minimal email - just a body, no headers const emailContent = 'This is a minimal email with no headers.\r\n'; socket.on('data', (data) => { receivedData += data.toString(); if (currentStep === 'connecting' && receivedData.includes('220')) { currentStep = 'ehlo'; socket.write('EHLO test.example.com\r\n'); } else if (currentStep === 'ehlo' && receivedData.includes('250')) { currentStep = 'mail_from'; socket.write(`MAIL FROM:<${fromAddress}>\r\n`); } else if (currentStep === 'mail_from' && receivedData.includes('250')) { currentStep = 'rcpt_to'; socket.write(`RCPT TO:<${toAddress}>\r\n`); } else if (currentStep === 'rcpt_to' && receivedData.includes('250')) { currentStep = 'data'; socket.write('DATA\r\n'); } else if (currentStep === 'data' && receivedData.includes('354')) { currentStep = 'email_content'; socket.write(emailContent); socket.write('\r\n.\r\n'); // End of data marker } else if (currentStep === 'email_content' && receivedData.includes('250')) { socket.write('QUIT\r\n'); setTimeout(() => { socket.destroy(); expect(receivedData).toInclude('250'); done.resolve(); }, 100); } }); socket.on('error', (error) => { done.reject(error); }); socket.on('timeout', () => { socket.destroy(); done.reject(new Error(`Connection timeout at step: ${currentStep}`)); }); await done.promise; }); // Teardown tap.test('teardown - stop SMTP server', async () => { await stopTestServer(); }); // Start the test tap.start();