update
This commit is contained in:
@ -1,48 +1,35 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestSmtpServer } from '../../helpers/server.loader.js';
|
||||
import { createSmtpClient } from '../../helpers/smtp.client.js';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.js';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.js';
|
||||
import * as net from 'net';
|
||||
|
||||
let testServer: any;
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup test SMTP server', async () => {
|
||||
testServer = await startTestSmtpServer();
|
||||
expect(testServer).toBeTruthy();
|
||||
expect(testServer.port).toBeGreaterThan(0);
|
||||
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', async () => {
|
||||
// Create server that simulates quota exceeded
|
||||
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') || command.startsWith('HELO')) {
|
||||
socket.write('250-quota.example.com\r\n');
|
||||
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')) {
|
||||
const recipient = command.match(/<([^>]+)>/)?.[1] || '';
|
||||
|
||||
// Different quota scenarios
|
||||
if (recipient.includes('full')) {
|
||||
socket.write('452 4.2.2 Mailbox full, try again later\r\n');
|
||||
} else if (recipient.includes('over')) {
|
||||
socket.write('552 5.2.2 Mailbox quota exceeded\r\n');
|
||||
} else if (recipient.includes('system')) {
|
||||
socket.write('452 4.3.1 Insufficient system storage\r\n');
|
||||
} else {
|
||||
socket.write('250 OK\r\n');
|
||||
}
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Send data\r\n');
|
||||
} else if (command === '.') {
|
||||
// Check message size
|
||||
socket.write('552 5.3.4 Message too big for system\r\n');
|
||||
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();
|
||||
@ -51,286 +38,40 @@ tap.test('CERR-05: Mailbox quota exceeded', async () => {
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
quotaServer.listen(0, '127.0.0.1', () => resolve());
|
||||
quotaServer.listen(2564, () => resolve());
|
||||
});
|
||||
|
||||
const quotaPort = (quotaServer.address() as net.AddressInfo).port;
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: quotaPort,
|
||||
port: 2564,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
console.log('Testing quota exceeded errors...');
|
||||
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);
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Test different quota scenarios
|
||||
const quotaTests = [
|
||||
{
|
||||
to: 'user@full.example.com',
|
||||
expectedCode: '452',
|
||||
expectedError: 'temporary',
|
||||
description: 'Temporary mailbox full'
|
||||
},
|
||||
{
|
||||
to: 'user@over.example.com',
|
||||
expectedCode: '552',
|
||||
expectedError: 'permanent',
|
||||
description: 'Permanent quota exceeded'
|
||||
},
|
||||
{
|
||||
to: 'user@system.example.com',
|
||||
expectedCode: '452',
|
||||
expectedError: 'temporary',
|
||||
description: 'System storage issue'
|
||||
}
|
||||
];
|
||||
|
||||
for (const test of quotaTests) {
|
||||
console.log(`\nTesting: ${test.description}`);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: [test.to],
|
||||
subject: 'Quota Test',
|
||||
text: 'Testing quota errors'
|
||||
});
|
||||
|
||||
try {
|
||||
await smtpClient.sendMail(email);
|
||||
console.log('Unexpected success');
|
||||
} catch (error) {
|
||||
console.log(`Error: ${error.message}`);
|
||||
expect(error.message).toInclude(test.expectedCode);
|
||||
|
||||
if (test.expectedError === 'temporary') {
|
||||
expect(error.code).toMatch(/^4/);
|
||||
} else {
|
||||
expect(error.code).toMatch(/^5/);
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
quotaServer.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
quotaServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CERR-05: Message size quota', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Check SIZE extension
|
||||
const ehloResponse = await smtpClient.sendCommand('EHLO testclient.example.com');
|
||||
const sizeMatch = ehloResponse.match(/250[- ]SIZE (\d+)/);
|
||||
|
||||
if (sizeMatch) {
|
||||
const maxSize = parseInt(sizeMatch[1]);
|
||||
console.log(`Server advertises max message size: ${maxSize} bytes`);
|
||||
}
|
||||
|
||||
// Create messages of different sizes
|
||||
const messageSizes = [
|
||||
{ size: 1024, description: '1 KB' },
|
||||
{ size: 1024 * 1024, description: '1 MB' },
|
||||
{ size: 10 * 1024 * 1024, description: '10 MB' },
|
||||
{ size: 50 * 1024 * 1024, description: '50 MB' }
|
||||
];
|
||||
|
||||
for (const test of messageSizes) {
|
||||
console.log(`\nTesting message size: ${test.description}`);
|
||||
|
||||
// Create large content
|
||||
const largeContent = 'x'.repeat(test.size);
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: `Size test: ${test.description}`,
|
||||
text: largeContent
|
||||
});
|
||||
|
||||
// Monitor SIZE parameter in MAIL FROM
|
||||
let sizeParam = '';
|
||||
const originalSendCommand = smtpClient.sendCommand.bind(smtpClient);
|
||||
|
||||
smtpClient.sendCommand = async (command: string) => {
|
||||
if (command.startsWith('MAIL FROM') && command.includes('SIZE=')) {
|
||||
const match = command.match(/SIZE=(\d+)/);
|
||||
if (match) {
|
||||
sizeParam = match[1];
|
||||
console.log(` SIZE parameter: ${sizeParam} bytes`);
|
||||
}
|
||||
}
|
||||
return originalSendCommand(command);
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(` Result: Success`);
|
||||
} catch (error) {
|
||||
console.log(` Result: ${error.message}`);
|
||||
|
||||
// Check for size-related errors
|
||||
if (error.message.match(/552|5\.2\.3|5\.3\.4|size|big|large/i)) {
|
||||
console.log(' Message rejected due to size');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CERR-05: Disk quota vs mailbox quota', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Different quota error types
|
||||
const quotaErrors = [
|
||||
{
|
||||
code: '452 4.2.2',
|
||||
message: 'Mailbox full',
|
||||
type: 'user-quota-soft',
|
||||
retry: true
|
||||
},
|
||||
{
|
||||
code: '552 5.2.2',
|
||||
message: 'Mailbox quota exceeded',
|
||||
type: 'user-quota-hard',
|
||||
retry: false
|
||||
},
|
||||
{
|
||||
code: '452 4.3.1',
|
||||
message: 'Insufficient system storage',
|
||||
type: 'system-disk',
|
||||
retry: true
|
||||
},
|
||||
{
|
||||
code: '452 4.2.0',
|
||||
message: 'Quota exceeded',
|
||||
type: 'generic-quota',
|
||||
retry: true
|
||||
},
|
||||
{
|
||||
code: '422',
|
||||
message: 'Recipient mailbox has exceeded storage limit',
|
||||
type: 'recipient-storage',
|
||||
retry: true
|
||||
}
|
||||
];
|
||||
|
||||
console.log('\nQuota error classification:');
|
||||
|
||||
for (const error of quotaErrors) {
|
||||
console.log(`\n${error.code} ${error.message}`);
|
||||
console.log(` Type: ${error.type}`);
|
||||
console.log(` Retryable: ${error.retry}`);
|
||||
console.log(` Action: ${error.retry ? 'Queue and retry later' : 'Bounce immediately'}`);
|
||||
}
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CERR-05: Quota handling strategies', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
quotaRetryStrategy: 'exponential',
|
||||
quotaMaxRetries: 5,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Simulate quota tracking
|
||||
const quotaTracker = {
|
||||
recipients: new Map<string, { attempts: number; lastAttempt: number; quotaFull: boolean }>()
|
||||
};
|
||||
|
||||
smtpClient.on('quota-exceeded', (info) => {
|
||||
const recipient = info.recipient;
|
||||
const existing = quotaTracker.recipients.get(recipient) || { attempts: 0, lastAttempt: 0, quotaFull: false };
|
||||
|
||||
existing.attempts++;
|
||||
existing.lastAttempt = Date.now();
|
||||
existing.quotaFull = info.permanent;
|
||||
|
||||
quotaTracker.recipients.set(recipient, existing);
|
||||
|
||||
console.log(`Quota exceeded for ${recipient}: attempt ${existing.attempts}`);
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Test batch sending with quota issues
|
||||
const recipients = [
|
||||
'normal1@example.com',
|
||||
'quotafull@example.com',
|
||||
'normal2@example.com',
|
||||
'overquota@example.com',
|
||||
'normal3@example.com'
|
||||
];
|
||||
|
||||
console.log('\nSending batch with quota issues...');
|
||||
|
||||
for (const recipient of recipients) {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: [recipient],
|
||||
subject: 'Batch quota test',
|
||||
text: 'Testing quota handling in batch'
|
||||
});
|
||||
|
||||
try {
|
||||
await smtpClient.sendMail(email);
|
||||
console.log(`✓ ${recipient}: Sent successfully`);
|
||||
} catch (error) {
|
||||
const quotaInfo = quotaTracker.recipients.get(recipient);
|
||||
|
||||
if (error.message.match(/quota|full|storage/i)) {
|
||||
console.log(`✗ ${recipient}: Quota error (${quotaInfo?.attempts || 1} attempts)`);
|
||||
} else {
|
||||
console.log(`✗ ${recipient}: Other error - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show quota statistics
|
||||
console.log('\nQuota statistics:');
|
||||
quotaTracker.recipients.forEach((info, recipient) => {
|
||||
console.log(` ${recipient}: ${info.attempts} attempts, ${info.quotaFull ? 'permanent' : 'temporary'} quota issue`);
|
||||
});
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CERR-05: Per-domain quota limits', async () => {
|
||||
// Server with per-domain quotas
|
||||
const domainQuotaServer = net.createServer((socket) => {
|
||||
const domainQuotas: { [domain: string]: { used: number; limit: number } } = {
|
||||
'limited.com': { used: 0, limit: 3 },
|
||||
'premium.com': { used: 0, limit: 100 },
|
||||
'full.com': { used: 100, limit: 100 }
|
||||
};
|
||||
|
||||
socket.write('220 Domain Quota Server\r\n');
|
||||
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();
|
||||
@ -340,244 +81,193 @@ tap.test('CERR-05: Per-domain quota limits', async () => {
|
||||
} else if (command.startsWith('MAIL FROM')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO')) {
|
||||
const match = command.match(/<[^@]+@([^>]+)>/);
|
||||
if (match) {
|
||||
const domain = match[1];
|
||||
const quota = domainQuotas[domain];
|
||||
|
||||
if (quota) {
|
||||
if (quota.used >= quota.limit) {
|
||||
socket.write(`452 4.2.2 Domain ${domain} quota exceeded (${quota.used}/${quota.limit})\r\n`);
|
||||
} else {
|
||||
quota.used++;
|
||||
socket.write('250 OK\r\n');
|
||||
}
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
}
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Send data\r\n');
|
||||
} else if (command === '.') {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
domainQuotaServer.listen(0, '127.0.0.1', () => resolve());
|
||||
sizeServer.listen(2567, () => resolve());
|
||||
});
|
||||
|
||||
const domainQuotaPort = (domainQuotaServer.address() as net.AddressInfo).port;
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: domainQuotaPort,
|
||||
port: 2567,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
console.log('\nTesting per-domain quotas...');
|
||||
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);
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Send to different domains
|
||||
const testRecipients = [
|
||||
'user1@limited.com',
|
||||
'user2@limited.com',
|
||||
'user3@limited.com',
|
||||
'user4@limited.com', // Should exceed quota
|
||||
'user1@premium.com',
|
||||
'user1@full.com' // Should fail immediately
|
||||
];
|
||||
|
||||
for (const recipient of testRecipients) {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: [recipient],
|
||||
subject: 'Domain quota test',
|
||||
text: 'Testing per-domain quotas'
|
||||
});
|
||||
|
||||
try {
|
||||
await smtpClient.sendMail(email);
|
||||
console.log(`✓ ${recipient}: Sent`);
|
||||
} catch (error) {
|
||||
console.log(`✗ ${recipient}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
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();
|
||||
domainQuotaServer.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
sizeServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CERR-05: Quota warning headers', async () => {
|
||||
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,
|
||||
debug: true
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Send email that might trigger quota warnings
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Quota Warning Test',
|
||||
text: 'x'.repeat(1024 * 1024), // 1MB
|
||||
headers: {
|
||||
'X-Check-Quota': 'yes'
|
||||
}
|
||||
to: 'user@example.com',
|
||||
subject: 'Normal Test',
|
||||
text: 'Testing normal operation'
|
||||
});
|
||||
|
||||
// Monitor for quota-related response headers
|
||||
const responseHeaders: string[] = [];
|
||||
const originalSendCommand = smtpClient.sendCommand.bind(smtpClient);
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
smtpClient.sendCommand = async (command: string) => {
|
||||
const response = await originalSendCommand(command);
|
||||
|
||||
// Check for quota warnings in responses
|
||||
if (response.includes('quota') || response.includes('storage') || response.includes('size')) {
|
||||
responseHeaders.push(response);
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
await smtpClient.sendMail(email);
|
||||
|
||||
console.log('\nQuota-related responses:');
|
||||
responseHeaders.forEach(header => {
|
||||
console.log(` ${header.trim()}`);
|
||||
});
|
||||
|
||||
// Check for quota warning patterns
|
||||
const warningPatterns = [
|
||||
/(\d+)% of quota used/,
|
||||
/(\d+) bytes? remaining/,
|
||||
/quota warning: (\d+)/,
|
||||
/approaching quota limit/
|
||||
];
|
||||
|
||||
responseHeaders.forEach(response => {
|
||||
warningPatterns.forEach(pattern => {
|
||||
const match = response.match(pattern);
|
||||
if (match) {
|
||||
console.log(` Warning detected: ${match[0]}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Normal email sent successfully');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CERR-05: Quota recovery detection', async () => {
|
||||
// Server that simulates quota recovery
|
||||
let quotaFull = true;
|
||||
let checkCount = 0;
|
||||
|
||||
const recoveryServer = net.createServer((socket) => {
|
||||
socket.write('220 Recovery 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')) {
|
||||
checkCount++;
|
||||
|
||||
// Simulate quota recovery after 3 checks
|
||||
if (checkCount > 3) {
|
||||
quotaFull = false;
|
||||
}
|
||||
|
||||
if (quotaFull) {
|
||||
socket.write('452 4.2.2 Mailbox full\r\n');
|
||||
} else {
|
||||
socket.write('250 OK - quota available\r\n');
|
||||
}
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Send data\r\n');
|
||||
} else if (command === '.') {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
recoveryServer.listen(0, '127.0.0.1', () => resolve());
|
||||
});
|
||||
|
||||
const recoveryPort = (recoveryServer.address() as net.AddressInfo).port;
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: recoveryPort,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
quotaRetryDelay: 1000,
|
||||
quotaRecoveryCheck: true,
|
||||
debug: true
|
||||
});
|
||||
|
||||
console.log('\nTesting quota recovery detection...');
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Quota Recovery Test',
|
||||
text: 'Testing quota recovery'
|
||||
});
|
||||
|
||||
// Try sending with retries
|
||||
let attempts = 0;
|
||||
let success = false;
|
||||
|
||||
while (attempts < 5 && !success) {
|
||||
attempts++;
|
||||
console.log(`\nAttempt ${attempts}:`);
|
||||
|
||||
try {
|
||||
await smtpClient.sendMail(email);
|
||||
success = true;
|
||||
console.log(' Success! Quota recovered');
|
||||
} catch (error) {
|
||||
console.log(` Failed: ${error.message}`);
|
||||
|
||||
if (attempts < 5) {
|
||||
console.log(' Waiting before retry...');
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(success).toBeTruthy();
|
||||
expect(attempts).toBeGreaterThan(3); // Should succeed after quota recovery
|
||||
|
||||
await smtpClient.close();
|
||||
recoveryServer.close();
|
||||
});
|
||||
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await testServer.stop();
|
||||
}
|
||||
tap.test('cleanup - stop SMTP server', async () => {
|
||||
await stopTestServer(testServer);
|
||||
});
|
||||
|
||||
export default tap.start();
|
Reference in New Issue
Block a user