333 lines
8.9 KiB
TypeScript
333 lines
8.9 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
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';
|
|
|
|
let testServer: ITestServer;
|
|
|
|
tap.test('setup test SMTP server', async () => {
|
|
testServer = await startTestServer({
|
|
port: 2548,
|
|
tlsEnabled: false,
|
|
authRequired: false
|
|
});
|
|
expect(testServer).toBeTruthy();
|
|
expect(testServer.port).toBeGreaterThan(0);
|
|
});
|
|
|
|
tap.test('CCMD-08: Client handles transaction reset internally', async () => {
|
|
const smtpClient = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000,
|
|
debug: true
|
|
});
|
|
|
|
// Send first email
|
|
const email1 = new Email({
|
|
from: 'sender1@example.com',
|
|
to: 'recipient1@example.com',
|
|
subject: 'First Email',
|
|
text: 'This is the first email'
|
|
});
|
|
|
|
const result1 = await smtpClient.sendMail(email1);
|
|
expect(result1.success).toBeTrue();
|
|
|
|
// Send second email - client handles RSET internally if needed
|
|
const email2 = new Email({
|
|
from: 'sender2@example.com',
|
|
to: 'recipient2@example.com',
|
|
subject: 'Second Email',
|
|
text: 'This is the second email'
|
|
});
|
|
|
|
const result2 = await smtpClient.sendMail(email2);
|
|
expect(result2.success).toBeTrue();
|
|
|
|
console.log('✅ Client handles transaction reset between emails');
|
|
console.log('RSET is used internally to ensure clean state');
|
|
|
|
await smtpClient.close();
|
|
});
|
|
|
|
tap.test('CCMD-08: Clean state after failed recipient', async () => {
|
|
const smtpClient = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000,
|
|
debug: true
|
|
});
|
|
|
|
// Send email with multiple recipients - if one fails, RSET ensures clean state
|
|
const email = new Email({
|
|
from: 'sender@example.com',
|
|
to: [
|
|
'valid1@example.com',
|
|
'valid2@example.com',
|
|
'valid3@example.com'
|
|
],
|
|
subject: 'Multi-recipient Email',
|
|
text: 'Testing state management'
|
|
});
|
|
|
|
const result = await smtpClient.sendMail(email);
|
|
expect(result.success).toBeTrue();
|
|
|
|
// All recipients should be accepted
|
|
expect(result.acceptedRecipients.length).toEqual(3);
|
|
|
|
console.log('✅ State remains clean with multiple recipients');
|
|
console.log('Internal RSET ensures proper transaction handling');
|
|
|
|
await smtpClient.close();
|
|
});
|
|
|
|
tap.test('CCMD-08: Multiple emails in sequence', async () => {
|
|
const smtpClient = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000,
|
|
debug: true
|
|
});
|
|
|
|
// Send multiple emails in sequence
|
|
const emails = [
|
|
{
|
|
from: 'sender1@example.com',
|
|
to: 'recipient1@example.com',
|
|
subject: 'Email 1',
|
|
text: 'First email'
|
|
},
|
|
{
|
|
from: 'sender2@example.com',
|
|
to: 'recipient2@example.com',
|
|
subject: 'Email 2',
|
|
text: 'Second email'
|
|
},
|
|
{
|
|
from: 'sender3@example.com',
|
|
to: 'recipient3@example.com',
|
|
subject: 'Email 3',
|
|
text: 'Third email'
|
|
}
|
|
];
|
|
|
|
for (const emailData of emails) {
|
|
const email = new Email(emailData);
|
|
const result = await smtpClient.sendMail(email);
|
|
expect(result.success).toBeTrue();
|
|
}
|
|
|
|
console.log('✅ Successfully sent multiple emails in sequence');
|
|
console.log('RSET ensures clean state between each transaction');
|
|
|
|
await smtpClient.close();
|
|
});
|
|
|
|
tap.test('CCMD-08: Connection pooling with clean state', async () => {
|
|
const smtpClient = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
pool: true,
|
|
maxConnections: 2,
|
|
connectionTimeout: 5000,
|
|
debug: true
|
|
});
|
|
|
|
// Send emails concurrently
|
|
const promises = Array.from({ length: 5 }, (_, i) => {
|
|
const email = new Email({
|
|
from: `sender${i}@example.com`,
|
|
to: `recipient${i}@example.com`,
|
|
subject: `Pooled Email ${i}`,
|
|
text: `This is pooled email ${i}`
|
|
});
|
|
return smtpClient.sendMail(email);
|
|
});
|
|
|
|
const results = await Promise.all(promises);
|
|
|
|
// Check results and log any failures
|
|
results.forEach((result, index) => {
|
|
console.log(`Email ${index}: ${result.success ? '✅' : '❌'} ${!result.success ? result.error?.message : ''}`);
|
|
});
|
|
|
|
// With connection pooling, at least some emails should succeed
|
|
const successCount = results.filter(r => r.success).length;
|
|
console.log(`Successfully sent ${successCount} of ${results.length} emails`);
|
|
expect(successCount).toBeGreaterThan(0);
|
|
|
|
console.log('✅ Connection pool maintains clean state');
|
|
console.log('RSET ensures each pooled connection starts fresh');
|
|
|
|
await smtpClient.close();
|
|
});
|
|
|
|
tap.test('CCMD-08: Error recovery with state reset', async () => {
|
|
const smtpClient = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000,
|
|
debug: true
|
|
});
|
|
|
|
// First, try with invalid sender (should fail early)
|
|
try {
|
|
const badEmail = new Email({
|
|
from: '', // Invalid
|
|
to: 'recipient@example.com',
|
|
subject: 'Bad Email',
|
|
text: 'This should fail'
|
|
});
|
|
await smtpClient.sendMail(badEmail);
|
|
} catch (error) {
|
|
// Expected to fail
|
|
console.log('✅ Invalid email rejected as expected');
|
|
}
|
|
|
|
// Now send a valid email - should work fine
|
|
const goodEmail = new Email({
|
|
from: 'sender@example.com',
|
|
to: 'recipient@example.com',
|
|
subject: 'Good Email',
|
|
text: 'This should succeed'
|
|
});
|
|
|
|
const result = await smtpClient.sendMail(goodEmail);
|
|
expect(result.success).toBeTrue();
|
|
|
|
console.log('✅ State recovered after error');
|
|
console.log('RSET ensures clean state after failures');
|
|
|
|
await smtpClient.close();
|
|
});
|
|
|
|
tap.test('CCMD-08: Verify command maintains session', async () => {
|
|
const smtpClient = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000,
|
|
debug: true
|
|
});
|
|
|
|
// verify() creates temporary connection
|
|
const verified1 = await smtpClient.verify();
|
|
expect(verified1).toBeTrue();
|
|
|
|
// Send email after verify
|
|
const email = new Email({
|
|
from: 'sender@example.com',
|
|
to: 'recipient@example.com',
|
|
subject: 'After Verify',
|
|
text: 'Email after verification'
|
|
});
|
|
|
|
const result = await smtpClient.sendMail(email);
|
|
expect(result.success).toBeTrue();
|
|
|
|
// verify() again
|
|
const verified2 = await smtpClient.verify();
|
|
expect(verified2).toBeTrue();
|
|
|
|
console.log('✅ Verify operations maintain clean session state');
|
|
console.log('Each operation ensures proper state management');
|
|
|
|
await smtpClient.close();
|
|
});
|
|
|
|
tap.test('CCMD-08: Rapid sequential sends', async () => {
|
|
const smtpClient = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000,
|
|
debug: true
|
|
});
|
|
|
|
// Send emails rapidly
|
|
const count = 10;
|
|
const startTime = Date.now();
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const email = new Email({
|
|
from: 'sender@example.com',
|
|
to: 'recipient@example.com',
|
|
subject: `Rapid Email ${i}`,
|
|
text: `Rapid test email ${i}`
|
|
});
|
|
|
|
const result = await smtpClient.sendMail(email);
|
|
expect(result.success).toBeTrue();
|
|
}
|
|
|
|
const elapsed = Date.now() - startTime;
|
|
const avgTime = elapsed / count;
|
|
|
|
console.log(`✅ Sent ${count} emails in ${elapsed}ms`);
|
|
console.log(`Average time per email: ${avgTime.toFixed(2)}ms`);
|
|
console.log('RSET maintains efficiency in rapid sends');
|
|
|
|
await smtpClient.close();
|
|
});
|
|
|
|
tap.test('CCMD-08: State isolation between clients', async () => {
|
|
// Create two separate clients
|
|
const client1 = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000
|
|
});
|
|
|
|
const client2 = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000
|
|
});
|
|
|
|
// Send from both clients
|
|
const email1 = new Email({
|
|
from: 'client1@example.com',
|
|
to: 'recipient1@example.com',
|
|
subject: 'From Client 1',
|
|
text: 'Email from client 1'
|
|
});
|
|
|
|
const email2 = new Email({
|
|
from: 'client2@example.com',
|
|
to: 'recipient2@example.com',
|
|
subject: 'From Client 2',
|
|
text: 'Email from client 2'
|
|
});
|
|
|
|
// Send concurrently
|
|
const [result1, result2] = await Promise.all([
|
|
client1.sendMail(email1),
|
|
client2.sendMail(email2)
|
|
]);
|
|
|
|
expect(result1.success).toBeTrue();
|
|
expect(result2.success).toBeTrue();
|
|
|
|
console.log('✅ Each client maintains isolated state');
|
|
console.log('RSET ensures no cross-contamination');
|
|
|
|
await client1.close();
|
|
await client2.close();
|
|
});
|
|
|
|
tap.test('cleanup test SMTP server', async () => {
|
|
await stopTestServer(testServer);
|
|
expect(testServer).toBeTruthy();
|
|
});
|
|
|
|
export default tap.start(); |