419 lines
14 KiB
TypeScript
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();
|