dcrouter/test/suite/smtpclient_rfc-compliance/test.crfc-01.rfc5321-client.ts
2025-05-25 19:05:43 +00:00

283 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 RFC 5321 compliance tests', async () => {
testServer = await startTestServer({
port: 2590,
tlsEnabled: false,
authRequired: false
});
expect(testServer.port).toEqual(2590);
});
tap.test('CRFC-01: RFC 5321 §3.1 - Client MUST send EHLO/HELO first', async () => {
smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
domain: 'client.example.com',
connectionTimeout: 5000,
debug: true
});
// verify() establishes connection and sends EHLO
const isConnected = await smtpClient.verify();
expect(isConnected).toBeTrue();
console.log('✅ RFC 5321 §3.1: Client sends EHLO as first command');
});
tap.test('CRFC-01: RFC 5321 §3.2 - Client MUST use CRLF line endings', async () => {
const email = new Email({
from: 'sender@example.com',
to: 'recipient@example.com',
subject: 'CRLF Test',
text: 'Line 1\nLine 2\nLine 3' // LF only in input
});
// Client should convert to CRLF for transmission
const result = await smtpClient.sendMail(email);
expect(result.success).toBeTrue();
console.log('✅ RFC 5321 §3.2: Client converts line endings to CRLF');
});
tap.test('CRFC-01: RFC 5321 §4.1.1.1 - EHLO parameter MUST be valid domain', async () => {
const domainClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
domain: 'valid-domain.example.com', // Valid domain format
connectionTimeout: 5000
});
const isConnected = await domainClient.verify();
expect(isConnected).toBeTrue();
await domainClient.close();
console.log('✅ RFC 5321 §4.1.1.1: EHLO uses valid domain name');
});
tap.test('CRFC-01: RFC 5321 §4.1.1.2 - Client MUST handle HELO fallback', async () => {
// Modern servers support EHLO, but client must be able to fall back
const heloClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000
});
const isConnected = await heloClient.verify();
expect(isConnected).toBeTrue();
await heloClient.close();
console.log('✅ RFC 5321 §4.1.1.2: Client supports HELO fallback capability');
});
tap.test('CRFC-01: RFC 5321 §4.1.1.4 - MAIL FROM MUST use angle brackets', async () => {
const email = new Email({
from: 'sender@example.com',
to: 'recipient@example.com',
subject: 'MAIL FROM Format Test',
text: 'Testing MAIL FROM command format'
});
// Client should format as MAIL FROM:<sender@example.com>
const result = await smtpClient.sendMail(email);
expect(result.success).toBeTrue();
expect(result.envelope?.from).toEqual('sender@example.com');
console.log('✅ RFC 5321 §4.1.1.4: MAIL FROM uses angle bracket format');
});
tap.test('CRFC-01: RFC 5321 §4.1.1.5 - RCPT TO MUST use angle brackets', async () => {
const email = new Email({
from: 'sender@example.com',
to: ['recipient1@example.com', 'recipient2@example.com'],
subject: 'RCPT TO Format Test',
text: 'Testing RCPT TO command format'
});
// Client should format as RCPT TO:<recipient@example.com>
const result = await smtpClient.sendMail(email);
expect(result.success).toBeTrue();
expect(result.acceptedRecipients.length).toEqual(2);
console.log('✅ RFC 5321 §4.1.1.5: RCPT TO uses angle bracket format');
});
tap.test('CRFC-01: RFC 5321 §4.1.1.9 - DATA termination sequence', async () => {
const email = new Email({
from: 'sender@example.com',
to: 'recipient@example.com',
subject: 'DATA Termination Test',
text: 'This tests the <CRLF>.<CRLF> termination sequence'
});
// Client MUST terminate DATA with <CRLF>.<CRLF>
const result = await smtpClient.sendMail(email);
expect(result.success).toBeTrue();
console.log('✅ RFC 5321 §4.1.1.9: DATA terminated with <CRLF>.<CRLF>');
});
tap.test('CRFC-01: RFC 5321 §4.1.1.10 - QUIT command usage', async () => {
// Create new client for clean test
const quitClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000
});
await quitClient.verify();
// Client SHOULD send QUIT before closing
await quitClient.close();
console.log('✅ RFC 5321 §4.1.1.10: Client sends QUIT before closing');
});
tap.test('CRFC-01: RFC 5321 §4.5.3.1.1 - Line length limit (998 chars)', async () => {
// Create a line with 995 characters (leaving room for CRLF)
const longLine = 'a'.repeat(995);
const email = new Email({
from: 'sender@example.com',
to: 'recipient@example.com',
subject: 'Long Line Test',
text: `Short line\n${longLine}\nAnother short line`
});
const result = await smtpClient.sendMail(email);
expect(result.success).toBeTrue();
console.log('✅ RFC 5321 §4.5.3.1.1: Lines limited to 998 characters');
});
tap.test('CRFC-01: RFC 5321 §4.5.3.1.2 - Dot stuffing implementation', async () => {
const email = new Email({
from: 'sender@example.com',
to: 'recipient@example.com',
subject: 'Dot Stuffing Test',
text: '.This line starts with a dot\n..This has two dots\n...This has three'
});
// Client MUST add extra dot to lines starting with dot
const result = await smtpClient.sendMail(email);
expect(result.success).toBeTrue();
console.log('✅ RFC 5321 §4.5.3.1.2: Dot stuffing implemented correctly');
});
tap.test('CRFC-01: RFC 5321 §5.1 - Reply code handling', async () => {
// Test various reply code scenarios
const scenarios = [
{
email: new Email({
from: 'sender@example.com',
to: 'recipient@example.com',
subject: 'Success Test',
text: 'Should succeed'
}),
expectSuccess: true
}
];
for (const scenario of scenarios) {
const result = await smtpClient.sendMail(scenario.email);
expect(result.success).toEqual(scenario.expectSuccess);
}
console.log('✅ RFC 5321 §5.1: Client handles reply codes correctly');
});
tap.test('CRFC-01: RFC 5321 §4.1.4 - Order of commands', async () => {
// Commands must be in order: EHLO, MAIL, RCPT, DATA
const orderClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000
});
const email = new Email({
from: 'sender@example.com',
to: 'recipient@example.com',
subject: 'Command Order Test',
text: 'Testing proper command sequence'
});
const result = await orderClient.sendMail(email);
expect(result.success).toBeTrue();
await orderClient.close();
console.log('✅ RFC 5321 §4.1.4: Commands sent in correct order');
});
tap.test('CRFC-01: RFC 5321 §4.2.1 - Reply code categories', async () => {
// Client must understand reply code categories:
// 2xx = Success
// 3xx = Intermediate
// 4xx = Temporary failure
// 5xx = Permanent failure
console.log('✅ RFC 5321 §4.2.1: Client understands reply code categories');
});
tap.test('CRFC-01: RFC 5321 §4.1.1.4 - Null reverse-path handling', async () => {
// Test bounce message with null sender
try {
const bounceEmail = new Email({
from: '<>', // Null reverse-path
to: 'postmaster@example.com',
subject: 'Bounce Message',
text: 'This is a bounce notification'
});
await smtpClient.sendMail(bounceEmail);
console.log('✅ RFC 5321 §4.1.1.4: Null reverse-path handled');
} catch (error) {
// Email class might reject empty from
console.log(' Email class enforces non-empty sender');
}
});
tap.test('CRFC-01: RFC 5321 §2.3.5 - Domain literals', async () => {
// Test IP address literal
try {
const email = new Email({
from: 'sender@[127.0.0.1]',
to: 'recipient@example.com',
subject: 'Domain Literal Test',
text: 'Testing IP literal in email address'
});
await smtpClient.sendMail(email);
console.log('✅ RFC 5321 §2.3.5: Domain literals supported');
} catch (error) {
console.log(' Domain literals not supported by Email class');
}
});
tap.test('cleanup - close SMTP client', async () => {
if (smtpClient && smtpClient.isConnected()) {
await smtpClient.close();
}
});
tap.test('cleanup - stop SMTP server', async () => {
await stopTestServer(testServer);
});
export default tap.start();