Files
smartmail/test/test.ts

419 lines
14 KiB
TypeScript

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<any>;
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: '<h1>{{title}}</h1><p>This is an HTML email with {{variable}}.</p>',
from: 'noreply@example.com'
});
const data = { title: 'Welcome', variable: 'dynamic content' };
const htmlContent = htmlMailer.getHtmlBody(data);
expect(htmlContent).toEqual('<h1>Welcome</h1><p>This is an HTML email with dynamic content.</p>');
});
tap.test('should convert to MIME format', async () => {
const mimeMailer = new smartmail.Smartmail({
subject: 'MIME Test',
body: 'Plain text content',
htmlBody: '<p>HTML content</p>',
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('<p>HTML content</p>');
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: '<p>Test HTML</p>',
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('<p>Test HTML</p>');
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<any> | 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();