305 lines
8.9 KiB
TypeScript
305 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;
|
|||
|
let smtpClient: SmtpClient;
|
|||
|
|
|||
|
tap.test('setup - start SMTP server for 5xx error tests', async () => {
|
|||
|
testServer = await startTestServer({
|
|||
|
port: 2552,
|
|||
|
tlsEnabled: false,
|
|||
|
authRequired: false,
|
|||
|
maxRecipients: 3 // Low limit to help trigger errors
|
|||
|
});
|
|||
|
|
|||
|
expect(testServer.port).toEqual(2552);
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CERR-02: 5xx Errors - should handle command not recognized (500)', async () => {
|
|||
|
smtpClient = createSmtpClient({
|
|||
|
host: testServer.hostname,
|
|||
|
port: testServer.port,
|
|||
|
secure: false,
|
|||
|
connectionTimeout: 5000,
|
|||
|
debug: true
|
|||
|
});
|
|||
|
|
|||
|
// The client should handle standard commands properly
|
|||
|
// This tests that the client doesn't send invalid commands
|
|||
|
const isConnected = await smtpClient.verify();
|
|||
|
expect(isConnected).toBeTrue();
|
|||
|
|
|||
|
console.log('✅ Client sends only valid SMTP commands');
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CERR-02: 5xx Errors - should handle syntax error (501)', async () => {
|
|||
|
// Test with malformed email that might cause syntax error
|
|||
|
let syntaxError = false;
|
|||
|
|
|||
|
try {
|
|||
|
// The Email class should catch this before sending
|
|||
|
const email = new Email({
|
|||
|
from: '<invalid>from>@example.com', // Malformed
|
|||
|
to: 'recipient@example.com',
|
|||
|
subject: 'Syntax Error Test',
|
|||
|
text: 'This should fail'
|
|||
|
});
|
|||
|
|
|||
|
await smtpClient.sendMail(email);
|
|||
|
} catch (error: any) {
|
|||
|
syntaxError = true;
|
|||
|
expect(error).toBeInstanceOf(Error);
|
|||
|
console.log('✅ Syntax error caught:', error.message);
|
|||
|
}
|
|||
|
|
|||
|
expect(syntaxError).toBeTrue();
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CERR-02: 5xx Errors - should handle command not implemented (502)', async () => {
|
|||
|
// Most servers implement all required commands
|
|||
|
// This test verifies client doesn't use optional/deprecated commands
|
|||
|
|
|||
|
const email = new Email({
|
|||
|
from: 'sender@example.com',
|
|||
|
to: 'recipient@example.com',
|
|||
|
subject: 'Standard Commands Test',
|
|||
|
text: 'Using only standard SMTP commands'
|
|||
|
});
|
|||
|
|
|||
|
const result = await smtpClient.sendMail(email);
|
|||
|
expect(result.success).toBeTrue();
|
|||
|
|
|||
|
console.log('✅ Client uses only widely-implemented commands');
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CERR-02: 5xx Errors - should handle bad sequence (503)', async () => {
|
|||
|
// The client should maintain proper command sequence
|
|||
|
// This tests internal state management
|
|||
|
|
|||
|
// Send multiple emails to ensure sequence is maintained
|
|||
|
for (let i = 0; i < 3; i++) {
|
|||
|
const email = new Email({
|
|||
|
from: 'sender@example.com',
|
|||
|
to: 'recipient@example.com',
|
|||
|
subject: `Sequence Test ${i}`,
|
|||
|
text: 'Testing command sequence'
|
|||
|
});
|
|||
|
|
|||
|
const result = await smtpClient.sendMail(email);
|
|||
|
expect(result.success).toBeTrue();
|
|||
|
}
|
|||
|
|
|||
|
console.log('✅ Client maintains proper command sequence');
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CERR-02: 5xx Errors - should handle authentication failed (535)', async () => {
|
|||
|
// Create server requiring authentication
|
|||
|
const authServer = await startTestServer({
|
|||
|
port: 2553,
|
|||
|
authRequired: true
|
|||
|
});
|
|||
|
|
|||
|
let authFailed = false;
|
|||
|
|
|||
|
try {
|
|||
|
const badAuthClient = createSmtpClient({
|
|||
|
host: authServer.hostname,
|
|||
|
port: authServer.port,
|
|||
|
secure: false,
|
|||
|
auth: {
|
|||
|
user: 'wronguser',
|
|||
|
pass: 'wrongpass'
|
|||
|
},
|
|||
|
connectionTimeout: 5000
|
|||
|
});
|
|||
|
|
|||
|
await badAuthClient.verify();
|
|||
|
} catch (error: any) {
|
|||
|
authFailed = true;
|
|||
|
expect(error).toBeInstanceOf(Error);
|
|||
|
console.log('✅ Authentication failure (535) handled:', error.message);
|
|||
|
}
|
|||
|
|
|||
|
expect(authFailed).toBeTrue();
|
|||
|
|
|||
|
await stopTestServer(authServer);
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CERR-02: 5xx Errors - should handle transaction failed (554)', async () => {
|
|||
|
// Try to send email that might be rejected
|
|||
|
const email = new Email({
|
|||
|
from: 'sender@example.com',
|
|||
|
to: 'postmaster@[127.0.0.1]', // IP literal might be rejected
|
|||
|
subject: 'Transaction Test',
|
|||
|
text: 'Testing transaction failure'
|
|||
|
});
|
|||
|
|
|||
|
const result = await smtpClient.sendMail(email);
|
|||
|
|
|||
|
// Depending on server configuration
|
|||
|
if (!result.success) {
|
|||
|
console.log('✅ Transaction failure handled gracefully');
|
|||
|
expect(result.error).toBeInstanceOf(Error);
|
|||
|
} else {
|
|||
|
console.log('ℹ️ Test server accepted IP literal recipient');
|
|||
|
expect(result.acceptedRecipients.length).toBeGreaterThan(0);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CERR-02: 5xx Errors - should not retry permanent 5xx errors', async () => {
|
|||
|
let attemptCount = 0;
|
|||
|
|
|||
|
// Create a client that tracks connection attempts
|
|||
|
const trackingClient = createSmtpClient({
|
|||
|
host: testServer.hostname,
|
|||
|
port: testServer.port,
|
|||
|
secure: false,
|
|||
|
connectionTimeout: 5000
|
|||
|
});
|
|||
|
|
|||
|
trackingClient.on('connect', () => attemptCount++);
|
|||
|
|
|||
|
// Try to send with permanently invalid data
|
|||
|
try {
|
|||
|
const email = new Email({
|
|||
|
from: '', // Empty from
|
|||
|
to: 'recipient@example.com',
|
|||
|
subject: 'Permanent Error Test',
|
|||
|
text: 'Should not retry'
|
|||
|
});
|
|||
|
|
|||
|
await trackingClient.sendMail(email);
|
|||
|
} catch (error) {
|
|||
|
console.log('✅ Permanent error not retried');
|
|||
|
}
|
|||
|
|
|||
|
// Should not retry permanent errors
|
|||
|
expect(attemptCount).toBeLessThanOrEqual(1);
|
|||
|
|
|||
|
await trackingClient.close();
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CERR-02: 5xx Errors - should handle server unavailable (550)', async () => {
|
|||
|
// Test with recipient that might be rejected
|
|||
|
const email = new Email({
|
|||
|
from: 'sender@example.com',
|
|||
|
to: 'no-such-user@localhost',
|
|||
|
subject: 'User Unknown Test',
|
|||
|
text: 'Testing unknown user rejection'
|
|||
|
});
|
|||
|
|
|||
|
const result = await smtpClient.sendMail(email);
|
|||
|
|
|||
|
if (result.rejectedRecipients.length > 0) {
|
|||
|
console.log('✅ Unknown user (550) rejection handled');
|
|||
|
expect(result.rejectedRecipients).toContain('no-such-user@localhost');
|
|||
|
} else {
|
|||
|
// Test server might accept all
|
|||
|
console.log('ℹ️ Test server accepted unknown user');
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CERR-02: 5xx Errors - should close connection after fatal error', async () => {
|
|||
|
// Test that client properly closes connection after fatal errors
|
|||
|
const fatalClient = createSmtpClient({
|
|||
|
host: testServer.hostname,
|
|||
|
port: testServer.port,
|
|||
|
secure: false,
|
|||
|
connectionTimeout: 5000
|
|||
|
});
|
|||
|
|
|||
|
// Verify connection works
|
|||
|
await fatalClient.verify();
|
|||
|
expect(fatalClient.isConnected()).toBeTrue();
|
|||
|
|
|||
|
// Simulate a scenario that might cause fatal error
|
|||
|
// In real scenarios, this might be server shutdown, etc.
|
|||
|
|
|||
|
// For this test, we'll close and verify state
|
|||
|
await fatalClient.close();
|
|||
|
expect(fatalClient.isConnected()).toBeFalse();
|
|||
|
|
|||
|
console.log('✅ Connection properly closed after errors');
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CERR-02: 5xx Errors - should provide detailed error information', async () => {
|
|||
|
// Test error detail extraction
|
|||
|
let errorDetails: any = null;
|
|||
|
|
|||
|
try {
|
|||
|
const email = new Email({
|
|||
|
from: 'a'.repeat(100) + '@example.com', // Very long local part
|
|||
|
to: 'recipient@example.com',
|
|||
|
subject: 'Error Details Test',
|
|||
|
text: 'Testing error details'
|
|||
|
});
|
|||
|
|
|||
|
await smtpClient.sendMail(email);
|
|||
|
} catch (error: any) {
|
|||
|
errorDetails = error;
|
|||
|
}
|
|||
|
|
|||
|
if (errorDetails) {
|
|||
|
expect(errorDetails).toBeInstanceOf(Error);
|
|||
|
expect(errorDetails.message).toBeTypeofString();
|
|||
|
console.log('✅ Detailed error information provided:', errorDetails.message);
|
|||
|
} else {
|
|||
|
console.log('ℹ️ Long email address accepted by validator');
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CERR-02: 5xx Errors - should handle multiple 5xx errors gracefully', async () => {
|
|||
|
// Send several emails that might trigger different 5xx errors
|
|||
|
const testEmails = [
|
|||
|
{
|
|||
|
from: 'sender@example.com',
|
|||
|
to: 'recipient@invalid-tld', // Invalid TLD
|
|||
|
subject: 'Invalid TLD Test',
|
|||
|
text: 'Test 1'
|
|||
|
},
|
|||
|
{
|
|||
|
from: 'sender@example.com',
|
|||
|
to: 'recipient@.com', // Missing domain part
|
|||
|
subject: 'Missing Domain Test',
|
|||
|
text: 'Test 2'
|
|||
|
},
|
|||
|
{
|
|||
|
from: 'sender@example.com',
|
|||
|
to: 'recipient@example.com',
|
|||
|
subject: 'Valid Email After Errors',
|
|||
|
text: 'This should work'
|
|||
|
}
|
|||
|
];
|
|||
|
|
|||
|
let successCount = 0;
|
|||
|
let errorCount = 0;
|
|||
|
|
|||
|
for (const emailData of testEmails) {
|
|||
|
try {
|
|||
|
const email = new Email(emailData);
|
|||
|
const result = await smtpClient.sendMail(email);
|
|||
|
if (result.success) successCount++;
|
|||
|
} catch (error) {
|
|||
|
errorCount++;
|
|||
|
console.log(` Error for ${emailData.to}: ${error}`);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
console.log(`✅ Handled multiple errors: ${errorCount} errors, ${successCount} successes`);
|
|||
|
expect(successCount).toBeGreaterThan(0); // At least the valid email should work
|
|||
|
});
|
|||
|
|
|||
|
tap.test('cleanup - close SMTP client', async () => {
|
|||
|
if (smtpClient && smtpClient.isConnected()) {
|
|||
|
await smtpClient.close();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
tap.test('cleanup - stop SMTP server', async () => {
|
|||
|
await stopTestServer(testServer);
|
|||
|
});
|
|||
|
|
|||
|
tap.start();
|