import { tap, expect } from '@git.zone/tstest/tapbundle'; import { startTestSmtpServer } from '../../helpers/server.loader.js'; import { createSmtpClient } from '../../helpers/smtp.client.js'; import { Email } from '../../../ts/mail/core/classes.email.js'; let testServer: any; tap.test('setup test SMTP server', async () => { testServer = await startTestSmtpServer(); expect(testServer).toBeTruthy(); expect(testServer.port).toBeGreaterThan(0); }); tap.test('CEP-07: Basic HTML email', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Create HTML email const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'HTML Email Test', html: `

Welcome!

This is an HTML email with formatting.

`, text: 'Welcome! This is an HTML email with formatting. Features: 1, 2, 3. © 2024 Example Corp' }); // Monitor content type let contentType = ''; let boundary = ''; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); smtpClient.sendCommand = async (command: string) => { if (command.toLowerCase().includes('content-type:')) { contentType = command; const boundaryMatch = command.match(/boundary="?([^";\s]+)"?/i); if (boundaryMatch) { boundary = boundaryMatch[1]; } } return originalSendCommand(command); }; const result = await smtpClient.sendMail(email); expect(result).toBeTruthy(); console.log('Content-Type:', contentType.trim()); console.log('Multipart boundary:', boundary || 'not found'); // Should be multipart/alternative for HTML+text expect(contentType.toLowerCase()).toInclude('multipart'); await smtpClient.close(); }); tap.test('CEP-07: HTML email with inline images', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 10000, debug: true }); await smtpClient.connect(); // Create a simple 1x1 red pixel PNG const redPixelBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='; // Create HTML email with inline image const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Email with Inline Images', html: `

Email with Inline Images

Here's an inline image:

Red pixel

And here's another one:

Company logo `, attachments: [ { filename: 'red-pixel.png', content: Buffer.from(redPixelBase64, 'base64'), contentType: 'image/png', cid: 'image001' // Content-ID for inline reference }, { filename: 'logo.png', content: Buffer.from(redPixelBase64, 'base64'), // Reuse for demo contentType: 'image/png', cid: 'logo' } ] }); // Monitor multipart structure let multipartType = ''; let partCount = 0; let hasContentId = false; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); smtpClient.sendCommand = async (command: string) => { if (command.toLowerCase().includes('content-type:')) { if (command.toLowerCase().includes('multipart/related')) { multipartType = 'related'; } else if (command.toLowerCase().includes('multipart/mixed')) { multipartType = 'mixed'; } if (command.includes('--')) { partCount++; } } if (command.toLowerCase().includes('content-id:')) { hasContentId = true; } return originalSendCommand(command); }; const result = await smtpClient.sendMail(email); expect(result).toBeTruthy(); console.log('Multipart type:', multipartType); console.log('Has Content-ID headers:', hasContentId); // Should use multipart/related for inline images expect(multipartType).toEqual('related'); expect(hasContentId).toBeTruthy(); await smtpClient.close(); }); tap.test('CEP-07: Complex HTML with multiple inline resources', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 10000, debug: true }); await smtpClient.connect(); // Create email with multiple inline resources const email = new Email({ from: 'newsletter@example.com', to: ['subscriber@example.com'], subject: 'Newsletter with Rich Content', html: `

Monthly Newsletter

Product 1

Product 1

Product 2

Product 2

Product 3

Product 3

© 2024 Example Corp

`, text: 'Monthly Newsletter - View in HTML for best experience', attachments: [ { filename: 'header-bg.jpg', content: Buffer.from('fake-image-data'), contentType: 'image/jpeg', cid: 'header-bg' }, { filename: 'logo.png', content: Buffer.from('fake-logo-data'), contentType: 'image/png', cid: 'logo' }, { filename: 'product1.jpg', content: Buffer.from('fake-product1-data'), contentType: 'image/jpeg', cid: 'product1' }, { filename: 'product2.jpg', content: Buffer.from('fake-product2-data'), contentType: 'image/jpeg', cid: 'product2' }, { filename: 'product3.jpg', content: Buffer.from('fake-product3-data'), contentType: 'image/jpeg', cid: 'product3' }, { filename: 'divider.gif', content: Buffer.from('fake-divider-data'), contentType: 'image/gif', cid: 'footer-divider' } ] }); // Count inline attachments let inlineAttachments = 0; let contentIds: string[] = []; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); smtpClient.sendCommand = async (command: string) => { if (command.toLowerCase().includes('content-disposition: inline')) { inlineAttachments++; } if (command.toLowerCase().includes('content-id:')) { const cidMatch = command.match(/content-id:\s*<([^>]+)>/i); if (cidMatch) { contentIds.push(cidMatch[1]); } } return originalSendCommand(command); }; const result = await smtpClient.sendMail(email); expect(result).toBeTruthy(); console.log(`Inline attachments: ${inlineAttachments}`); console.log(`Content-IDs found: ${contentIds.length}`); console.log('CIDs:', contentIds); // Should have all inline attachments expect(contentIds.length).toEqual(6); await smtpClient.close(); }); tap.test('CEP-07: HTML with external and inline images mixed', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Mix of inline and external images const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Mixed Image Sources', html: `

Mixed Image Sources

Inline Image:

Inline Logo

External Images:

External Image 1 External Image 2

Data URI Image:

Data URI `, attachments: [ { filename: 'logo.png', content: Buffer.from('logo-data'), contentType: 'image/png', cid: 'inline-logo' } ] }); const result = await smtpClient.sendMail(email); expect(result).toBeTruthy(); console.log('Successfully sent email with mixed image sources'); await smtpClient.close(); }); tap.test('CEP-07: HTML email responsive design', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Responsive HTML email const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Responsive HTML Email', html: `

Responsive Design Test

Left Column

Left column content

Right Column

Right column content

This text is hidden on mobile devices

`, text: 'Responsive Design Test - View in HTML', attachments: [ { filename: 'left.jpg', content: Buffer.from('left-image-data'), contentType: 'image/jpeg', cid: 'left-image' }, { filename: 'right.jpg', content: Buffer.from('right-image-data'), contentType: 'image/jpeg', cid: 'right-image' } ] }); const result = await smtpClient.sendMail(email); expect(result).toBeTruthy(); console.log('Successfully sent responsive HTML email'); await smtpClient.close(); }); tap.test('CEP-07: HTML sanitization and security', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Email with potentially dangerous HTML const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'HTML Security Test', html: `

Security Test

Dangerous Link

This is safe text content.

Safe Image `, text: 'Security Test - Plain text version', attachments: [ { filename: 'safe.png', content: Buffer.from('safe-image-data'), contentType: 'image/png', cid: 'safe-image' } ] }); // Note: The Email class should handle dangerous content appropriately const result = await smtpClient.sendMail(email); expect(result).toBeTruthy(); console.log('Sent email with potentially dangerous HTML (should be handled by Email class)'); await smtpClient.close(); }); tap.test('CEP-07: Large HTML email with many inline images', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 30000, debug: false // Quiet for performance }); await smtpClient.connect(); // Create email with many inline images const imageCount = 20; const attachments: any[] = []; let htmlContent = '

Performance Test

'; for (let i = 0; i < imageCount; i++) { const cid = `image${i}`; htmlContent += `Image ${i}`; attachments.push({ filename: `image${i}.png`, content: Buffer.from(`fake-image-data-${i}`), contentType: 'image/png', cid: cid }); } htmlContent += ''; const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: `Email with ${imageCount} inline images`, html: htmlContent, attachments: attachments }); console.log(`Sending email with ${imageCount} inline images...`); const startTime = Date.now(); const result = await smtpClient.sendMail(email); const elapsed = Date.now() - startTime; expect(result).toBeTruthy(); console.log(`Sent in ${elapsed}ms (${(elapsed/imageCount).toFixed(2)}ms per image)`); await smtpClient.close(); }); tap.test('CEP-07: Alternative content for non-HTML clients', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Email with rich HTML and good plain text alternative const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Newsletter - March 2024', html: `
Company Newsletter

March Newsletter

Featured Articles

Special Offer!

Get 20% off with code: SPRING20

Special Offer

© 2024 Example Corp | Unsubscribe

`, text: `COMPANY NEWSLETTER March 2024 FEATURED ARTICLES * 10 Tips for Spring Cleaning https://example.com/article1 * New Product Launch https://example.com/article2 * Customer Success Story https://example.com/article3 SPECIAL OFFER! Get 20% off with code: SPRING20 --- © 2024 Example Corp Unsubscribe: https://example.com/unsubscribe`, attachments: [ { filename: 'header.jpg', content: Buffer.from('header-image'), contentType: 'image/jpeg', cid: 'header' }, { filename: 'offer.jpg', content: Buffer.from('offer-image'), contentType: 'image/jpeg', cid: 'offer' } ] }); // Check multipart/alternative structure let hasAlternative = false; let hasTextPart = false; let hasHtmlPart = false; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); smtpClient.sendCommand = async (command: string) => { if (command.toLowerCase().includes('content-type: multipart/alternative')) { hasAlternative = true; } if (command.toLowerCase().includes('content-type: text/plain')) { hasTextPart = true; } if (command.toLowerCase().includes('content-type: text/html')) { hasHtmlPart = true; } return originalSendCommand(command); }; const result = await smtpClient.sendMail(email); expect(result).toBeTruthy(); console.log('Multipart/alternative:', hasAlternative); console.log('Has text part:', hasTextPart); console.log('Has HTML part:', hasHtmlPart); // Should have both versions expect(hasTextPart).toBeTruthy(); expect(hasHtmlPart).toBeTruthy(); await smtpClient.close(); }); tap.test('cleanup test SMTP server', async () => { if (testServer) { await testServer.stop(); } }); export default tap.start();