2025-05-24 16:19:19 +00:00
import { tap , expect } from '@git.zone/tstest/tapbundle' ;
2025-05-25 11:18:12 +00:00
import { startTestServer , stopTestServer , type ITestServer } from '../../helpers/server.loader.js' ;
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.js' ;
import type { SmtpClient } from '../../../ts/mail/delivery/smtpclient/smtp-client.js' ;
import { Email } from '../../../ts/mail/core/classes.email.js' ;
2025-05-25 19:02:18 +00:00
import { EmailValidator } from '../../../ts/mail/core/classes.emailvalidator.js' ;
2025-05-24 16:19:19 +00:00
2025-05-25 11:18:12 +00:00
let testServer : ITestServer ;
2025-05-25 19:02:18 +00:00
let smtpClient : SmtpClient ;
2025-05-24 16:19:19 +00:00
tap . test ( 'setup test SMTP server' , async ( ) = > {
2025-05-25 11:18:12 +00:00
testServer = await startTestServer ( {
port : 2550 ,
tlsEnabled : false ,
authRequired : false
2025-05-24 16:19:19 +00:00
} ) ;
expect ( testServer ) . toBeTruthy ( ) ;
expect ( testServer . port ) . toBeGreaterThan ( 0 ) ;
2025-05-25 19:02:18 +00:00
smtpClient = createSmtpClient ( {
2025-05-24 16:19:19 +00:00
host : testServer.hostname ,
port : testServer.port ,
secure : false ,
2025-05-25 19:02:18 +00:00
connectionTimeout : 5000
2025-05-24 16:19:19 +00:00
} ) ;
2025-05-25 19:02:18 +00:00
} ) ;
2025-05-24 16:19:19 +00:00
2025-05-25 19:02:18 +00:00
tap . test ( 'CCMD-10: Email address validation' , async ( ) = > {
// Test email address validation which is what VRFY conceptually does
const validator = new EmailValidator ( ) ;
2025-05-24 16:19:19 +00:00
const testAddresses = [
2025-05-25 19:02:18 +00:00
{ address : 'user@example.com' , expected : true } ,
{ address : 'postmaster@example.com' , expected : true } ,
{ address : 'admin@example.com' , expected : true } ,
{ address : 'user.name+tag@example.com' , expected : true } ,
{ address : 'test@sub.domain.example.com' , expected : true } ,
{ address : 'invalid@' , expected : false } ,
{ address : '@example.com' , expected : false } ,
{ address : 'not-an-email' , expected : false } ,
{ address : '' , expected : false } ,
{ address : 'user@' , expected : false }
2025-05-24 16:19:19 +00:00
] ;
2025-05-25 19:02:18 +00:00
console . log ( 'Testing email address validation (VRFY equivalent):\n' ) ;
for ( const test of testAddresses ) {
const isValid = validator . isValidFormat ( test . address ) ;
expect ( isValid ) . toEqual ( test . expected ) ;
console . log ( ` Address: " ${ test . address } " - Valid: ${ isValid } (expected: ${ test . expected } ) ` ) ;
2025-05-24 16:19:19 +00:00
}
2025-05-25 19:02:18 +00:00
// Test sending to valid addresses
const validEmail = new Email ( {
from : 'sender@example.com' ,
to : [ 'user@example.com' ] ,
subject : 'Address validation test' ,
text : 'Testing address validation'
} ) ;
await smtpClient . sendMail ( validEmail ) ;
console . log ( '\nEmail sent successfully to validated address' ) ;
2025-05-24 16:19:19 +00:00
} ) ;
2025-05-25 19:02:18 +00:00
tap . test ( 'CCMD-10: Multiple recipient handling (EXPN equivalent)' , async ( ) = > {
// Test multiple recipients which is conceptually similar to mailing list expansion
console . log ( 'Testing multiple recipient handling (EXPN equivalent):\n' ) ;
// Create email with multiple recipients (like a mailing list)
const multiRecipientEmail = new Email ( {
from : 'sender@example.com' ,
to : [
'user1@example.com' ,
'user2@example.com' ,
'user3@example.com'
] ,
cc : [
'cc1@example.com' ,
'cc2@example.com'
] ,
bcc : [
'bcc1@example.com'
] ,
subject : 'Multi-recipient test (mailing list)' ,
text : 'Testing email distribution to multiple recipients'
2025-05-24 16:19:19 +00:00
} ) ;
2025-05-25 19:02:18 +00:00
const toAddresses = multiRecipientEmail . getToAddresses ( ) ;
const ccAddresses = multiRecipientEmail . getCcAddresses ( ) ;
const bccAddresses = multiRecipientEmail . getBccAddresses ( ) ;
console . log ( ` To recipients: ${ toAddresses . length } ` ) ;
toAddresses . forEach ( addr = > console . log ( ` - ${ addr } ` ) ) ;
console . log ( ` \ nCC recipients: ${ ccAddresses . length } ` ) ;
ccAddresses . forEach ( addr = > console . log ( ` - ${ addr } ` ) ) ;
console . log ( ` \ nBCC recipients: ${ bccAddresses . length } ` ) ;
bccAddresses . forEach ( addr = > console . log ( ` - ${ addr } ` ) ) ;
console . log ( ` \ nTotal recipients: ${ toAddresses . length + ccAddresses . length + bccAddresses . length } ` ) ;
// Send the email
await smtpClient . sendMail ( multiRecipientEmail ) ;
console . log ( '\nEmail sent successfully to all recipients' ) ;
} ) ;
2025-05-24 16:19:19 +00:00
2025-05-25 19:02:18 +00:00
tap . test ( 'CCMD-10: Email addresses with display names' , async ( ) = > {
// Test email addresses with display names (full names)
console . log ( 'Testing email addresses with display names:\n' ) ;
const fullNameTests = [
{ from : '"John Doe" <john@example.com>' , expectedAddress : 'john@example.com' } ,
{ from : '"Smith, John" <john.smith@example.com>' , expectedAddress : 'john.smith@example.com' } ,
{ from : 'Mary Johnson <mary@example.com>' , expectedAddress : 'mary@example.com' } ,
{ from : '<bob@example.com>' , expectedAddress : 'bob@example.com' }
2025-05-24 16:19:19 +00:00
] ;
2025-05-25 19:02:18 +00:00
for ( const test of fullNameTests ) {
const email = new Email ( {
from : test . from ,
to : [ 'recipient@example.com' ] ,
subject : 'Display name test' ,
text : ` Testing from: ${ test . from } `
} ) ;
2025-05-24 16:19:19 +00:00
2025-05-25 19:02:18 +00:00
const fromAddress = email . getFromAddress ( ) ;
console . log ( ` Full: " ${ test . from } " ` ) ;
console . log ( ` Extracted: " ${ fromAddress } " ` ) ;
expect ( fromAddress ) . toEqual ( test . expectedAddress ) ;
2025-05-24 16:19:19 +00:00
2025-05-25 19:02:18 +00:00
await smtpClient . sendMail ( email ) ;
console . log ( 'Email sent successfully\n' ) ;
2025-05-24 16:19:19 +00:00
}
} ) ;
2025-05-25 19:02:18 +00:00
tap . test ( 'CCMD-10: Email validation security' , async ( ) = > {
// Test security aspects of email validation
console . log ( 'Testing email validation security considerations:\n' ) ;
// Test common system/role addresses that should be handled carefully
const systemAddresses = [
'root@example.com' ,
'admin@example.com' ,
'administrator@example.com' ,
'webmaster@example.com' ,
'hostmaster@example.com' ,
'abuse@example.com' ,
'postmaster@example.com' ,
'noreply@example.com'
2025-05-24 16:19:19 +00:00
] ;
2025-05-25 19:02:18 +00:00
const validator = new EmailValidator ( ) ;
console . log ( 'Checking if addresses are role accounts:' ) ;
for ( const addr of systemAddresses ) {
const validationResult = await validator . validate ( addr , { checkRole : true , checkMx : false } ) ;
console . log ( ` ${ addr } : ${ validationResult . details ? . role ? 'Role account' : 'Not a role account' } (format valid: ${ validationResult . details ? . formatValid } ) ` ) ;
}
// Test that we don't expose information about which addresses exist
console . log ( '\nTesting information disclosure prevention:' ) ;
try {
// Try sending to a non-existent address
const testEmail = new Email ( {
from : 'sender@example.com' ,
to : [ 'definitely-does-not-exist-12345@example.com' ] ,
subject : 'Test' ,
text : 'Test'
} ) ;
2025-05-24 16:19:19 +00:00
2025-05-25 19:02:18 +00:00
await smtpClient . sendMail ( testEmail ) ;
console . log ( 'Server accepted email (does not disclose non-existence)' ) ;
} catch ( error ) {
console . log ( 'Server rejected email:' , error . message ) ;
2025-05-24 16:19:19 +00:00
}
2025-05-25 19:02:18 +00:00
console . log ( '\nSecurity best practice: Servers should not disclose address existence' ) ;
2025-05-24 16:19:19 +00:00
} ) ;
2025-05-25 19:02:18 +00:00
tap . test ( 'CCMD-10: Validation during email sending' , async ( ) = > {
// Test that validation doesn't interfere with email sending
console . log ( 'Testing validation during email transaction:\n' ) ;
const validator = new EmailValidator ( ) ;
// Create a series of emails with validation between them
const emails = [
{
from : 'sender1@example.com' ,
to : [ 'recipient1@example.com' ] ,
subject : 'First email' ,
text : 'Testing validation during transaction'
} ,
{
from : 'sender2@example.com' ,
to : [ 'recipient2@example.com' , 'recipient3@example.com' ] ,
subject : 'Second email' ,
text : 'Multiple recipients'
} ,
{
from : '"Test User" <sender3@example.com>' ,
to : [ 'recipient4@example.com' ] ,
subject : 'Third email' ,
text : 'Display name test'
}
2025-05-24 16:19:19 +00:00
] ;
2025-05-25 19:02:18 +00:00
for ( let i = 0 ; i < emails . length ; i ++ ) {
const emailData = emails [ i ] ;
// Validate addresses before sending
console . log ( ` Email ${ i + 1 } : ` ) ;
const fromAddr = emailData . from . includes ( '<' ) ? emailData . from . match ( /<([^>]+)>/ ) ? . [ 1 ] || emailData.from : emailData.from ;
console . log ( ` From: ${ emailData . from } - Valid: ${ validator . isValidFormat ( fromAddr ) } ` ) ;
for ( const to of emailData . to ) {
console . log ( ` To: ${ to } - Valid: ${ validator . isValidFormat ( to ) } ` ) ;
2025-05-24 16:19:19 +00:00
}
2025-05-25 19:02:18 +00:00
// Create and send email
const email = new Email ( emailData ) ;
await smtpClient . sendMail ( email ) ;
console . log ( ` Sent successfully \ n ` ) ;
2025-05-24 16:19:19 +00:00
}
2025-05-25 19:02:18 +00:00
console . log ( 'All emails sent successfully with validation' ) ;
2025-05-24 16:19:19 +00:00
} ) ;
2025-05-25 19:02:18 +00:00
tap . test ( 'CCMD-10: Special characters in email addresses' , async ( ) = > {
// Test email addresses with special characters
console . log ( 'Testing email addresses with special characters:\n' ) ;
const validator = new EmailValidator ( ) ;
2025-05-24 16:19:19 +00:00
const specialAddresses = [
2025-05-25 19:02:18 +00:00
{ address : 'user+tag@example.com' , shouldBeValid : true , description : 'Plus addressing' } ,
{ address : 'first.last@example.com' , shouldBeValid : true , description : 'Dots in local part' } ,
{ address : 'user_name@example.com' , shouldBeValid : true , description : 'Underscore' } ,
{ address : 'user-name@example.com' , shouldBeValid : true , description : 'Hyphen' } ,
{ address : '"quoted string"@example.com' , shouldBeValid : true , description : 'Quoted string' } ,
{ address : 'user@sub.domain.example.com' , shouldBeValid : true , description : 'Subdomain' } ,
{ address : 'user@example.co.uk' , shouldBeValid : true , description : 'Multi-part TLD' } ,
{ address : 'user..name@example.com' , shouldBeValid : false , description : 'Double dots' } ,
{ address : '.user@example.com' , shouldBeValid : false , description : 'Leading dot' } ,
{ address : 'user.@example.com' , shouldBeValid : false , description : 'Trailing dot' }
2025-05-24 16:19:19 +00:00
] ;
2025-05-25 19:02:18 +00:00
for ( const test of specialAddresses ) {
const isValid = validator . isValidFormat ( test . address ) ;
console . log ( ` ${ test . description } : ` ) ;
console . log ( ` Address: " ${ test . address } " ` ) ;
console . log ( ` Valid: ${ isValid } (expected: ${ test . shouldBeValid } ) ` ) ;
2025-05-24 16:19:19 +00:00
2025-05-25 19:02:18 +00:00
if ( test . shouldBeValid && isValid ) {
// Try sending an email with this address
try {
const email = new Email ( {
from : 'sender@example.com' ,
to : [ test . address ] ,
subject : 'Special character test' ,
text : ` Testing special characters in: ${ test . address } `
} ) ;
await smtpClient . sendMail ( email ) ;
console . log ( ` Email sent successfully ` ) ;
} catch ( error ) {
console . log ( ` Failed to send: ${ error . message } ` ) ;
2025-05-24 16:19:19 +00:00
}
2025-05-25 19:02:18 +00:00
}
console . log ( '' ) ;
2025-05-24 16:19:19 +00:00
}
} ) ;
2025-05-25 19:02:18 +00:00
tap . test ( 'CCMD-10: Large recipient lists' , async ( ) = > {
// Test handling of large recipient lists (similar to EXPN multi-line)
console . log ( 'Testing large recipient lists:\n' ) ;
// Create email with many recipients
const recipientCount = 20 ;
const toRecipients = [ ] ;
const ccRecipients = [ ] ;
for ( let i = 1 ; i <= recipientCount ; i ++ ) {
if ( i <= 10 ) {
toRecipients . push ( ` user ${ i } @example.com ` ) ;
} else {
ccRecipients . push ( ` user ${ i } @example.com ` ) ;
}
}
console . log ( ` Creating email with ${ recipientCount } total recipients: ` ) ;
console . log ( ` To: ${ toRecipients . length } recipients ` ) ;
console . log ( ` CC: ${ ccRecipients . length } recipients ` ) ;
const largeListEmail = new Email ( {
from : 'sender@example.com' ,
to : toRecipients ,
cc : ccRecipients ,
subject : 'Large distribution list test' ,
text : ` This email is being sent to ${ recipientCount } recipients total `
2025-05-24 16:19:19 +00:00
} ) ;
2025-05-25 19:02:18 +00:00
// Show extracted addresses
const allTo = largeListEmail . getToAddresses ( ) ;
const allCc = largeListEmail . getCcAddresses ( ) ;
console . log ( '\nExtracted addresses:' ) ;
console . log ( ` To (first 3): ${ allTo . slice ( 0 , 3 ) . join ( ', ' ) } ... ` ) ;
console . log ( ` CC (first 3): ${ allCc . slice ( 0 , 3 ) . join ( ', ' ) } ... ` ) ;
// Send the email
2025-05-24 16:19:19 +00:00
const startTime = Date . now ( ) ;
2025-05-25 19:02:18 +00:00
await smtpClient . sendMail ( largeListEmail ) ;
const elapsed = Date . now ( ) - startTime ;
console . log ( ` \ nEmail sent to all ${ recipientCount } recipients in ${ elapsed } ms ` ) ;
console . log ( ` Average: ${ ( elapsed / recipientCount ) . toFixed ( 2 ) } ms per recipient ` ) ;
} ) ;
2025-05-24 16:19:19 +00:00
2025-05-25 19:02:18 +00:00
tap . test ( 'CCMD-10: Email validation performance' , async ( ) = > {
// Test validation performance
console . log ( 'Testing email validation performance:\n' ) ;
const validator = new EmailValidator ( ) ;
const testCount = 1000 ;
// Generate test addresses
const testAddresses = [ ] ;
for ( let i = 0 ; i < testCount ; i ++ ) {
testAddresses . push ( ` user ${ i } @example ${ i % 10 } .com ` ) ;
}
// Time validation
const startTime = Date . now ( ) ;
let validCount = 0 ;
for ( const address of testAddresses ) {
if ( validator . isValidFormat ( address ) ) {
validCount ++ ;
2025-05-24 16:19:19 +00:00
}
}
2025-05-25 19:02:18 +00:00
2025-05-24 16:19:19 +00:00
const elapsed = Date . now ( ) - startTime ;
2025-05-25 19:02:18 +00:00
const rate = ( testCount / elapsed ) * 1000 ;
console . log ( ` Validated ${ testCount } addresses in ${ elapsed } ms ` ) ;
console . log ( ` Rate: ${ rate . toFixed ( 0 ) } validations/second ` ) ;
console . log ( ` Valid addresses: ${ validCount } / ${ testCount } ` ) ;
// Test rapid email sending to see if there's rate limiting
console . log ( '\nTesting rapid email sending:' ) ;
const emailCount = 10 ;
const sendStartTime = Date . now ( ) ;
let sentCount = 0 ;
2025-05-24 16:19:19 +00:00
2025-05-25 19:02:18 +00:00
for ( let i = 0 ; i < emailCount ; i ++ ) {
try {
const email = new Email ( {
from : 'sender@example.com' ,
to : [ ` recipient ${ i } @example.com ` ] ,
subject : ` Rate test ${ i + 1 } ` ,
text : 'Testing rate limits'
} ) ;
await smtpClient . sendMail ( email ) ;
sentCount ++ ;
} catch ( error ) {
console . log ( ` Rate limit hit at email ${ i + 1 } : ${ error . message } ` ) ;
break ;
}
2025-05-24 16:19:19 +00:00
}
2025-05-25 19:02:18 +00:00
const sendElapsed = Date . now ( ) - sendStartTime ;
const sendRate = ( sentCount / sendElapsed ) * 1000 ;
console . log ( ` Sent ${ sentCount } / ${ emailCount } emails in ${ sendElapsed } ms ` ) ;
console . log ( ` Rate: ${ sendRate . toFixed ( 2 ) } emails/second ` ) ;
2025-05-24 16:19:19 +00:00
} ) ;
2025-05-25 19:02:18 +00:00
tap . test ( 'CCMD-10: Email validation error handling' , async ( ) = > {
// Test error handling for invalid email addresses
console . log ( 'Testing email validation error handling:\n' ) ;
const validator = new EmailValidator ( ) ;
2025-05-24 16:19:19 +00:00
const errorTests = [
2025-05-25 19:02:18 +00:00
{ address : null , description : 'Null address' } ,
{ address : undefined , description : 'Undefined address' } ,
{ address : '' , description : 'Empty string' } ,
{ address : ' ' , description : 'Whitespace only' } ,
{ address : '@' , description : 'Just @ symbol' } ,
{ address : 'user@' , description : 'Missing domain' } ,
{ address : '@domain.com' , description : 'Missing local part' } ,
{ address : 'user@@domain.com' , description : 'Double @ symbol' } ,
{ address : 'user@domain@com' , description : 'Multiple @ symbols' } ,
{ address : 'user space@domain.com' , description : 'Space in local part' } ,
{ address : 'user@domain .com' , description : 'Space in domain' } ,
{ address : 'x' . repeat ( 256 ) + '@domain.com' , description : 'Very long local part' } ,
{ address : 'user@' + 'x' . repeat ( 256 ) + '.com' , description : 'Very long domain' }
2025-05-24 16:19:19 +00:00
] ;
2025-05-25 19:02:18 +00:00
2025-05-24 16:19:19 +00:00
for ( const test of errorTests ) {
2025-05-25 19:02:18 +00:00
console . log ( ` ${ test . description } : ` ) ;
console . log ( ` Input: " ${ test . address } " ` ) ;
// Test validation
let isValid = false ;
2025-05-24 16:19:19 +00:00
try {
2025-05-25 19:02:18 +00:00
isValid = validator . isValidFormat ( test . address as any ) ;
2025-05-24 16:19:19 +00:00
} catch ( error ) {
2025-05-25 19:02:18 +00:00
console . log ( ` Validation threw: ${ error . message } ` ) ;
2025-05-24 16:19:19 +00:00
}
2025-05-25 19:02:18 +00:00
if ( ! isValid ) {
console . log ( ` Correctly rejected as invalid ` ) ;
} else {
console . log ( ` WARNING: Accepted as valid! ` ) ;
}
// Try to send email with invalid address
if ( test . address ) {
try {
const email = new Email ( {
from : 'sender@example.com' ,
to : [ test . address ] ,
subject : 'Error test' ,
text : 'Testing invalid address'
} ) ;
await smtpClient . sendMail ( email ) ;
console . log ( ` WARNING: Email sent with invalid address! ` ) ;
} catch ( error ) {
console . log ( ` Email correctly rejected: ${ error . message } ` ) ;
}
}
console . log ( '' ) ;
2025-05-24 16:19:19 +00:00
}
} ) ;
tap . test ( 'cleanup test SMTP server' , async ( ) = > {
if ( testServer ) {
2025-05-25 19:02:18 +00:00
await stopTestServer ( testServer ) ;
2025-05-24 16:19:19 +00:00
}
} ) ;
export default tap . start ( ) ;