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,389 @@
 | 
			
		||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
 | 
			
		||||
import * as net from 'net';
 | 
			
		||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
 | 
			
		||||
 | 
			
		||||
const TEST_PORT = 30034;
 | 
			
		||||
const TEST_TIMEOUT = 30000;
 | 
			
		||||
 | 
			
		||||
tap.test('Very Small Email - should handle minimal email with single character body', async (tools) => {
 | 
			
		||||
  const done = tools.defer();
 | 
			
		||||
  
 | 
			
		||||
  // Start test server
 | 
			
		||||
  const testServer = await startTestServer({ port: TEST_PORT });
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    const socket = net.createConnection({
 | 
			
		||||
      host: 'localhost',
 | 
			
		||||
      port: TEST_PORT,
 | 
			
		||||
      timeout: TEST_TIMEOUT
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    await new Promise<void>((resolve, reject) => {
 | 
			
		||||
      socket.once('connect', () => resolve());
 | 
			
		||||
      socket.once('error', reject);
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Get banner
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Send EHLO
 | 
			
		||||
    socket.write('EHLO testhost\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      let data = '';
 | 
			
		||||
      const handler = (chunk: Buffer) => {
 | 
			
		||||
        data += chunk.toString();
 | 
			
		||||
        if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
 | 
			
		||||
          socket.removeListener('data', handler);
 | 
			
		||||
          resolve(data);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      socket.on('data', handler);
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Send MAIL FROM
 | 
			
		||||
    socket.write('MAIL FROM:<sender@example.com>\r\n');
 | 
			
		||||
    const mailResponse = await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    expect(mailResponse).toInclude('250');
 | 
			
		||||
    
 | 
			
		||||
    // Send RCPT TO
 | 
			
		||||
    socket.write('RCPT TO:<recipient@example.com>\r\n');
 | 
			
		||||
    const rcptResponse = await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    expect(rcptResponse).toInclude('250');
 | 
			
		||||
    
 | 
			
		||||
    // Send DATA
 | 
			
		||||
    socket.write('DATA\r\n');
 | 
			
		||||
    const dataResponse = await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    expect(dataResponse).toInclude('354');
 | 
			
		||||
    
 | 
			
		||||
    // Send minimal email - just required headers and single character body
 | 
			
		||||
    const minimalEmail = 'From: sender@example.com\r\nTo: recipient@example.com\r\nSubject: \r\n\r\nX\r\n.\r\n';
 | 
			
		||||
    socket.write(minimalEmail);
 | 
			
		||||
    
 | 
			
		||||
    const finalResponse = await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    expect(finalResponse).toInclude('250');
 | 
			
		||||
    console.log(`Minimal email (${minimalEmail.length} bytes) processed successfully`);
 | 
			
		||||
    
 | 
			
		||||
    // Clean up
 | 
			
		||||
    socket.write('QUIT\r\n');
 | 
			
		||||
    socket.end();
 | 
			
		||||
    
 | 
			
		||||
  } finally {
 | 
			
		||||
    await stopTestServer(testServer);
 | 
			
		||||
    done.resolve();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
tap.test('Very Small Email - should handle email with empty body', async (tools) => {
 | 
			
		||||
  const done = tools.defer();
 | 
			
		||||
  
 | 
			
		||||
  // Start test server
 | 
			
		||||
  const testServer = await startTestServer({ port: TEST_PORT });
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    const socket = net.createConnection({
 | 
			
		||||
      host: 'localhost',
 | 
			
		||||
      port: TEST_PORT,
 | 
			
		||||
      timeout: TEST_TIMEOUT
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    await new Promise<void>((resolve, reject) => {
 | 
			
		||||
      socket.once('connect', () => resolve());
 | 
			
		||||
      socket.once('error', reject);
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Get banner
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Send EHLO
 | 
			
		||||
    socket.write('EHLO testhost\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      let data = '';
 | 
			
		||||
      const handler = (chunk: Buffer) => {
 | 
			
		||||
        data += chunk.toString();
 | 
			
		||||
        if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
 | 
			
		||||
          socket.removeListener('data', handler);
 | 
			
		||||
          resolve(data);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      socket.on('data', handler);
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Complete envelope
 | 
			
		||||
    socket.write('MAIL FROM:<sender@example.com>\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    socket.write('RCPT TO:<recipient@example.com>\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    socket.write('DATA\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Send email with empty body
 | 
			
		||||
    const emptyBodyEmail = 'From: sender@example.com\r\nTo: recipient@example.com\r\n\r\n.\r\n';
 | 
			
		||||
    socket.write(emptyBodyEmail);
 | 
			
		||||
    
 | 
			
		||||
    const finalResponse = await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    expect(finalResponse).toInclude('250');
 | 
			
		||||
    console.log('Email with empty body processed successfully');
 | 
			
		||||
    
 | 
			
		||||
    // Clean up
 | 
			
		||||
    socket.write('QUIT\r\n');
 | 
			
		||||
    socket.end();
 | 
			
		||||
    
 | 
			
		||||
  } finally {
 | 
			
		||||
    await stopTestServer(testServer);
 | 
			
		||||
    done.resolve();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
tap.test('Very Small Email - should handle email with minimal headers only', async (tools) => {
 | 
			
		||||
  const done = tools.defer();
 | 
			
		||||
  
 | 
			
		||||
  // Start test server
 | 
			
		||||
  const testServer = await startTestServer({ port: TEST_PORT });
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    const socket = net.createConnection({
 | 
			
		||||
      host: 'localhost',
 | 
			
		||||
      port: TEST_PORT,
 | 
			
		||||
      timeout: TEST_TIMEOUT
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    await new Promise<void>((resolve, reject) => {
 | 
			
		||||
      socket.once('connect', () => resolve());
 | 
			
		||||
      socket.once('error', reject);
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Get banner and send EHLO
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    socket.write('EHLO testhost\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      let data = '';
 | 
			
		||||
      const handler = (chunk: Buffer) => {
 | 
			
		||||
        data += chunk.toString();
 | 
			
		||||
        if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
 | 
			
		||||
          socket.removeListener('data', handler);
 | 
			
		||||
          resolve(data);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      socket.on('data', handler);
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Complete envelope - use valid email addresses
 | 
			
		||||
    socket.write('MAIL FROM:<a@example.com>\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    socket.write('RCPT TO:<b@example.com>\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    socket.write('DATA\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Send absolutely minimal valid email
 | 
			
		||||
    const minimalHeaders = 'From: a@example.com\r\n\r\n.\r\n';
 | 
			
		||||
    socket.write(minimalHeaders);
 | 
			
		||||
    
 | 
			
		||||
    const finalResponse = await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    expect(finalResponse).toInclude('250');
 | 
			
		||||
    console.log(`Ultra-minimal email (${minimalHeaders.length} bytes) processed`);
 | 
			
		||||
    
 | 
			
		||||
    // Clean up
 | 
			
		||||
    socket.write('QUIT\r\n');
 | 
			
		||||
    socket.end();
 | 
			
		||||
    
 | 
			
		||||
  } finally {
 | 
			
		||||
    await stopTestServer(testServer);
 | 
			
		||||
    done.resolve();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
tap.test('Very Small Email - should handle single dot line correctly', async (tools) => {
 | 
			
		||||
  const done = tools.defer();
 | 
			
		||||
  
 | 
			
		||||
  // Start test server
 | 
			
		||||
  const testServer = await startTestServer({ port: TEST_PORT });
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    const socket = net.createConnection({
 | 
			
		||||
      host: 'localhost',
 | 
			
		||||
      port: TEST_PORT,
 | 
			
		||||
      timeout: TEST_TIMEOUT
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    await new Promise<void>((resolve, reject) => {
 | 
			
		||||
      socket.once('connect', () => resolve());
 | 
			
		||||
      socket.once('error', reject);
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Setup connection
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    socket.write('EHLO testhost\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      let data = '';
 | 
			
		||||
      const handler = (chunk: Buffer) => {
 | 
			
		||||
        data += chunk.toString();
 | 
			
		||||
        if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
 | 
			
		||||
          socket.removeListener('data', handler);
 | 
			
		||||
          resolve(data);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      socket.on('data', handler);
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Complete envelope
 | 
			
		||||
    socket.write('MAIL FROM:<sender@example.com>\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    socket.write('RCPT TO:<recipient@example.com>\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    socket.write('DATA\r\n');
 | 
			
		||||
    const dataResponse = await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    expect(dataResponse).toInclude('354');
 | 
			
		||||
    
 | 
			
		||||
    // Test edge case: just the terminating dot
 | 
			
		||||
    socket.write('.\r\n');
 | 
			
		||||
    
 | 
			
		||||
    const finalResponse = await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Server should accept this as an email with no headers or body
 | 
			
		||||
    expect(finalResponse).toMatch(/^[2-5]\d{2}/);
 | 
			
		||||
    console.log('Single dot terminator handled:', finalResponse.trim());
 | 
			
		||||
    
 | 
			
		||||
    // Clean up
 | 
			
		||||
    socket.write('QUIT\r\n');
 | 
			
		||||
    socket.end();
 | 
			
		||||
    
 | 
			
		||||
  } finally {
 | 
			
		||||
    await stopTestServer(testServer);
 | 
			
		||||
    done.resolve();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
tap.test('Very Small Email - should handle email with empty subject', async (tools) => {
 | 
			
		||||
  const done = tools.defer();
 | 
			
		||||
  
 | 
			
		||||
  // Start test server
 | 
			
		||||
  const testServer = await startTestServer({ port: TEST_PORT });
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    const socket = net.createConnection({
 | 
			
		||||
      host: 'localhost',
 | 
			
		||||
      port: TEST_PORT,
 | 
			
		||||
      timeout: TEST_TIMEOUT
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    await new Promise<void>((resolve, reject) => {
 | 
			
		||||
      socket.once('connect', () => resolve());
 | 
			
		||||
      socket.once('error', reject);
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Setup connection
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    socket.write('EHLO testhost\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      let data = '';
 | 
			
		||||
      const handler = (chunk: Buffer) => {
 | 
			
		||||
        data += chunk.toString();
 | 
			
		||||
        if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
 | 
			
		||||
          socket.removeListener('data', handler);
 | 
			
		||||
          resolve(data);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      socket.on('data', handler);
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Complete envelope
 | 
			
		||||
    socket.write('MAIL FROM:<sender@example.com>\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    socket.write('RCPT TO:<recipient@example.com>\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    socket.write('DATA\r\n');
 | 
			
		||||
    await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Send email with empty subject line
 | 
			
		||||
    const emptySubjectEmail = 
 | 
			
		||||
      'From: sender@example.com\r\n' +
 | 
			
		||||
      'To: recipient@example.com\r\n' +
 | 
			
		||||
      'Subject: \r\n' +
 | 
			
		||||
      'Date: ' + new Date().toUTCString() + '\r\n' +
 | 
			
		||||
      '\r\n' +
 | 
			
		||||
      'Email with empty subject.\r\n' +
 | 
			
		||||
      '.\r\n';
 | 
			
		||||
    
 | 
			
		||||
    socket.write(emptySubjectEmail);
 | 
			
		||||
    
 | 
			
		||||
    const finalResponse = await new Promise<string>((resolve) => {
 | 
			
		||||
      socket.once('data', (chunk) => resolve(chunk.toString()));
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    expect(finalResponse).toInclude('250');
 | 
			
		||||
    console.log('Email with empty subject processed successfully');
 | 
			
		||||
    
 | 
			
		||||
    // Clean up
 | 
			
		||||
    socket.write('QUIT\r\n');
 | 
			
		||||
    socket.end();
 | 
			
		||||
    
 | 
			
		||||
  } finally {
 | 
			
		||||
    await stopTestServer(testServer);
 | 
			
		||||
    done.resolve();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default tap.start();
 | 
			
		||||
		Reference in New Issue
	
	Block a user