import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as smartmail from '../ts/index.js'; import * as plugins from '../ts/smartmail.plugins.js'; let emailAddressValidatorInstance: smartmail.EmailAddressValidator; // EmailAddressValidator Tests tap.test('should create an instance of EmailAddressValidator', async () => { emailAddressValidatorInstance = new smartmail.EmailAddressValidator(); expect(emailAddressValidatorInstance).toBeInstanceOf(smartmail.EmailAddressValidator); }); tap.test('should validate an email with detailed information', async () => { const result = await emailAddressValidatorInstance.validate('sandbox@bleu.de'); expect(result.freemail).toBeFalse(); expect(result.disposable).toBeFalse(); expect(result.formatValid).toBeTrue(); expect(result.localPartValid).toBeTrue(); expect(result.domainPartValid).toBeTrue(); expect(result.reason).toBeDefined(); }); tap.test('should recognize an email as freemail', async () => { const result = await emailAddressValidatorInstance.validate('sandbox@gmail.com'); expect(result.freemail).toBeTrue(); expect(result.disposable).toBeFalse(); expect(result.formatValid).toBeTrue(); expect(result.valid).toBeTrue(); }); tap.test('should recognize an email as disposable', async () => { const result = await emailAddressValidatorInstance.validate('sandbox@gmx.de'); expect(result.freemail).toBeFalse(); expect(result.disposable).toBeTrue(); expect(result.formatValid).toBeTrue(); }); tap.test('should detect invalid email format', async () => { const result = await emailAddressValidatorInstance.validate('invalid-email'); expect(result.formatValid).toBeFalse(); expect(result.valid).toBeFalse(); expect(result.reason).toEqual('Invalid email format'); }); tap.test('should validate email format parts separately', async () => { expect(emailAddressValidatorInstance.isValidEmailFormat('valid.email@example.com')).toBeTrue(); expect(emailAddressValidatorInstance.isValidEmailFormat('invalid-email')).toBeFalse(); expect(emailAddressValidatorInstance.isValidLocalPart('valid.local.part')).toBeTrue(); expect(emailAddressValidatorInstance.isValidLocalPart('invalid..part')).toBeFalse(); expect(emailAddressValidatorInstance.isValidDomainPart('example.com')).toBeTrue(); expect(emailAddressValidatorInstance.isValidDomainPart('invalid')).toBeFalse(); }); // Smartmail Tests let testSmartmail: smartmail.Smartmail; tap.test('should create a SmartMail instance', async () => { testSmartmail = new smartmail.Smartmail({ body: 'hi there', from: 'noreply@mail.lossless.com', subject: 'hi from here', }); expect(testSmartmail).toBeInstanceOf(smartmail.Smartmail); }); tap.test('should handle email recipients', async () => { testSmartmail.addRecipient('user1@example.com'); testSmartmail.addRecipients(['user2@example.com', 'user3@example.com'], 'cc'); testSmartmail.addRecipient('user4@example.com', 'bcc'); expect(testSmartmail.options.to!.length).toEqual(1); expect(testSmartmail.options.cc!.length).toEqual(2); expect(testSmartmail.options.bcc!.length).toEqual(1); }); tap.test('should set reply-to and priority', async () => { testSmartmail.setReplyTo('replies@example.com'); testSmartmail.setPriority('high'); expect(testSmartmail.options.replyTo).toEqual('replies@example.com'); expect(testSmartmail.options.priority).toEqual('high'); }); tap.test('should apply template data to subject and body', async () => { const templateMailer = new smartmail.Smartmail({ subject: 'Hello {{name}}', body: 'Welcome, {{name}}! Your ID is {{userId}}.', from: 'noreply@example.com' }); const data = { name: 'John Doe', userId: '12345' }; expect(templateMailer.getSubject(data)).toEqual('Hello John Doe'); expect(templateMailer.getBody(data)).toEqual('Welcome, John Doe! Your ID is 12345.'); }); tap.test('should handle HTML email body', async () => { const htmlMailer = new smartmail.Smartmail({ subject: 'HTML Test', body: 'Plain text version', htmlBody: '

{{title}}

This is an HTML email with {{variable}}.

', from: 'noreply@example.com' }); const data = { title: 'Welcome', variable: 'dynamic content' }; const htmlContent = htmlMailer.getHtmlBody(data); expect(htmlContent).toEqual('

Welcome

This is an HTML email with dynamic content.

'); }); tap.test('should convert to MIME format', async () => { const mimeMailer = new smartmail.Smartmail({ subject: 'MIME Test', body: 'Plain text content', htmlBody: '

HTML content

', from: 'sender@example.com', to: ['recipient@example.com'], headers: { 'X-Custom': 'Value' } }); const mimeObj = await mimeMailer.toMimeFormat(); expect(mimeObj.from).toEqual('sender@example.com'); expect(mimeObj.to).toInclude('recipient@example.com'); expect(mimeObj.subject).toEqual('MIME Test'); expect(mimeObj.text).toEqual('Plain text content'); expect(mimeObj.html).toEqual('

HTML content

'); expect(mimeObj.headers['X-Custom']).toEqual('Value'); }); tap.test('should add email headers', async () => { const headerMailer = new smartmail.Smartmail({ subject: 'Header Test', body: 'Test body', from: 'noreply@example.com' }); headerMailer.addHeader('X-Test-Header', 'TestValue'); headerMailer.addHeader('X-Tracking-ID', '12345'); const mimeObj = await headerMailer.toMimeFormat(); expect(mimeObj.headers['X-Test-Header']).toEqual('TestValue'); expect(mimeObj.headers['X-Tracking-ID']).toEqual('12345'); }); // Fluent Chaining Tests tap.test('should support fluent chaining for all mutation methods', async () => { const email = new smartmail.Smartmail({ from: 'sender@example.com', subject: 'Fluent {{name}}', body: 'Hello {{name}}' }); // Chain all methods together const result = email .addRecipient('user1@example.com') .addRecipient('cc@example.com', 'cc') .addRecipients(['user2@example.com', 'user3@example.com']) .setReplyTo('reply@example.com') .setPriority('high') .addHeader('X-Custom', 'value') .applyVariables({ name: 'John' }); // Result should be the same instance expect(result).toEqual(email); // All values should be set expect(email.options.to!.length).toEqual(3); expect(email.options.cc!.length).toEqual(1); expect(email.options.replyTo).toEqual('reply@example.com'); expect(email.options.priority).toEqual('high'); expect(email.options.headers!['X-Custom']).toEqual('value'); expect(email.options.subject).toEqual('Fluent John'); expect(email.options.body).toEqual('Hello John'); }); // Wire Format Serialization Tests tap.test('should serialize to JSON and back with toObject/fromObject', async () => { const original = new smartmail.Smartmail({ from: 'sender@example.com', to: ['recipient@example.com'], cc: ['cc@example.com'], subject: 'Test Subject', body: 'Test Body', htmlBody: '

Test HTML

', priority: 'high', headers: { 'X-Custom': 'value' }, creationObjectRef: { orderId: '12345' } }); const obj = original.toObject(); expect(obj.from).toEqual('sender@example.com'); expect(obj.to).toInclude('recipient@example.com'); expect(obj.cc).toInclude('cc@example.com'); expect(obj.subject).toEqual('Test Subject'); expect(obj.body).toEqual('Test Body'); expect(obj.htmlBody).toEqual('

Test HTML

'); expect(obj.priority).toEqual('high'); expect(obj.headers!['X-Custom']).toEqual('value'); expect(obj.creationObjectRef).toEqual({ orderId: '12345' }); expect(obj.attachments).toBeDefined(); // Reconstruct from object const reconstructed = smartmail.Smartmail.fromObject(obj); expect(reconstructed.options.from).toEqual(original.options.from); expect(reconstructed.options.subject).toEqual(original.options.subject); expect(reconstructed.options.body).toEqual(original.options.body); expect(reconstructed.options.htmlBody).toEqual(original.options.htmlBody); expect(reconstructed.options.priority).toEqual(original.options.priority); expect(reconstructed.getCreationObject()).toEqual({ orderId: '12345' }); }); tap.test('should serialize to JSON string and back with toJson/fromJson', async () => { const original = new smartmail.Smartmail({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'JSON Test', body: 'JSON Body' }); const jsonString = original.toJson(); // Should be valid JSON const parsed = JSON.parse(jsonString); expect(parsed.from).toEqual('sender@example.com'); expect(parsed.subject).toEqual('JSON Test'); // Reconstruct from JSON string const reconstructed = smartmail.Smartmail.fromJson(jsonString); expect(reconstructed.options.from).toEqual(original.options.from); expect(reconstructed.options.subject).toEqual(original.options.subject); expect(reconstructed.options.body).toEqual(original.options.body); }); tap.test('should serialize attachments to base64 and back', async () => { const testContent = 'Hello, this is test file content!'; const testBuffer = Buffer.from(testContent); const original = new smartmail.Smartmail({ from: 'sender@example.com', subject: 'Attachment Test', body: 'Test Body' }); // Create a SmartFile and add it as attachment const smartfile = new plugins.smartfile.SmartFile({ path: 'test-file.txt', contentBuffer: testBuffer, base: './' }); original.addAttachment(smartfile); // Serialize to object const obj = original.toObject(); expect(obj.attachments.length).toEqual(1); expect(obj.attachments[0].filename).toEqual('test-file.txt'); expect(obj.attachments[0].contentBase64).toEqual(testBuffer.toString('base64')); // Reconstruct const reconstructed = smartmail.Smartmail.fromObject(obj); expect(reconstructed.attachments.length).toEqual(1); expect(reconstructed.attachments[0].contentBuffer.toString()).toEqual(testContent); }); // Wire Protocol Message Types Tests tap.test('should have correct wire message type interfaces', async () => { // Test createMessageId and createTimestamp helpers const messageId = smartmail.createMessageId(); const timestamp = smartmail.createTimestamp(); expect(typeof messageId).toEqual('string'); expect(messageId.length).toBeGreaterThan(0); expect(typeof timestamp).toEqual('string'); expect(timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/); }); // WireParser Tests tap.test('should parse and handle mail.send requests with WireParser', async () => { let receivedEmail: smartmail.Smartmail | null = null; const parser = new smartmail.WireParser({ onMailSend: async (email, options) => { receivedEmail = email; return { type: 'mail.send.response', messageId: smartmail.createMessageId(), timestamp: smartmail.createTimestamp(), success: true, deliveryId: 'test-delivery-id' }; } }); const testEmail = new smartmail.Smartmail({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Wire Test', body: 'Wire Body' }); const request: smartmail.IMailSendRequest = { type: 'mail.send', messageId: smartmail.createMessageId(), timestamp: smartmail.createTimestamp(), email: testEmail.toObject() }; const response = await parser.handle(request) as smartmail.IMailSendResponse; expect(response.type).toEqual('mail.send.response'); expect(response.success).toBeTrue(); expect(response.deliveryId).toEqual('test-delivery-id'); expect(receivedEmail).not.toBeNull(); expect(receivedEmail!.options.subject).toEqual('Wire Test'); }); tap.test('should parse and handle settings.update requests with WireParser', async () => { let receivedSettings: smartmail.IWireSettings | null = null; const parser = new smartmail.WireParser({ onSettingsUpdate: async (settings) => { receivedSettings = settings; return { type: 'settings.update.response', messageId: smartmail.createMessageId(), timestamp: smartmail.createTimestamp(), success: true }; } }); const request: smartmail.ISettingsUpdateRequest = { type: 'settings.update', messageId: smartmail.createMessageId(), timestamp: smartmail.createTimestamp(), settings: { smtp: { host: 'smtp.example.com', port: 587, secure: true, username: 'user', password: 'pass' }, defaultFrom: 'noreply@example.com', customSetting: 'custom-value' } }; const response = await parser.handle(request) as smartmail.ISettingsUpdateResponse; expect(response.type).toEqual('settings.update.response'); expect(response.success).toBeTrue(); expect(receivedSettings).not.toBeNull(); expect(receivedSettings!.smtp!.host).toEqual('smtp.example.com'); expect(receivedSettings!.defaultFrom).toEqual('noreply@example.com'); expect(receivedSettings!.customSetting).toEqual('custom-value'); }); tap.test('should handle parseAndHandle convenience method', async () => { const parser = new smartmail.WireParser({ onMailSend: async (email) => ({ type: 'mail.send.response', messageId: smartmail.createMessageId(), timestamp: smartmail.createTimestamp(), success: true, deliveryId: 'convenience-test-id' }) }); const testEmail = new smartmail.Smartmail({ from: 'sender@example.com', subject: 'Convenience Test', body: 'Test Body' }); const requestJson = JSON.stringify({ type: 'mail.send', messageId: smartmail.createMessageId(), timestamp: smartmail.createTimestamp(), email: testEmail.toObject() }); const responseJson = await parser.parseAndHandle(requestJson); const response = JSON.parse(responseJson); expect(response.type).toEqual('mail.send.response'); expect(response.success).toBeTrue(); expect(response.deliveryId).toEqual('convenience-test-id'); }); tap.test('should return error response for unsupported handlers', async () => { const parser = new smartmail.WireParser({}); // No handlers const request: smartmail.IMailSendRequest = { type: 'mail.send', messageId: 'test-msg-id', timestamp: smartmail.createTimestamp(), email: new smartmail.Smartmail({ from: 'sender@example.com', subject: 'Test', body: 'Test' }).toObject() }; const response = await parser.handle(request) as smartmail.IMailSendResponse; expect(response.type).toEqual('mail.send.response'); expect(response.success).toBeFalse(); expect(response.error).toEqual('Mail send not supported'); }); export default tap.start();