dcrouter/test/suite/smtpclient_error-handling/test.cerr-01.4xx-errors.ts

231 lines
6.7 KiB
TypeScript
Raw Normal View History

2025-05-24 16:19:19 +00:00
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 error handling tests', async () => {
testServer = await startTestServer({
port: 2550,
tlsEnabled: false,
authRequired: false,
maxRecipients: 5 // Low limit to trigger errors
});
expect(testServer.port).toEqual(2550);
});
tap.test('CERR-01: 4xx Errors - should handle invalid recipient (450)', async () => {
smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
// Create email with invalid recipient format
const email = new Email({
from: 'test@example.com',
to: 'invalid@address@multiple@signs.com', // Invalid format
subject: 'Testing 4xx Error',
text: 'This should trigger a 4xx error'
});
let errorCaught = false;
try {
await smtpClient.sendMail(email);
} catch (error) {
errorCaught = true;
expect(error).toBeInstanceOf(Error);
console.log('✅ Invalid recipient error caught:', error.message);
}
expect(errorCaught).toBeTrue();
});
tap.test('CERR-01: 4xx Errors - should handle mailbox unavailable (450)', async () => {
const email = new Email({
from: 'test@example.com',
to: 'nonexistent@localhost', // Local domain should trigger mailbox check
subject: 'Mailbox Unavailable Test',
text: 'Testing mailbox unavailable error'
});
const result = await smtpClient.sendMail(email);
// Depending on server configuration, this might be accepted or rejected
if (!result.success) {
expect(result.error).toBeInstanceOf(Error);
console.log('✅ Mailbox unavailable handled:', result.error?.message);
} else {
// Some test servers accept all recipients
expect(result.acceptedRecipients.length).toBeGreaterThan(0);
console.log(' Test server accepted recipient (common in test environments)');
}
});
tap.test('CERR-01: 4xx Errors - should handle quota exceeded (452)', async () => {
// Send multiple emails to trigger quota/limit errors
const emails = [];
for (let i = 0; i < 10; i++) {
emails.push(new Email({
from: 'test@example.com',
to: `recipient${i}@example.com`,
subject: `Quota Test ${i}`,
text: 'Testing quota limits'
}));
}
let quotaErrorCount = 0;
const results = await Promise.allSettled(
emails.map(email => smtpClient.sendMail(email))
);
results.forEach((result, index) => {
if (result.status === 'rejected') {
quotaErrorCount++;
console.log(`Email ${index} rejected:`, result.reason);
}
});
console.log(`✅ Handled ${quotaErrorCount} quota-related errors`);
});
tap.test('CERR-01: 4xx Errors - should handle too many recipients (452)', async () => {
// Create email with many recipients to exceed limit
const recipients = [];
for (let i = 0; i < 10; i++) {
recipients.push(`recipient${i}@example.com`);
}
const email = new Email({
from: 'test@example.com',
to: recipients, // Many recipients
subject: 'Too Many Recipients Test',
text: 'Testing recipient limit'
});
const result = await smtpClient.sendMail(email);
// Check if some recipients were rejected due to limits
if (result.rejectedRecipients.length > 0) {
console.log(`✅ Rejected ${result.rejectedRecipients.length} recipients due to limits`);
expect(result.rejectedRecipients).toBeArray();
} else {
// Server might accept all
expect(result.acceptedRecipients.length).toEqual(recipients.length);
console.log(' Server accepted all recipients');
}
});
tap.test('CERR-01: 4xx Errors - should handle authentication required (450)', async () => {
// Create new server requiring auth
const authServer = await startTestServer({
port: 2551,
authRequired: true // This will reject unauthenticated commands
});
const unauthClient = createSmtpClient({
host: authServer.hostname,
port: authServer.port,
secure: false,
// No auth credentials provided
connectionTimeout: 5000
});
const email = new Email({
from: 'test@example.com',
to: 'recipient@example.com',
subject: 'Auth Required Test',
text: 'Should fail without auth'
});
let authError = false;
try {
await unauthClient.sendMail(email);
} catch (error) {
authError = true;
expect(error).toBeInstanceOf(Error);
console.log('✅ Authentication required error caught');
}
expect(authError).toBeTrue();
await unauthClient.close();
await stopTestServer(authServer);
});
tap.test('CERR-01: 4xx Errors - should parse enhanced status codes', async () => {
// 4xx errors often include enhanced status codes (e.g., 4.7.1)
const email = new Email({
from: 'test@blocked-domain.com', // Might trigger policy rejection
to: 'recipient@example.com',
subject: 'Enhanced Status Code Test',
text: 'Testing enhanced status codes'
});
try {
const result = await smtpClient.sendMail(email);
if (!result.success && result.error) {
console.log('✅ Error details:', {
message: result.error.message,
response: result.response
});
}
} catch (error: any) {
// Check if error includes status information
expect(error.message).toBeTypeofString();
console.log('✅ Error with potential enhanced status:', error.message);
}
});
tap.test('CERR-01: 4xx Errors - should not retry permanent 4xx errors', async () => {
// Track retry attempts
let attemptCount = 0;
const trackingClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
// Monitor connection attempts
trackingClient.on('connect', () => attemptCount++);
const email = new Email({
from: 'invalid sender format', // Clearly invalid
to: 'recipient@example.com',
subject: 'Permanent Error Test',
text: 'Should not retry'
});
try {
await trackingClient.sendMail(email);
} catch (error) {
console.log('✅ Permanent error not retried');
}
// Should not have retried
expect(attemptCount).toBeLessThanOrEqual(1);
await trackingClient.close();
});
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();