feat(storage): add comprehensive tests for StorageManager with memory, filesystem, and custom function backends
feat(email): implement EmailSendJob class for robust email delivery with retry logic and MX record resolution feat(mail): restructure mail module exports for simplified access to core and delivery functionalities
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.ts';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.ts';
|
||||
import * as net from 'net';
|
||||
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup - start SMTP server for quota tests', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2563,
|
||||
tlsEnabled: false,
|
||||
authRequired: false
|
||||
});
|
||||
|
||||
expect(testServer.port).toEqual(2563);
|
||||
});
|
||||
|
||||
tap.test('CERR-05: Mailbox quota exceeded - 452 temporary', async () => {
|
||||
// Create server that simulates temporary quota full
|
||||
const quotaServer = net.createServer((socket) => {
|
||||
socket.write('220 Quota Test Server\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO')) {
|
||||
socket.write('452 4.2.2 Mailbox full, try again later\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
quotaServer.listen(2564, () => resolve());
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: 2564,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'user@example.com',
|
||||
subject: 'Quota Test',
|
||||
text: 'Testing quota errors'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeFalse();
|
||||
console.log('Actual error:', result.error?.message);
|
||||
expect(result.error?.message).toMatch(/452|mailbox|full|recipient/i);
|
||||
console.log('✅ 452 temporary quota error handled');
|
||||
|
||||
await smtpClient.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
quotaServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CERR-05: Mailbox quota exceeded - 552 permanent', async () => {
|
||||
// Create server that simulates permanent quota exceeded
|
||||
const quotaServer = net.createServer((socket) => {
|
||||
socket.write('220 Quota Test Server\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO')) {
|
||||
socket.write('552 5.2.2 Mailbox quota exceeded\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
quotaServer.listen(2565, () => resolve());
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: 2565,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'user@example.com',
|
||||
subject: 'Quota Test',
|
||||
text: 'Testing quota errors'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeFalse();
|
||||
console.log('Actual error:', result.error?.message);
|
||||
expect(result.error?.message).toMatch(/552|quota|recipient/i);
|
||||
console.log('✅ 552 permanent quota error handled');
|
||||
|
||||
await smtpClient.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
quotaServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CERR-05: System storage error - 452', async () => {
|
||||
// Create server that simulates system storage issue
|
||||
const storageServer = net.createServer((socket) => {
|
||||
socket.write('220 Storage Test Server\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO')) {
|
||||
socket.write('452 4.3.1 Insufficient system storage\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
storageServer.listen(2566, () => resolve());
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: 2566,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'user@example.com',
|
||||
subject: 'Storage Test',
|
||||
text: 'Testing storage errors'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeFalse();
|
||||
console.log('Actual error:', result.error?.message);
|
||||
expect(result.error?.message).toMatch(/452|storage|recipient/i);
|
||||
console.log('✅ 452 system storage error handled');
|
||||
|
||||
await smtpClient.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
storageServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CERR-05: Message too large - 552', async () => {
|
||||
// Create server that simulates message size limit
|
||||
const sizeServer = net.createServer((socket) => {
|
||||
socket.write('220 Size Test Server\r\n');
|
||||
let inData = false;
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const lines = data.toString().split('\r\n');
|
||||
|
||||
lines.forEach(line => {
|
||||
if (!line && lines[lines.length - 1] === '') return;
|
||||
|
||||
if (inData) {
|
||||
// We're in DATA mode - look for the terminating dot
|
||||
if (line === '.') {
|
||||
socket.write('552 5.3.4 Message too big for system\r\n');
|
||||
inData = false;
|
||||
}
|
||||
// Otherwise, just consume the data
|
||||
} else {
|
||||
// We're in command mode
|
||||
if (line.startsWith('EHLO')) {
|
||||
socket.write('250-SIZE 1000\r\n250 OK\r\n');
|
||||
} else if (line.startsWith('MAIL FROM')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (line.startsWith('RCPT TO')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (line === 'DATA') {
|
||||
socket.write('354 Send data\r\n');
|
||||
inData = true;
|
||||
} else if (line === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
sizeServer.listen(2567, () => resolve());
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: 2567,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'user@example.com',
|
||||
subject: 'Large Message Test',
|
||||
text: 'This is supposed to be a large message that exceeds the size limit'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeFalse();
|
||||
console.log('Actual error:', result.error?.message);
|
||||
expect(result.error?.message).toMatch(/552|big|size|data/i);
|
||||
console.log('✅ 552 message size error handled');
|
||||
|
||||
await smtpClient.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
sizeServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CERR-05: Successful email with normal server', async () => {
|
||||
// Test successful email send with working server
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'user@example.com',
|
||||
subject: 'Normal Test',
|
||||
text: 'Testing normal operation'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Normal email sent successfully');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('cleanup - stop SMTP server', async () => {
|
||||
await stopTestServer(testServer);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user