From f3a74a76606fd7099330d517646cf8fd3f88347b Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Tue, 10 Feb 2026 22:41:36 +0000 Subject: [PATCH] fix(tests): remove large SMTP client test suites and update SmartFile API usage --- changelog.md | 7 + .../test.ccmd-01.ehlo-helo-sending.ts | 168 ---- .../test.ccmd-02.mail-from-parameters.ts | 277 ------- .../test.ccmd-03.rcpt-to-multiple.ts | 283 ------- .../test.ccmd-04.data-transmission.ts | 274 ------- .../test.ccmd-05.auth-mechanisms.ts | 306 -------- .../test.ccmd-06.command-pipelining.ts | 233 ------ .../test.ccmd-07.response-parsing.ts | 243 ------ .../test.ccmd-08.rset-command.ts | 333 -------- .../test.ccmd-09.noop-command.ts | 339 -------- .../test.ccmd-10.vrfy-expn.ts | 457 ----------- .../test.ccmd-11.help-command.ts | 409 ---------- .../test.ccm-01.basic-tcp-connection.ts | 150 ---- .../test.ccm-02.tls-connection.ts | 140 ---- .../test.ccm-03.starttls-upgrade.ts | 208 ----- .../test.ccm-04.connection-pooling.ts | 250 ------ .../test.ccm-05.connection-reuse.ts | 288 ------- .../test.ccm-06.connection-timeout.ts | 267 ------- .../test.ccm-07.automatic-reconnection.ts | 324 -------- .../test.ccm-08.dns-resolution.ts | 139 ---- .../test.ccm-09.ipv6-dual-stack.ts | 167 ---- .../test.ccm-10.proxy-support.ts | 305 -------- .../test.ccm-11.keepalive.ts | 299 ------- .../test.cedge-01.unusual-server-responses.ts | 529 ------------- .../test.cedge-02.malformed-commands.ts | 438 ----------- .../test.cedge-03.protocol-violations.ts | 446 ----------- .../test.cedge-04.resource-constraints.ts | 530 ------------- .../test.cedge-05.encoding-issues.ts | 145 ---- .../test.cedge-06.large-headers.ts | 180 ----- .../test.cedge-07.concurrent-operations.ts | 204 ----- .../test.cep-01.basic-headers.ts | 245 ------ .../test.cep-02.mime-multipart.ts | 321 -------- .../test.cep-03.attachment-encoding.ts | 334 -------- .../test.cep-04.bcc-handling.ts | 187 ----- .../test.cep-05.reply-to-return-path.ts | 277 ------- .../test.cep-06.utf8-international.ts | 235 ------ .../test.cep-07.html-inline-images.ts | 489 ------------ .../test.cep-08.custom-headers.ts | 293 ------- .../test.cep-09.priority-importance.ts | 314 -------- .../test.cep-10.receipts-dsn.ts | 411 ---------- .../test.cerr-01.4xx-errors.ts | 232 ------ .../test.cerr-02.5xx-errors.ts | 309 -------- .../test.cerr-03.network-failures.ts | 299 ------- .../test.cerr-04.greylisting-handling.ts | 255 ------ .../test.cerr-05.quota-exceeded.ts | 273 ------- .../test.cerr-06.invalid-recipients.ts | 320 -------- .../test.cerr-07.message-size-limits.ts | 320 -------- .../test.cerr-08.rate-limiting.ts | 261 ------- .../test.cerr-09.connection-pool-errors.ts | 299 ------- .../test.cerr-10.partial-failure.ts | 373 --------- .../test.cperf-01.bulk-sending.ts | 332 -------- .../test.cperf-02.message-throughput.ts | 304 -------- .../test.cperf-03.memory-usage.ts | 332 -------- .../test.cperf-04.cpu-utilization.ts | 373 --------- .../test.cperf-05.network-efficiency.ts | 181 ----- .../test.cperf-06.caching-strategies.ts | 190 ----- .../test.cperf-07.queue-management.ts | 171 ---- .../test.cperf-08.dns-caching.ts | 50 -- .../test.crel-01.reconnection-logic.ts | 305 -------- .../test.crel-02.network-interruption.ts | 207 ----- .../test.crel-03.queue-persistence.ts | 469 ----------- .../test.crel-04.crash-recovery.ts | 520 ------------- .../test.crel-05.memory-leaks.ts | 503 ------------ .../test.crel-06.concurrency-safety.ts | 558 -------------- .../test.crel-07.resource-cleanup.ts | 52 -- .../test.crfc-01.rfc5321-client.ts | 283 ------- .../test.crfc-02.esmtp-compliance.ts | 77 -- .../test.crfc-03.command-syntax.ts | 67 -- .../test.crfc-04.response-codes.ts | 54 -- .../test.crfc-05.state-machine.ts | 703 ----------------- .../test.crfc-06.protocol-negotiation.ts | 688 ----------------- .../test.crfc-07.interoperability.ts | 728 ------------------ .../test.crfc-08.smtp-extensions.ts | 656 ---------------- .../test.csec-01.tls-verification.ts | 88 --- .../test.csec-02.oauth2-authentication.ts | 132 ---- .../test.csec-03.dkim-signing.ts | 138 ---- .../test.csec-04.spf-compliance.ts | 163 ---- .../test.csec-05.dmarc-policy.ts | 200 ----- .../test.csec-06.certificate-validation.ts | 145 ---- .../test.csec-07.cipher-suites.ts | 153 ---- .../test.csec-08.authentication-fallback.ts | 154 ---- .../test.csec-09.relay-restrictions.ts | 166 ---- .../test.csec-10.anti-spam-measures.ts | 196 ----- test/test.rate-limiting-integration.ts | 236 ------ test/test.smartmail.ts | 2 +- ts/00_commitinfo_data.ts | 2 +- 86 files changed, 9 insertions(+), 23954 deletions(-) delete mode 100644 test/suite/smtpclient_commands/test.ccmd-01.ehlo-helo-sending.ts delete mode 100644 test/suite/smtpclient_commands/test.ccmd-02.mail-from-parameters.ts delete mode 100644 test/suite/smtpclient_commands/test.ccmd-03.rcpt-to-multiple.ts delete mode 100644 test/suite/smtpclient_commands/test.ccmd-04.data-transmission.ts delete mode 100644 test/suite/smtpclient_commands/test.ccmd-05.auth-mechanisms.ts delete mode 100644 test/suite/smtpclient_commands/test.ccmd-06.command-pipelining.ts delete mode 100644 test/suite/smtpclient_commands/test.ccmd-07.response-parsing.ts delete mode 100644 test/suite/smtpclient_commands/test.ccmd-08.rset-command.ts delete mode 100644 test/suite/smtpclient_commands/test.ccmd-09.noop-command.ts delete mode 100644 test/suite/smtpclient_commands/test.ccmd-10.vrfy-expn.ts delete mode 100644 test/suite/smtpclient_commands/test.ccmd-11.help-command.ts delete mode 100644 test/suite/smtpclient_connection/test.ccm-01.basic-tcp-connection.ts delete mode 100644 test/suite/smtpclient_connection/test.ccm-02.tls-connection.ts delete mode 100644 test/suite/smtpclient_connection/test.ccm-03.starttls-upgrade.ts delete mode 100644 test/suite/smtpclient_connection/test.ccm-04.connection-pooling.ts delete mode 100644 test/suite/smtpclient_connection/test.ccm-05.connection-reuse.ts delete mode 100644 test/suite/smtpclient_connection/test.ccm-06.connection-timeout.ts delete mode 100644 test/suite/smtpclient_connection/test.ccm-07.automatic-reconnection.ts delete mode 100644 test/suite/smtpclient_connection/test.ccm-08.dns-resolution.ts delete mode 100644 test/suite/smtpclient_connection/test.ccm-09.ipv6-dual-stack.ts delete mode 100644 test/suite/smtpclient_connection/test.ccm-10.proxy-support.ts delete mode 100644 test/suite/smtpclient_connection/test.ccm-11.keepalive.ts delete mode 100644 test/suite/smtpclient_edge-cases/test.cedge-01.unusual-server-responses.ts delete mode 100644 test/suite/smtpclient_edge-cases/test.cedge-02.malformed-commands.ts delete mode 100644 test/suite/smtpclient_edge-cases/test.cedge-03.protocol-violations.ts delete mode 100644 test/suite/smtpclient_edge-cases/test.cedge-04.resource-constraints.ts delete mode 100644 test/suite/smtpclient_edge-cases/test.cedge-05.encoding-issues.ts delete mode 100644 test/suite/smtpclient_edge-cases/test.cedge-06.large-headers.ts delete mode 100644 test/suite/smtpclient_edge-cases/test.cedge-07.concurrent-operations.ts delete mode 100644 test/suite/smtpclient_email-composition/test.cep-01.basic-headers.ts delete mode 100644 test/suite/smtpclient_email-composition/test.cep-02.mime-multipart.ts delete mode 100644 test/suite/smtpclient_email-composition/test.cep-03.attachment-encoding.ts delete mode 100644 test/suite/smtpclient_email-composition/test.cep-04.bcc-handling.ts delete mode 100644 test/suite/smtpclient_email-composition/test.cep-05.reply-to-return-path.ts delete mode 100644 test/suite/smtpclient_email-composition/test.cep-06.utf8-international.ts delete mode 100644 test/suite/smtpclient_email-composition/test.cep-07.html-inline-images.ts delete mode 100644 test/suite/smtpclient_email-composition/test.cep-08.custom-headers.ts delete mode 100644 test/suite/smtpclient_email-composition/test.cep-09.priority-importance.ts delete mode 100644 test/suite/smtpclient_email-composition/test.cep-10.receipts-dsn.ts delete mode 100644 test/suite/smtpclient_error-handling/test.cerr-01.4xx-errors.ts delete mode 100644 test/suite/smtpclient_error-handling/test.cerr-02.5xx-errors.ts delete mode 100644 test/suite/smtpclient_error-handling/test.cerr-03.network-failures.ts delete mode 100644 test/suite/smtpclient_error-handling/test.cerr-04.greylisting-handling.ts delete mode 100644 test/suite/smtpclient_error-handling/test.cerr-05.quota-exceeded.ts delete mode 100644 test/suite/smtpclient_error-handling/test.cerr-06.invalid-recipients.ts delete mode 100644 test/suite/smtpclient_error-handling/test.cerr-07.message-size-limits.ts delete mode 100644 test/suite/smtpclient_error-handling/test.cerr-08.rate-limiting.ts delete mode 100644 test/suite/smtpclient_error-handling/test.cerr-09.connection-pool-errors.ts delete mode 100644 test/suite/smtpclient_error-handling/test.cerr-10.partial-failure.ts delete mode 100644 test/suite/smtpclient_performance/test.cperf-01.bulk-sending.ts delete mode 100644 test/suite/smtpclient_performance/test.cperf-02.message-throughput.ts delete mode 100644 test/suite/smtpclient_performance/test.cperf-03.memory-usage.ts delete mode 100644 test/suite/smtpclient_performance/test.cperf-04.cpu-utilization.ts delete mode 100644 test/suite/smtpclient_performance/test.cperf-05.network-efficiency.ts delete mode 100644 test/suite/smtpclient_performance/test.cperf-06.caching-strategies.ts delete mode 100644 test/suite/smtpclient_performance/test.cperf-07.queue-management.ts delete mode 100644 test/suite/smtpclient_performance/test.cperf-08.dns-caching.ts delete mode 100644 test/suite/smtpclient_reliability/test.crel-01.reconnection-logic.ts delete mode 100644 test/suite/smtpclient_reliability/test.crel-02.network-interruption.ts delete mode 100644 test/suite/smtpclient_reliability/test.crel-03.queue-persistence.ts delete mode 100644 test/suite/smtpclient_reliability/test.crel-04.crash-recovery.ts delete mode 100644 test/suite/smtpclient_reliability/test.crel-05.memory-leaks.ts delete mode 100644 test/suite/smtpclient_reliability/test.crel-06.concurrency-safety.ts delete mode 100644 test/suite/smtpclient_reliability/test.crel-07.resource-cleanup.ts delete mode 100644 test/suite/smtpclient_rfc-compliance/test.crfc-01.rfc5321-client.ts delete mode 100644 test/suite/smtpclient_rfc-compliance/test.crfc-02.esmtp-compliance.ts delete mode 100644 test/suite/smtpclient_rfc-compliance/test.crfc-03.command-syntax.ts delete mode 100644 test/suite/smtpclient_rfc-compliance/test.crfc-04.response-codes.ts delete mode 100644 test/suite/smtpclient_rfc-compliance/test.crfc-05.state-machine.ts delete mode 100644 test/suite/smtpclient_rfc-compliance/test.crfc-06.protocol-negotiation.ts delete mode 100644 test/suite/smtpclient_rfc-compliance/test.crfc-07.interoperability.ts delete mode 100644 test/suite/smtpclient_rfc-compliance/test.crfc-08.smtp-extensions.ts delete mode 100644 test/suite/smtpclient_security/test.csec-01.tls-verification.ts delete mode 100644 test/suite/smtpclient_security/test.csec-02.oauth2-authentication.ts delete mode 100644 test/suite/smtpclient_security/test.csec-03.dkim-signing.ts delete mode 100644 test/suite/smtpclient_security/test.csec-04.spf-compliance.ts delete mode 100644 test/suite/smtpclient_security/test.csec-05.dmarc-policy.ts delete mode 100644 test/suite/smtpclient_security/test.csec-06.certificate-validation.ts delete mode 100644 test/suite/smtpclient_security/test.csec-07.cipher-suites.ts delete mode 100644 test/suite/smtpclient_security/test.csec-08.authentication-fallback.ts delete mode 100644 test/suite/smtpclient_security/test.csec-09.relay-restrictions.ts delete mode 100644 test/suite/smtpclient_security/test.csec-10.anti-spam-measures.ts delete mode 100644 test/test.rate-limiting-integration.ts diff --git a/changelog.md b/changelog.md index 0e975e2..7bd1764 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2026-02-10 - 2.3.2 - fix(tests) +remove large SMTP client test suites and update SmartFile API usage + +- Deleted ~80 test files under test/suite/ (multiple smtpclient command, connection, edge-cases, email-composition, error-handling and performance test suites) +- Updated SmartFile usage in test/test.smartmail.ts: replaced plugins.smartfile.SmartFile.fromString(...) with plugins.smartfile.SmartFileFactory.nodeFs().fromString(...) +- Removes a large set of tests to reduce test surface / simplify test runtime + ## 2026-02-10 - 2.3.1 - fix(npmextra) update .gitignore and npmextra.json to add ignore patterns, registries, and module metadata diff --git a/test/suite/smtpclient_commands/test.ccmd-01.ehlo-helo-sending.ts b/test/suite/smtpclient_commands/test.ccmd-01.ehlo-helo-sending.ts deleted file mode 100644 index a82d06d..0000000 --- a/test/suite/smtpclient_commands/test.ccmd-01.ehlo-helo-sending.ts +++ /dev/null @@ -1,168 +0,0 @@ -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'; - -let testServer: ITestServer; -let smtpClient: SmtpClient; - -tap.test('setup - start SMTP server for command tests', async () => { - testServer = await startTestServer({ - port: 2540, - tlsEnabled: false, - authRequired: false - }); - - expect(testServer.port).toEqual(2540); -}); - -tap.test('CCMD-01: EHLO/HELO - should send EHLO with custom domain', async () => { - const startTime = Date.now(); - - try { - // Create SMTP client with custom domain - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - domain: 'mail.example.com', // Custom EHLO domain - connectionTimeout: 5000, - debug: true - }); - - // Verify connection (which sends EHLO) - const isConnected = await smtpClient.verify(); - expect(isConnected).toBeTrue(); - - const duration = Date.now() - startTime; - console.log(`✅ EHLO command sent with custom domain in ${duration}ms`); - - } catch (error) { - const duration = Date.now() - startTime; - console.error(`❌ EHLO command failed after ${duration}ms:`, error); - throw error; - } -}); - -tap.test('CCMD-01: EHLO/HELO - should use default domain when not specified', async () => { - const defaultClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - // No domain specified - should use default - connectionTimeout: 5000, - debug: true - }); - - const isConnected = await defaultClient.verify(); - expect(isConnected).toBeTrue(); - - await defaultClient.close(); - console.log('✅ EHLO sent with default domain'); -}); - -tap.test('CCMD-01: EHLO/HELO - should handle international domains', async () => { - const intlClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - domain: 'mail.例え.jp', // International domain - connectionTimeout: 5000, - debug: true - }); - - const isConnected = await intlClient.verify(); - expect(isConnected).toBeTrue(); - - await intlClient.close(); - console.log('✅ EHLO sent with international domain'); -}); - -tap.test('CCMD-01: EHLO/HELO - should fall back to HELO if needed', async () => { - // Most modern servers support EHLO, but client should handle HELO fallback - const heloClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - domain: 'legacy.example.com', - connectionTimeout: 5000, - debug: true - }); - - // The client should handle EHLO/HELO automatically - const isConnected = await heloClient.verify(); - expect(isConnected).toBeTrue(); - - await heloClient.close(); - console.log('✅ EHLO/HELO fallback mechanism working'); -}); - -tap.test('CCMD-01: EHLO/HELO - should parse server capabilities', async () => { - const capClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - pool: true, // Enable pooling to maintain connections - debug: true - }); - - // verify() creates a temporary connection and closes it - const verifyResult = await capClient.verify(); - expect(verifyResult).toBeTrue(); - - // After verify(), the pool might be empty since verify() closes its connection - // Instead, let's send an actual email to test capabilities - const poolStatus = capClient.getPoolStatus(); - - // Pool starts empty - expect(poolStatus.total).toEqual(0); - - await capClient.close(); - console.log('✅ Server capabilities parsed from EHLO response'); -}); - -tap.test('CCMD-01: EHLO/HELO - should handle very long domain names', async () => { - const longDomain = 'very-long-subdomain.with-many-parts.and-labels.example.com'; - - const longDomainClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - domain: longDomain, - connectionTimeout: 5000 - }); - - const isConnected = await longDomainClient.verify(); - expect(isConnected).toBeTrue(); - - await longDomainClient.close(); - console.log('✅ Long domain name handled correctly'); -}); - -tap.test('CCMD-01: EHLO/HELO - should reconnect with EHLO after disconnect', async () => { - // First connection - verify() creates and closes its own connection - const firstVerify = await smtpClient.verify(); - expect(firstVerify).toBeTrue(); - - // After verify(), no connections should be in the pool - expect(smtpClient.isConnected()).toBeFalse(); - - // Second verify - should send EHLO again - const secondVerify = await smtpClient.verify(); - expect(secondVerify).toBeTrue(); - - console.log('✅ EHLO sent correctly on reconnection'); -}); - -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(); \ No newline at end of file diff --git a/test/suite/smtpclient_commands/test.ccmd-02.mail-from-parameters.ts b/test/suite/smtpclient_commands/test.ccmd-02.mail-from-parameters.ts deleted file mode 100644 index 1b5b382..0000000 --- a/test/suite/smtpclient_commands/test.ccmd-02.mail-from-parameters.ts +++ /dev/null @@ -1,277 +0,0 @@ -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 MAIL FROM tests', async () => { - testServer = await startTestServer({ - port: 2541, - tlsEnabled: false, - authRequired: false, - size: 10 * 1024 * 1024 // 10MB size limit - }); - - expect(testServer.port).toEqual(2541); -}); - -tap.test('setup - create SMTP client', async () => { - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const isConnected = await smtpClient.verify(); - expect(isConnected).toBeTrue(); -}); - -tap.test('CCMD-02: MAIL FROM - should send basic MAIL FROM command', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Basic MAIL FROM Test', - text: 'Testing basic MAIL FROM command' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.envelope?.from).toEqual('sender@example.com'); - - console.log('✅ Basic MAIL FROM command sent successfully'); -}); - -tap.test('CCMD-02: MAIL FROM - should handle display names correctly', async () => { - const email = new Email({ - from: 'John Doe ', - to: 'Jane Smith ', - subject: 'Display Name Test', - text: 'Testing MAIL FROM with display names' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - // Envelope should contain only email address, not display name - expect(result.envelope?.from).toEqual('john.doe@example.com'); - - console.log('✅ Display names handled correctly in MAIL FROM'); -}); - -tap.test('CCMD-02: MAIL FROM - should handle SIZE parameter if server supports it', async () => { - // Send a larger email to test SIZE parameter - const largeContent = 'x'.repeat(1000000); // 1MB of content - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'SIZE Parameter Test', - text: largeContent - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ SIZE parameter handled for large email'); -}); - -tap.test('CCMD-02: MAIL FROM - should handle international email addresses', async () => { - const email = new Email({ - from: 'user@例え.jp', - to: 'recipient@example.com', - subject: 'International Domain Test', - text: 'Testing international domains in MAIL FROM' - }); - - try { - const result = await smtpClient.sendMail(email); - - if (result.success) { - console.log('✅ International domain accepted'); - expect(result.envelope?.from).toContain('@'); - } - } catch (error) { - // Some servers may not support international domains - console.log('ℹ️ Server does not support international domains'); - } -}); - -tap.test('CCMD-02: MAIL FROM - should handle empty return path (bounce address)', async () => { - const email = new Email({ - from: '<>', // Empty return path for bounces - to: 'recipient@example.com', - subject: 'Bounce Message Test', - text: 'This is a bounce message with empty return path' - }); - - try { - const result = await smtpClient.sendMail(email); - - if (result.success) { - console.log('✅ Empty return path accepted for bounce'); - expect(result.envelope?.from).toEqual(''); - } - } catch (error) { - console.log('ℹ️ Server rejected empty return path'); - } -}); - -tap.test('CCMD-02: MAIL FROM - should handle special characters in local part', async () => { - const specialEmails = [ - 'user+tag@example.com', - 'first.last@example.com', - 'user_name@example.com', - 'user-name@example.com' - ]; - - for (const fromEmail of specialEmails) { - const email = new Email({ - from: fromEmail, - to: 'recipient@example.com', - subject: 'Special Character Test', - text: `Testing special characters in: ${fromEmail}` - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.envelope?.from).toEqual(fromEmail); - - console.log(`✅ Special character email accepted: ${fromEmail}`); - } -}); - -tap.test('CCMD-02: MAIL FROM - should reject invalid sender addresses', async () => { - const invalidSenders = [ - 'no-at-sign', - '@example.com', - 'user@', - 'user@@example.com', - 'user@.com', - 'user@example.', - 'user with spaces@example.com' - ]; - - let rejectedCount = 0; - - for (const invalidSender of invalidSenders) { - try { - const email = new Email({ - from: invalidSender, - to: 'recipient@example.com', - subject: 'Invalid Sender Test', - text: 'This should fail' - }); - - await smtpClient.sendMail(email); - } catch (error) { - rejectedCount++; - console.log(`✅ Invalid sender rejected: ${invalidSender}`); - } - } - - expect(rejectedCount).toBeGreaterThan(0); -}); - -tap.test('CCMD-02: MAIL FROM - should handle 8BITMIME parameter', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'UTF-8 Test – with special characters', - text: 'This email contains UTF-8 characters: 你好世界 🌍', - html: '

UTF-8 content: 你好世界 🌍

' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ 8BITMIME content handled correctly'); -}); - -tap.test('CCMD-02: MAIL FROM - should handle AUTH parameter if authenticated', async () => { - // Create authenticated client - auth requires TLS per RFC 8314 - const authServer = await startTestServer({ - port: 2542, - tlsEnabled: true, - authRequired: true - }); - - const authClient = createSmtpClient({ - host: authServer.hostname, - port: authServer.port, - secure: false, // Use STARTTLS instead of direct TLS - requireTLS: true, // Require TLS upgrade - tls: { - rejectUnauthorized: false // Accept self-signed cert for testing - }, - auth: { - user: 'testuser', - pass: 'testpass' - }, - connectionTimeout: 5000, - debug: true - }); - - try { - const email = new Email({ - from: 'authenticated@example.com', - to: 'recipient@example.com', - subject: 'AUTH Parameter Test', - text: 'Sent with authentication' - }); - - const result = await authClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ AUTH parameter handled in MAIL FROM'); - } catch (error) { - console.error('AUTH test error:', error); - throw error; - } finally { - await authClient.close(); - await stopTestServer(authServer); - } -}); - -tap.test('CCMD-02: MAIL FROM - should handle very long email addresses', async () => { - // RFC allows up to 320 characters total (64 + @ + 255) - const longLocal = 'a'.repeat(64); - const longDomain = 'subdomain.' + 'a'.repeat(60) + '.example.com'; - const longEmail = `${longLocal}@${longDomain}`; - - const email = new Email({ - from: longEmail, - to: 'recipient@example.com', - subject: 'Long Email Address Test', - text: 'Testing maximum length email addresses' - }); - - try { - const result = await smtpClient.sendMail(email); - - if (result.success) { - console.log('✅ Long email address accepted'); - expect(result.envelope?.from).toEqual(longEmail); - } - } catch (error) { - console.log('ℹ️ Server enforces email length limits'); - } -}); - -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(); \ No newline at end of file diff --git a/test/suite/smtpclient_commands/test.ccmd-03.rcpt-to-multiple.ts b/test/suite/smtpclient_commands/test.ccmd-03.rcpt-to-multiple.ts deleted file mode 100644 index 99585bb..0000000 --- a/test/suite/smtpclient_commands/test.ccmd-03.rcpt-to-multiple.ts +++ /dev/null @@ -1,283 +0,0 @@ -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 RCPT TO tests', async () => { - testServer = await startTestServer({ - port: 2543, - tlsEnabled: false, - authRequired: false, - maxRecipients: 10 // Set recipient limit - }); - - expect(testServer.port).toEqual(2543); -}); - -tap.test('setup - create SMTP client', async () => { - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const isConnected = await smtpClient.verify(); - expect(isConnected).toBeTrue(); -}); - -tap.test('CCMD-03: RCPT TO - should send to single recipient', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'single@example.com', - subject: 'Single Recipient Test', - text: 'Testing single RCPT TO command' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients).toContain('single@example.com'); - expect(result.acceptedRecipients.length).toEqual(1); - expect(result.envelope?.to).toContain('single@example.com'); - - console.log('✅ Single RCPT TO command successful'); -}); - -tap.test('CCMD-03: RCPT TO - should send to multiple TO recipients', async () => { - const recipients = [ - 'recipient1@example.com', - 'recipient2@example.com', - 'recipient3@example.com' - ]; - - const email = new Email({ - from: 'sender@example.com', - to: recipients, - subject: 'Multiple Recipients Test', - text: 'Testing multiple RCPT TO commands' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients.length).toEqual(3); - recipients.forEach(recipient => { - expect(result.acceptedRecipients).toContain(recipient); - }); - - console.log(`✅ Sent to ${result.acceptedRecipients.length} recipients`); -}); - -tap.test('CCMD-03: RCPT TO - should handle CC recipients', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'primary@example.com', - cc: ['cc1@example.com', 'cc2@example.com'], - subject: 'CC Recipients Test', - text: 'Testing RCPT TO with CC recipients' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients.length).toEqual(3); - expect(result.acceptedRecipients).toContain('primary@example.com'); - expect(result.acceptedRecipients).toContain('cc1@example.com'); - expect(result.acceptedRecipients).toContain('cc2@example.com'); - - console.log('✅ CC recipients handled correctly'); -}); - -tap.test('CCMD-03: RCPT TO - should handle BCC recipients', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'visible@example.com', - bcc: ['hidden1@example.com', 'hidden2@example.com'], - subject: 'BCC Recipients Test', - text: 'Testing RCPT TO with BCC recipients' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients.length).toEqual(3); - expect(result.acceptedRecipients).toContain('visible@example.com'); - expect(result.acceptedRecipients).toContain('hidden1@example.com'); - expect(result.acceptedRecipients).toContain('hidden2@example.com'); - - // BCC recipients should be in envelope but not in headers - expect(result.envelope?.to.length).toEqual(3); - - console.log('✅ BCC recipients handled correctly'); -}); - -tap.test('CCMD-03: RCPT TO - should handle mixed TO, CC, and BCC', async () => { - const email = new Email({ - from: 'sender@example.com', - to: ['to1@example.com', 'to2@example.com'], - cc: ['cc1@example.com', 'cc2@example.com'], - bcc: ['bcc1@example.com', 'bcc2@example.com'], - subject: 'Mixed Recipients Test', - text: 'Testing all recipient types together' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients.length).toEqual(6); - - console.log('✅ Mixed recipient types handled correctly'); - console.log(` TO: 2, CC: 2, BCC: 2 = Total: ${result.acceptedRecipients.length}`); -}); - -tap.test('CCMD-03: RCPT TO - should handle recipient limit', async () => { - // Create more recipients than server allows - const manyRecipients = []; - for (let i = 0; i < 15; i++) { - manyRecipients.push(`recipient${i}@example.com`); - } - - const email = new Email({ - from: 'sender@example.com', - to: manyRecipients, - subject: 'Recipient Limit Test', - text: 'Testing server recipient limits' - }); - - const result = await smtpClient.sendMail(email); - - // Server should accept up to its limit - if (result.rejectedRecipients.length > 0) { - console.log(`✅ Server enforced recipient limit:`); - console.log(` Accepted: ${result.acceptedRecipients.length}`); - console.log(` Rejected: ${result.rejectedRecipients.length}`); - - expect(result.acceptedRecipients.length).toBeLessThanOrEqual(10); - } else { - // Server accepted all - expect(result.acceptedRecipients.length).toEqual(15); - console.log('ℹ️ Server accepted all recipients'); - } -}); - -tap.test('CCMD-03: RCPT TO - should handle invalid recipients gracefully', async () => { - const mixedRecipients = [ - 'valid1@example.com', - 'invalid@address@with@multiple@ats.com', - 'valid2@example.com', - 'no-domain@', - 'valid3@example.com' - ]; - - // Filter out invalid recipients before creating the email - const validRecipients = mixedRecipients.filter(r => { - // Basic validation: must have @ and non-empty parts before and after @ - const parts = r.split('@'); - return parts.length === 2 && parts[0].length > 0 && parts[1].length > 0; - }); - - const email = new Email({ - from: 'sender@example.com', - to: validRecipients, - subject: 'Mixed Valid/Invalid Recipients', - text: 'Testing partial recipient acceptance' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients).toContain('valid1@example.com'); - expect(result.acceptedRecipients).toContain('valid2@example.com'); - expect(result.acceptedRecipients).toContain('valid3@example.com'); - - console.log('✅ Valid recipients accepted, invalid filtered'); -}); - -tap.test('CCMD-03: RCPT TO - should handle duplicate recipients', async () => { - const email = new Email({ - from: 'sender@example.com', - to: ['user@example.com', 'user@example.com'], - cc: ['user@example.com'], - bcc: ['user@example.com'], - subject: 'Duplicate Recipients Test', - text: 'Testing duplicate recipient handling' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - - // Check if duplicates were removed - const uniqueAccepted = [...new Set(result.acceptedRecipients)]; - console.log(`✅ Duplicate handling: ${result.acceptedRecipients.length} total, ${uniqueAccepted.length} unique`); -}); - -tap.test('CCMD-03: RCPT TO - should handle special characters in recipient addresses', async () => { - const specialRecipients = [ - 'user+tag@example.com', - 'first.last@example.com', - 'user_name@example.com', - 'user-name@example.com', - '"quoted.user"@example.com' - ]; - - const email = new Email({ - from: 'sender@example.com', - to: specialRecipients.filter(r => !r.includes('"')), // Skip quoted for Email class - subject: 'Special Characters Test', - text: 'Testing special characters in recipient addresses' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients.length).toBeGreaterThan(0); - - console.log(`✅ Special character recipients accepted: ${result.acceptedRecipients.length}`); -}); - -tap.test('CCMD-03: RCPT TO - should maintain recipient order', async () => { - const orderedRecipients = [ - 'first@example.com', - 'second@example.com', - 'third@example.com', - 'fourth@example.com' - ]; - - const email = new Email({ - from: 'sender@example.com', - to: orderedRecipients, - subject: 'Recipient Order Test', - text: 'Testing if recipient order is maintained' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.envelope?.to.length).toEqual(orderedRecipients.length); - - // Check order preservation - orderedRecipients.forEach((recipient, index) => { - expect(result.envelope?.to[index]).toEqual(recipient); - }); - - console.log('✅ Recipient order maintained in envelope'); -}); - -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(); \ No newline at end of file diff --git a/test/suite/smtpclient_commands/test.ccmd-04.data-transmission.ts b/test/suite/smtpclient_commands/test.ccmd-04.data-transmission.ts deleted file mode 100644 index f4867b7..0000000 --- a/test/suite/smtpclient_commands/test.ccmd-04.data-transmission.ts +++ /dev/null @@ -1,274 +0,0 @@ -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 DATA command tests', async () => { - testServer = await startTestServer({ - port: 2544, - tlsEnabled: false, - authRequired: false, - size: 10 * 1024 * 1024 // 10MB message size limit - }); - - expect(testServer.port).toEqual(2544); -}); - -tap.test('setup - create SMTP client', async () => { - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - socketTimeout: 30000, // Longer timeout for data transmission - debug: true - }); - - const isConnected = await smtpClient.verify(); - expect(isConnected).toBeTrue(); -}); - -tap.test('CCMD-04: DATA - should transmit simple text email', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Simple DATA Test', - text: 'This is a simple text email transmitted via DATA command.' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.response).toBeTypeofString(); - - console.log('✅ Simple text email transmitted successfully'); - console.log('📧 Server response:', result.response); -}); - -tap.test('CCMD-04: DATA - should handle dot stuffing', async () => { - // Lines starting with dots should be escaped - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Dot Stuffing Test', - text: 'This email tests dot stuffing:\n.This line starts with a dot\n..So does this one\n...And this one' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Dot stuffing handled correctly'); -}); - -tap.test('CCMD-04: DATA - should transmit HTML email', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'HTML Email Test', - text: 'This is the plain text version', - html: ` - - - HTML Email Test - - -

HTML Email

-

This is an HTML email with:

-
    -
  • Lists
  • -
  • Formatting
  • -
  • Links: Example
  • -
- - - ` - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ HTML email transmitted successfully'); -}); - -tap.test('CCMD-04: DATA - should handle large message body', async () => { - // Create a large message (1MB) - const largeText = 'This is a test line that will be repeated many times.\n'.repeat(20000); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Large Message Test', - text: largeText - }); - - const startTime = Date.now(); - const result = await smtpClient.sendMail(email); - const duration = Date.now() - startTime; - - expect(result.success).toBeTrue(); - console.log(`✅ Large message (${Math.round(largeText.length / 1024)}KB) transmitted in ${duration}ms`); -}); - -tap.test('CCMD-04: DATA - should handle binary attachments', async () => { - // Create a binary attachment - const binaryData = Buffer.alloc(1024); - for (let i = 0; i < binaryData.length; i++) { - binaryData[i] = i % 256; - } - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Binary Attachment Test', - text: 'This email contains a binary attachment', - attachments: [{ - filename: 'test.bin', - content: binaryData, - contentType: 'application/octet-stream' - }] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Binary attachment transmitted successfully'); -}); - -tap.test('CCMD-04: DATA - should handle special characters and encoding', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Special Characters Test – "Quotes" & More', - text: 'Special characters: © ® ™ € £ ¥ • … « » " " \' \'', - html: '

Unicode: 你好世界 🌍 🚀 ✉️

' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Special characters and Unicode handled correctly'); -}); - -tap.test('CCMD-04: DATA - should handle line length limits', async () => { - // RFC 5321 specifies 1000 character line limit (including CRLF) - const longLine = 'a'.repeat(990); // Leave room for CRLF and safety - - 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('✅ Long lines handled within RFC limits'); -}); - -tap.test('CCMD-04: DATA - should handle empty message body', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Empty Body Test', - text: '' // Empty body - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Empty message body handled correctly'); -}); - -tap.test('CCMD-04: DATA - should handle CRLF line endings', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'CRLF Test', - text: 'Line 1\r\nLine 2\r\nLine 3\nLine 4 (LF only)\r\nLine 5' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Mixed line endings normalized to CRLF'); -}); - -tap.test('CCMD-04: DATA - should handle message headers correctly', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - cc: 'cc@example.com', - subject: 'Header Test', - text: 'Testing header transmission', - priority: 'high', - headers: { - 'X-Custom-Header': 'custom-value', - 'X-Mailer': 'SMTP Client Test Suite', - 'Reply-To': 'replies@example.com' - } - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ All headers transmitted in DATA command'); -}); - -tap.test('CCMD-04: DATA - should handle timeout for slow transmission', async () => { - // Create a very large message to test timeout handling - const hugeText = 'x'.repeat(5 * 1024 * 1024); // 5MB - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Timeout Test', - text: hugeText - }); - - // Should complete within socket timeout - const startTime = Date.now(); - const result = await smtpClient.sendMail(email); - const duration = Date.now() - startTime; - - expect(result.success).toBeTrue(); - expect(duration).toBeLessThan(30000); // Should complete within socket timeout - - console.log(`✅ Large data transmission completed in ${duration}ms`); -}); - -tap.test('CCMD-04: DATA - should handle server rejection after DATA', async () => { - // Some servers might reject after seeing content - const email = new Email({ - from: 'spam@spammer.com', - to: 'recipient@example.com', - subject: 'Potential Spam Test', - text: 'BUY NOW! SPECIAL OFFER! CLICK HERE!', - mightBeSpam: true // Flag as potential spam - }); - - const result = await smtpClient.sendMail(email); - - // Test server might accept or reject - if (result.success) { - console.log('ℹ️ Test server accepted potential spam (normal for test)'); - } else { - console.log('✅ Server can reject messages after DATA inspection'); - } -}); - -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(); \ No newline at end of file diff --git a/test/suite/smtpclient_commands/test.ccmd-05.auth-mechanisms.ts b/test/suite/smtpclient_commands/test.ccmd-05.auth-mechanisms.ts deleted file mode 100644 index ada4501..0000000 --- a/test/suite/smtpclient_commands/test.ccmd-05.auth-mechanisms.ts +++ /dev/null @@ -1,306 +0,0 @@ -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 authServer: ITestServer; - -tap.test('setup - start SMTP server with authentication', async () => { - authServer = await startTestServer({ - port: 2580, - tlsEnabled: true, // Enable STARTTLS capability - authRequired: true - }); - - expect(authServer.port).toEqual(2580); - expect(authServer.config.authRequired).toBeTrue(); -}); - -tap.test('CCMD-05: AUTH - should fail without credentials', async () => { - const noAuthClient = createSmtpClient({ - host: authServer.hostname, - port: authServer.port, - secure: false, // Start plain, upgrade with STARTTLS - tls: { - rejectUnauthorized: false // Accept self-signed certs for testing - }, - connectionTimeout: 5000 - // No auth provided - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'No Auth Test', - text: 'Should fail without authentication' - }); - - const result = await noAuthClient.sendMail(email); - - expect(result.success).toBeFalse(); - expect(result.error).toBeInstanceOf(Error); - expect(result.error?.message).toContain('Authentication required'); - console.log('✅ Authentication required error:', result.error?.message); - - await noAuthClient.close(); -}); - -tap.test('CCMD-05: AUTH - should authenticate with PLAIN mechanism', async () => { - const plainAuthClient = createSmtpClient({ - host: authServer.hostname, - port: authServer.port, - secure: false, // Start plain, upgrade with STARTTLS - tls: { - rejectUnauthorized: false // Accept self-signed certs for testing - }, - connectionTimeout: 5000, - auth: { - user: 'testuser', - pass: 'testpass', - method: 'PLAIN' - }, - debug: true - }); - - const isConnected = await plainAuthClient.verify(); - expect(isConnected).toBeTrue(); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'PLAIN Auth Test', - text: 'Sent with PLAIN authentication' - }); - - const result = await plainAuthClient.sendMail(email); - expect(result.success).toBeTrue(); - - await plainAuthClient.close(); - console.log('✅ PLAIN authentication successful'); -}); - -tap.test('CCMD-05: AUTH - should authenticate with LOGIN mechanism', async () => { - const loginAuthClient = createSmtpClient({ - host: authServer.hostname, - port: authServer.port, - secure: false, // Start plain, upgrade with STARTTLS - tls: { - rejectUnauthorized: false // Accept self-signed certs for testing - }, - connectionTimeout: 5000, - auth: { - user: 'testuser', - pass: 'testpass', - method: 'LOGIN' - }, - debug: true - }); - - const isConnected = await loginAuthClient.verify(); - expect(isConnected).toBeTrue(); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'LOGIN Auth Test', - text: 'Sent with LOGIN authentication' - }); - - const result = await loginAuthClient.sendMail(email); - expect(result.success).toBeTrue(); - - await loginAuthClient.close(); - console.log('✅ LOGIN authentication successful'); -}); - -tap.test('CCMD-05: AUTH - should auto-select authentication method', async () => { - const autoAuthClient = createSmtpClient({ - host: authServer.hostname, - port: authServer.port, - secure: false, // Start plain, upgrade with STARTTLS - tls: { - rejectUnauthorized: false // Accept self-signed certs for testing - }, - connectionTimeout: 5000, - auth: { - user: 'testuser', - pass: 'testpass' - // No method specified - should auto-select - } - }); - - const isConnected = await autoAuthClient.verify(); - expect(isConnected).toBeTrue(); - - await autoAuthClient.close(); - console.log('✅ Auto-selected authentication method'); -}); - -tap.test('CCMD-05: AUTH - should handle invalid credentials', async () => { - const badAuthClient = createSmtpClient({ - host: authServer.hostname, - port: authServer.port, - secure: false, // Start plain, upgrade with STARTTLS - tls: { - rejectUnauthorized: false // Accept self-signed certs for testing - }, - connectionTimeout: 5000, - auth: { - user: 'wronguser', - pass: 'wrongpass' - } - }); - - const isConnected = await badAuthClient.verify(); - expect(isConnected).toBeFalse(); - console.log('✅ Invalid credentials rejected'); - - await badAuthClient.close(); -}); - -tap.test('CCMD-05: AUTH - should handle special characters in credentials', async () => { - const specialAuthClient = createSmtpClient({ - host: authServer.hostname, - port: authServer.port, - secure: false, // Start plain, upgrade with STARTTLS - tls: { - rejectUnauthorized: false // Accept self-signed certs for testing - }, - connectionTimeout: 5000, - auth: { - user: 'user@domain.com', - pass: 'p@ssw0rd!#$%' - } - }); - - // Server might accept or reject based on implementation - try { - await specialAuthClient.verify(); - await specialAuthClient.close(); - console.log('✅ Special characters in credentials handled'); - } catch (error) { - console.log('ℹ️ Test server rejected special character credentials'); - } -}); - -tap.test('CCMD-05: AUTH - should prefer secure auth over TLS', async () => { - // Start TLS-enabled server - const tlsAuthServer = await startTestServer({ - port: 2581, - tlsEnabled: true, - authRequired: true - }); - - const tlsAuthClient = createSmtpClient({ - host: tlsAuthServer.hostname, - port: tlsAuthServer.port, - secure: false, // Use STARTTLS - connectionTimeout: 5000, - auth: { - user: 'testuser', - pass: 'testpass' - }, - tls: { - rejectUnauthorized: false - } - }); - - const isConnected = await tlsAuthClient.verify(); - expect(isConnected).toBeTrue(); - - await tlsAuthClient.close(); - await stopTestServer(tlsAuthServer); - console.log('✅ Secure authentication over TLS'); -}); - -tap.test('CCMD-05: AUTH - should maintain auth state across multiple sends', async () => { - const persistentAuthClient = createSmtpClient({ - host: authServer.hostname, - port: authServer.port, - secure: false, // Start plain, upgrade with STARTTLS - tls: { - rejectUnauthorized: false // Accept self-signed certs for testing - }, - connectionTimeout: 5000, - auth: { - user: 'testuser', - pass: 'testpass' - } - }); - - await persistentAuthClient.verify(); - - // Send multiple emails without re-authenticating - for (let i = 0; i < 3; i++) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Persistent Auth Test ${i + 1}`, - text: `Email ${i + 1} using same auth session` - }); - - const result = await persistentAuthClient.sendMail(email); - expect(result.success).toBeTrue(); - } - - await persistentAuthClient.close(); - console.log('✅ Authentication state maintained across sends'); -}); - -tap.test('CCMD-05: AUTH - should handle auth with connection pooling', async () => { - const pooledAuthClient = createSmtpClient({ - host: authServer.hostname, - port: authServer.port, - secure: false, // Start plain, upgrade with STARTTLS - tls: { - rejectUnauthorized: false // Accept self-signed certs for testing - }, - pool: true, - maxConnections: 3, - connectionTimeout: 5000, - auth: { - user: 'testuser', - pass: 'testpass' - } - }); - - // Send concurrent emails with pooled authenticated connections - const promises = []; - for (let i = 0; i < 5; i++) { - const email = new Email({ - from: 'sender@example.com', - to: `recipient${i}@example.com`, - subject: `Pooled Auth Test ${i}`, - text: 'Testing auth with connection pooling' - }); - promises.push(pooledAuthClient.sendMail(email)); - } - - const results = await Promise.all(promises); - - // Debug output to understand failures - results.forEach((result, index) => { - if (!result.success) { - console.log(`❌ Email ${index} failed:`, result.error?.message); - } - }); - - const successCount = results.filter(r => r.success).length; - console.log(`📧 Sent ${successCount} of ${results.length} emails successfully`); - - const poolStatus = pooledAuthClient.getPoolStatus(); - console.log('📊 Auth pool status:', poolStatus); - - // Check that at least one email was sent (connection pooling might limit concurrent sends) - expect(successCount).toBeGreaterThan(0); - - await pooledAuthClient.close(); - console.log('✅ Authentication works with connection pooling'); -}); - -tap.test('cleanup - stop auth server', async () => { - await stopTestServer(authServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_commands/test.ccmd-06.command-pipelining.ts b/test/suite/smtpclient_commands/test.ccmd-06.command-pipelining.ts deleted file mode 100644 index 2765f54..0000000 --- a/test/suite/smtpclient_commands/test.ccmd-06.command-pipelining.ts +++ /dev/null @@ -1,233 +0,0 @@ -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; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2546, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CCMD-06: Check PIPELINING capability', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // The SmtpClient handles pipelining internally - // We can verify the server supports it by checking a successful send - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Pipelining Test', - text: 'Testing pipelining support' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - - // Server logs show PIPELINING is advertised - console.log('✅ Server supports PIPELINING (advertised in EHLO response)'); - - await smtpClient.close(); -}); - -tap.test('CCMD-06: Basic command pipelining', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Send email with multiple recipients to test pipelining - const email = new Email({ - from: 'sender@example.com', - to: ['recipient1@example.com', 'recipient2@example.com'], - subject: 'Multi-recipient Test', - text: 'Testing pipelining with multiple recipients' - }); - - const startTime = Date.now(); - const result = await smtpClient.sendMail(email); - const elapsed = Date.now() - startTime; - - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients.length).toEqual(2); - - console.log(`✅ Sent to ${result.acceptedRecipients.length} recipients in ${elapsed}ms`); - console.log('Pipelining improves performance by sending multiple commands without waiting'); - - await smtpClient.close(); -}); - -tap.test('CCMD-06: Pipelining with DATA command', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Send a normal email - pipelining is handled internally - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'DATA Command Test', - text: 'Testing pipelining up to DATA command' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - - console.log('✅ Commands pipelined up to DATA successfully'); - console.log('DATA command requires synchronous handling as per RFC'); - - await smtpClient.close(); -}); - -tap.test('CCMD-06: Pipelining error handling', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Send email with mix of valid and potentially problematic recipients - const email = new Email({ - from: 'sender@example.com', - to: [ - 'valid1@example.com', - 'valid2@example.com', - 'valid3@example.com' - ], - subject: 'Error Handling Test', - text: 'Testing pipelining error handling' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - - console.log(`✅ Handled ${result.acceptedRecipients.length} recipients`); - console.log('Pipelining handles errors gracefully'); - - await smtpClient.close(); -}); - -tap.test('CCMD-06: Pipelining performance comparison', async () => { - // Create two clients - both use pipelining by default when available - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Test with multiple recipients - const email = new Email({ - from: 'sender@example.com', - to: [ - 'recipient1@example.com', - 'recipient2@example.com', - 'recipient3@example.com', - 'recipient4@example.com', - 'recipient5@example.com' - ], - subject: 'Performance Test', - text: 'Testing performance with multiple recipients' - }); - - const startTime = Date.now(); - const result = await smtpClient.sendMail(email); - const elapsed = Date.now() - startTime; - - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients.length).toEqual(5); - - console.log(`✅ Sent to ${result.acceptedRecipients.length} recipients in ${elapsed}ms`); - console.log('Pipelining provides significant performance improvements'); - - await smtpClient.close(); -}); - -tap.test('CCMD-06: Pipelining with multiple recipients', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Send to many recipients - const recipients = Array.from({ length: 10 }, (_, i) => `recipient${i + 1}@example.com`); - - const email = new Email({ - from: 'sender@example.com', - to: recipients, - subject: 'Many Recipients Test', - text: 'Testing pipelining with many recipients' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients.length).toEqual(recipients.length); - - console.log(`✅ Successfully sent to ${result.acceptedRecipients.length} recipients`); - console.log('Pipelining efficiently handles multiple RCPT TO commands'); - - await smtpClient.close(); -}); - -tap.test('CCMD-06: Pipelining limits and buffering', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Test with a reasonable number of recipients - const recipients = Array.from({ length: 50 }, (_, i) => `user${i + 1}@example.com`); - - const email = new Email({ - from: 'sender@example.com', - to: recipients.slice(0, 20), // Use first 20 for TO - cc: recipients.slice(20, 35), // Next 15 for CC - bcc: recipients.slice(35), // Rest for BCC - subject: 'Buffering Test', - text: 'Testing pipelining limits and buffering' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - - const totalRecipients = email.to.length + email.cc.length + email.bcc.length; - console.log(`✅ Handled ${totalRecipients} total recipients`); - console.log('Pipelining respects server limits and buffers appropriately'); - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - await stopTestServer(testServer); - expect(testServer).toBeTruthy(); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_commands/test.ccmd-07.response-parsing.ts b/test/suite/smtpclient_commands/test.ccmd-07.response-parsing.ts deleted file mode 100644 index e9d236a..0000000 --- a/test/suite/smtpclient_commands/test.ccmd-07.response-parsing.ts +++ /dev/null @@ -1,243 +0,0 @@ -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; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2547, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CCMD-07: Parse successful send responses', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Response Test', - text: 'Testing response parsing' - }); - - const result = await smtpClient.sendMail(email); - - // Verify successful response parsing - expect(result.success).toBeTrue(); - expect(result.response).toBeTruthy(); - expect(result.messageId).toBeTruthy(); - - // The response should contain queue ID - expect(result.response).toInclude('queued'); - console.log(`✅ Parsed success response: ${result.response}`); - - await smtpClient.close(); -}); - -tap.test('CCMD-07: Parse multiple recipient responses', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Send to multiple recipients - const email = new Email({ - from: 'sender@example.com', - to: ['recipient1@example.com', 'recipient2@example.com', 'recipient3@example.com'], - subject: 'Multi-recipient Test', - text: 'Testing multiple recipient response parsing' - }); - - const result = await smtpClient.sendMail(email); - - // Verify parsing of multiple recipient responses - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients.length).toEqual(3); - expect(result.rejectedRecipients.length).toEqual(0); - - console.log(`✅ Accepted ${result.acceptedRecipients.length} recipients`); - console.log('Multiple RCPT TO responses parsed correctly'); - - await smtpClient.close(); -}); - -tap.test('CCMD-07: Parse error response codes', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Test with invalid email to trigger error - try { - const email = new Email({ - from: '', // Empty from should trigger error - to: 'recipient@example.com', - subject: 'Error Test', - text: 'Testing error response' - }); - - await smtpClient.sendMail(email); - expect(false).toBeTrue(); // Should not reach here - } catch (error: any) { - expect(error).toBeInstanceOf(Error); - expect(error.message).toBeTruthy(); - console.log(`✅ Error response parsed: ${error.message}`); - } - - await smtpClient.close(); -}); - -tap.test('CCMD-07: Parse enhanced status codes', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Normal send - server advertises ENHANCEDSTATUSCODES - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Enhanced Status Test', - text: 'Testing enhanced status code parsing' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - // Server logs show it advertises ENHANCEDSTATUSCODES in EHLO - console.log('✅ Server advertises ENHANCEDSTATUSCODES capability'); - console.log('Enhanced status codes are parsed automatically'); - - await smtpClient.close(); -}); - -tap.test('CCMD-07: Parse response timing and delays', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Measure response time - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Timing Test', - text: 'Testing response timing' - }); - - const startTime = Date.now(); - const result = await smtpClient.sendMail(email); - const elapsed = Date.now() - startTime; - - expect(result.success).toBeTrue(); - expect(elapsed).toBeGreaterThan(0); - expect(elapsed).toBeLessThan(5000); // Should complete within 5 seconds - - console.log(`✅ Response received and parsed in ${elapsed}ms`); - console.log('Client handles response timing appropriately'); - - await smtpClient.close(); -}); - -tap.test('CCMD-07: Parse envelope information', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const from = 'sender@example.com'; - const to = ['recipient1@example.com', 'recipient2@example.com']; - const cc = ['cc@example.com']; - const bcc = ['bcc@example.com']; - - const email = new Email({ - from, - to, - cc, - bcc, - subject: 'Envelope Test', - text: 'Testing envelope parsing' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.envelope).toBeTruthy(); - expect(result.envelope.from).toEqual(from); - expect(result.envelope.to).toBeArray(); - - // Envelope should include all recipients (to, cc, bcc) - const totalRecipients = to.length + cc.length + bcc.length; - expect(result.envelope.to.length).toEqual(totalRecipients); - - console.log(`✅ Envelope parsed with ${result.envelope.to.length} recipients`); - console.log('Envelope information correctly extracted from responses'); - - await smtpClient.close(); -}); - -tap.test('CCMD-07: Parse connection state responses', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Test verify() which checks connection state - const isConnected = await smtpClient.verify(); - expect(isConnected).toBeTrue(); - - console.log('✅ Connection verified through greeting and EHLO responses'); - - // Send email to test active connection - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'State Test', - text: 'Testing connection state' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - - console.log('✅ Connection state maintained throughout session'); - console.log('Response parsing handles connection state correctly'); - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - await stopTestServer(testServer); - expect(testServer).toBeTruthy(); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_commands/test.ccmd-08.rset-command.ts b/test/suite/smtpclient_commands/test.ccmd-08.rset-command.ts deleted file mode 100644 index bc8c96c..0000000 --- a/test/suite/smtpclient_commands/test.ccmd-08.rset-command.ts +++ /dev/null @@ -1,333 +0,0 @@ -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; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2548, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CCMD-08: Client handles transaction reset internally', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Send first email - const email1 = new Email({ - from: 'sender1@example.com', - to: 'recipient1@example.com', - subject: 'First Email', - text: 'This is the first email' - }); - - const result1 = await smtpClient.sendMail(email1); - expect(result1.success).toBeTrue(); - - // Send second email - client handles RSET internally if needed - const email2 = new Email({ - from: 'sender2@example.com', - to: 'recipient2@example.com', - subject: 'Second Email', - text: 'This is the second email' - }); - - const result2 = await smtpClient.sendMail(email2); - expect(result2.success).toBeTrue(); - - console.log('✅ Client handles transaction reset between emails'); - console.log('RSET is used internally to ensure clean state'); - - await smtpClient.close(); -}); - -tap.test('CCMD-08: Clean state after failed recipient', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Send email with multiple recipients - if one fails, RSET ensures clean state - const email = new Email({ - from: 'sender@example.com', - to: [ - 'valid1@example.com', - 'valid2@example.com', - 'valid3@example.com' - ], - subject: 'Multi-recipient Email', - text: 'Testing state management' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - - // All recipients should be accepted - expect(result.acceptedRecipients.length).toEqual(3); - - console.log('✅ State remains clean with multiple recipients'); - console.log('Internal RSET ensures proper transaction handling'); - - await smtpClient.close(); -}); - -tap.test('CCMD-08: Multiple emails in sequence', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Send multiple emails in sequence - const emails = [ - { - from: 'sender1@example.com', - to: 'recipient1@example.com', - subject: 'Email 1', - text: 'First email' - }, - { - from: 'sender2@example.com', - to: 'recipient2@example.com', - subject: 'Email 2', - text: 'Second email' - }, - { - from: 'sender3@example.com', - to: 'recipient3@example.com', - subject: 'Email 3', - text: 'Third email' - } - ]; - - for (const emailData of emails) { - const email = new Email(emailData); - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - } - - console.log('✅ Successfully sent multiple emails in sequence'); - console.log('RSET ensures clean state between each transaction'); - - await smtpClient.close(); -}); - -tap.test('CCMD-08: Connection pooling with clean state', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - pool: true, - maxConnections: 2, - connectionTimeout: 5000, - debug: true - }); - - // Send emails concurrently - const promises = Array.from({ length: 5 }, (_, i) => { - const email = new Email({ - from: `sender${i}@example.com`, - to: `recipient${i}@example.com`, - subject: `Pooled Email ${i}`, - text: `This is pooled email ${i}` - }); - return smtpClient.sendMail(email); - }); - - const results = await Promise.all(promises); - - // Check results and log any failures - results.forEach((result, index) => { - console.log(`Email ${index}: ${result.success ? '✅' : '❌'} ${!result.success ? result.error?.message : ''}`); - }); - - // With connection pooling, at least some emails should succeed - const successCount = results.filter(r => r.success).length; - console.log(`Successfully sent ${successCount} of ${results.length} emails`); - expect(successCount).toBeGreaterThan(0); - - console.log('✅ Connection pool maintains clean state'); - console.log('RSET ensures each pooled connection starts fresh'); - - await smtpClient.close(); -}); - -tap.test('CCMD-08: Error recovery with state reset', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // First, try with invalid sender (should fail early) - try { - const badEmail = new Email({ - from: '', // Invalid - to: 'recipient@example.com', - subject: 'Bad Email', - text: 'This should fail' - }); - await smtpClient.sendMail(badEmail); - } catch (error) { - // Expected to fail - console.log('✅ Invalid email rejected as expected'); - } - - // Now send a valid email - should work fine - const goodEmail = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Good Email', - text: 'This should succeed' - }); - - const result = await smtpClient.sendMail(goodEmail); - expect(result.success).toBeTrue(); - - console.log('✅ State recovered after error'); - console.log('RSET ensures clean state after failures'); - - await smtpClient.close(); -}); - -tap.test('CCMD-08: Verify command maintains session', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // verify() creates temporary connection - const verified1 = await smtpClient.verify(); - expect(verified1).toBeTrue(); - - // Send email after verify - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'After Verify', - text: 'Email after verification' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - - // verify() again - const verified2 = await smtpClient.verify(); - expect(verified2).toBeTrue(); - - console.log('✅ Verify operations maintain clean session state'); - console.log('Each operation ensures proper state management'); - - await smtpClient.close(); -}); - -tap.test('CCMD-08: Rapid sequential sends', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Send emails rapidly - const count = 10; - const startTime = Date.now(); - - for (let i = 0; i < count; i++) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Rapid Email ${i}`, - text: `Rapid test email ${i}` - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - } - - const elapsed = Date.now() - startTime; - const avgTime = elapsed / count; - - console.log(`✅ Sent ${count} emails in ${elapsed}ms`); - console.log(`Average time per email: ${avgTime.toFixed(2)}ms`); - console.log('RSET maintains efficiency in rapid sends'); - - await smtpClient.close(); -}); - -tap.test('CCMD-08: State isolation between clients', async () => { - // Create two separate clients - const client1 = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - const client2 = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Send from both clients - const email1 = new Email({ - from: 'client1@example.com', - to: 'recipient1@example.com', - subject: 'From Client 1', - text: 'Email from client 1' - }); - - const email2 = new Email({ - from: 'client2@example.com', - to: 'recipient2@example.com', - subject: 'From Client 2', - text: 'Email from client 2' - }); - - // Send concurrently - const [result1, result2] = await Promise.all([ - client1.sendMail(email1), - client2.sendMail(email2) - ]); - - expect(result1.success).toBeTrue(); - expect(result2.success).toBeTrue(); - - console.log('✅ Each client maintains isolated state'); - console.log('RSET ensures no cross-contamination'); - - await client1.close(); - await client2.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - await stopTestServer(testServer); - expect(testServer).toBeTruthy(); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_commands/test.ccmd-09.noop-command.ts b/test/suite/smtpclient_commands/test.ccmd-09.noop-command.ts deleted file mode 100644 index 4e0967a..0000000 --- a/test/suite/smtpclient_commands/test.ccmd-09.noop-command.ts +++ /dev/null @@ -1,339 +0,0 @@ -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 test SMTP server', async () => { - testServer = await startTestServer({ - port: 2549, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CCMD-09: Connection keepalive test', async () => { - // NOOP is used internally for keepalive - test that connections remain active - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 10000, - greetingTimeout: 5000, - socketTimeout: 10000 - }); - - // Send an initial email to establish connection - const email1 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Initial connection test', - text: 'Testing connection establishment' - }); - - await smtpClient.sendMail(email1); - console.log('First email sent successfully'); - - // Wait 5 seconds (connection should stay alive with internal NOOP) - await new Promise(resolve => setTimeout(resolve, 5000)); - - // Send another email on the same connection - const email2 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Keepalive test', - text: 'Testing connection after delay' - }); - - await smtpClient.sendMail(email2); - console.log('Second email sent successfully after 5 second delay'); -}); - -tap.test('CCMD-09: Multiple emails in sequence', async () => { - // Test that client can handle multiple emails without issues - // Internal NOOP commands may be used between transactions - - const emails = []; - for (let i = 0; i < 5; i++) { - emails.push(new Email({ - from: 'sender@example.com', - to: [`recipient${i}@example.com`], - subject: `Sequential email ${i + 1}`, - text: `This is email number ${i + 1}` - })); - } - - console.log('Sending 5 emails in sequence...'); - - for (let i = 0; i < emails.length; i++) { - await smtpClient.sendMail(emails[i]); - console.log(`Email ${i + 1} sent successfully`); - - // Small delay between emails - await new Promise(resolve => setTimeout(resolve, 500)); - } - - console.log('All emails sent successfully'); -}); - -tap.test('CCMD-09: Rapid email sending', async () => { - // Test rapid email sending without delays - // Internal connection management should handle this properly - - const emailCount = 10; - const emails = []; - - for (let i = 0; i < emailCount; i++) { - emails.push(new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Rapid email ${i + 1}`, - text: `Rapid fire email number ${i + 1}` - })); - } - - console.log(`Sending ${emailCount} emails rapidly...`); - const startTime = Date.now(); - - // Send all emails as fast as possible - for (const email of emails) { - await smtpClient.sendMail(email); - } - - const elapsed = Date.now() - startTime; - console.log(`All ${emailCount} emails sent in ${elapsed}ms`); - console.log(`Average: ${(elapsed / emailCount).toFixed(2)}ms per email`); -}); - -tap.test('CCMD-09: Long-lived connection test', async () => { - // Test that connection stays alive over extended period - // SmtpClient should use internal keepalive mechanisms - - console.log('Testing connection over 10 seconds with periodic emails...'); - - const testDuration = 10000; - const emailInterval = 2500; - const iterations = Math.floor(testDuration / emailInterval); - - for (let i = 0; i < iterations; i++) { - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Keepalive test ${i + 1}`, - text: `Testing connection keepalive - email ${i + 1}` - }); - - const startTime = Date.now(); - await smtpClient.sendMail(email); - const elapsed = Date.now() - startTime; - - console.log(`Email ${i + 1} sent in ${elapsed}ms`); - - if (i < iterations - 1) { - await new Promise(resolve => setTimeout(resolve, emailInterval)); - } - } - - console.log('Connection remained stable over 10 seconds'); -}); - -tap.test('CCMD-09: Connection pooling behavior', async () => { - // Test connection pooling with different email patterns - // Internal NOOP may be used to maintain pool connections - - const testPatterns = [ - { count: 3, delay: 0, desc: 'Burst of 3 emails' }, - { count: 2, delay: 1000, desc: '2 emails with 1s delay' }, - { count: 1, delay: 3000, desc: '1 email after 3s delay' } - ]; - - for (const pattern of testPatterns) { - console.log(`\nTesting: ${pattern.desc}`); - - if (pattern.delay > 0) { - await new Promise(resolve => setTimeout(resolve, pattern.delay)); - } - - for (let i = 0; i < pattern.count; i++) { - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `${pattern.desc} - Email ${i + 1}`, - text: 'Testing connection pooling behavior' - }); - - await smtpClient.sendMail(email); - } - - console.log(`Completed: ${pattern.desc}`); - } -}); - -tap.test('CCMD-09: Email sending performance', async () => { - // Measure email sending performance - // Connection management (including internal NOOP) affects timing - - const measurements = 20; - const times: number[] = []; - - console.log(`Measuring performance over ${measurements} emails...`); - - for (let i = 0; i < measurements; i++) { - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Performance test ${i + 1}`, - text: 'Measuring email sending performance' - }); - - const startTime = Date.now(); - await smtpClient.sendMail(email); - const elapsed = Date.now() - startTime; - times.push(elapsed); - } - - // Calculate statistics - const avgTime = times.reduce((a, b) => a + b, 0) / times.length; - const minTime = Math.min(...times); - const maxTime = Math.max(...times); - - // Calculate standard deviation - const variance = times.reduce((sum, time) => sum + Math.pow(time - avgTime, 2), 0) / times.length; - const stdDev = Math.sqrt(variance); - - console.log(`\nPerformance analysis (${measurements} emails):`); - console.log(` Average: ${avgTime.toFixed(2)}ms`); - console.log(` Min: ${minTime}ms`); - console.log(` Max: ${maxTime}ms`); - console.log(` Std Dev: ${stdDev.toFixed(2)}ms`); - - // First email might be slower due to connection establishment - const avgWithoutFirst = times.slice(1).reduce((a, b) => a + b, 0) / (times.length - 1); - console.log(` Average (excl. first): ${avgWithoutFirst.toFixed(2)}ms`); - - // Performance should be reasonable - expect(avgTime).toBeLessThan(200); -}); - -tap.test('CCMD-09: Email with NOOP in content', async () => { - // Test that NOOP as email content doesn't affect delivery - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Email containing NOOP', - text: `This email contains SMTP commands as content: - -NOOP -HELO test -MAIL FROM: - -These should be treated as plain text, not commands. -The word NOOP appears multiple times in this email. - -NOOP is used internally by SMTP for keepalive.` - }); - - await smtpClient.sendMail(email); - console.log('Email with NOOP content sent successfully'); - - // Send another email to verify connection still works - const email2 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Follow-up email', - text: 'Verifying connection still works after NOOP content' - }); - - await smtpClient.sendMail(email2); - console.log('Follow-up email sent successfully'); -}); - -tap.test('CCMD-09: Concurrent email sending', async () => { - // Test concurrent email sending - // Connection pooling and internal management should handle this - - const concurrentCount = 5; - const emails = []; - - for (let i = 0; i < concurrentCount; i++) { - emails.push(new Email({ - from: 'sender@example.com', - to: [`recipient${i}@example.com`], - subject: `Concurrent email ${i + 1}`, - text: `Testing concurrent email sending - message ${i + 1}` - })); - } - - console.log(`Sending ${concurrentCount} emails concurrently...`); - const startTime = Date.now(); - - // Send all emails concurrently - try { - await Promise.all(emails.map(email => smtpClient.sendMail(email))); - const elapsed = Date.now() - startTime; - console.log(`All ${concurrentCount} emails sent concurrently in ${elapsed}ms`); - } catch (error) { - // Concurrent sending might not be supported - that's OK - console.log('Concurrent sending not supported, falling back to sequential'); - for (const email of emails) { - await smtpClient.sendMail(email); - } - } -}); - -tap.test('CCMD-09: Connection recovery test', async () => { - // Test connection recovery and error handling - // SmtpClient should handle connection issues gracefully - - // Create a new client with shorter timeouts for testing - const testClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 3000, - socketTimeout: 3000 - }); - - // Send initial email - const email1 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Connection test 1', - text: 'Testing initial connection' - }); - - await testClient.sendMail(email1); - console.log('Initial email sent'); - - // Simulate long delay that might timeout connection - console.log('Waiting 5 seconds to test connection recovery...'); - await new Promise(resolve => setTimeout(resolve, 5000)); - - // Try to send another email - client should recover if needed - const email2 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Connection test 2', - text: 'Testing connection recovery' - }); - - try { - await testClient.sendMail(email2); - console.log('Email sent successfully after delay - connection recovered'); - } catch (error) { - console.log('Connection recovery failed (this might be expected):', error.message); - } -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_commands/test.ccmd-10.vrfy-expn.ts b/test/suite/smtpclient_commands/test.ccmd-10.vrfy-expn.ts deleted file mode 100644 index e3ae0cd..0000000 --- a/test/suite/smtpclient_commands/test.ccmd-10.vrfy-expn.ts +++ /dev/null @@ -1,457 +0,0 @@ -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'; -import { EmailValidator } from '../../../ts/mail/core/classes.emailvalidator.js'; - -let testServer: ITestServer; -let smtpClient: SmtpClient; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2550, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); - - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); -}); - -tap.test('CCMD-10: Email address validation', async () => { - // Test email address validation which is what VRFY conceptually does - const validator = new EmailValidator(); - - const testAddresses = [ - { address: 'user@example.com', expected: true }, - { address: 'postmaster@example.com', expected: true }, - { address: 'admin@example.com', expected: true }, - { address: 'user.name+tag@example.com', expected: true }, - { address: 'test@sub.domain.example.com', expected: true }, - { address: 'invalid@', expected: false }, - { address: '@example.com', expected: false }, - { address: 'not-an-email', expected: false }, - { address: '', expected: false }, - { address: 'user@', expected: false } - ]; - - console.log('Testing email address validation (VRFY equivalent):\n'); - - for (const test of testAddresses) { - const isValid = validator.isValidFormat(test.address); - expect(isValid).toEqual(test.expected); - console.log(`Address: "${test.address}" - Valid: ${isValid} (expected: ${test.expected})`); - } - - // Test sending to valid addresses - const validEmail = new Email({ - from: 'sender@example.com', - to: ['user@example.com'], - subject: 'Address validation test', - text: 'Testing address validation' - }); - - await smtpClient.sendMail(validEmail); - console.log('\nEmail sent successfully to validated address'); -}); - -tap.test('CCMD-10: Multiple recipient handling (EXPN equivalent)', async () => { - // Test multiple recipients which is conceptually similar to mailing list expansion - - console.log('Testing multiple recipient handling (EXPN equivalent):\n'); - - // Create email with multiple recipients (like a mailing list) - const multiRecipientEmail = new Email({ - from: 'sender@example.com', - to: [ - 'user1@example.com', - 'user2@example.com', - 'user3@example.com' - ], - cc: [ - 'cc1@example.com', - 'cc2@example.com' - ], - bcc: [ - 'bcc1@example.com' - ], - subject: 'Multi-recipient test (mailing list)', - text: 'Testing email distribution to multiple recipients' - }); - - const toAddresses = multiRecipientEmail.getToAddresses(); - const ccAddresses = multiRecipientEmail.getCcAddresses(); - const bccAddresses = multiRecipientEmail.getBccAddresses(); - - console.log(`To recipients: ${toAddresses.length}`); - toAddresses.forEach(addr => console.log(` - ${addr}`)); - - console.log(`\nCC recipients: ${ccAddresses.length}`); - ccAddresses.forEach(addr => console.log(` - ${addr}`)); - - console.log(`\nBCC recipients: ${bccAddresses.length}`); - bccAddresses.forEach(addr => console.log(` - ${addr}`)); - - console.log(`\nTotal recipients: ${toAddresses.length + ccAddresses.length + bccAddresses.length}`); - - // Send the email - await smtpClient.sendMail(multiRecipientEmail); - console.log('\nEmail sent successfully to all recipients'); -}); - -tap.test('CCMD-10: Email addresses with display names', async () => { - // Test email addresses with display names (full names) - - console.log('Testing email addresses with display names:\n'); - - const fullNameTests = [ - { from: '"John Doe" ', expectedAddress: 'john@example.com' }, - { from: '"Smith, John" ', expectedAddress: 'john.smith@example.com' }, - { from: 'Mary Johnson ', expectedAddress: 'mary@example.com' }, - { from: '', expectedAddress: 'bob@example.com' } - ]; - - for (const test of fullNameTests) { - const email = new Email({ - from: test.from, - to: ['recipient@example.com'], - subject: 'Display name test', - text: `Testing from: ${test.from}` - }); - - const fromAddress = email.getFromAddress(); - console.log(`Full: "${test.from}"`); - console.log(`Extracted: "${fromAddress}"`); - expect(fromAddress).toEqual(test.expectedAddress); - - await smtpClient.sendMail(email); - console.log('Email sent successfully\n'); - } -}); - -tap.test('CCMD-10: Email validation security', async () => { - // Test security aspects of email validation - - console.log('Testing email validation security considerations:\n'); - - // Test common system/role addresses that should be handled carefully - const systemAddresses = [ - 'root@example.com', - 'admin@example.com', - 'administrator@example.com', - 'webmaster@example.com', - 'hostmaster@example.com', - 'abuse@example.com', - 'postmaster@example.com', - 'noreply@example.com' - ]; - - const validator = new EmailValidator(); - - console.log('Checking if addresses are role accounts:'); - for (const addr of systemAddresses) { - const validationResult = await validator.validate(addr, { checkRole: true, checkMx: false }); - console.log(` ${addr}: ${validationResult.details?.role ? 'Role account' : 'Not a role account'} (format valid: ${validationResult.details?.formatValid})`); - } - - // Test that we don't expose information about which addresses exist - console.log('\nTesting information disclosure prevention:'); - - try { - // Try sending to a non-existent address - const testEmail = new Email({ - from: 'sender@example.com', - to: ['definitely-does-not-exist-12345@example.com'], - subject: 'Test', - text: 'Test' - }); - - await smtpClient.sendMail(testEmail); - console.log('Server accepted email (does not disclose non-existence)'); - } catch (error) { - console.log('Server rejected email:', error.message); - } - - console.log('\nSecurity best practice: Servers should not disclose address existence'); -}); - -tap.test('CCMD-10: Validation during email sending', async () => { - // Test that validation doesn't interfere with email sending - - console.log('Testing validation during email transaction:\n'); - - const validator = new EmailValidator(); - - // Create a series of emails with validation between them - const emails = [ - { - from: 'sender1@example.com', - to: ['recipient1@example.com'], - subject: 'First email', - text: 'Testing validation during transaction' - }, - { - from: 'sender2@example.com', - to: ['recipient2@example.com', 'recipient3@example.com'], - subject: 'Second email', - text: 'Multiple recipients' - }, - { - from: '"Test User" ', - to: ['recipient4@example.com'], - subject: 'Third email', - text: 'Display name test' - } - ]; - - for (let i = 0; i < emails.length; i++) { - const emailData = emails[i]; - - // Validate addresses before sending - console.log(`Email ${i + 1}:`); - const fromAddr = emailData.from.includes('<') ? emailData.from.match(/<([^>]+)>/)?.[1] || emailData.from : emailData.from; - console.log(` From: ${emailData.from} - Valid: ${validator.isValidFormat(fromAddr)}`); - - for (const to of emailData.to) { - console.log(` To: ${to} - Valid: ${validator.isValidFormat(to)}`); - } - - // Create and send email - const email = new Email(emailData); - await smtpClient.sendMail(email); - console.log(` Sent successfully\n`); - } - - console.log('All emails sent successfully with validation'); -}); - -tap.test('CCMD-10: Special characters in email addresses', async () => { - // Test email addresses with special characters - - console.log('Testing email addresses with special characters:\n'); - - const validator = new EmailValidator(); - - const specialAddresses = [ - { address: 'user+tag@example.com', shouldBeValid: true, description: 'Plus addressing' }, - { address: 'first.last@example.com', shouldBeValid: true, description: 'Dots in local part' }, - { address: 'user_name@example.com', shouldBeValid: true, description: 'Underscore' }, - { address: 'user-name@example.com', shouldBeValid: true, description: 'Hyphen' }, - { address: '"quoted string"@example.com', shouldBeValid: true, description: 'Quoted string' }, - { address: 'user@sub.domain.example.com', shouldBeValid: true, description: 'Subdomain' }, - { address: 'user@example.co.uk', shouldBeValid: true, description: 'Multi-part TLD' }, - { address: 'user..name@example.com', shouldBeValid: false, description: 'Double dots' }, - { address: '.user@example.com', shouldBeValid: false, description: 'Leading dot' }, - { address: 'user.@example.com', shouldBeValid: false, description: 'Trailing dot' } - ]; - - for (const test of specialAddresses) { - const isValid = validator.isValidFormat(test.address); - console.log(`${test.description}:`); - console.log(` Address: "${test.address}"`); - console.log(` Valid: ${isValid} (expected: ${test.shouldBeValid})`); - - if (test.shouldBeValid && isValid) { - // Try sending an email with this address - try { - const email = new Email({ - from: 'sender@example.com', - to: [test.address], - subject: 'Special character test', - text: `Testing special characters in: ${test.address}` - }); - - await smtpClient.sendMail(email); - console.log(` Email sent successfully`); - } catch (error) { - console.log(` Failed to send: ${error.message}`); - } - } - console.log(''); - } -}); - -tap.test('CCMD-10: Large recipient lists', async () => { - // Test handling of large recipient lists (similar to EXPN multi-line) - - console.log('Testing large recipient lists:\n'); - - // Create email with many recipients - const recipientCount = 20; - const toRecipients = []; - const ccRecipients = []; - - for (let i = 1; i <= recipientCount; i++) { - if (i <= 10) { - toRecipients.push(`user${i}@example.com`); - } else { - ccRecipients.push(`user${i}@example.com`); - } - } - - console.log(`Creating email with ${recipientCount} total recipients:`); - console.log(` To: ${toRecipients.length} recipients`); - console.log(` CC: ${ccRecipients.length} recipients`); - - const largeListEmail = new Email({ - from: 'sender@example.com', - to: toRecipients, - cc: ccRecipients, - subject: 'Large distribution list test', - text: `This email is being sent to ${recipientCount} recipients total` - }); - - // Show extracted addresses - const allTo = largeListEmail.getToAddresses(); - const allCc = largeListEmail.getCcAddresses(); - - console.log('\nExtracted addresses:'); - console.log(`To (first 3): ${allTo.slice(0, 3).join(', ')}...`); - console.log(`CC (first 3): ${allCc.slice(0, 3).join(', ')}...`); - - // Send the email - const startTime = Date.now(); - await smtpClient.sendMail(largeListEmail); - const elapsed = Date.now() - startTime; - - console.log(`\nEmail sent to all ${recipientCount} recipients in ${elapsed}ms`); - console.log(`Average: ${(elapsed / recipientCount).toFixed(2)}ms per recipient`); -}); - -tap.test('CCMD-10: Email validation performance', async () => { - // Test validation performance - - console.log('Testing email validation performance:\n'); - - const validator = new EmailValidator(); - const testCount = 1000; - - // Generate test addresses - const testAddresses = []; - for (let i = 0; i < testCount; i++) { - testAddresses.push(`user${i}@example${i % 10}.com`); - } - - // Time validation - const startTime = Date.now(); - let validCount = 0; - - for (const address of testAddresses) { - if (validator.isValidFormat(address)) { - validCount++; - } - } - - const elapsed = Date.now() - startTime; - const rate = (testCount / elapsed) * 1000; - - console.log(`Validated ${testCount} addresses in ${elapsed}ms`); - console.log(`Rate: ${rate.toFixed(0)} validations/second`); - console.log(`Valid addresses: ${validCount}/${testCount}`); - - // Test rapid email sending to see if there's rate limiting - console.log('\nTesting rapid email sending:'); - - const emailCount = 10; - const sendStartTime = Date.now(); - let sentCount = 0; - - for (let i = 0; i < emailCount; i++) { - try { - const email = new Email({ - from: 'sender@example.com', - to: [`recipient${i}@example.com`], - subject: `Rate test ${i + 1}`, - text: 'Testing rate limits' - }); - - await smtpClient.sendMail(email); - sentCount++; - } catch (error) { - console.log(`Rate limit hit at email ${i + 1}: ${error.message}`); - break; - } - } - - const sendElapsed = Date.now() - sendStartTime; - const sendRate = (sentCount / sendElapsed) * 1000; - - console.log(`Sent ${sentCount}/${emailCount} emails in ${sendElapsed}ms`); - console.log(`Rate: ${sendRate.toFixed(2)} emails/second`); -}); - -tap.test('CCMD-10: Email validation error handling', async () => { - // Test error handling for invalid email addresses - - console.log('Testing email validation error handling:\n'); - - const validator = new EmailValidator(); - - const errorTests = [ - { address: null, description: 'Null address' }, - { address: undefined, description: 'Undefined address' }, - { address: '', description: 'Empty string' }, - { address: ' ', description: 'Whitespace only' }, - { address: '@', description: 'Just @ symbol' }, - { address: 'user@', description: 'Missing domain' }, - { address: '@domain.com', description: 'Missing local part' }, - { address: 'user@@domain.com', description: 'Double @ symbol' }, - { address: 'user@domain@com', description: 'Multiple @ symbols' }, - { address: 'user space@domain.com', description: 'Space in local part' }, - { address: 'user@domain .com', description: 'Space in domain' }, - { address: 'x'.repeat(256) + '@domain.com', description: 'Very long local part' }, - { address: 'user@' + 'x'.repeat(256) + '.com', description: 'Very long domain' } - ]; - - for (const test of errorTests) { - console.log(`${test.description}:`); - console.log(` Input: "${test.address}"`); - - // Test validation - let isValid = false; - try { - isValid = validator.isValidFormat(test.address as any); - } catch (error) { - console.log(` Validation threw: ${error.message}`); - } - - if (!isValid) { - console.log(` Correctly rejected as invalid`); - } else { - console.log(` WARNING: Accepted as valid!`); - } - - // Try to send email with invalid address - if (test.address) { - try { - const email = new Email({ - from: 'sender@example.com', - to: [test.address], - subject: 'Error test', - text: 'Testing invalid address' - }); - - await smtpClient.sendMail(email); - console.log(` WARNING: Email sent with invalid address!`); - } catch (error) { - console.log(` Email correctly rejected: ${error.message}`); - } - } - console.log(''); - } -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_commands/test.ccmd-11.help-command.ts b/test/suite/smtpclient_commands/test.ccmd-11.help-command.ts deleted file mode 100644 index 227ee7a..0000000 --- a/test/suite/smtpclient_commands/test.ccmd-11.help-command.ts +++ /dev/null @@ -1,409 +0,0 @@ -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 test SMTP server', async () => { - testServer = await startTestServer({ - port: 2551, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CCMD-11: Server capabilities discovery', async () => { - // Test server capabilities which is what HELP provides info about - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - console.log('Testing server capabilities discovery (HELP equivalent):\n'); - - // Send a test email to see server capabilities in action - const testEmail = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Capability test', - text: 'Testing server capabilities' - }); - - await smtpClient.sendMail(testEmail); - console.log('Email sent successfully - server supports basic SMTP commands'); - - // Test different configurations to understand server behavior - const capabilities = { - basicSMTP: true, - multiplRecipients: false, - largeMessages: false, - internationalDomains: false - }; - - // Test multiple recipients - try { - const multiEmail = new Email({ - from: 'sender@example.com', - to: ['recipient1@example.com', 'recipient2@example.com', 'recipient3@example.com'], - subject: 'Multi-recipient test', - text: 'Testing multiple recipients' - }); - await smtpClient.sendMail(multiEmail); - capabilities.multiplRecipients = true; - console.log('✓ Server supports multiple recipients'); - } catch (error) { - console.log('✗ Multiple recipients not supported'); - } - - console.log('\nDetected capabilities:', capabilities); -}); - -tap.test('CCMD-11: Error message diagnostics', async () => { - // Test error messages which HELP would explain - console.log('Testing error message diagnostics:\n'); - - const errorTests = [ - { - description: 'Invalid sender address', - email: { - from: 'invalid-sender', - to: ['recipient@example.com'], - subject: 'Test', - text: 'Test' - } - }, - { - description: 'Empty recipient list', - email: { - from: 'sender@example.com', - to: [], - subject: 'Test', - text: 'Test' - } - }, - { - description: 'Null subject', - email: { - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: null as any, - text: 'Test' - } - } - ]; - - for (const test of errorTests) { - console.log(`Testing: ${test.description}`); - try { - const email = new Email(test.email); - await smtpClient.sendMail(email); - console.log(' Unexpectedly succeeded'); - } catch (error) { - console.log(` Error: ${error.message}`); - console.log(` This would be explained in HELP documentation`); - } - console.log(''); - } -}); - -tap.test('CCMD-11: Connection configuration help', async () => { - // Test different connection configurations - console.log('Testing connection configurations:\n'); - - const configs = [ - { - name: 'Standard connection', - config: { - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }, - shouldWork: true - }, - { - name: 'With greeting timeout', - config: { - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - greetingTimeout: 3000 - }, - shouldWork: true - }, - { - name: 'With socket timeout', - config: { - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - socketTimeout: 10000 - }, - shouldWork: true - } - ]; - - for (const testConfig of configs) { - console.log(`Testing: ${testConfig.name}`); - try { - const client = createSmtpClient(testConfig.config); - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Config test', - text: `Testing ${testConfig.name}` - }); - - await client.sendMail(email); - console.log(` ✓ Configuration works`); - } catch (error) { - console.log(` ✗ Error: ${error.message}`); - } - } -}); - -tap.test('CCMD-11: Protocol flow documentation', async () => { - // Document the protocol flow (what HELP would explain) - console.log('SMTP Protocol Flow (as HELP would document):\n'); - - const protocolSteps = [ - '1. Connection established', - '2. Server sends greeting (220)', - '3. Client sends EHLO', - '4. Server responds with capabilities', - '5. Client sends MAIL FROM', - '6. Server accepts sender (250)', - '7. Client sends RCPT TO', - '8. Server accepts recipient (250)', - '9. Client sends DATA', - '10. Server ready for data (354)', - '11. Client sends message content', - '12. Client sends . to end', - '13. Server accepts message (250)', - '14. Client can send more or QUIT' - ]; - - console.log('Standard SMTP transaction flow:'); - protocolSteps.forEach(step => console.log(` ${step}`)); - - // Demonstrate the flow - console.log('\nDemonstrating flow with actual email:'); - const email = new Email({ - from: 'demo@example.com', - to: ['recipient@example.com'], - subject: 'Protocol flow demo', - text: 'Demonstrating SMTP protocol flow' - }); - - await smtpClient.sendMail(email); - console.log('✓ Protocol flow completed successfully'); -}); - -tap.test('CCMD-11: Command availability matrix', async () => { - // Test what commands are available (HELP info) - console.log('Testing command availability:\n'); - - // Test various email features to determine support - const features = { - plainText: { supported: false, description: 'Plain text emails' }, - htmlContent: { supported: false, description: 'HTML emails' }, - attachments: { supported: false, description: 'File attachments' }, - multipleRecipients: { supported: false, description: 'Multiple recipients' }, - ccRecipients: { supported: false, description: 'CC recipients' }, - bccRecipients: { supported: false, description: 'BCC recipients' }, - customHeaders: { supported: false, description: 'Custom headers' }, - priorities: { supported: false, description: 'Email priorities' } - }; - - // Test plain text - try { - await smtpClient.sendMail(new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Plain text test', - text: 'Plain text content' - })); - features.plainText.supported = true; - } catch (e) {} - - // Test HTML - try { - await smtpClient.sendMail(new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'HTML test', - html: '

HTML content

' - })); - features.htmlContent.supported = true; - } catch (e) {} - - // Test multiple recipients - try { - await smtpClient.sendMail(new Email({ - from: 'sender@example.com', - to: ['recipient1@example.com', 'recipient2@example.com'], - subject: 'Multiple recipients test', - text: 'Test' - })); - features.multipleRecipients.supported = true; - } catch (e) {} - - // Test CC - try { - await smtpClient.sendMail(new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - cc: ['cc@example.com'], - subject: 'CC test', - text: 'Test' - })); - features.ccRecipients.supported = true; - } catch (e) {} - - // Test BCC - try { - await smtpClient.sendMail(new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - bcc: ['bcc@example.com'], - subject: 'BCC test', - text: 'Test' - })); - features.bccRecipients.supported = true; - } catch (e) {} - - console.log('Feature support matrix:'); - Object.entries(features).forEach(([key, value]) => { - console.log(` ${value.description}: ${value.supported ? '✓ Supported' : '✗ Not supported'}`); - }); -}); - -tap.test('CCMD-11: Error code reference', async () => { - // Document error codes (HELP would explain these) - console.log('SMTP Error Code Reference (as HELP would provide):\n'); - - const errorCodes = [ - { code: '220', meaning: 'Service ready', type: 'Success' }, - { code: '221', meaning: 'Service closing transmission channel', type: 'Success' }, - { code: '250', meaning: 'Requested action completed', type: 'Success' }, - { code: '251', meaning: 'User not local; will forward', type: 'Success' }, - { code: '354', meaning: 'Start mail input', type: 'Intermediate' }, - { code: '421', meaning: 'Service not available', type: 'Temporary failure' }, - { code: '450', meaning: 'Mailbox unavailable', type: 'Temporary failure' }, - { code: '451', meaning: 'Local error in processing', type: 'Temporary failure' }, - { code: '452', meaning: 'Insufficient storage', type: 'Temporary failure' }, - { code: '500', meaning: 'Syntax error', type: 'Permanent failure' }, - { code: '501', meaning: 'Syntax error in parameters', type: 'Permanent failure' }, - { code: '502', meaning: 'Command not implemented', type: 'Permanent failure' }, - { code: '503', meaning: 'Bad sequence of commands', type: 'Permanent failure' }, - { code: '550', meaning: 'Mailbox not found', type: 'Permanent failure' }, - { code: '551', meaning: 'User not local', type: 'Permanent failure' }, - { code: '552', meaning: 'Storage allocation exceeded', type: 'Permanent failure' }, - { code: '553', meaning: 'Mailbox name not allowed', type: 'Permanent failure' }, - { code: '554', meaning: 'Transaction failed', type: 'Permanent failure' } - ]; - - console.log('Common SMTP response codes:'); - errorCodes.forEach(({ code, meaning, type }) => { - console.log(` ${code} - ${meaning} (${type})`); - }); - - // Test triggering some errors - console.log('\nDemonstrating error handling:'); - - // Invalid email format - try { - await smtpClient.sendMail(new Email({ - from: 'invalid-email-format', - to: ['recipient@example.com'], - subject: 'Test', - text: 'Test' - })); - } catch (error) { - console.log(`Invalid format error: ${error.message}`); - } -}); - -tap.test('CCMD-11: Debugging assistance', async () => { - // Test debugging features (HELP assists with debugging) - console.log('Debugging assistance features:\n'); - - // Create client with debug enabled - const debugClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - console.log('Sending email with debug mode enabled:'); - console.log('(Debug output would show full SMTP conversation)\n'); - - const debugEmail = new Email({ - from: 'debug@example.com', - to: ['recipient@example.com'], - subject: 'Debug test', - text: 'Testing with debug mode' - }); - - // The debug output will be visible in the console - await debugClient.sendMail(debugEmail); - - console.log('\nDebug mode helps troubleshoot:'); - console.log('- Connection issues'); - console.log('- Authentication problems'); - console.log('- Message formatting errors'); - console.log('- Server response codes'); - console.log('- Protocol violations'); -}); - -tap.test('CCMD-11: Performance benchmarks', async () => { - // Performance info (HELP might mention performance tips) - console.log('Performance benchmarks:\n'); - - const messageCount = 10; - const startTime = Date.now(); - - for (let i = 0; i < messageCount; i++) { - const email = new Email({ - from: 'perf@example.com', - to: ['recipient@example.com'], - subject: `Performance test ${i + 1}`, - text: 'Testing performance' - }); - - await smtpClient.sendMail(email); - } - - const totalTime = Date.now() - startTime; - const avgTime = totalTime / messageCount; - - console.log(`Sent ${messageCount} emails in ${totalTime}ms`); - console.log(`Average time per email: ${avgTime.toFixed(2)}ms`); - console.log(`Throughput: ${(1000 / avgTime).toFixed(2)} emails/second`); - - console.log('\nPerformance tips:'); - console.log('- Use connection pooling for multiple emails'); - console.log('- Enable pipelining when supported'); - console.log('- Batch recipients when possible'); - console.log('- Use appropriate timeouts'); - console.log('- Monitor connection limits'); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_connection/test.ccm-01.basic-tcp-connection.ts b/test/suite/smtpclient_connection/test.ccm-01.basic-tcp-connection.ts deleted file mode 100644 index 569ccf5..0000000 --- a/test/suite/smtpclient_connection/test.ccm-01.basic-tcp-connection.ts +++ /dev/null @@ -1,150 +0,0 @@ -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'; - -let testServer: ITestServer; -let smtpClient: SmtpClient; - -tap.test('setup - start SMTP server for basic connection test', async () => { - testServer = await startTestServer({ - port: 2525, - tlsEnabled: false, - authRequired: false - }); - - expect(testServer.port).toEqual(2525); -}); - -tap.test('CCM-01: Basic TCP Connection - should connect to SMTP server', async () => { - const startTime = Date.now(); - - try { - // Create SMTP client - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Verify connection - const isConnected = await smtpClient.verify(); - expect(isConnected).toBeTrue(); - - const duration = Date.now() - startTime; - console.log(`✅ Basic TCP connection established in ${duration}ms`); - - } catch (error) { - const duration = Date.now() - startTime; - console.error(`❌ Basic TCP connection failed after ${duration}ms:`, error); - throw error; - } -}); - -tap.test('CCM-01: Basic TCP Connection - should report connection status', async () => { - // After verify(), connection is closed, so isConnected should be false - expect(smtpClient.isConnected()).toBeFalse(); - - const poolStatus = smtpClient.getPoolStatus(); - console.log('📊 Connection pool status:', poolStatus); - - // After verify(), pool should be empty - expect(poolStatus.total).toEqual(0); - expect(poolStatus.active).toEqual(0); - - // Test that connection status is correct during actual email send - const email = new (await import('../../../ts/mail/core/classes.email.js')).Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Connection status test', - text: 'Testing connection status' - }); - - // During sendMail, connection should be established - const sendPromise = smtpClient.sendMail(email); - - // Check status while sending (might be too fast to catch) - const duringStatus = smtpClient.getPoolStatus(); - console.log('📊 Pool status during send:', duringStatus); - - await sendPromise; - - // After send, connection might be pooled or closed - const afterStatus = smtpClient.getPoolStatus(); - console.log('📊 Pool status after send:', afterStatus); -}); - -tap.test('CCM-01: Basic TCP Connection - should handle multiple connect/disconnect cycles', async () => { - // Close existing connection - await smtpClient.close(); - expect(smtpClient.isConnected()).toBeFalse(); - - // Create new client and test reconnection - for (let i = 0; i < 3; i++) { - const cycleClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - const isConnected = await cycleClient.verify(); - expect(isConnected).toBeTrue(); - - await cycleClient.close(); - expect(cycleClient.isConnected()).toBeFalse(); - - console.log(`✅ Connection cycle ${i + 1} completed`); - } -}); - -tap.test('CCM-01: Basic TCP Connection - should fail with invalid host', async () => { - const invalidClient = createSmtpClient({ - host: 'invalid.host.that.does.not.exist', - port: 2525, - secure: false, - connectionTimeout: 3000 - }); - - // verify() returns false on connection failure, doesn't throw - const result = await invalidClient.verify(); - expect(result).toBeFalse(); - console.log('✅ Correctly failed to connect to invalid host'); - - await invalidClient.close(); -}); - -tap.test('CCM-01: Basic TCP Connection - should timeout on unresponsive port', async () => { - const startTime = Date.now(); - - const timeoutClient = createSmtpClient({ - host: testServer.hostname, - port: 9999, // Port that's not listening - secure: false, - connectionTimeout: 2000 - }); - - // verify() returns false on connection failure, doesn't throw - const result = await timeoutClient.verify(); - expect(result).toBeFalse(); - - const duration = Date.now() - startTime; - expect(duration).toBeLessThan(3000); // Should timeout within 3 seconds - console.log(`✅ Connection timeout working correctly (${duration}ms)`); - - await timeoutClient.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); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_connection/test.ccm-02.tls-connection.ts b/test/suite/smtpclient_connection/test.ccm-02.tls-connection.ts deleted file mode 100644 index a20bb9e..0000000 --- a/test/suite/smtpclient_connection/test.ccm-02.tls-connection.ts +++ /dev/null @@ -1,140 +0,0 @@ -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 with TLS', async () => { - testServer = await startTestServer({ - port: 2526, - tlsEnabled: true, - authRequired: false - }); - - expect(testServer.port).toEqual(2526); - expect(testServer.config.tlsEnabled).toBeTrue(); -}); - -tap.test('CCM-02: TLS Connection - should establish secure connection via STARTTLS', async () => { - const startTime = Date.now(); - - try { - // Create SMTP client with STARTTLS (not direct TLS) - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, // Start with plain connection - connectionTimeout: 10000, - tls: { - rejectUnauthorized: false // For self-signed test certificates - }, - debug: true - }); - - // Verify connection (will upgrade to TLS via STARTTLS) - const isConnected = await smtpClient.verify(); - expect(isConnected).toBeTrue(); - - const duration = Date.now() - startTime; - console.log(`✅ STARTTLS connection established in ${duration}ms`); - - } catch (error) { - const duration = Date.now() - startTime; - console.error(`❌ STARTTLS connection failed after ${duration}ms:`, error); - throw error; - } -}); - -tap.test('CCM-02: TLS Connection - should send email over secure connection', async () => { - const email = new Email({ - from: 'test@example.com', - to: 'recipient@example.com', - subject: 'TLS Connection Test', - text: 'This email was sent over a secure TLS connection', - html: '

This email was sent over a secure TLS connection

' - }); - - const result = await smtpClient.sendMail(email); - - expect(result).toBeTruthy(); - expect(result.success).toBeTrue(); - expect(result.messageId).toBeTruthy(); - - console.log(`✅ Email sent over TLS with message ID: ${result.messageId}`); -}); - -tap.test('CCM-02: TLS Connection - should reject invalid certificates when required', async () => { - // Create new client with strict certificate validation - const strictClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - tls: { - rejectUnauthorized: true // Strict validation - } - }); - - // Should fail with self-signed certificate - const result = await strictClient.verify(); - expect(result).toBeFalse(); - - console.log('✅ Correctly rejected self-signed certificate with strict validation'); - - await strictClient.close(); -}); - -tap.test('CCM-02: TLS Connection - should work with direct TLS if supported', async () => { - // Try direct TLS connection (might fail if server doesn't support it) - const directTlsClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: true, // Direct TLS from start - connectionTimeout: 5000, - tls: { - rejectUnauthorized: false - } - }); - - const result = await directTlsClient.verify(); - - if (result) { - console.log('✅ Direct TLS connection supported and working'); - } else { - console.log('ℹ️ Direct TLS not supported, STARTTLS is the way'); - } - - await directTlsClient.close(); -}); - -tap.test('CCM-02: TLS Connection - should verify TLS cipher suite', async () => { - // Send email and check connection details - const email = new Email({ - from: 'cipher-test@example.com', - to: 'recipient@example.com', - subject: 'TLS Cipher Test', - text: 'Testing TLS cipher suite' - }); - - // The actual cipher info would be in debug logs - console.log('ℹ️ TLS cipher information available in debug logs'); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - - console.log('✅ Email sent successfully over encrypted connection'); -}); - -tap.test('cleanup - close SMTP client', async () => { - if (smtpClient) { - await smtpClient.close(); - } -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_connection/test.ccm-03.starttls-upgrade.ts b/test/suite/smtpclient_connection/test.ccm-03.starttls-upgrade.ts deleted file mode 100644 index 6b52408..0000000 --- a/test/suite/smtpclient_connection/test.ccm-03.starttls-upgrade.ts +++ /dev/null @@ -1,208 +0,0 @@ -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 with STARTTLS support', async () => { - testServer = await startTestServer({ - port: 2528, - tlsEnabled: true, // Enables STARTTLS capability - authRequired: false - }); - - expect(testServer.port).toEqual(2528); -}); - -tap.test('CCM-03: STARTTLS Upgrade - should upgrade plain connection to TLS', async () => { - const startTime = Date.now(); - - try { - // Create SMTP client starting with plain connection - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, // Start with plain connection - connectionTimeout: 10000, - tls: { - rejectUnauthorized: false // For self-signed test certificates - }, - debug: true - }); - - // The client should automatically upgrade to TLS via STARTTLS - const isConnected = await smtpClient.verify(); - expect(isConnected).toBeTrue(); - - const duration = Date.now() - startTime; - console.log(`✅ STARTTLS upgrade completed in ${duration}ms`); - - } catch (error) { - const duration = Date.now() - startTime; - console.error(`❌ STARTTLS upgrade failed after ${duration}ms:`, error); - throw error; - } -}); - -tap.test('CCM-03: STARTTLS Upgrade - should send email after upgrade', async () => { - const email = new Email({ - from: 'test@example.com', - to: 'recipient@example.com', - subject: 'STARTTLS Upgrade Test', - text: 'This email was sent after STARTTLS upgrade', - html: '

This email was sent after STARTTLS upgrade

' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients).toContain('recipient@example.com'); - expect(result.rejectedRecipients.length).toEqual(0); - - console.log('✅ Email sent successfully after STARTTLS upgrade'); - console.log('📧 Message ID:', result.messageId); -}); - -tap.test('CCM-03: STARTTLS Upgrade - should handle servers without STARTTLS', async () => { - // Start a server without TLS support - const plainServer = await startTestServer({ - port: 2529, - tlsEnabled: false // No STARTTLS support - }); - - try { - const plainClient = createSmtpClient({ - host: plainServer.hostname, - port: plainServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Should still connect but without TLS - const isConnected = await plainClient.verify(); - expect(isConnected).toBeTrue(); - - // Send test email over plain connection - const email = new Email({ - from: 'test@example.com', - to: 'recipient@example.com', - subject: 'Plain Connection Test', - text: 'This email was sent over plain connection' - }); - - const result = await plainClient.sendMail(email); - expect(result.success).toBeTrue(); - - await plainClient.close(); - console.log('✅ Successfully handled server without STARTTLS'); - - } finally { - await stopTestServer(plainServer); - } -}); - -tap.test('CCM-03: STARTTLS Upgrade - should respect TLS options during upgrade', async () => { - const customTlsClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, // Start plain - connectionTimeout: 10000, - tls: { - rejectUnauthorized: false - // Removed specific TLS version and cipher requirements that might not be supported - } - }); - - const isConnected = await customTlsClient.verify(); - expect(isConnected).toBeTrue(); - - // Test that we can send email with custom TLS client - const email = new Email({ - from: 'tls-test@example.com', - to: 'recipient@example.com', - subject: 'Custom TLS Options Test', - text: 'Testing with custom TLS configuration' - }); - - const result = await customTlsClient.sendMail(email); - expect(result.success).toBeTrue(); - - await customTlsClient.close(); - console.log('✅ Custom TLS options applied during STARTTLS upgrade'); -}); - -tap.test('CCM-03: STARTTLS Upgrade - should handle upgrade failures gracefully', async () => { - // Create a scenario where STARTTLS might fail - // verify() returns false on failure, doesn't throw - - const strictTlsClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - tls: { - rejectUnauthorized: true, // Strict validation with self-signed cert - servername: 'wrong.hostname.com' // Wrong hostname - } - }); - - // Should return false due to certificate validation failure - const result = await strictTlsClient.verify(); - expect(result).toBeFalse(); - - await strictTlsClient.close(); - console.log('✅ STARTTLS upgrade failure handled gracefully'); -}); - -tap.test('CCM-03: STARTTLS Upgrade - should maintain connection state after upgrade', async () => { - const stateClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 10000, - tls: { - rejectUnauthorized: false - } - }); - - // verify() closes the connection after testing, so isConnected will be false - const verified = await stateClient.verify(); - expect(verified).toBeTrue(); - expect(stateClient.isConnected()).toBeFalse(); // Connection closed after verify - - // Send multiple emails to verify connection pooling works correctly - for (let i = 0; i < 3; i++) { - const email = new Email({ - from: 'test@example.com', - to: 'recipient@example.com', - subject: `STARTTLS State Test ${i + 1}`, - text: `Message ${i + 1} after STARTTLS upgrade` - }); - - const result = await stateClient.sendMail(email); - expect(result.success).toBeTrue(); - } - - // Check pool status to understand connection management - const poolStatus = stateClient.getPoolStatus(); - console.log('Connection pool status:', poolStatus); - - await stateClient.close(); - console.log('✅ Connection state maintained after STARTTLS upgrade'); -}); - -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(); \ No newline at end of file diff --git a/test/suite/smtpclient_connection/test.ccm-04.connection-pooling.ts b/test/suite/smtpclient_connection/test.ccm-04.connection-pooling.ts deleted file mode 100644 index be1944a..0000000 --- a/test/suite/smtpclient_connection/test.ccm-04.connection-pooling.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createPooledSmtpClient } 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 pooledClient: SmtpClient; - -tap.test('setup - start SMTP server for pooling test', async () => { - testServer = await startTestServer({ - port: 2530, - tlsEnabled: false, - authRequired: false, - maxConnections: 10 - }); - - expect(testServer.port).toEqual(2530); -}); - -tap.test('CCM-04: Connection Pooling - should create pooled client', async () => { - const startTime = Date.now(); - - try { - // Create pooled SMTP client - pooledClient = createPooledSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - maxConnections: 5, - maxMessages: 100, - connectionTimeout: 5000, - debug: true - }); - - // Verify connection pool is working - const isConnected = await pooledClient.verify(); - expect(isConnected).toBeTrue(); - - const poolStatus = pooledClient.getPoolStatus(); - console.log('📊 Initial pool status:', poolStatus); - expect(poolStatus.total).toBeGreaterThanOrEqual(0); - - const duration = Date.now() - startTime; - console.log(`✅ Connection pool created in ${duration}ms`); - - } catch (error) { - const duration = Date.now() - startTime; - console.error(`❌ Connection pool creation failed after ${duration}ms:`, error); - throw error; - } -}); - -tap.test('CCM-04: Connection Pooling - should handle concurrent connections', async () => { - // Send multiple emails concurrently - const emailPromises = []; - const concurrentCount = 5; - - for (let i = 0; i < concurrentCount; i++) { - const email = new Email({ - from: 'test@example.com', - to: `recipient${i}@example.com`, - subject: `Concurrent Email ${i}`, - text: `This is concurrent email number ${i}` - }); - - emailPromises.push( - pooledClient.sendMail(email).catch(error => { - console.error(`❌ Failed to send email ${i}:`, error); - return { success: false, error: error.message, acceptedRecipients: [] }; - }) - ); - } - - // Wait for all emails to be sent - const results = await Promise.all(emailPromises); - - // Check results and count successes - let successCount = 0; - results.forEach((result, index) => { - if (result.success) { - successCount++; - expect(result.acceptedRecipients).toContain(`recipient${index}@example.com`); - } else { - console.log(`Email ${index} failed:`, result.error); - } - }); - - // At least some emails should succeed with pooling - expect(successCount).toBeGreaterThan(0); - console.log(`✅ Sent ${successCount}/${concurrentCount} emails successfully`); - - // Check pool status after concurrent sends - const poolStatus = pooledClient.getPoolStatus(); - console.log('📊 Pool status after concurrent sends:', poolStatus); - expect(poolStatus.total).toBeGreaterThanOrEqual(1); - expect(poolStatus.total).toBeLessThanOrEqual(5); // Should not exceed max -}); - -tap.test('CCM-04: Connection Pooling - should reuse connections', async () => { - // Get initial pool status - const initialStatus = pooledClient.getPoolStatus(); - console.log('📊 Initial status:', initialStatus); - - // Send emails sequentially to test connection reuse - const emailCount = 10; - const connectionCounts = []; - - for (let i = 0; i < emailCount; i++) { - const email = new Email({ - from: 'test@example.com', - to: 'recipient@example.com', - subject: `Sequential Email ${i}`, - text: `Testing connection reuse - email ${i}` - }); - - await pooledClient.sendMail(email); - - const status = pooledClient.getPoolStatus(); - connectionCounts.push(status.total); - } - - // Check that connections were reused (total shouldn't grow linearly) - const maxConnections = Math.max(...connectionCounts); - expect(maxConnections).toBeLessThan(emailCount); // Should reuse connections - - console.log(`✅ Sent ${emailCount} emails using max ${maxConnections} connections`); - console.log('📊 Connection counts:', connectionCounts); -}); - -tap.test('CCM-04: Connection Pooling - should respect max connections limit', async () => { - // Create a client with small pool - const limitedClient = createPooledSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - maxConnections: 2, // Very small pool - connectionTimeout: 5000 - }); - - // Send many concurrent emails - const emailPromises = []; - for (let i = 0; i < 10; i++) { - const email = new Email({ - from: 'test@example.com', - to: `test${i}@example.com`, - subject: `Pool Limit Test ${i}`, - text: 'Testing pool limits' - }); - emailPromises.push(limitedClient.sendMail(email)); - } - - // Monitor pool during sending - const checkInterval = setInterval(() => { - const status = limitedClient.getPoolStatus(); - console.log('📊 Pool status during load:', status); - expect(status.total).toBeLessThanOrEqual(2); // Should never exceed max - }, 100); - - await Promise.all(emailPromises); - clearInterval(checkInterval); - - await limitedClient.close(); - console.log('✅ Connection pool respected max connections limit'); -}); - -tap.test('CCM-04: Connection Pooling - should handle connection failures in pool', async () => { - // Create a new pooled client - const resilientClient = createPooledSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - maxConnections: 3, - connectionTimeout: 5000 - }); - - // Send some emails successfully - for (let i = 0; i < 3; i++) { - const email = new Email({ - from: 'test@example.com', - to: 'recipient@example.com', - subject: `Pre-failure Email ${i}`, - text: 'Before simulated failure' - }); - - const result = await resilientClient.sendMail(email); - expect(result.success).toBeTrue(); - } - - // Pool should recover and continue working - const poolStatus = resilientClient.getPoolStatus(); - console.log('📊 Pool status after recovery test:', poolStatus); - expect(poolStatus.total).toBeGreaterThanOrEqual(1); - - await resilientClient.close(); - console.log('✅ Connection pool handled failures gracefully'); -}); - -tap.test('CCM-04: Connection Pooling - should clean up idle connections', async () => { - // Create client with specific idle settings - const idleClient = createPooledSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - maxConnections: 5, - connectionTimeout: 5000 - }); - - // Send burst of emails - const promises = []; - for (let i = 0; i < 5; i++) { - const email = new Email({ - from: 'test@example.com', - to: 'recipient@example.com', - subject: `Idle Test ${i}`, - text: 'Testing idle cleanup' - }); - promises.push(idleClient.sendMail(email)); - } - - await Promise.all(promises); - - const activeStatus = idleClient.getPoolStatus(); - console.log('📊 Pool status after burst:', activeStatus); - - // Wait for connections to become idle - await new Promise(resolve => setTimeout(resolve, 2000)); - - const idleStatus = idleClient.getPoolStatus(); - console.log('📊 Pool status after idle period:', idleStatus); - - await idleClient.close(); - console.log('✅ Idle connection management working'); -}); - -tap.test('cleanup - close pooled client', async () => { - if (pooledClient && pooledClient.isConnected()) { - await pooledClient.close(); - - // Verify pool is cleaned up - const finalStatus = pooledClient.getPoolStatus(); - console.log('📊 Final pool status:', finalStatus); - } -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_connection/test.ccm-05.connection-reuse.ts b/test/suite/smtpclient_connection/test.ccm-05.connection-reuse.ts deleted file mode 100644 index 236f2b3..0000000 --- a/test/suite/smtpclient_connection/test.ccm-05.connection-reuse.ts +++ /dev/null @@ -1,288 +0,0 @@ -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 connection reuse test', async () => { - testServer = await startTestServer({ - port: 2531, - tlsEnabled: false, - authRequired: false - }); - - expect(testServer.port).toEqual(2531); -}); - -tap.test('CCM-05: Connection Reuse - should reuse single connection for multiple emails', async () => { - const startTime = Date.now(); - - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Verify initial connection - const verified = await smtpClient.verify(); - expect(verified).toBeTrue(); - // Note: verify() closes the connection, so isConnected() will be false - - // Send multiple emails on same connection - const emailCount = 5; - const results = []; - - for (let i = 0; i < emailCount; i++) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Connection Reuse Test ${i + 1}`, - text: `This is email ${i + 1} using the same connection` - }); - - const result = await smtpClient.sendMail(email); - results.push(result); - - // Note: Connection state may vary depending on implementation - console.log(`Connection status after email ${i + 1}: ${smtpClient.isConnected() ? 'connected' : 'disconnected'}`); - } - - // All emails should succeed - results.forEach((result, index) => { - expect(result.success).toBeTrue(); - console.log(`✅ Email ${index + 1} sent successfully`); - }); - - const duration = Date.now() - startTime; - console.log(`✅ Sent ${emailCount} emails on single connection in ${duration}ms`); -}); - -tap.test('CCM-05: Connection Reuse - should track message count per connection', async () => { - // Create a new client with message limit - const limitedClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - maxMessages: 3, // Limit messages per connection - connectionTimeout: 5000 - }); - - // Send emails up to and beyond the limit - for (let i = 0; i < 5; i++) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Message Limit Test ${i + 1}`, - text: `Testing message limits` - }); - - const result = await limitedClient.sendMail(email); - expect(result.success).toBeTrue(); - - // After 3 messages, connection should be refreshed - if (i === 2) { - console.log('✅ Connection should refresh after message limit'); - } - } - - await limitedClient.close(); -}); - -tap.test('CCM-05: Connection Reuse - should handle connection state changes', async () => { - // Test connection state management - const stateClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // First email - const email1 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'First Email', - text: 'Testing connection state' - }); - - const result1 = await stateClient.sendMail(email1); - expect(result1.success).toBeTrue(); - - // Second email - const email2 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Second Email', - text: 'Testing connection reuse' - }); - - const result2 = await stateClient.sendMail(email2); - expect(result2.success).toBeTrue(); - - await stateClient.close(); - console.log('✅ Connection state handled correctly'); -}); - -tap.test('CCM-05: Connection Reuse - should handle idle connection timeout', async () => { - const idleClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - socketTimeout: 3000 // Short timeout for testing - }); - - // Send first email - const email1 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Pre-idle Email', - text: 'Before idle period' - }); - - const result1 = await idleClient.sendMail(email1); - expect(result1.success).toBeTrue(); - - // Wait for potential idle timeout - console.log('⏳ Testing idle connection behavior...'); - await new Promise(resolve => setTimeout(resolve, 4000)); - - // Send another email - const email2 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Post-idle Email', - text: 'After idle period' - }); - - // Should handle reconnection if needed - const result = await idleClient.sendMail(email2); - expect(result.success).toBeTrue(); - - await idleClient.close(); - console.log('✅ Idle connection handling working correctly'); -}); - -tap.test('CCM-05: Connection Reuse - should optimize performance with reuse', async () => { - // Compare performance with and without connection reuse - - // Test 1: Multiple connections (no reuse) - const noReuseStart = Date.now(); - for (let i = 0; i < 3; i++) { - const tempClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `No Reuse ${i}`, - text: 'Testing without reuse' - }); - - await tempClient.sendMail(email); - await tempClient.close(); - } - const noReuseDuration = Date.now() - noReuseStart; - - // Test 2: Single connection (with reuse) - const reuseStart = Date.now(); - const reuseClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - for (let i = 0; i < 3; i++) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `With Reuse ${i}`, - text: 'Testing with reuse' - }); - - await reuseClient.sendMail(email); - } - - await reuseClient.close(); - const reuseDuration = Date.now() - reuseStart; - - console.log(`📊 Performance comparison:`); - console.log(` Without reuse: ${noReuseDuration}ms`); - console.log(` With reuse: ${reuseDuration}ms`); - console.log(` Improvement: ${Math.round((1 - reuseDuration/noReuseDuration) * 100)}%`); - - // Both approaches should work, performance may vary based on implementation - // Connection reuse doesn't always guarantee better performance for local connections - expect(noReuseDuration).toBeGreaterThan(0); - expect(reuseDuration).toBeGreaterThan(0); - console.log('✅ Both connection strategies completed successfully'); -}); - -tap.test('CCM-05: Connection Reuse - should handle errors without breaking reuse', async () => { - const resilientClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Send valid email - const validEmail = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Valid Email', - text: 'This should work' - }); - - const result1 = await resilientClient.sendMail(validEmail); - expect(result1.success).toBeTrue(); - - // Try to send invalid email - try { - const invalidEmail = new Email({ - from: 'invalid sender format', - to: 'recipient@example.com', - subject: 'Invalid Email', - text: 'This should fail' - }); - await resilientClient.sendMail(invalidEmail); - } catch (error) { - console.log('✅ Invalid email rejected as expected'); - } - - // Connection should still be usable - const validEmail2 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Valid Email After Error', - text: 'Connection should still work' - }); - - const result2 = await resilientClient.sendMail(validEmail2); - expect(result2.success).toBeTrue(); - - await resilientClient.close(); - console.log('✅ Connection reuse survived error condition'); -}); - -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(); \ No newline at end of file diff --git a/test/suite/smtpclient_connection/test.ccm-06.connection-timeout.ts b/test/suite/smtpclient_connection/test.ccm-06.connection-timeout.ts deleted file mode 100644 index 0bc37ae..0000000 --- a/test/suite/smtpclient_connection/test.ccm-06.connection-timeout.ts +++ /dev/null @@ -1,267 +0,0 @@ -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'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup - start SMTP server for timeout tests', async () => { - testServer = await startTestServer({ - port: 2532, - tlsEnabled: false, - authRequired: false - }); - - expect(testServer.port).toEqual(2532); -}); - -tap.test('CCM-06: Connection Timeout - should timeout on unresponsive server', async () => { - const startTime = Date.now(); - - const timeoutClient = createSmtpClient({ - host: testServer.hostname, - port: 9999, // Non-existent port - secure: false, - connectionTimeout: 2000, // 2 second timeout - debug: true - }); - - // verify() returns false on connection failure, doesn't throw - const verified = await timeoutClient.verify(); - const duration = Date.now() - startTime; - - expect(verified).toBeFalse(); - expect(duration).toBeLessThan(3000); // Should timeout within 3s - - console.log(`✅ Connection timeout after ${duration}ms`); -}); - -tap.test('CCM-06: Connection Timeout - should handle slow server response', async () => { - // Create a mock slow server - const slowServer = net.createServer((socket) => { - // Accept connection but delay response - setTimeout(() => { - socket.write('220 Slow server ready\r\n'); - }, 3000); // 3 second delay - }); - - await new Promise((resolve) => { - slowServer.listen(2533, () => resolve()); - }); - - const startTime = Date.now(); - - const slowClient = createSmtpClient({ - host: 'localhost', - port: 2533, - secure: false, - connectionTimeout: 1000, // 1 second timeout - debug: true - }); - - // verify() should return false when server is too slow - const verified = await slowClient.verify(); - const duration = Date.now() - startTime; - - expect(verified).toBeFalse(); - // Note: actual timeout might be longer due to system defaults - console.log(`✅ Slow server timeout after ${duration}ms`); - - slowServer.close(); - await new Promise(resolve => setTimeout(resolve, 100)); -}); - -tap.test('CCM-06: Connection Timeout - should respect socket timeout during data transfer', async () => { - const socketTimeoutClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - socketTimeout: 10000, // 10 second socket timeout - debug: true - }); - - await socketTimeoutClient.verify(); - - // Send a normal email - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Socket Timeout Test', - text: 'Testing socket timeout configuration' - }); - - const result = await socketTimeoutClient.sendMail(email); - expect(result.success).toBeTrue(); - - await socketTimeoutClient.close(); - console.log('✅ Socket timeout configuration applied'); -}); - -tap.test('CCM-06: Connection Timeout - should handle timeout during TLS handshake', async () => { - // Create a server that accepts connections but doesn't complete TLS - const badTlsServer = net.createServer((socket) => { - // Accept connection but don't respond to TLS - socket.on('data', () => { - // Do nothing - simulate hung TLS handshake - }); - }); - - await new Promise((resolve) => { - badTlsServer.listen(2534, () => resolve()); - }); - - const startTime = Date.now(); - - const tlsTimeoutClient = createSmtpClient({ - host: 'localhost', - port: 2534, - secure: true, // Try TLS - connectionTimeout: 2000, - tls: { - rejectUnauthorized: false - } - }); - - // verify() should return false when TLS handshake times out - const verified = await tlsTimeoutClient.verify(); - const duration = Date.now() - startTime; - - expect(verified).toBeFalse(); - // Note: actual timeout might be longer due to system defaults - console.log(`✅ TLS handshake timeout after ${duration}ms`); - - badTlsServer.close(); - await new Promise(resolve => setTimeout(resolve, 100)); -}); - -tap.test('CCM-06: Connection Timeout - should not timeout on successful quick connection', async () => { - const quickClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 30000, // Very long timeout - debug: true - }); - - const startTime = Date.now(); - - const isConnected = await quickClient.verify(); - const duration = Date.now() - startTime; - - expect(isConnected).toBeTrue(); - expect(duration).toBeLessThan(5000); // Should connect quickly - - await quickClient.close(); - console.log(`✅ Quick connection established in ${duration}ms`); -}); - -tap.test('CCM-06: Connection Timeout - should handle timeout during authentication', async () => { - // Start auth server - const authServer = await startTestServer({ - port: 2535, - authRequired: true - }); - - // Create mock auth that delays - const authTimeoutClient = createSmtpClient({ - host: authServer.hostname, - port: authServer.port, - secure: false, - connectionTimeout: 5000, - socketTimeout: 1000, // Very short socket timeout - auth: { - user: 'testuser', - pass: 'testpass' - } - }); - - try { - await authTimeoutClient.verify(); - // If this succeeds, auth was fast enough - await authTimeoutClient.close(); - console.log('✅ Authentication completed within timeout'); - } catch (error) { - console.log('✅ Authentication timeout handled'); - } - - await stopTestServer(authServer); -}); - -tap.test('CCM-06: Connection Timeout - should apply different timeouts for different operations', async () => { - const multiTimeoutClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, // Connection establishment - socketTimeout: 30000, // Data operations - debug: true - }); - - // Connection should be quick - const connectStart = Date.now(); - await multiTimeoutClient.verify(); - const connectDuration = Date.now() - connectStart; - - expect(connectDuration).toBeLessThan(5000); - - // Send email with potentially longer operation - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Multi-timeout Test', - text: 'Testing different timeout values', - attachments: [{ - filename: 'test.txt', - content: Buffer.from('Test content'), - contentType: 'text/plain' - }] - }); - - const sendStart = Date.now(); - const result = await multiTimeoutClient.sendMail(email); - const sendDuration = Date.now() - sendStart; - - expect(result.success).toBeTrue(); - console.log(`✅ Different timeouts applied: connect=${connectDuration}ms, send=${sendDuration}ms`); - - await multiTimeoutClient.close(); -}); - -tap.test('CCM-06: Connection Timeout - should retry after timeout with pooled connections', async () => { - const retryClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - pool: true, - maxConnections: 2, - connectionTimeout: 5000, - debug: true - }); - - // First connection should succeed - const email1 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Pre-timeout Email', - text: 'Before any timeout' - }); - - const result1 = await retryClient.sendMail(email1); - expect(result1.success).toBeTrue(); - - // Pool should handle connection management - const poolStatus = retryClient.getPoolStatus(); - console.log('📊 Pool status:', poolStatus); - - await retryClient.close(); - console.log('✅ Connection pool handles timeouts gracefully'); -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_connection/test.ccm-07.automatic-reconnection.ts b/test/suite/smtpclient_connection/test.ccm-07.automatic-reconnection.ts deleted file mode 100644 index 9f467e4..0000000 --- a/test/suite/smtpclient_connection/test.ccm-07.automatic-reconnection.ts +++ /dev/null @@ -1,324 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createSmtpClient, createPooledSmtpClient } 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'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup - start SMTP server for reconnection tests', async () => { - testServer = await startTestServer({ - port: 2533, - tlsEnabled: false, - authRequired: false - }); - - expect(testServer.port).toEqual(2533); -}); - -tap.test('CCM-07: Automatic Reconnection - should reconnect after connection loss', async () => { - const client = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // First connection and email - const email1 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Before Disconnect', - text: 'First email before connection loss' - }); - - const result1 = await client.sendMail(email1); - expect(result1.success).toBeTrue(); - // Note: Connection state may vary after sending - - // Force disconnect - await client.close(); - expect(client.isConnected()).toBeFalse(); - - // Try to send another email - should auto-reconnect - const email2 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'After Reconnect', - text: 'Email after automatic reconnection' - }); - - const result2 = await client.sendMail(email2); - expect(result2.success).toBeTrue(); - // Connection successfully handled reconnection - - await client.close(); - console.log('✅ Automatic reconnection successful'); -}); - -tap.test('CCM-07: Automatic Reconnection - pooled client should reconnect failed connections', async () => { - const pooledClient = createPooledSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - maxConnections: 3, - connectionTimeout: 5000, - debug: true - }); - - // Send emails to establish pool connections - const promises = []; - for (let i = 0; i < 3; i++) { - const email = new Email({ - from: 'sender@example.com', - to: `recipient${i}@example.com`, - subject: `Pool Test ${i}`, - text: 'Testing connection pool' - }); - promises.push( - pooledClient.sendMail(email).catch(error => { - console.error(`Failed to send initial email ${i}:`, error.message); - return { success: false, error: error.message }; - }) - ); - } - - await Promise.all(promises); - - const poolStatus1 = pooledClient.getPoolStatus(); - console.log('📊 Pool status before disruption:', poolStatus1); - - // Send more emails - pool should handle any connection issues - const promises2 = []; - for (let i = 0; i < 5; i++) { - const email = new Email({ - from: 'sender@example.com', - to: `recipient${i}@example.com`, - subject: `Pool Recovery ${i}`, - text: 'Testing pool recovery' - }); - promises2.push( - pooledClient.sendMail(email).catch(error => { - console.error(`Failed to send email ${i}:`, error.message); - return { success: false, error: error.message }; - }) - ); - } - - const results = await Promise.all(promises2); - let successCount = 0; - results.forEach(result => { - if (result.success) { - successCount++; - } - }); - - // At least some emails should succeed - expect(successCount).toBeGreaterThan(0); - console.log(`✅ Pool recovery: ${successCount}/${results.length} emails succeeded`); - - const poolStatus2 = pooledClient.getPoolStatus(); - console.log('📊 Pool status after recovery:', poolStatus2); - - await pooledClient.close(); - console.log('✅ Connection pool handles reconnection automatically'); -}); - -tap.test('CCM-07: Automatic Reconnection - should handle server restart', async () => { - // Create client - const client = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Send first email - const email1 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Before Server Restart', - text: 'Email before server restart' - }); - - const result1 = await client.sendMail(email1); - expect(result1.success).toBeTrue(); - - // Simulate server restart - console.log('🔄 Simulating server restart...'); - await stopTestServer(testServer); - await new Promise(resolve => setTimeout(resolve, 1000)); - - // Restart server on same port - testServer = await startTestServer({ - port: 2533, - tlsEnabled: false, - authRequired: false - }); - - // Try to send another email - const email2 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'After Server Restart', - text: 'Email after server restart' - }); - - const result2 = await client.sendMail(email2); - expect(result2.success).toBeTrue(); - - await client.close(); - console.log('✅ Client recovered from server restart'); -}); - -tap.test('CCM-07: Automatic Reconnection - should handle network interruption', async () => { - const client = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - socketTimeout: 10000 - }); - - // Establish connection - await client.verify(); - - // Send emails with simulated network issues - for (let i = 0; i < 3; i++) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Network Test ${i}`, - text: `Testing network resilience ${i}` - }); - - try { - const result = await client.sendMail(email); - expect(result.success).toBeTrue(); - console.log(`✅ Email ${i + 1} sent successfully`); - } catch (error) { - console.log(`⚠️ Email ${i + 1} failed, will retry`); - // Client should recover on next attempt - } - - // Add small delay between sends - await new Promise(resolve => setTimeout(resolve, 100)); - } - - await client.close(); -}); - -tap.test('CCM-07: Automatic Reconnection - should limit reconnection attempts', async () => { - // Connect to a port that will be closed - const tempServer = net.createServer(); - await new Promise((resolve) => { - tempServer.listen(2534, () => resolve()); - }); - - const client = createSmtpClient({ - host: 'localhost', - port: 2534, - secure: false, - connectionTimeout: 2000 - }); - - // Close the server to simulate failure - tempServer.close(); - await new Promise(resolve => setTimeout(resolve, 100)); - - let failureCount = 0; - const maxAttempts = 3; - - // Try multiple times - for (let i = 0; i < maxAttempts; i++) { - const verified = await client.verify(); - if (!verified) { - failureCount++; - } - } - - expect(failureCount).toEqual(maxAttempts); - console.log('✅ Reconnection attempts are limited to prevent infinite loops'); -}); - -tap.test('CCM-07: Automatic Reconnection - should maintain state after reconnect', async () => { - const client = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Send email with specific settings - const email1 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'State Test 1', - text: 'Testing state persistence', - priority: 'high', - headers: { - 'X-Test-ID': 'test-123' - } - }); - - const result1 = await client.sendMail(email1); - expect(result1.success).toBeTrue(); - - // Force reconnection - await client.close(); - - // Send another email - client state should be maintained - const email2 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'State Test 2', - text: 'After reconnection', - priority: 'high', - headers: { - 'X-Test-ID': 'test-456' - } - }); - - const result2 = await client.sendMail(email2); - expect(result2.success).toBeTrue(); - - await client.close(); - console.log('✅ Client state maintained after reconnection'); -}); - -tap.test('CCM-07: Automatic Reconnection - should handle rapid reconnections', async () => { - const client = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Rapid connect/disconnect cycles - for (let i = 0; i < 5; i++) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Rapid Test ${i}`, - text: 'Testing rapid reconnections' - }); - - const result = await client.sendMail(email); - expect(result.success).toBeTrue(); - - // Force disconnect - await client.close(); - - // No delay - immediate next attempt - } - - console.log('✅ Rapid reconnections handled successfully'); -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_connection/test.ccm-08.dns-resolution.ts b/test/suite/smtpclient_connection/test.ccm-08.dns-resolution.ts deleted file mode 100644 index 9eb6104..0000000 --- a/test/suite/smtpclient_connection/test.ccm-08.dns-resolution.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import * as dns from 'dns'; -import { promisify } from 'util'; - -const resolveMx = promisify(dns.resolveMx); -const resolve4 = promisify(dns.resolve4); -const resolve6 = promisify(dns.resolve6); - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2534, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2534); -}); - -tap.test('CCM-08: DNS resolution and MX record lookup', async () => { - // Test basic DNS resolution - try { - const ipv4Addresses = await resolve4('example.com'); - expect(ipv4Addresses).toBeArray(); - expect(ipv4Addresses.length).toBeGreaterThan(0); - console.log('IPv4 addresses for example.com:', ipv4Addresses); - } catch (error) { - console.log('IPv4 resolution failed (may be expected in test environment):', error.message); - } - - // Test IPv6 resolution - try { - const ipv6Addresses = await resolve6('example.com'); - expect(ipv6Addresses).toBeArray(); - console.log('IPv6 addresses for example.com:', ipv6Addresses); - } catch (error) { - console.log('IPv6 resolution failed (common for many domains):', error.message); - } - - // Test MX record lookup - try { - const mxRecords = await resolveMx('example.com'); - expect(mxRecords).toBeArray(); - if (mxRecords.length > 0) { - expect(mxRecords[0]).toHaveProperty('priority'); - expect(mxRecords[0]).toHaveProperty('exchange'); - console.log('MX records for example.com:', mxRecords); - } - } catch (error) { - console.log('MX record lookup failed (may be expected in test environment):', error.message); - } - - // Test local resolution (should work in test environment) - try { - const localhostIpv4 = await resolve4('localhost'); - expect(localhostIpv4).toContain('127.0.0.1'); - } catch (error) { - // Fallback for environments where localhost doesn't resolve via DNS - console.log('Localhost DNS resolution not available, using direct IP'); - } - - // Test invalid domain handling - try { - await resolve4('this-domain-definitely-does-not-exist-12345.com'); - expect(true).toBeFalsy(); // Should not reach here - } catch (error) { - expect(error.code).toMatch(/ENOTFOUND|ENODATA/); - } - - // Test MX record priority sorting - const mockMxRecords = [ - { priority: 20, exchange: 'mx2.example.com' }, - { priority: 10, exchange: 'mx1.example.com' }, - { priority: 30, exchange: 'mx3.example.com' } - ]; - - const sortedRecords = mockMxRecords.sort((a, b) => a.priority - b.priority); - expect(sortedRecords[0].exchange).toEqual('mx1.example.com'); - expect(sortedRecords[1].exchange).toEqual('mx2.example.com'); - expect(sortedRecords[2].exchange).toEqual('mx3.example.com'); -}); - -tap.test('CCM-08: DNS caching behavior', async () => { - const startTime = Date.now(); - - // First resolution (cold cache) - try { - await resolve4('example.com'); - } catch (error) { - // Ignore errors, we're testing timing - } - - const firstResolutionTime = Date.now() - startTime; - - // Second resolution (potentially cached) - const secondStartTime = Date.now(); - try { - await resolve4('example.com'); - } catch (error) { - // Ignore errors, we're testing timing - } - - const secondResolutionTime = Date.now() - secondStartTime; - - console.log(`First resolution: ${firstResolutionTime}ms, Second resolution: ${secondResolutionTime}ms`); - - // Note: We can't guarantee caching behavior in all environments - // so we just log the times for manual inspection -}); - -tap.test('CCM-08: Multiple A record handling', async () => { - // Test handling of domains with multiple A records - try { - const googleIps = await resolve4('google.com'); - if (googleIps.length > 1) { - expect(googleIps).toBeArray(); - expect(googleIps.length).toBeGreaterThan(1); - console.log('Multiple A records found for google.com:', googleIps); - - // Verify all are valid IPv4 addresses - const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; - for (const ip of googleIps) { - expect(ip).toMatch(ipv4Regex); - } - } - } catch (error) { - console.log('Could not resolve google.com:', error.message); - } -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_connection/test.ccm-09.ipv6-dual-stack.ts b/test/suite/smtpclient_connection/test.ccm-09.ipv6-dual-stack.ts deleted file mode 100644 index da7648e..0000000 --- a/test/suite/smtpclient_connection/test.ccm-09.ipv6-dual-stack.ts +++ /dev/null @@ -1,167 +0,0 @@ -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 * as net from 'net'; -import * as os from 'os'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2535, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2535); -}); - -tap.test('CCM-09: Check system IPv6 support', async () => { - const networkInterfaces = os.networkInterfaces(); - let hasIPv6 = false; - - for (const interfaceName in networkInterfaces) { - const interfaces = networkInterfaces[interfaceName]; - if (interfaces) { - for (const iface of interfaces) { - if (iface.family === 'IPv6' && !iface.internal) { - hasIPv6 = true; - console.log(`Found IPv6 address: ${iface.address} on ${interfaceName}`); - } - } - } - } - - console.log(`System has IPv6 support: ${hasIPv6}`); -}); - -tap.test('CCM-09: IPv4 connection test', async () => { - const smtpClient = createSmtpClient({ - host: '127.0.0.1', // Explicit IPv4 - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Test connection using verify - const verified = await smtpClient.verify(); - expect(verified).toBeTrue(); - - console.log('Successfully connected via IPv4'); - - await smtpClient.close(); -}); - -tap.test('CCM-09: IPv6 connection test (if supported)', async () => { - // Check if IPv6 is available - const hasIPv6 = await new Promise((resolve) => { - const testSocket = net.createConnection({ - host: '::1', - port: 1, // Any port, will fail but tells us if IPv6 works - timeout: 100 - }); - - testSocket.on('error', (err: any) => { - // ECONNREFUSED means IPv6 works but port is closed (expected) - // ENETUNREACH or EAFNOSUPPORT means IPv6 not available - resolve(err.code === 'ECONNREFUSED'); - }); - - testSocket.on('connect', () => { - testSocket.end(); - resolve(true); - }); - }); - - if (!hasIPv6) { - console.log('IPv6 not available on this system, skipping IPv6 tests'); - return; - } - - // Try IPv6 connection - const smtpClient = createSmtpClient({ - host: '::1', // IPv6 loopback - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - try { - const verified = await smtpClient.verify(); - if (verified) { - console.log('Successfully connected via IPv6'); - await smtpClient.close(); - } else { - console.log('IPv6 connection failed (server may not support IPv6)'); - } - } catch (error: any) { - console.log('IPv6 connection failed (server may not support IPv6):', error.message); - } -}); - -tap.test('CCM-09: Hostname resolution preference', async () => { - // Test that client can handle hostnames that resolve to both IPv4 and IPv6 - const smtpClient = createSmtpClient({ - host: 'localhost', // Should resolve to both 127.0.0.1 and ::1 - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const verified = await smtpClient.verify(); - expect(verified).toBeTrue(); - - console.log('Successfully connected to localhost'); - - await smtpClient.close(); -}); - -tap.test('CCM-09: Happy Eyeballs algorithm simulation', async () => { - // Test connecting to multiple addresses with preference - const addresses = ['127.0.0.1', '::1', 'localhost']; - const results: Array<{ address: string; time: number; success: boolean }> = []; - - for (const address of addresses) { - const startTime = Date.now(); - const smtpClient = createSmtpClient({ - host: address, - port: testServer.port, - secure: false, - connectionTimeout: 1000, - debug: false - }); - - try { - const verified = await smtpClient.verify(); - const elapsed = Date.now() - startTime; - results.push({ address, time: elapsed, success: verified }); - - if (verified) { - await smtpClient.close(); - } - } catch (error) { - const elapsed = Date.now() - startTime; - results.push({ address, time: elapsed, success: false }); - } - } - - console.log('Connection race results:'); - results.forEach(r => { - console.log(` ${r.address}: ${r.success ? 'SUCCESS' : 'FAILED'} in ${r.time}ms`); - }); - - // At least one should succeed - const successfulConnections = results.filter(r => r.success); - expect(successfulConnections.length).toBeGreaterThan(0); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_connection/test.ccm-10.proxy-support.ts b/test/suite/smtpclient_connection/test.ccm-10.proxy-support.ts deleted file mode 100644 index 155357c..0000000 --- a/test/suite/smtpclient_connection/test.ccm-10.proxy-support.ts +++ /dev/null @@ -1,305 +0,0 @@ -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 * as net from 'net'; -import * as http from 'http'; - -let testServer: ITestServer; -let proxyServer: http.Server; -let socksProxyServer: net.Server; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2536, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2536); -}); - -tap.test('CCM-10: Setup HTTP CONNECT proxy', async () => { - // Create a simple HTTP CONNECT proxy - proxyServer = http.createServer(); - - proxyServer.on('connect', (req, clientSocket, head) => { - console.log(`Proxy CONNECT request to ${req.url}`); - - const [host, port] = req.url!.split(':'); - const serverSocket = net.connect(parseInt(port), host, () => { - clientSocket.write('HTTP/1.1 200 Connection Established\r\n' + - 'Proxy-agent: Test-Proxy\r\n' + - '\r\n'); - - // Pipe data between client and server - serverSocket.pipe(clientSocket); - clientSocket.pipe(serverSocket); - }); - - serverSocket.on('error', (err) => { - console.error('Proxy server socket error:', err); - clientSocket.end(); - }); - - clientSocket.on('error', (err) => { - console.error('Proxy client socket error:', err); - serverSocket.end(); - }); - }); - - await new Promise((resolve) => { - proxyServer.listen(0, '127.0.0.1', () => { - const address = proxyServer.address() as net.AddressInfo; - console.log(`HTTP proxy listening on port ${address.port}`); - resolve(); - }); - }); -}); - -tap.test('CCM-10: Test connection through HTTP proxy', async () => { - const proxyAddress = proxyServer.address() as net.AddressInfo; - - // Note: Real SMTP clients would need proxy configuration - // This simulates what a proxy-aware SMTP client would do - const proxyOptions = { - host: proxyAddress.address, - port: proxyAddress.port, - method: 'CONNECT', - path: `127.0.0.1:${testServer.port}`, - headers: { - 'Proxy-Authorization': 'Basic dGVzdDp0ZXN0' // test:test in base64 - } - }; - - const connected = await new Promise((resolve) => { - const timeout = setTimeout(() => { - console.log('Proxy test timed out'); - resolve(false); - }, 10000); // 10 second timeout - - const req = http.request(proxyOptions); - - req.on('connect', (res, socket, head) => { - console.log('Connected through proxy, status:', res.statusCode); - expect(res.statusCode).toEqual(200); - - // Now we have a raw socket to the SMTP server through the proxy - clearTimeout(timeout); - - // For the purpose of this test, just verify we can connect through the proxy - // Real SMTP operations through proxy would require more complex handling - socket.end(); - resolve(true); - - socket.on('error', (err) => { - console.error('Socket error:', err); - resolve(false); - }); - }); - - req.on('error', (err) => { - console.error('Proxy request error:', err); - resolve(false); - }); - - req.end(); - }); - - expect(connected).toBeTruthy(); -}); - -tap.test('CCM-10: Test SOCKS5 proxy simulation', async () => { - // Create a minimal SOCKS5 proxy for testing - socksProxyServer = net.createServer((clientSocket) => { - let authenticated = false; - let targetHost: string; - let targetPort: number; - - clientSocket.on('data', (data) => { - if (!authenticated) { - // SOCKS5 handshake - if (data[0] === 0x05) { // SOCKS version 5 - // Send back: no authentication required - clientSocket.write(Buffer.from([0x05, 0x00])); - authenticated = true; - } - } else if (!targetHost) { - // Connection request - if (data[0] === 0x05 && data[1] === 0x01) { // CONNECT command - const addressType = data[3]; - - if (addressType === 0x01) { // IPv4 - targetHost = `${data[4]}.${data[5]}.${data[6]}.${data[7]}`; - targetPort = (data[8] << 8) + data[9]; - - // Connect to target - const serverSocket = net.connect(targetPort, targetHost, () => { - // Send success response - const response = Buffer.alloc(10); - response[0] = 0x05; // SOCKS version - response[1] = 0x00; // Success - response[2] = 0x00; // Reserved - response[3] = 0x01; // IPv4 - response[4] = data[4]; // Copy address - response[5] = data[5]; - response[6] = data[6]; - response[7] = data[7]; - response[8] = data[8]; // Copy port - response[9] = data[9]; - - clientSocket.write(response); - - // Start proxying - serverSocket.pipe(clientSocket); - clientSocket.pipe(serverSocket); - }); - - serverSocket.on('error', (err) => { - console.error('SOCKS target connection error:', err); - clientSocket.end(); - }); - } - } - } - }); - - clientSocket.on('error', (err) => { - console.error('SOCKS client error:', err); - }); - }); - - await new Promise((resolve) => { - socksProxyServer.listen(0, '127.0.0.1', () => { - const address = socksProxyServer.address() as net.AddressInfo; - console.log(`SOCKS5 proxy listening on port ${address.port}`); - resolve(); - }); - }); - - // Test connection through SOCKS proxy - const socksAddress = socksProxyServer.address() as net.AddressInfo; - const socksClient = net.connect(socksAddress.port, socksAddress.address); - - const connected = await new Promise((resolve) => { - let phase = 'handshake'; - - socksClient.on('connect', () => { - // Send SOCKS5 handshake - socksClient.write(Buffer.from([0x05, 0x01, 0x00])); // Version 5, 1 method, no auth - }); - - socksClient.on('data', (data) => { - if (phase === 'handshake' && data[0] === 0x05 && data[1] === 0x00) { - phase = 'connect'; - // Send connection request - const connectReq = Buffer.alloc(10); - connectReq[0] = 0x05; // SOCKS version - connectReq[1] = 0x01; // CONNECT - connectReq[2] = 0x00; // Reserved - connectReq[3] = 0x01; // IPv4 - connectReq[4] = 127; // 127.0.0.1 - connectReq[5] = 0; - connectReq[6] = 0; - connectReq[7] = 1; - connectReq[8] = (testServer.port >> 8) & 0xFF; // Port high byte - connectReq[9] = testServer.port & 0xFF; // Port low byte - - socksClient.write(connectReq); - } else if (phase === 'connect' && data[0] === 0x05 && data[1] === 0x00) { - phase = 'connected'; - console.log('Connected through SOCKS5 proxy'); - // Now we're connected to the SMTP server - } else if (phase === 'connected') { - const response = data.toString(); - console.log('SMTP response through SOCKS:', response.trim()); - if (response.includes('220')) { - socksClient.write('QUIT\r\n'); - socksClient.end(); - resolve(true); - } - } - }); - - socksClient.on('error', (err) => { - console.error('SOCKS client error:', err); - resolve(false); - }); - - setTimeout(() => resolve(false), 5000); // Timeout after 5 seconds - }); - - expect(connected).toBeTruthy(); -}); - -tap.test('CCM-10: Test proxy authentication failure', async () => { - // Create a proxy that requires authentication - const authProxyServer = http.createServer(); - - authProxyServer.on('connect', (req, clientSocket, head) => { - const authHeader = req.headers['proxy-authorization']; - - if (!authHeader || authHeader !== 'Basic dGVzdDp0ZXN0') { - clientSocket.write('HTTP/1.1 407 Proxy Authentication Required\r\n' + - 'Proxy-Authenticate: Basic realm="Test Proxy"\r\n' + - '\r\n'); - clientSocket.end(); - return; - } - - // Authentication successful, proceed with connection - const [host, port] = req.url!.split(':'); - const serverSocket = net.connect(parseInt(port), host, () => { - clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n'); - serverSocket.pipe(clientSocket); - clientSocket.pipe(serverSocket); - }); - }); - - await new Promise((resolve) => { - authProxyServer.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const authProxyAddress = authProxyServer.address() as net.AddressInfo; - - // Test without authentication - const failedAuth = await new Promise((resolve) => { - const req = http.request({ - host: authProxyAddress.address, - port: authProxyAddress.port, - method: 'CONNECT', - path: `127.0.0.1:${testServer.port}` - }); - - req.on('connect', () => resolve(false)); - req.on('response', (res) => { - expect(res.statusCode).toEqual(407); - resolve(true); - }); - req.on('error', () => resolve(false)); - - req.end(); - }); - - // Skip strict assertion as proxy behavior can vary - console.log('Proxy authentication test completed'); - - authProxyServer.close(); -}); - -tap.test('cleanup test servers', async () => { - if (proxyServer) { - await new Promise((resolve) => proxyServer.close(() => resolve())); - } - - if (socksProxyServer) { - await new Promise((resolve) => socksProxyServer.close(() => resolve())); - } - - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_connection/test.ccm-11.keepalive.ts b/test/suite/smtpclient_connection/test.ccm-11.keepalive.ts deleted file mode 100644 index 54db6bf..0000000 --- a/test/suite/smtpclient_connection/test.ccm-11.keepalive.ts +++ /dev/null @@ -1,299 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2537, - tlsEnabled: false, - authRequired: false, - socketTimeout: 30000 // 30 second timeout for keep-alive tests - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2537); -}); - -tap.test('CCM-11: Basic keep-alive functionality', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - keepAlive: true, - keepAliveInterval: 5000, // 5 seconds - connectionTimeout: 10000, - debug: true - }); - - // Verify connection works - const verified = await smtpClient.verify(); - expect(verified).toBeTrue(); - - // Send an email to establish connection - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Keep-alive test', - text: 'Testing connection keep-alive' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - - // Wait to simulate idle time - await new Promise(resolve => setTimeout(resolve, 3000)); - - // Send another email to verify connection is still working - const result2 = await smtpClient.sendMail(email); - expect(result2.success).toBeTrue(); - - console.log('✅ Keep-alive functionality verified'); - - await smtpClient.close(); -}); - -tap.test('CCM-11: Connection reuse with keep-alive', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - keepAlive: true, - keepAliveInterval: 3000, - connectionTimeout: 10000, - poolSize: 1, // Use single connection to test keep-alive - debug: true - }); - - // Send multiple emails with delays to test keep-alive - const emails = []; - for (let i = 0; i < 3; i++) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Keep-alive test ${i + 1}`, - text: `Testing connection keep-alive - email ${i + 1}` - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - emails.push(result); - - // Wait between emails (less than keep-alive interval) - if (i < 2) { - await new Promise(resolve => setTimeout(resolve, 2000)); - } - } - - // All emails should have been sent successfully - expect(emails.length).toEqual(3); - expect(emails.every(r => r.success)).toBeTrue(); - - console.log('✅ Connection reused successfully with keep-alive'); - - await smtpClient.close(); -}); - -tap.test('CCM-11: Connection without keep-alive', async () => { - // Create a client without keep-alive - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - keepAlive: false, // Disabled - connectionTimeout: 5000, - socketTimeout: 5000, // 5 second socket timeout - poolSize: 1, - debug: true - }); - - // Send first email - const email1 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'No keep-alive test 1', - text: 'Testing without keep-alive' - }); - - const result1 = await smtpClient.sendMail(email1); - expect(result1.success).toBeTrue(); - - // Wait longer than socket timeout - await new Promise(resolve => setTimeout(resolve, 7000)); - - // Send second email - connection might need to be re-established - const email2 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'No keep-alive test 2', - text: 'Testing without keep-alive after timeout' - }); - - const result2 = await smtpClient.sendMail(email2); - expect(result2.success).toBeTrue(); - - console.log('✅ Client handles reconnection without keep-alive'); - - await smtpClient.close(); -}); - -tap.test('CCM-11: Keep-alive with long operations', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - keepAlive: true, - keepAliveInterval: 2000, - connectionTimeout: 10000, - poolSize: 2, // Use small pool - debug: true - }); - - // Send multiple emails with varying delays - const operations = []; - - for (let i = 0; i < 5; i++) { - operations.push((async () => { - // Simulate random processing delay - await new Promise(resolve => setTimeout(resolve, Math.random() * 3000)); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Long operation test ${i + 1}`, - text: `Testing keep-alive during long operations - email ${i + 1}` - }); - - const result = await smtpClient.sendMail(email); - return { index: i, result }; - })()); - } - - const results = await Promise.all(operations); - - // All operations should succeed - const successCount = results.filter(r => r.result.success).length; - expect(successCount).toEqual(5); - - console.log('✅ Keep-alive maintained during long operations'); - - await smtpClient.close(); -}); - -tap.test('CCM-11: Keep-alive interval effect on connection pool', async () => { - const intervals = [1000, 3000, 5000]; // Different intervals to test - - for (const interval of intervals) { - console.log(`\nTesting keep-alive with ${interval}ms interval`); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - keepAlive: true, - keepAliveInterval: interval, - connectionTimeout: 10000, - poolSize: 2, - debug: false // Less verbose for this test - }); - - const startTime = Date.now(); - - // Send multiple emails over time period longer than interval - const emails = []; - for (let i = 0; i < 3; i++) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Interval test ${i + 1}`, - text: `Testing with ${interval}ms keep-alive interval` - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - emails.push(result); - - // Wait approximately one interval - if (i < 2) { - await new Promise(resolve => setTimeout(resolve, interval)); - } - } - - const totalTime = Date.now() - startTime; - console.log(`Sent ${emails.length} emails in ${totalTime}ms with ${interval}ms keep-alive`); - - // Check pool status - const poolStatus = smtpClient.getPoolStatus(); - console.log(`Pool status: ${JSON.stringify(poolStatus)}`); - - await smtpClient.close(); - } -}); - -tap.test('CCM-11: Event monitoring during keep-alive', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - keepAlive: true, - keepAliveInterval: 2000, - connectionTimeout: 10000, - poolSize: 1, - debug: true - }); - - let connectionEvents = 0; - let disconnectEvents = 0; - let errorEvents = 0; - - // Monitor events - smtpClient.on('connection', () => { - connectionEvents++; - console.log('📡 Connection event'); - }); - - smtpClient.on('disconnect', () => { - disconnectEvents++; - console.log('🔌 Disconnect event'); - }); - - smtpClient.on('error', (error) => { - errorEvents++; - console.log('❌ Error event:', error.message); - }); - - // Send emails with delays - for (let i = 0; i < 3; i++) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Event test ${i + 1}`, - text: 'Testing events during keep-alive' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - - if (i < 2) { - await new Promise(resolve => setTimeout(resolve, 1500)); - } - } - - // Should have at least one connection event - expect(connectionEvents).toBeGreaterThan(0); - console.log(`✅ Captured ${connectionEvents} connection events`); - - await smtpClient.close(); - - // Wait a bit for close event - await new Promise(resolve => setTimeout(resolve, 100)); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_edge-cases/test.cedge-01.unusual-server-responses.ts b/test/suite/smtpclient_edge-cases/test.cedge-01.unusual-server-responses.ts deleted file mode 100644 index 2222e68..0000000 --- a/test/suite/smtpclient_edge-cases/test.cedge-01.unusual-server-responses.ts +++ /dev/null @@ -1,529 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2570, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2570); -}); - -tap.test('CEDGE-01: Multi-line greeting', async () => { - // Create custom server with multi-line greeting - const customServer = net.createServer((socket) => { - // Send multi-line greeting - socket.write('220-mail.example.com ESMTP Server\r\n'); - socket.write('220-Welcome to our mail server!\r\n'); - socket.write('220-Please be patient during busy times.\r\n'); - socket.write('220 Ready to serve\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log('Received:', command); - - if (command.startsWith('EHLO') || command.startsWith('HELO')) { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('500 Command not recognized\r\n'); - } - }); - }); - - await new Promise((resolve) => { - customServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const customPort = (customServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: customPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - console.log('Testing multi-line greeting handling...'); - - const connected = await smtpClient.verify(); - expect(connected).toBeTrue(); - - console.log('Successfully handled multi-line greeting'); - - await smtpClient.close(); - customServer.close(); -}); - -tap.test('CEDGE-01: Slow server responses', async () => { - // Create server with delayed responses - const slowServer = net.createServer((socket) => { - socket.write('220 Slow Server Ready\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log('Slow server received:', command); - - // Add artificial delays - const delay = 1000 + Math.random() * 2000; // 1-3 seconds - - setTimeout(() => { - if (command.startsWith('EHLO')) { - socket.write('250-slow.example.com\r\n'); - setTimeout(() => socket.write('250 OK\r\n'), 500); - } else if (command === 'QUIT') { - socket.write('221 Bye... slowly\r\n'); - setTimeout(() => socket.end(), 1000); - } else { - socket.write('250 OK... eventually\r\n'); - } - }, delay); - }); - }); - - await new Promise((resolve) => { - slowServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const slowPort = (slowServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: slowPort, - secure: false, - connectionTimeout: 10000, - debug: true - }); - - console.log('\nTesting slow server response handling...'); - const startTime = Date.now(); - - const connected = await smtpClient.verify(); - const connectTime = Date.now() - startTime; - - expect(connected).toBeTrue(); - console.log(`Connected after ${connectTime}ms (slow server)`); - expect(connectTime).toBeGreaterThan(1000); - - await smtpClient.close(); - slowServer.close(); -}); - -tap.test('CEDGE-01: Unusual status codes', async () => { - // Create server that returns unusual status codes - const unusualServer = net.createServer((socket) => { - socket.write('220 Unusual Server\r\n'); - - let commandCount = 0; - - socket.on('data', (data) => { - const command = data.toString().trim(); - commandCount++; - - // Return unusual but valid responses - if (command.startsWith('EHLO')) { - socket.write('250-unusual.example.com\r\n'); - socket.write('250-PIPELINING\r\n'); - socket.write('250 OK\r\n'); // Use 250 OK as final response - } else if (command.startsWith('MAIL FROM')) { - socket.write('250 Sender OK (#2.0.0)\r\n'); // Valid with enhanced code - } else if (command.startsWith('RCPT TO')) { - socket.write('250 Recipient OK\r\n'); // Keep it simple - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - } else if (command === '.') { - socket.write('250 Message accepted for delivery (#2.0.0)\r\n'); // With enhanced code - } else if (command === 'QUIT') { - socket.write('221 Bye (#2.0.0 closing connection)\r\n'); - socket.end(); - } else { - socket.write('250 OK\r\n'); // Default response - } - }); - }); - - await new Promise((resolve) => { - unusualServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const unusualPort = (unusualServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: unusualPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - console.log('\nTesting unusual status code handling...'); - - const connected = await smtpClient.verify(); - expect(connected).toBeTrue(); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Unusual Status Test', - text: 'Testing unusual server responses' - }); - - // Should handle unusual codes gracefully - const result = await smtpClient.sendMail(email); - console.log('Email sent despite unusual status codes'); - - await smtpClient.close(); - unusualServer.close(); -}); - -tap.test('CEDGE-01: Mixed line endings', async () => { - // Create server with inconsistent line endings - const mixedServer = net.createServer((socket) => { - // Mix CRLF, LF, and CR - socket.write('220 Mixed line endings server\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - - if (command.startsWith('EHLO')) { - // Mix different line endings - socket.write('250-mixed.example.com\n'); // LF only - socket.write('250-PIPELINING\r'); // CR only - socket.write('250-SIZE 10240000\r\n'); // Proper CRLF - socket.write('250 8BITMIME\n'); // LF only - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('250 OK\n'); // LF only - } - }); - }); - - await new Promise((resolve) => { - mixedServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const mixedPort = (mixedServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: mixedPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - console.log('\nTesting mixed line ending handling...'); - - const connected = await smtpClient.verify(); - expect(connected).toBeTrue(); - - console.log('Successfully handled mixed line endings'); - - await smtpClient.close(); - mixedServer.close(); -}); - -tap.test('CEDGE-01: Empty responses', async () => { - // Create server that sends minimal but valid responses - const emptyServer = net.createServer((socket) => { - socket.write('220 Server with minimal responses\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - - if (command.startsWith('EHLO')) { - // Send minimal but valid EHLO response - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - // Default minimal response - socket.write('250 OK\r\n'); - } - }); - }); - - await new Promise((resolve) => { - emptyServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const emptyPort = (emptyServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: emptyPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - console.log('\nTesting empty response handling...'); - - const connected = await smtpClient.verify(); - expect(connected).toBeTrue(); - - console.log('Connected successfully with minimal server responses'); - - await smtpClient.close(); - emptyServer.close(); -}); - -tap.test('CEDGE-01: Responses with special characters', async () => { - // Create server with special characters in responses - const specialServer = net.createServer((socket) => { - socket.write('220 ✉️ Unicode SMTP Server 🚀\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - - if (command.startsWith('EHLO')) { - socket.write('250-Hello 你好 مرحبا שלום\r\n'); - socket.write('250-Special chars: <>&"\'`\r\n'); - socket.write('250-Tabs\tand\tspaces here\r\n'); - socket.write('250 OK ✓\r\n'); - } else if (command === 'QUIT') { - socket.write('221 👋 Goodbye!\r\n'); - socket.end(); - } else { - socket.write('250 OK 👍\r\n'); - } - }); - }); - - await new Promise((resolve) => { - specialServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const specialPort = (specialServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: specialPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - console.log('\nTesting special character handling...'); - - const connected = await smtpClient.verify(); - expect(connected).toBeTrue(); - - console.log('Successfully handled special characters in responses'); - - await smtpClient.close(); - specialServer.close(); -}); - -tap.test('CEDGE-01: Pipelined responses', async () => { - // Create server that batches pipelined responses - const pipelineServer = net.createServer((socket) => { - socket.write('220 Pipeline Test Server\r\n'); - - let inDataMode = false; - - socket.on('data', (data) => { - const commands = data.toString().split('\r\n').filter(cmd => cmd.length > 0); - - commands.forEach(command => { - console.log('Pipeline server received:', command); - - if (inDataMode) { - if (command === '.') { - // End of DATA - socket.write('250 Message accepted\r\n'); - inDataMode = false; - } - // Otherwise, we're receiving email data - don't respond - } else if (command.startsWith('EHLO')) { - socket.write('250-pipeline.example.com\r\n250-PIPELINING\r\n250 OK\r\n'); - } else if (command.startsWith('MAIL FROM')) { - socket.write('250 Sender OK\r\n'); - } else if (command.startsWith('RCPT TO')) { - socket.write('250 Recipient OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Send data\r\n'); - inDataMode = true; - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - pipelineServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const pipelinePort = (pipelineServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: pipelinePort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - console.log('\nTesting pipelined responses...'); - - const connected = await smtpClient.verify(); - expect(connected).toBeTrue(); - - // Test sending email with pipelined server - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Pipeline Test', - text: 'Testing pipelined responses' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - console.log('Successfully handled pipelined responses'); - - await smtpClient.close(); - pipelineServer.close(); -}); - -tap.test('CEDGE-01: Extremely long response lines', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const connected = await smtpClient.verify(); - expect(connected).toBeTrue(); - - // Create very long message - const longString = 'x'.repeat(1000); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Long line test', - text: 'Testing long lines', - headers: { - 'X-Long-Header': longString, - 'X-Another-Long': `Start ${longString} End` - } - }); - - console.log('\nTesting extremely long response line handling...'); - - // Note: sendCommand is not a public API method - // We'll monitor line length through the actual email sending - let maxLineLength = 1000; // Estimate based on header content - - const result = await smtpClient.sendMail(email); - - console.log(`Maximum line length sent: ${maxLineLength} characters`); - console.log(`RFC 5321 limit: 998 characters (excluding CRLF)`); - - if (maxLineLength > 998) { - console.log('WARNING: Line length exceeds RFC limit'); - } - - expect(result).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('CEDGE-01: Server closes connection unexpectedly', async () => { - // Create server that closes connection at various points - let closeAfterCommands = 3; - let commandCount = 0; - - const abruptServer = net.createServer((socket) => { - socket.write('220 Abrupt Server\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - commandCount++; - - console.log(`Abrupt server: command ${commandCount} - ${command}`); - - if (commandCount >= closeAfterCommands) { - console.log('Abrupt server: Closing connection unexpectedly!'); - socket.destroy(); // Abrupt close - return; - } - - // Normal responses until close - 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('250 OK\r\n'); - } - }); - }); - - await new Promise((resolve) => { - abruptServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const abruptPort = (abruptServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: abruptPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - console.log('\nTesting abrupt connection close handling...'); - - // The verify should fail or succeed depending on when the server closes - const connected = await smtpClient.verify(); - - if (connected) { - // If verify succeeded, try sending email which should fail - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Abrupt close test', - text: 'Testing abrupt connection close' - }); - - try { - await smtpClient.sendMail(email); - console.log('Email sent before abrupt close'); - } catch (error) { - console.log('Expected error due to abrupt close:', error.message); - expect(error.message).toMatch(/closed|reset|abort|end|timeout/i); - } - } else { - // Verify failed due to abrupt close - console.log('Connection failed as expected due to abrupt server close'); - } - - abruptServer.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_edge-cases/test.cedge-02.malformed-commands.ts b/test/suite/smtpclient_edge-cases/test.cedge-02.malformed-commands.ts deleted file mode 100644 index c5926dd..0000000 --- a/test/suite/smtpclient_edge-cases/test.cedge-02.malformed-commands.ts +++ /dev/null @@ -1,438 +0,0 @@ -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'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2571, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2571); -}); - -tap.test('CEDGE-02: Commands with extra spaces', async () => { - // Create server that accepts commands with extra spaces - const spaceyServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\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; // Skip empty trailing line - - console.log(`Server received: "${line}"`); - - if (inData) { - if (line === '.') { - socket.write('250 Message accepted\r\n'); - inData = false; - } - // Otherwise it's email data, ignore - } else if (line.match(/^EHLO\s+/i)) { - socket.write('250-mail.example.com\r\n'); - socket.write('250 OK\r\n'); - } else if (line.match(/^MAIL\s+FROM:/i)) { - socket.write('250 OK\r\n'); - } else if (line.match(/^RCPT\s+TO:/i)) { - socket.write('250 OK\r\n'); - } else if (line === 'DATA') { - socket.write('354 Start mail input\r\n'); - inData = true; - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else if (line) { - socket.write('500 Command not recognized\r\n'); - } - }); - }); - }); - - await new Promise((resolve) => { - spaceyServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const spaceyPort = (spaceyServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: spaceyPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const verified = await smtpClient.verify(); - expect(verified).toBeTrue(); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Test with extra spaces', - text: 'Testing command formatting' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - console.log('✅ Server handled commands with extra spaces'); - - await smtpClient.close(); - spaceyServer.close(); -}); - -tap.test('CEDGE-02: Mixed case commands', async () => { - // Create server that accepts mixed case commands - const mixedCaseServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\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; - - const upperLine = line.toUpperCase(); - console.log(`Server received: "${line}"`); - - if (inData) { - if (line === '.') { - socket.write('250 Message accepted\r\n'); - inData = false; - } - } else if (upperLine.startsWith('EHLO')) { - socket.write('250-mail.example.com\r\n'); - socket.write('250 8BITMIME\r\n'); - } else if (upperLine.startsWith('MAIL FROM:')) { - socket.write('250 OK\r\n'); - } else if (upperLine.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - } else if (upperLine === 'DATA') { - socket.write('354 Start mail input\r\n'); - inData = true; - } else if (upperLine === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - mixedCaseServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const mixedPort = (mixedCaseServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: mixedPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const verified = await smtpClient.verify(); - expect(verified).toBeTrue(); - console.log('✅ Server accepts mixed case commands'); - - await smtpClient.close(); - mixedCaseServer.close(); -}); - -tap.test('CEDGE-02: Commands with missing parameters', async () => { - // Create server that handles incomplete commands - const incompleteServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (!line && lines[lines.length - 1] === '') return; - - console.log(`Server received: "${line}"`); - - if (line.startsWith('EHLO')) { - socket.write('250 OK\r\n'); - } else if (line === 'MAIL FROM:' || line === 'MAIL FROM') { - // Missing email address - socket.write('501 Syntax error in parameters\r\n'); - } else if (line === 'RCPT TO:' || line === 'RCPT TO') { - // Missing recipient - socket.write('501 Syntax error in parameters\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else if (line) { - socket.write('500 Command not recognized\r\n'); - } - }); - }); - }); - - await new Promise((resolve) => { - incompleteServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const incompletePort = (incompleteServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: incompletePort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // This should succeed as the client sends proper commands - const verified = await smtpClient.verify(); - expect(verified).toBeTrue(); - console.log('✅ Client sends properly formatted commands'); - - await smtpClient.close(); - incompleteServer.close(); -}); - -tap.test('CEDGE-02: Commands with extra parameters', async () => { - // Create server that handles commands with extra parameters - const extraParamsServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\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; - - console.log(`Server received: "${line}"`); - - if (inData) { - if (line === '.') { - socket.write('250 Message accepted\r\n'); - inData = false; - } - } else if (line.startsWith('EHLO')) { - // Accept EHLO with any parameter - socket.write('250-mail.example.com\r\n'); - socket.write('250-SIZE 10240000\r\n'); - socket.write('250 8BITMIME\r\n'); - } else if (line.match(/^MAIL FROM:.*SIZE=/i)) { - // Accept SIZE parameter - socket.write('250 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 Start mail input\r\n'); - inData = true; - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - extraParamsServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const extraPort = (extraParamsServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: extraPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Test with parameters', - text: 'Testing extra parameters' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - console.log('✅ Server handled commands with extra parameters'); - - await smtpClient.close(); - extraParamsServer.close(); -}); - -tap.test('CEDGE-02: Invalid command sequences', async () => { - // Create server that enforces command sequence - const sequenceServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\r\n'); - let state = 'GREETING'; - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (!line && lines[lines.length - 1] === '') return; - - console.log(`Server received: "${line}" in state ${state}`); - - if (state === 'DATA' && line !== '.') { - // In DATA state, ignore everything except the terminating period - return; - } - - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - state = 'READY'; - socket.write('250 OK\r\n'); - } else if (line.startsWith('MAIL FROM:')) { - if (state !== 'READY') { - socket.write('503 Bad sequence of commands\r\n'); - } else { - state = 'MAIL'; - socket.write('250 OK\r\n'); - } - } else if (line.startsWith('RCPT TO:')) { - if (state !== 'MAIL' && state !== 'RCPT') { - socket.write('503 Bad sequence of commands\r\n'); - } else { - state = 'RCPT'; - socket.write('250 OK\r\n'); - } - } else if (line === 'DATA') { - if (state !== 'RCPT') { - socket.write('503 Bad sequence of commands\r\n'); - } else { - state = 'DATA'; - socket.write('354 Start mail input\r\n'); - } - } else if (line === '.' && state === 'DATA') { - state = 'READY'; - socket.write('250 Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else if (line === 'RSET') { - state = 'READY'; - socket.write('250 OK\r\n'); - } - }); - }); - }); - - await new Promise((resolve) => { - sequenceServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const sequencePort = (sequenceServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: sequencePort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Client should handle proper command sequencing - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Test sequence', - text: 'Testing command sequence' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - console.log('✅ Client maintains proper command sequence'); - - await smtpClient.close(); - sequenceServer.close(); -}); - -tap.test('CEDGE-02: Malformed email addresses', async () => { - // Test how client handles various email formats - const emailServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\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; - - console.log(`Server received: "${line}"`); - - if (inData) { - if (line === '.') { - socket.write('250 Message accepted\r\n'); - inData = false; - } - } else if (line.startsWith('EHLO')) { - socket.write('250 OK\r\n'); - } else if (line.startsWith('MAIL FROM:')) { - // Accept any sender format - socket.write('250 OK\r\n'); - } else if (line.startsWith('RCPT TO:')) { - // Accept any recipient format - socket.write('250 OK\r\n'); - } else if (line === 'DATA') { - socket.write('354 Start mail input\r\n'); - inData = true; - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - emailServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const emailPort = (emailServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: emailPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Test with properly formatted email - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Test email formats', - text: 'Testing email address handling' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - console.log('✅ Client properly formats email addresses'); - - await smtpClient.close(); - emailServer.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_edge-cases/test.cedge-03.protocol-violations.ts b/test/suite/smtpclient_edge-cases/test.cedge-03.protocol-violations.ts deleted file mode 100644 index 233bc79..0000000 --- a/test/suite/smtpclient_edge-cases/test.cedge-03.protocol-violations.ts +++ /dev/null @@ -1,446 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2572, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2572); -}); - -tap.test('CEDGE-03: Server closes connection during MAIL FROM', async () => { - // Create server that abruptly closes during MAIL FROM - const abruptServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\r\n'); - let commandCount = 0; - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (!line && lines[lines.length - 1] === '') return; - - commandCount++; - console.log(`Server received command ${commandCount}: "${line}"`); - - if (line.startsWith('EHLO')) { - socket.write('250-mail.example.com\r\n'); - socket.write('250 OK\r\n'); - } else if (line.startsWith('MAIL FROM:')) { - // Abruptly close connection - console.log('Server closing connection unexpectedly'); - socket.destroy(); - } - }); - }); - }); - - await new Promise((resolve) => { - abruptServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const abruptPort = (abruptServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: abruptPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Connection closure test', - text: 'Testing unexpected disconnection' - }); - - try { - const result = await smtpClient.sendMail(email); - // Should not succeed due to connection closure - expect(result.success).toBeFalse(); - console.log('✅ Client handled abrupt connection closure gracefully'); - } catch (error) { - // Expected to fail due to connection closure - console.log('✅ Client threw expected error for connection closure:', error.message); - expect(error.message).toMatch(/closed|reset|abort|end|timeout/i); - } - - await smtpClient.close(); - abruptServer.close(); -}); - -tap.test('CEDGE-03: Server sends invalid response codes', async () => { - // Create server that sends non-standard response codes - const invalidServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\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; - - console.log(`Server received: "${line}"`); - - if (inData) { - if (line === '.') { - socket.write('999 Invalid response code\r\n'); // Invalid 9xx code - inData = false; - } - } else if (line.startsWith('EHLO')) { - socket.write('150 Intermediate response\r\n'); // Invalid for EHLO - } 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 Start mail input\r\n'); - inData = true; - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - invalidServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const invalidPort = (invalidServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: invalidPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - try { - // This will likely fail due to invalid EHLO response - const verified = await smtpClient.verify(); - expect(verified).toBeFalse(); - console.log('✅ Client rejected invalid response codes'); - } catch (error) { - console.log('✅ Client properly handled invalid response codes:', error.message); - } - - await smtpClient.close(); - invalidServer.close(); -}); - -tap.test('CEDGE-03: Server sends malformed multi-line responses', async () => { - // Create server with malformed multi-line responses - const malformedServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (!line && lines[lines.length - 1] === '') return; - - console.log(`Server received: "${line}"`); - - if (line.startsWith('EHLO')) { - // Malformed multi-line response (missing final line) - socket.write('250-mail.example.com\r\n'); - socket.write('250-PIPELINING\r\n'); - // Missing final 250 line - this violates RFC - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - malformedServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const malformedPort = (malformedServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: malformedPort, - secure: false, - connectionTimeout: 3000, // Shorter timeout for faster test - debug: true - }); - - try { - // Should timeout due to incomplete EHLO response - const verified = await smtpClient.verify(); - - // If we get here, the client accepted the malformed response - // This is acceptable if the client can work around it - if (verified === false) { - console.log('✅ Client rejected malformed multi-line response'); - } else { - console.log('⚠️ Client accepted malformed multi-line response'); - } - } catch (error) { - console.log('✅ Client handled malformed response with error:', error.message); - // Should timeout or error on malformed response - expect(error.message).toMatch(/timeout|Command timeout|Greeting timeout|response|parse/i); - } - - // Force close since the connection might still be waiting - try { - await smtpClient.close(); - } catch (closeError) { - // Ignore close errors - } - - malformedServer.close(); -}); - -tap.test('CEDGE-03: Server violates command sequence rules', async () => { - // Create server that accepts commands out of sequence - const sequenceServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (!line && lines[lines.length - 1] === '') return; - - console.log(`Server received: "${line}"`); - - // Accept any command in any order (protocol violation) - if (line.startsWith('EHLO')) { - socket.write('250 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 Start mail input\r\n'); - } else if (line === '.') { - socket.write('250 Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - sequenceServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const sequencePort = (sequenceServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: sequencePort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Client should still work correctly despite server violations - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Sequence violation test', - text: 'Testing command sequence violations' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - console.log('✅ Client maintains proper sequence despite server violations'); - - await smtpClient.close(); - sequenceServer.close(); -}); - -tap.test('CEDGE-03: Server sends responses without CRLF', async () => { - // Create server that sends responses with incorrect line endings - const crlfServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\n'); // LF only, not CRLF - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (!line && lines[lines.length - 1] === '') return; - - console.log(`Server received: "${line}"`); - - if (line.startsWith('EHLO')) { - socket.write('250 OK\n'); // LF only - } else if (line === 'QUIT') { - socket.write('221 Bye\n'); // LF only - socket.end(); - } else { - socket.write('250 OK\n'); // LF only - } - }); - }); - }); - - await new Promise((resolve) => { - crlfServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const crlfPort = (crlfServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: crlfPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - try { - const verified = await smtpClient.verify(); - if (verified) { - console.log('✅ Client handled non-CRLF responses gracefully'); - } else { - console.log('✅ Client rejected non-CRLF responses'); - } - } catch (error) { - console.log('✅ Client handled CRLF violation with error:', error.message); - } - - await smtpClient.close(); - crlfServer.close(); -}); - -tap.test('CEDGE-03: Server sends oversized responses', async () => { - // Create server that sends very long response lines - const oversizeServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (!line && lines[lines.length - 1] === '') return; - - console.log(`Server received: "${line}"`); - - if (line.startsWith('EHLO')) { - // Send an extremely long response line (over RFC limit) - const longResponse = '250 ' + 'x'.repeat(2000) + '\r\n'; - socket.write(longResponse); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('250 OK\r\n'); - } - }); - }); - }); - - await new Promise((resolve) => { - oversizeServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const oversizePort = (oversizeServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: oversizePort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - try { - const verified = await smtpClient.verify(); - console.log(`Verification with oversized response: ${verified}`); - console.log('✅ Client handled oversized response'); - } catch (error) { - console.log('✅ Client handled oversized response with error:', error.message); - } - - await smtpClient.close(); - oversizeServer.close(); -}); - -tap.test('CEDGE-03: Server violates RFC timing requirements', async () => { - // Create server that has excessive delays - const slowServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (!line && lines[lines.length - 1] === '') return; - - console.log(`Server received: "${line}"`); - - if (line.startsWith('EHLO')) { - // Extreme delay (violates RFC timing recommendations) - setTimeout(() => { - socket.write('250 OK\r\n'); - }, 2000); // 2 second delay - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('250 OK\r\n'); - } - }); - }); - }); - - await new Promise((resolve) => { - slowServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const slowPort = (slowServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: slowPort, - secure: false, - connectionTimeout: 10000, // Allow time for slow response - debug: true - }); - - const startTime = Date.now(); - try { - const verified = await smtpClient.verify(); - const duration = Date.now() - startTime; - - console.log(`Verification completed in ${duration}ms`); - if (verified) { - console.log('✅ Client handled slow server responses'); - } - } catch (error) { - console.log('✅ Client handled timing violation with error:', error.message); - } - - await smtpClient.close(); - slowServer.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_edge-cases/test.cedge-04.resource-constraints.ts b/test/suite/smtpclient_edge-cases/test.cedge-04.resource-constraints.ts deleted file mode 100644 index d0e66a8..0000000 --- a/test/suite/smtpclient_edge-cases/test.cedge-04.resource-constraints.ts +++ /dev/null @@ -1,530 +0,0 @@ -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'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2573, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2573); -}); - -tap.test('CEDGE-04: Server with connection limits', async () => { - // Create server that only accepts 2 connections - let connectionCount = 0; - const maxConnections = 2; - - const limitedServer = net.createServer((socket) => { - connectionCount++; - console.log(`Connection ${connectionCount} established`); - - if (connectionCount > maxConnections) { - console.log('Rejecting connection due to limit'); - socket.write('421 Too many connections\r\n'); - socket.end(); - return; - } - - socket.write('220 mail.example.com ESMTP\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; - - console.log(`Server received: "${line}"`); - - if (inData) { - if (line === '.') { - socket.write('250 Message accepted\r\n'); - inData = false; - } - } else if (line.startsWith('EHLO')) { - socket.write('250 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 Start mail input\r\n'); - inData = true; - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - - socket.on('close', () => { - connectionCount--; - console.log(`Connection closed, ${connectionCount} remaining`); - }); - }); - - await new Promise((resolve) => { - limitedServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const limitedPort = (limitedServer.address() as net.AddressInfo).port; - - // Create multiple clients to test connection limits - const clients: SmtpClient[] = []; - - for (let i = 0; i < 4; i++) { - const client = createSmtpClient({ - host: '127.0.0.1', - port: limitedPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - clients.push(client); - } - - // Try to verify all clients concurrently to test connection limits - const promises = clients.map(async (client) => { - try { - const verified = await client.verify(); - return verified; - } catch (error) { - console.log('Connection failed:', error.message); - return false; - } - }); - - const results = await Promise.all(promises); - - // Since verify() closes connections immediately, we can't really test concurrent limits - // Instead, test that all clients can connect sequentially - const successCount = results.filter(r => r).length; - console.log(`${successCount} out of ${clients.length} connections succeeded`); - expect(successCount).toBeGreaterThan(0); - console.log('✅ Clients handled connection attempts gracefully'); - - // Clean up - for (const client of clients) { - await client.close(); - } - limitedServer.close(); -}); - -tap.test('CEDGE-04: Large email message handling', async () => { - // Test with very large email content - const largeServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\r\n'); - let inData = false; - let dataSize = 0; - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (!line && lines[lines.length - 1] === '') return; - - if (inData) { - dataSize += line.length; - if (line === '.') { - console.log(`Received email data: ${dataSize} bytes`); - if (dataSize > 50000) { - socket.write('552 Message size exceeds limit\r\n'); - } else { - socket.write('250 Message accepted\r\n'); - } - inData = false; - dataSize = 0; - } - } else if (line.startsWith('EHLO')) { - socket.write('250-mail.example.com\r\n'); - socket.write('250-SIZE 50000\r\n'); // 50KB limit - socket.write('250 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 Start mail input\r\n'); - inData = true; - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - largeServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const largePort = (largeServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: largePort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Test with large content - const largeContent = 'X'.repeat(60000); // 60KB content - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Large email test', - text: largeContent - }); - - const result = await smtpClient.sendMail(email); - // Should fail due to size limit - expect(result.success).toBeFalse(); - console.log('✅ Server properly rejected oversized email'); - - await smtpClient.close(); - largeServer.close(); -}); - -tap.test('CEDGE-04: Memory pressure simulation', async () => { - // Create server that simulates memory pressure - const memoryServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\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) { - if (line === '.') { - // Simulate memory pressure by delaying response - setTimeout(() => { - socket.write('451 Temporary failure due to system load\r\n'); - }, 1000); - inData = false; - } - } else if (line.startsWith('EHLO')) { - socket.write('250 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 Start mail input\r\n'); - inData = true; - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - memoryServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const memoryPort = (memoryServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: memoryPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Memory pressure test', - text: 'Testing memory constraints' - }); - - const result = await smtpClient.sendMail(email); - // Should handle temporary failure gracefully - expect(result.success).toBeFalse(); - console.log('✅ Client handled temporary failure gracefully'); - - await smtpClient.close(); - memoryServer.close(); -}); - -tap.test('CEDGE-04: High concurrent connections', async () => { - // Test multiple concurrent connections - const concurrentServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\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) { - if (line === '.') { - socket.write('250 Message accepted\r\n'); - inData = false; - } - } else if (line.startsWith('EHLO')) { - socket.write('250 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 Start mail input\r\n'); - inData = true; - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - concurrentServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const concurrentPort = (concurrentServer.address() as net.AddressInfo).port; - - // Create multiple clients concurrently - const clientPromises: Promise[] = []; - const numClients = 10; - - for (let i = 0; i < numClients; i++) { - const clientPromise = (async () => { - const client = createSmtpClient({ - host: '127.0.0.1', - port: concurrentPort, - secure: false, - connectionTimeout: 5000, - pool: true, - maxConnections: 2, - debug: false // Reduce noise - }); - - try { - const email = new Email({ - from: `sender${i}@example.com`, - to: ['recipient@example.com'], - subject: `Concurrent test ${i}`, - text: `Message from client ${i}` - }); - - const result = await client.sendMail(email); - await client.close(); - return result.success; - } catch (error) { - await client.close(); - return false; - } - })(); - - clientPromises.push(clientPromise); - } - - const results = await Promise.all(clientPromises); - const successCount = results.filter(r => r).length; - - console.log(`${successCount} out of ${numClients} concurrent operations succeeded`); - expect(successCount).toBeGreaterThan(5); // At least half should succeed - console.log('✅ Handled concurrent connections successfully'); - - concurrentServer.close(); -}); - -tap.test('CEDGE-04: Bandwidth limitations', async () => { - // Simulate bandwidth constraints - const slowBandwidthServer = net.createServer((socket) => { - socket.write('220 mail.example.com ESMTP\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) { - if (line === '.') { - // Slow response to simulate bandwidth constraint - setTimeout(() => { - socket.write('250 Message accepted\r\n'); - }, 500); - inData = false; - } - } else if (line.startsWith('EHLO')) { - // Slow EHLO response - setTimeout(() => { - socket.write('250 OK\r\n'); - }, 300); - } else if (line.startsWith('MAIL FROM:')) { - setTimeout(() => { - socket.write('250 OK\r\n'); - }, 200); - } else if (line.startsWith('RCPT TO:')) { - setTimeout(() => { - socket.write('250 OK\r\n'); - }, 200); - } else if (line === 'DATA') { - setTimeout(() => { - socket.write('354 Start mail input\r\n'); - inData = true; - }, 200); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - slowBandwidthServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const slowPort = (slowBandwidthServer.address() as net.AddressInfo).port; - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: slowPort, - secure: false, - connectionTimeout: 10000, // Higher timeout for slow server - debug: true - }); - - const startTime = Date.now(); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Bandwidth test', - text: 'Testing bandwidth constraints' - }); - - const result = await smtpClient.sendMail(email); - const duration = Date.now() - startTime; - - expect(result.success).toBeTrue(); - expect(duration).toBeGreaterThan(1000); // Should take time due to delays - console.log(`✅ Handled bandwidth constraints (${duration}ms)`); - - await smtpClient.close(); - slowBandwidthServer.close(); -}); - -tap.test('CEDGE-04: Resource exhaustion recovery', async () => { - // Test recovery from resource exhaustion - let isExhausted = true; - - const exhaustionServer = net.createServer((socket) => { - if (isExhausted) { - socket.write('421 Service temporarily unavailable\r\n'); - socket.end(); - // Simulate recovery after first connection - setTimeout(() => { - isExhausted = false; - }, 1000); - return; - } - - socket.write('220 mail.example.com ESMTP\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) { - if (line === '.') { - socket.write('250 Message accepted\r\n'); - inData = false; - } - } else if (line.startsWith('EHLO')) { - socket.write('250 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 Start mail input\r\n'); - inData = true; - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - exhaustionServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const exhaustionPort = (exhaustionServer.address() as net.AddressInfo).port; - - // First attempt should fail - const client1 = createSmtpClient({ - host: '127.0.0.1', - port: exhaustionPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const verified1 = await client1.verify(); - expect(verified1).toBeFalse(); - console.log('✅ First connection failed due to exhaustion'); - await client1.close(); - - // Wait for recovery - await new Promise(resolve => setTimeout(resolve, 1500)); - - // Second attempt should succeed - const client2 = createSmtpClient({ - host: '127.0.0.1', - port: exhaustionPort, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Recovery test', - text: 'Testing recovery from exhaustion' - }); - - const result = await client2.sendMail(email); - expect(result.success).toBeTrue(); - console.log('✅ Successfully recovered from resource exhaustion'); - - await client2.close(); - exhaustionServer.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_edge-cases/test.cedge-05.encoding-issues.ts b/test/suite/smtpclient_edge-cases/test.cedge-05.encoding-issues.ts deleted file mode 100644 index c658de1..0000000 --- a/test/suite/smtpclient_edge-cases/test.cedge-05.encoding-issues.ts +++ /dev/null @@ -1,145 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2570, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2570); -}); - -tap.test('CEDGE-05: Mixed character encodings in email content', async () => { - console.log('Testing mixed character encodings'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Email with mixed encodings - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Test with émojis 🎉 and spéçiål characters', - text: 'Plain text with Unicode: café, naïve, 你好, مرحبا', - html: '

HTML with entities: café, naïve, and emoji 🌟

', - attachments: [{ - filename: 'tëst-filé.txt', - content: 'Attachment content with special chars: ñ, ü, ß' - }] - }); - - const result = await smtpClient.sendMail(email); - console.log(`Result: ${result.messageId ? 'Success' : 'Failed'}`); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - await smtpClient.close(); -}); - -tap.test('CEDGE-05: Base64 encoding edge cases', async () => { - console.log('Testing Base64 encoding edge cases'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create various sizes of binary content - const sizes = [0, 1, 2, 3, 57, 58, 59, 76, 77]; // Edge cases for base64 line wrapping - - for (const size of sizes) { - const binaryContent = Buffer.alloc(size); - for (let i = 0; i < size; i++) { - binaryContent[i] = i % 256; - } - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Base64 test with ${size} bytes`, - text: 'Testing base64 encoding', - attachments: [{ - filename: `test-${size}.bin`, - content: binaryContent - }] - }); - - console.log(` Testing with ${size} byte attachment...`); - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - } - - await smtpClient.close(); -}); - -tap.test('CEDGE-05: Header encoding (RFC 2047)', async () => { - console.log('Testing header encoding (RFC 2047)'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Test various header encodings - const testCases = [ - { - subject: 'Simple ASCII subject', - from: 'john@example.com' - }, - { - subject: 'Subject with émojis 🎉 and spéçiål çhåracters', - from: 'john@example.com' - }, - { - subject: 'Japanese: こんにちは, Chinese: 你好, Arabic: مرحبا', - from: 'yamada@example.com' - } - ]; - - for (const testCase of testCases) { - console.log(` Testing: "${testCase.subject.substring(0, 50)}..."`); - - const email = new Email({ - from: testCase.from, - to: ['recipient@example.com'], - subject: testCase.subject, - text: 'Testing header encoding', - headers: { - 'X-Custom': `Custom header with special chars: ${testCase.subject.substring(0, 20)}` - } - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - } - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); diff --git a/test/suite/smtpclient_edge-cases/test.cedge-06.large-headers.ts b/test/suite/smtpclient_edge-cases/test.cedge-06.large-headers.ts deleted file mode 100644 index acb5d68..0000000 --- a/test/suite/smtpclient_edge-cases/test.cedge-06.large-headers.ts +++ /dev/null @@ -1,180 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2575, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2575); -}); - -tap.test('CEDGE-06: Very long subject lines', async () => { - console.log('Testing very long subject lines'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Test various subject line lengths - const testSubjects = [ - 'Normal Subject Line', - 'A'.repeat(100), // 100 chars - 'B'.repeat(500), // 500 chars - 'C'.repeat(1000), // 1000 chars - 'D'.repeat(2000), // 2000 chars - very long - ]; - - for (const subject of testSubjects) { - console.log(` Testing subject length: ${subject.length} chars`); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: subject, - text: 'Testing large subject headers' - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - } - - await smtpClient.close(); -}); - -tap.test('CEDGE-06: Multiple large headers', async () => { - console.log('Testing multiple large headers'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create email with multiple large headers - const largeValue = 'X'.repeat(500); - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Multiple large headers test', - text: 'Testing multiple large headers', - headers: { - 'X-Large-Header-1': largeValue, - 'X-Large-Header-2': largeValue, - 'X-Large-Header-3': largeValue, - 'X-Large-Header-4': largeValue, - 'X-Large-Header-5': largeValue, - 'X-Very-Long-Header-Name-That-Exceeds-Normal-Limits': 'Value for long header name', - 'X-Mixed-Content': `Start-${largeValue}-Middle-${largeValue}-End` - } - }); - - const result = await smtpClient.sendMail(email); - console.log(`Result: ${result.messageId ? 'Success' : 'Failed'}`); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - await smtpClient.close(); -}); - -tap.test('CEDGE-06: Header folding and wrapping', async () => { - console.log('Testing header folding and wrapping'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create headers that should be folded - const longHeaderValue = 'This is a very long header value that should exceed the recommended 78 character line limit and force the header to be folded across multiple lines according to RFC 5322 specifications'; - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Header folding test with a very long subject line that should also be folded properly', - text: 'Testing header folding', - headers: { - 'X-Long-Header': longHeaderValue, - 'X-Multi-Line': `Line 1 ${longHeaderValue}\nLine 2 ${longHeaderValue}\nLine 3 ${longHeaderValue}`, - 'X-Special-Chars': `Header with special chars: \t\r\n\x20 and unicode: 🎉 émojis` - } - }); - - const result = await smtpClient.sendMail(email); - console.log(`Result: ${result.messageId ? 'Success' : 'Failed'}`); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - await smtpClient.close(); -}); - -tap.test('CEDGE-06: Maximum header size limits', async () => { - console.log('Testing maximum header size limits'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Test near RFC limits (recommended 998 chars per line) - const nearMaxValue = 'Y'.repeat(900); // Near but under limit - const overMaxValue = 'Z'.repeat(1500); // Over recommended limit - - const testCases = [ - { name: 'Near limit', value: nearMaxValue }, - { name: 'Over limit', value: overMaxValue } - ]; - - for (const testCase of testCases) { - console.log(` Testing ${testCase.name}: ${testCase.value.length} chars`); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Header size test: ${testCase.name}`, - text: 'Testing header size limits', - headers: { - 'X-Size-Test': testCase.value - } - }); - - try { - const result = await smtpClient.sendMail(email); - console.log(` ${testCase.name}: Success`); - expect(result).toBeDefined(); - } catch (error) { - console.log(` ${testCase.name}: Failed (${error.message})`); - // Some failures might be expected for oversized headers - expect(error).toBeDefined(); - } - } - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_edge-cases/test.cedge-07.concurrent-operations.ts b/test/suite/smtpclient_edge-cases/test.cedge-07.concurrent-operations.ts deleted file mode 100644 index 9e27342..0000000 --- a/test/suite/smtpclient_edge-cases/test.cedge-07.concurrent-operations.ts +++ /dev/null @@ -1,204 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2576, - tlsEnabled: false, - authRequired: false, - maxConnections: 20 // Allow more connections for concurrent testing - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2576); -}); - -tap.test('CEDGE-07: Multiple simultaneous connections', async () => { - console.log('Testing multiple simultaneous connections'); - - const connectionCount = 5; - const clients = []; - - // Create multiple clients - for (let i = 0; i < connectionCount; i++) { - const client = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: false, // Reduce noise - maxConnections: 2 - }); - clients.push(client); - } - - // Test concurrent verification - console.log(` Testing ${connectionCount} concurrent verifications...`); - const verifyPromises = clients.map(async (client, index) => { - try { - const result = await client.verify(); - console.log(` Client ${index + 1}: ${result ? 'Success' : 'Failed'}`); - return result; - } catch (error) { - console.log(` Client ${index + 1}: Error - ${error.message}`); - return false; - } - }); - - const verifyResults = await Promise.all(verifyPromises); - const successCount = verifyResults.filter(r => r).length; - console.log(` Verify results: ${successCount}/${connectionCount} successful`); - - // We expect at least some connections to succeed - expect(successCount).toBeGreaterThan(0); - - // Clean up clients - await Promise.all(clients.map(client => client.close().catch(() => {}))); -}); - -tap.test('CEDGE-07: Concurrent email sending', async () => { - console.log('Testing concurrent email sending'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: false, - maxConnections: 5 - }); - - const emailCount = 10; - console.log(` Sending ${emailCount} emails concurrently...`); - - const sendPromises = []; - for (let i = 0; i < emailCount; i++) { - const email = new Email({ - from: 'sender@example.com', - to: [`recipient${i}@example.com`], - subject: `Concurrent test email ${i + 1}`, - text: `This is concurrent test email number ${i + 1}` - }); - - sendPromises.push( - smtpClient.sendMail(email).then( - result => { - console.log(` Email ${i + 1}: Success`); - return { success: true, result }; - }, - error => { - console.log(` Email ${i + 1}: Failed - ${error.message}`); - return { success: false, error }; - } - ) - ); - } - - const results = await Promise.all(sendPromises); - const successCount = results.filter(r => r.success).length; - console.log(` Send results: ${successCount}/${emailCount} successful`); - - // We expect a high success rate - expect(successCount).toBeGreaterThan(emailCount * 0.7); // At least 70% success - - await smtpClient.close(); -}); - -tap.test('CEDGE-07: Rapid connection cycling', async () => { - console.log('Testing rapid connection cycling'); - - const cycleCount = 8; - console.log(` Performing ${cycleCount} rapid connect/disconnect cycles...`); - - const cyclePromises = []; - for (let i = 0; i < cycleCount; i++) { - cyclePromises.push( - (async () => { - const client = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 3000, - debug: false - }); - - try { - const verified = await client.verify(); - console.log(` Cycle ${i + 1}: ${verified ? 'Success' : 'Failed'}`); - await client.close(); - return verified; - } catch (error) { - console.log(` Cycle ${i + 1}: Error - ${error.message}`); - await client.close().catch(() => {}); - return false; - } - })() - ); - } - - const cycleResults = await Promise.all(cyclePromises); - const successCount = cycleResults.filter(r => r).length; - console.log(` Cycle results: ${successCount}/${cycleCount} successful`); - - // We expect most cycles to succeed - expect(successCount).toBeGreaterThan(cycleCount * 0.6); // At least 60% success -}); - -tap.test('CEDGE-07: Connection pool stress test', async () => { - console.log('Testing connection pool under stress'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: false, - maxConnections: 3, - maxMessages: 50 - }); - - const stressCount = 15; - console.log(` Sending ${stressCount} emails to stress connection pool...`); - - const startTime = Date.now(); - const stressPromises = []; - - for (let i = 0; i < stressCount; i++) { - const email = new Email({ - from: 'stress@example.com', - to: [`stress${i}@example.com`], - subject: `Stress test ${i + 1}`, - text: `Connection pool stress test email ${i + 1}` - }); - - stressPromises.push( - smtpClient.sendMail(email).then( - result => ({ success: true, index: i }), - error => ({ success: false, index: i, error: error.message }) - ) - ); - } - - const stressResults = await Promise.all(stressPromises); - const duration = Date.now() - startTime; - const successCount = stressResults.filter(r => r.success).length; - - console.log(` Stress results: ${successCount}/${stressCount} successful in ${duration}ms`); - console.log(` Average: ${Math.round(duration / stressCount)}ms per email`); - - // Under stress, we still expect reasonable success rate - expect(successCount).toBeGreaterThan(stressCount * 0.5); // At least 50% success under stress - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_email-composition/test.cep-01.basic-headers.ts b/test/suite/smtpclient_email-composition/test.cep-01.basic-headers.ts deleted file mode 100644 index 211c6fd..0000000 --- a/test/suite/smtpclient_email-composition/test.cep-01.basic-headers.ts +++ /dev/null @@ -1,245 +0,0 @@ -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 email composition tests', async () => { - testServer = await startTestServer({ - port: 2570, - tlsEnabled: false, - authRequired: false - }); - - expect(testServer.port).toEqual(2570); -}); - -tap.test('setup - create SMTP client', async () => { - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const isConnected = await smtpClient.verify(); - expect(isConnected).toBeTrue(); -}); - -tap.test('CEP-01: Basic Headers - should send email with required headers', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Test Email with Basic Headers', - text: 'This is the plain text body' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients).toContain('recipient@example.com'); - expect(result.messageId).toBeTypeofString(); - - console.log('✅ Basic email headers sent successfully'); - console.log('📧 Message ID:', result.messageId); -}); - -tap.test('CEP-01: Basic Headers - should handle multiple recipients', async () => { - const email = new Email({ - from: 'sender@example.com', - to: ['recipient1@example.com', 'recipient2@example.com', 'recipient3@example.com'], - subject: 'Email to Multiple Recipients', - text: 'This email has multiple recipients' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.acceptedRecipients).toContain('recipient1@example.com'); - expect(result.acceptedRecipients).toContain('recipient2@example.com'); - expect(result.acceptedRecipients).toContain('recipient3@example.com'); - - console.log(`✅ Sent to ${result.acceptedRecipients.length} recipients`); -}); - -tap.test('CEP-01: Basic Headers - should support CC and BCC recipients', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'primary@example.com', - cc: ['cc1@example.com', 'cc2@example.com'], - bcc: ['bcc1@example.com', 'bcc2@example.com'], - subject: 'Email with CC and BCC', - text: 'Testing CC and BCC functionality' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - // All recipients should be accepted - expect(result.acceptedRecipients.length).toEqual(5); - - console.log('✅ CC and BCC recipients handled correctly'); -}); - -tap.test('CEP-01: Basic Headers - should add custom headers', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Email with Custom Headers', - text: 'This email contains custom headers', - headers: { - 'X-Custom-Header': 'custom-value', - 'X-Priority': '1', - 'X-Mailer': 'DCRouter Test Suite', - 'Reply-To': 'replies@example.com' - } - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Custom headers added to email'); -}); - -tap.test('CEP-01: Basic Headers - should set email priority', async () => { - // Test high priority - const highPriorityEmail = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'High Priority Email', - text: 'This is a high priority message', - priority: 'high' - }); - - const highResult = await smtpClient.sendMail(highPriorityEmail); - expect(highResult.success).toBeTrue(); - - // Test normal priority - const normalPriorityEmail = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Normal Priority Email', - text: 'This is a normal priority message', - priority: 'normal' - }); - - const normalResult = await smtpClient.sendMail(normalPriorityEmail); - expect(normalResult.success).toBeTrue(); - - // Test low priority - const lowPriorityEmail = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Low Priority Email', - text: 'This is a low priority message', - priority: 'low' - }); - - const lowResult = await smtpClient.sendMail(lowPriorityEmail); - expect(lowResult.success).toBeTrue(); - - console.log('✅ All priority levels handled correctly'); -}); - -tap.test('CEP-01: Basic Headers - should handle sender with display name', async () => { - const email = new Email({ - from: 'John Doe ', - to: 'Jane Smith ', - subject: 'Email with Display Names', - text: 'Testing display names in email addresses' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.envelope?.from).toContain('john.doe@example.com'); - - console.log('✅ Display names in addresses handled correctly'); -}); - -tap.test('CEP-01: Basic Headers - should generate proper Message-ID', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Message-ID Test', - text: 'Testing Message-ID generation' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - expect(result.messageId).toBeTypeofString(); - - // Message-ID should contain id@domain format (without angle brackets) - expect(result.messageId).toMatch(/^.+@.+$/); - - console.log('✅ Valid Message-ID generated:', result.messageId); -}); - -tap.test('CEP-01: Basic Headers - should handle long subject lines', async () => { - const longSubject = 'This is a very long subject line that exceeds the typical length and might need to be wrapped according to RFC specifications for email headers'; - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: longSubject, - text: 'Email with long subject line' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Long subject line handled correctly'); -}); - -tap.test('CEP-01: Basic Headers - should sanitize header values', async () => { - // Test with potentially problematic characters - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Subject with\nnewline and\rcarriage return', - text: 'Testing header sanitization', - headers: { - 'X-Test-Header': 'Value with\nnewline' - } - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Header values sanitized correctly'); -}); - -tap.test('CEP-01: Basic Headers - should include Date header', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Date Header Test', - text: 'Testing automatic Date header' - }); - - const beforeSend = new Date(); - const result = await smtpClient.sendMail(email); - const afterSend = new Date(); - - expect(result.success).toBeTrue(); - - // The email should have been sent between beforeSend and afterSend - console.log('✅ Date header automatically included'); -}); - -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(); \ No newline at end of file diff --git a/test/suite/smtpclient_email-composition/test.cep-02.mime-multipart.ts b/test/suite/smtpclient_email-composition/test.cep-02.mime-multipart.ts deleted file mode 100644 index 70f3428..0000000 --- a/test/suite/smtpclient_email-composition/test.cep-02.mime-multipart.ts +++ /dev/null @@ -1,321 +0,0 @@ -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 MIME tests', async () => { - testServer = await startTestServer({ - port: 2571, - tlsEnabled: false, - authRequired: false, - size: 25 * 1024 * 1024 // 25MB for attachment tests - }); - - expect(testServer.port).toEqual(2571); -}); - -tap.test('setup - create SMTP client', async () => { - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - socketTimeout: 60000, // Longer timeout for large attachments - debug: true - }); - - const isConnected = await smtpClient.verify(); - expect(isConnected).toBeTrue(); -}); - -tap.test('CEP-02: MIME Multipart - should send multipart/alternative (text + HTML)', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Multipart Alternative Test', - text: 'This is the plain text version of the email.', - html: '

HTML Version

This is the HTML version of the email.

' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Multipart/alternative email sent successfully'); -}); - -tap.test('CEP-02: MIME Multipart - should send multipart/mixed with attachments', async () => { - const textAttachment = Buffer.from('This is a text file attachment content.'); - const csvData = 'Name,Email,Score\nJohn Doe,john@example.com,95\nJane Smith,jane@example.com,87'; - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Multipart Mixed with Attachments', - text: 'This email contains attachments.', - html: '

This email contains attachments.

', - attachments: [ - { - filename: 'document.txt', - content: textAttachment, - contentType: 'text/plain' - }, - { - filename: 'data.csv', - content: Buffer.from(csvData), - contentType: 'text/csv' - } - ] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Multipart/mixed with attachments sent successfully'); -}); - -tap.test('CEP-02: MIME Multipart - should handle inline images', async () => { - // Create a small test image (1x1 red pixel PNG) - const redPixelPng = Buffer.from( - 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==', - 'base64' - ); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Inline Image Test', - text: 'This email contains an inline image.', - html: '

Here is an inline image: Red Pixel

', - attachments: [ - { - filename: 'red-pixel.png', - content: redPixelPng, - contentType: 'image/png', - contentId: 'red-pixel' // Content-ID for inline reference - } - ] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Email with inline image sent successfully'); -}); - -tap.test('CEP-02: MIME Multipart - should handle multiple attachment types', async () => { - const attachments = [ - { - filename: 'text.txt', - content: Buffer.from('Plain text file'), - contentType: 'text/plain' - }, - { - filename: 'data.json', - content: Buffer.from(JSON.stringify({ test: 'data', value: 123 })), - contentType: 'application/json' - }, - { - filename: 'binary.bin', - content: Buffer.from([0x00, 0x01, 0x02, 0x03, 0xFF, 0xFE, 0xFD]), - contentType: 'application/octet-stream' - }, - { - filename: 'document.pdf', - content: Buffer.from('%PDF-1.4\n%fake pdf content for testing'), - contentType: 'application/pdf' - } - ]; - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Multiple Attachment Types', - text: 'Testing various attachment types', - attachments - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Multiple attachment types handled correctly'); -}); - -tap.test('CEP-02: MIME Multipart - should encode binary attachments with base64', async () => { - // Create binary data with all byte values - const binaryData = Buffer.alloc(256); - for (let i = 0; i < 256; i++) { - binaryData[i] = i; - } - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Binary Attachment Encoding Test', - text: 'This email contains binary data that must be base64 encoded', - attachments: [ - { - filename: 'binary-data.bin', - content: binaryData, - contentType: 'application/octet-stream', - encoding: 'base64' // Explicitly specify encoding - } - ] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Binary attachment base64 encoded correctly'); -}); - -tap.test('CEP-02: MIME Multipart - should handle large attachments', async () => { - // Create a 5MB attachment - const largeData = Buffer.alloc(5 * 1024 * 1024); - for (let i = 0; i < largeData.length; i++) { - largeData[i] = i % 256; - } - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Large Attachment Test', - text: 'This email contains a large attachment', - attachments: [ - { - filename: 'large-file.dat', - content: largeData, - contentType: 'application/octet-stream' - } - ] - }); - - const startTime = Date.now(); - const result = await smtpClient.sendMail(email); - const duration = Date.now() - startTime; - - expect(result.success).toBeTrue(); - console.log(`✅ Large attachment (5MB) sent in ${duration}ms`); -}); - -tap.test('CEP-02: MIME Multipart - should handle nested multipart structures', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Complex Multipart Structure', - text: 'Plain text version', - html: '

HTML version with Logo

', - attachments: [ - { - filename: 'logo.png', - content: Buffer.from('fake png data'), - contentType: 'image/png', - contentId: 'logo' // Inline image - }, - { - filename: 'attachment.txt', - content: Buffer.from('Regular attachment'), - contentType: 'text/plain' - } - ] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Nested multipart structure (mixed + related + alternative) handled'); -}); - -tap.test('CEP-02: MIME Multipart - should handle attachment filenames with special characters', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Special Filename Test', - text: 'Testing attachments with special filenames', - attachments: [ - { - filename: 'file with spaces.txt', - content: Buffer.from('Content 1'), - contentType: 'text/plain' - }, - { - filename: 'файл.txt', // Cyrillic - content: Buffer.from('Content 2'), - contentType: 'text/plain' - }, - { - filename: '文件.txt', // Chinese - content: Buffer.from('Content 3'), - contentType: 'text/plain' - } - ] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Special characters in filenames handled correctly'); -}); - -tap.test('CEP-02: MIME Multipart - should handle empty attachments', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Empty Attachment Test', - text: 'This email has an empty attachment', - attachments: [ - { - filename: 'empty.txt', - content: Buffer.from(''), // Empty content - contentType: 'text/plain' - } - ] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Empty attachment handled correctly'); -}); - -tap.test('CEP-02: MIME Multipart - should respect content-type parameters', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Content-Type Parameters Test', - text: 'Testing content-type with charset', - html: '

HTML with specific charset

', - attachments: [ - { - filename: 'utf8-text.txt', - content: Buffer.from('UTF-8 text: 你好世界'), - contentType: 'text/plain; charset=utf-8' - }, - { - filename: 'data.xml', - content: Buffer.from('Test'), - contentType: 'application/xml; charset=utf-8' - } - ] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Content-type parameters preserved correctly'); -}); - -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(); \ No newline at end of file diff --git a/test/suite/smtpclient_email-composition/test.cep-03.attachment-encoding.ts b/test/suite/smtpclient_email-composition/test.cep-03.attachment-encoding.ts deleted file mode 100644 index 36fd4e2..0000000 --- a/test/suite/smtpclient_email-composition/test.cep-03.attachment-encoding.ts +++ /dev/null @@ -1,334 +0,0 @@ -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'; -import * as crypto from 'crypto'; - -let testServer: ITestServer; -let smtpClient: SmtpClient; - -tap.test('setup - start SMTP server for attachment encoding tests', async () => { - testServer = await startTestServer({ - port: 2572, - tlsEnabled: false, - authRequired: false, - size: 50 * 1024 * 1024 // 50MB for large attachment tests - }); - - expect(testServer.port).toEqual(2572); -}); - -tap.test('setup - create SMTP client', async () => { - smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - socketTimeout: 120000, // 2 minutes for large attachments - debug: true - }); - - const isConnected = await smtpClient.verify(); - expect(isConnected).toBeTrue(); -}); - -tap.test('CEP-03: Attachment Encoding - should encode text attachment with base64', async () => { - const textContent = 'This is a test text file.\nIt contains multiple lines.\nAnd some special characters: © ® ™'; - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Text Attachment Base64 Test', - text: 'Email with text attachment', - attachments: [{ - filename: 'test.txt', - content: Buffer.from(textContent), - contentType: 'text/plain', - encoding: 'base64' - }] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Text attachment encoded with base64'); -}); - -tap.test('CEP-03: Attachment Encoding - should encode binary data correctly', async () => { - // Create binary data with all possible byte values - const binaryData = Buffer.alloc(256); - for (let i = 0; i < 256; i++) { - binaryData[i] = i; - } - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Binary Attachment Test', - text: 'Email with binary attachment', - attachments: [{ - filename: 'binary.dat', - content: binaryData, - contentType: 'application/octet-stream' - }] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Binary data encoded correctly'); -}); - -tap.test('CEP-03: Attachment Encoding - should handle various file types', async () => { - const attachments = [ - { - filename: 'image.jpg', - content: Buffer.from('/9j/4AAQSkZJRgABAQEASABIAAD/2wBD', 'base64'), // Partial JPEG header - contentType: 'image/jpeg' - }, - { - filename: 'document.pdf', - content: Buffer.from('%PDF-1.4\n%âÃÏÓ\n', 'utf8'), - contentType: 'application/pdf' - }, - { - filename: 'archive.zip', - content: Buffer.from('PK\x03\x04'), // ZIP magic number - contentType: 'application/zip' - }, - { - filename: 'audio.mp3', - content: Buffer.from('ID3'), // MP3 ID3 tag - contentType: 'audio/mpeg' - } - ]; - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Multiple File Types Test', - text: 'Testing various attachment types', - attachments - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Various file types encoded correctly'); -}); - -tap.test('CEP-03: Attachment Encoding - should handle quoted-printable encoding', async () => { - const textWithSpecialChars = 'This line has special chars: café, naïve, résumé\r\nThis line is very long and might need soft line breaks when encoded with quoted-printable encoding method\r\n=This line starts with equals sign'; - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Quoted-Printable Test', - text: 'Email with quoted-printable attachment', - attachments: [{ - filename: 'special-chars.txt', - content: Buffer.from(textWithSpecialChars, 'utf8'), - contentType: 'text/plain; charset=utf-8', - encoding: 'quoted-printable' - }] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Quoted-printable encoding handled correctly'); -}); - -tap.test('CEP-03: Attachment Encoding - should handle content-disposition', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Content-Disposition Test', - text: 'Testing attachment vs inline disposition', - html: '

Image below:

', - attachments: [ - { - filename: 'attachment.txt', - content: Buffer.from('This is an attachment'), - contentType: 'text/plain' - // Default disposition is 'attachment' - }, - { - filename: 'inline-image.png', - content: Buffer.from('fake png data'), - contentType: 'image/png', - contentId: 'inline-image' // Makes it inline - } - ] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Content-disposition handled correctly'); -}); - -tap.test('CEP-03: Attachment Encoding - should handle large attachments efficiently', async () => { - // Create a 10MB attachment - const largeSize = 10 * 1024 * 1024; - const largeData = crypto.randomBytes(largeSize); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Large Attachment Test', - text: 'Email with large attachment', - attachments: [{ - filename: 'large-file.bin', - content: largeData, - contentType: 'application/octet-stream' - }] - }); - - const startTime = Date.now(); - const result = await smtpClient.sendMail(email); - const duration = Date.now() - startTime; - - expect(result.success).toBeTrue(); - console.log(`✅ Large attachment (${largeSize / 1024 / 1024}MB) sent in ${duration}ms`); - console.log(` Throughput: ${(largeSize / 1024 / 1024 / (duration / 1000)).toFixed(2)} MB/s`); -}); - -tap.test('CEP-03: Attachment Encoding - should handle Unicode filenames', async () => { - const unicodeAttachments = [ - { - filename: '文档.txt', // Chinese - content: Buffer.from('Chinese filename test'), - contentType: 'text/plain' - }, - { - filename: 'файл.txt', // Russian - content: Buffer.from('Russian filename test'), - contentType: 'text/plain' - }, - { - filename: 'ファイル.txt', // Japanese - content: Buffer.from('Japanese filename test'), - contentType: 'text/plain' - }, - { - filename: '🎉emoji🎊.txt', // Emoji - content: Buffer.from('Emoji filename test'), - contentType: 'text/plain' - } - ]; - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Unicode Filenames Test', - text: 'Testing Unicode characters in filenames', - attachments: unicodeAttachments - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Unicode filenames encoded correctly'); -}); - -tap.test('CEP-03: Attachment Encoding - should handle special MIME headers', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'MIME Headers Test', - text: 'Testing special MIME headers', - attachments: [{ - filename: 'report.xml', - content: Buffer.from('test'), - contentType: 'application/xml; charset=utf-8', - encoding: 'base64', - headers: { - 'Content-Description': 'Monthly Report', - 'Content-Transfer-Encoding': 'base64', - 'Content-ID': '' - } - }] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Special MIME headers handled correctly'); -}); - -tap.test('CEP-03: Attachment Encoding - should handle attachment size limits', async () => { - // Test with attachment near server limit - const nearLimitSize = 45 * 1024 * 1024; // 45MB (near 50MB limit) - const nearLimitData = Buffer.alloc(nearLimitSize); - - // Fill with some pattern to avoid compression benefits - for (let i = 0; i < nearLimitSize; i++) { - nearLimitData[i] = i % 256; - } - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Near Size Limit Test', - text: 'Testing attachment near size limit', - attachments: [{ - filename: 'near-limit.bin', - content: nearLimitData, - contentType: 'application/octet-stream' - }] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log(`✅ Attachment near size limit (${nearLimitSize / 1024 / 1024}MB) accepted`); -}); - -tap.test('CEP-03: Attachment Encoding - should handle mixed encoding types', async () => { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Mixed Encoding Test', - text: 'Plain text body', - html: '

HTML body with special chars: café

', - attachments: [ - { - filename: 'base64.bin', - content: crypto.randomBytes(1024), - contentType: 'application/octet-stream', - encoding: 'base64' - }, - { - filename: 'quoted.txt', - content: Buffer.from('Text with special chars: naïve café résumé'), - contentType: 'text/plain; charset=utf-8', - encoding: 'quoted-printable' - }, - { - filename: '7bit.txt', - content: Buffer.from('Simple ASCII text only'), - contentType: 'text/plain', - encoding: '7bit' - } - ] - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Mixed encoding types handled correctly'); -}); - -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(); \ No newline at end of file diff --git a/test/suite/smtpclient_email-composition/test.cep-04.bcc-handling.ts b/test/suite/smtpclient_email-composition/test.cep-04.bcc-handling.ts deleted file mode 100644 index d72a1dd..0000000 --- a/test/suite/smtpclient_email-composition/test.cep-04.bcc-handling.ts +++ /dev/null @@ -1,187 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2577, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2577); -}); - -tap.test('CEP-04: Basic BCC handling', async () => { - console.log('Testing basic BCC handling'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create email with BCC recipients - const email = new Email({ - from: 'sender@example.com', - to: ['visible@example.com'], - bcc: ['hidden1@example.com', 'hidden2@example.com'], - subject: 'BCC Test Email', - text: 'This email tests BCC functionality' - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent email with BCC recipients'); - - await smtpClient.close(); -}); - -tap.test('CEP-04: Multiple BCC recipients', async () => { - console.log('Testing multiple BCC recipients'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create email with many BCC recipients - const bccRecipients = Array.from({ length: 10 }, - (_, i) => `bcc${i + 1}@example.com` - ); - - const email = new Email({ - from: 'sender@example.com', - to: ['primary@example.com'], - bcc: bccRecipients, - subject: 'Multiple BCC Test', - text: 'Testing with multiple BCC recipients' - }); - - console.log(`Sending email with ${bccRecipients.length} BCC recipients...`); - - const startTime = Date.now(); - const result = await smtpClient.sendMail(email); - const elapsed = Date.now() - startTime; - - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log(`Processed ${bccRecipients.length} BCC recipients in ${elapsed}ms`); - - await smtpClient.close(); -}); - -tap.test('CEP-04: BCC-only email', async () => { - console.log('Testing BCC-only email'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create email with only BCC recipients (no TO or CC) - const email = new Email({ - from: 'sender@example.com', - bcc: ['hidden1@example.com', 'hidden2@example.com', 'hidden3@example.com'], - subject: 'BCC-Only Email', - text: 'This email has only BCC recipients' - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent BCC-only email'); - - await smtpClient.close(); -}); - -tap.test('CEP-04: Mixed recipient types', async () => { - console.log('Testing mixed recipient types'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create email with all recipient types - const email = new Email({ - from: 'sender@example.com', - to: ['to1@example.com', 'to2@example.com'], - cc: ['cc1@example.com', 'cc2@example.com'], - bcc: ['bcc1@example.com', 'bcc2@example.com'], - subject: 'Mixed Recipients Test', - text: 'Testing all recipient types together' - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Recipient breakdown:'); - console.log(` TO: ${email.to?.length || 0} recipients`); - console.log(` CC: ${email.cc?.length || 0} recipients`); - console.log(` BCC: ${email.bcc?.length || 0} recipients`); - - await smtpClient.close(); -}); - -tap.test('CEP-04: BCC with special characters in addresses', async () => { - console.log('Testing BCC with special characters'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // BCC addresses with special characters - const specialBccAddresses = [ - 'user+tag@example.com', - 'first.last@example.com', - 'user_name@example.com' - ]; - - const email = new Email({ - from: 'sender@example.com', - to: ['visible@example.com'], - bcc: specialBccAddresses, - subject: 'BCC Special Characters Test', - text: 'Testing BCC with special character addresses' - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully processed BCC addresses with special characters'); - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_email-composition/test.cep-05.reply-to-return-path.ts b/test/suite/smtpclient_email-composition/test.cep-05.reply-to-return-path.ts deleted file mode 100644 index de9e770..0000000 --- a/test/suite/smtpclient_email-composition/test.cep-05.reply-to-return-path.ts +++ /dev/null @@ -1,277 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2578, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2578); -}); - -tap.test('CEP-05: Basic Reply-To header', async () => { - console.log('Testing basic Reply-To header'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create email with Reply-To header - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - replyTo: 'replies@example.com', - subject: 'Reply-To Test', - text: 'This email tests Reply-To header functionality' - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent email with Reply-To header'); - - await smtpClient.close(); -}); - -tap.test('CEP-05: Multiple Reply-To addresses', async () => { - console.log('Testing multiple Reply-To addresses'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create email with multiple Reply-To addresses - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - replyTo: ['reply1@example.com', 'reply2@example.com'], - subject: 'Multiple Reply-To Test', - text: 'This email tests multiple Reply-To addresses' - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent email with multiple Reply-To addresses'); - - await smtpClient.close(); -}); - -tap.test('CEP-05: Reply-To with display names', async () => { - console.log('Testing Reply-To with display names'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create email with Reply-To containing display names - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - replyTo: 'Support Team ', - subject: 'Reply-To Display Name Test', - text: 'This email tests Reply-To with display names' - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent email with Reply-To display name'); - - await smtpClient.close(); -}); - -tap.test('CEP-05: Return-Path header', async () => { - console.log('Testing Return-Path header'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create email with custom Return-Path - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Return-Path Test', - text: 'This email tests Return-Path functionality', - headers: { - 'Return-Path': '' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent email with Return-Path header'); - - await smtpClient.close(); -}); - -tap.test('CEP-05: Different From and Return-Path', async () => { - console.log('Testing different From and Return-Path addresses'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create email with different From and Return-Path - const email = new Email({ - from: 'noreply@example.com', - to: ['recipient@example.com'], - subject: 'Different Return-Path Test', - text: 'This email has different From and Return-Path addresses', - headers: { - 'Return-Path': '' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent email with different From and Return-Path'); - - await smtpClient.close(); -}); - -tap.test('CEP-05: Reply-To and Return-Path together', async () => { - console.log('Testing Reply-To and Return-Path together'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create email with both Reply-To and Return-Path - const email = new Email({ - from: 'notifications@example.com', - to: ['user@example.com'], - replyTo: 'support@example.com', - subject: 'Reply-To and Return-Path Test', - text: 'This email tests both Reply-To and Return-Path headers', - headers: { - 'Return-Path': '' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent email with both Reply-To and Return-Path'); - - await smtpClient.close(); -}); - -tap.test('CEP-05: International characters in Reply-To', async () => { - console.log('Testing international characters in Reply-To'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create email with international characters in Reply-To - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - replyTo: 'Suppört Téam ', - subject: 'International Reply-To Test', - text: 'This email tests international characters in Reply-To' - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent email with international Reply-To'); - - await smtpClient.close(); -}); - -tap.test('CEP-05: Empty and invalid Reply-To handling', async () => { - console.log('Testing empty and invalid Reply-To handling'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Test with empty Reply-To (should work) - const email1 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'No Reply-To Test', - text: 'This email has no Reply-To header' - }); - - const result1 = await smtpClient.sendMail(email1); - expect(result1).toBeDefined(); - expect(result1.messageId).toBeDefined(); - - console.log('Successfully sent email without Reply-To'); - - // Test with empty string Reply-To - const email2 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - replyTo: '', - subject: 'Empty Reply-To Test', - text: 'This email has empty Reply-To' - }); - - const result2 = await smtpClient.sendMail(email2); - expect(result2).toBeDefined(); - expect(result2.messageId).toBeDefined(); - - console.log('Successfully sent email with empty Reply-To'); - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_email-composition/test.cep-06.utf8-international.ts b/test/suite/smtpclient_email-composition/test.cep-06.utf8-international.ts deleted file mode 100644 index 8064039..0000000 --- a/test/suite/smtpclient_email-composition/test.cep-06.utf8-international.ts +++ /dev/null @@ -1,235 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2579, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2579); -}); - -tap.test('CEP-06: Basic UTF-8 characters', async () => { - console.log('Testing basic UTF-8 characters'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Email with basic UTF-8 characters - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'UTF-8 Test: café, naïve, résumé', - text: 'This email contains UTF-8 characters: café, naïve, résumé, piñata', - html: '

HTML with UTF-8: café, naïve, résumé, piñata

' - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent email with basic UTF-8 characters'); - - await smtpClient.close(); -}); - -tap.test('CEP-06: European characters', async () => { - console.log('Testing European characters'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Email with European characters - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'European: ñ, ü, ø, å, ß, æ', - text: [ - 'German: Müller, Größe, Weiß', - 'Spanish: niño, señor, España', - 'French: français, crème, être', - 'Nordic: København, Göteborg, Ålesund', - 'Polish: Kraków, Gdańsk, Wrocław' - ].join('\n'), - html: ` -

European Characters Test

-
    -
  • German: Müller, Größe, Weiß
  • -
  • Spanish: niño, señor, España
  • -
  • French: français, crème, être
  • -
  • Nordic: København, Göteborg, Ålesund
  • -
  • Polish: Kraków, Gdańsk, Wrocław
  • -
- ` - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent email with European characters'); - - await smtpClient.close(); -}); - -tap.test('CEP-06: Asian characters', async () => { - console.log('Testing Asian characters'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Email with Asian characters - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Asian: 你好, こんにちは, 안녕하세요', - text: [ - 'Chinese (Simplified): 你好世界', - 'Chinese (Traditional): 你好世界', - 'Japanese: こんにちは世界', - 'Korean: 안녕하세요 세계', - 'Thai: สวัสดีโลก', - 'Hindi: नमस्ते संसार' - ].join('\n'), - html: ` -

Asian Characters Test

- - - - - - - -
Chinese (Simplified):你好世界
Chinese (Traditional):你好世界
Japanese:こんにちは世界
Korean:안녕하세요 세계
Thai:สวัสดีโลก
Hindi:नमस्ते संसार
- ` - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent email with Asian characters'); - - await smtpClient.close(); -}); - -tap.test('CEP-06: Emojis and symbols', async () => { - console.log('Testing emojis and symbols'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Email with emojis and symbols - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Emojis: 🎉 🚀 ✨ 🌈', - text: [ - 'Faces: 😀 😃 😄 😁 😆 😅 😂', - 'Objects: 🎉 🚀 ✨ 🌈 ⭐ 🔥 💎', - 'Animals: 🐶 🐱 🐭 🐹 🐰 🦊 🐻', - 'Food: 🍎 🍌 🍇 🍓 🥝 🍅 🥑', - 'Symbols: ✓ ✗ ⚠ ♠ ♣ ♥ ♦', - 'Math: ∑ ∏ ∫ ∞ ± × ÷ ≠ ≤ ≥' - ].join('\n'), - html: ` -

Emojis and Symbols Test 🎉

-

Faces: 😀 😃 😄 😁 😆 😅 😂

-

Objects: 🎉 🚀 ✨ 🌈 ⭐ 🔥 💎

-

Animals: 🐶 🐱 🐭 🐹 🐰 🦊 🐻

-

Food: 🍎 🍌 🍇 🍓 🥝 🍅 🥑

-

Symbols: ✓ ✗ ⚠ ♠ ♣ ♥ ♦

-

Math: ∑ ∏ ∫ ∞ ± × ÷ ≠ ≤ ≥

- ` - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent email with emojis and symbols'); - - await smtpClient.close(); -}); - -tap.test('CEP-06: Mixed international content', async () => { - console.log('Testing mixed international content'); - - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Email with mixed international content - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Mixed: Hello 你好 مرحبا こんにちは 🌍', - text: [ - 'English: Hello World!', - 'Chinese: 你好世界!', - 'Arabic: مرحبا بالعالم!', - 'Japanese: こんにちは世界!', - 'Russian: Привет мир!', - 'Greek: Γεια σας κόσμε!', - 'Mixed: Hello 世界 🌍 مرحبا こんにちは!' - ].join('\n'), - html: ` -

International Mix 🌍

-
-

English: Hello World!

-

Chinese: 你好世界!

-

Arabic: مرحبا بالعالم!

-

Japanese: こんにちは世界!

-

Russian: Привет мир!

-

Greek: Γεια σας κόσμε!

-

Mixed: Hello 世界 🌍 مرحبا こんにちは!

-
- ` - }); - - const result = await smtpClient.sendMail(email); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - console.log('Successfully sent email with mixed international content'); - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_email-composition/test.cep-07.html-inline-images.ts b/test/suite/smtpclient_email-composition/test.cep-07.html-inline-images.ts deleted file mode 100644 index b0d2c83..0000000 --- a/test/suite/smtpclient_email-composition/test.cep-07.html-inline-images.ts +++ /dev/null @@ -1,489 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2567, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2567); -}); - -tap.test('CEP-07: Basic HTML email', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Create HTML email - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'HTML Email Test', - html: ` - - - - - - -
-

Welcome!

-
-
-

This is an HTML email with formatting.

-
    -
  • Feature 1
  • -
  • Feature 2
  • -
  • Feature 3
  • -
-
- - - - `, - text: 'Welcome! This is an HTML email with formatting. Features: 1, 2, 3. © 2024 Example Corp' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Basic HTML email sent successfully'); -}); - -tap.test('CEP-07: HTML email with inline images', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 10000 - }); - - // Create a simple 1x1 red pixel PNG - const redPixelBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='; - - // Create HTML email with inline image - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Email with Inline Images', - html: ` - - -

Email with Inline Images

-

Here's an inline image:

- Red pixel -

And here's another one:

- Company logo - - - `, - attachments: [ - { - filename: 'red-pixel.png', - content: Buffer.from(redPixelBase64, 'base64'), - contentType: 'image/png', - cid: 'image001' // Content-ID for inline reference - }, - { - filename: 'logo.png', - content: Buffer.from(redPixelBase64, 'base64'), // Reuse for demo - contentType: 'image/png', - cid: 'logo' - } - ] - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('HTML email with inline images sent successfully'); -}); - -tap.test('CEP-07: Complex HTML with multiple inline resources', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 10000 - }); - - // Create email with multiple inline resources - const email = new Email({ - from: 'newsletter@example.com', - to: 'subscriber@example.com', - subject: 'Newsletter with Rich Content', - html: ` - - - - - -
- -
-

Monthly Newsletter

-
-
- Product 1 -

Product 1

-
-
- Product 2 -

Product 2

-
-
- Product 3 -

Product 3

-
-
- -

© 2024 Example Corp

- - - `, - text: 'Monthly Newsletter - View in HTML for best experience', - attachments: [ - { - filename: 'header-bg.jpg', - content: Buffer.from('fake-image-data'), - contentType: 'image/jpeg', - cid: 'header-bg' - }, - { - filename: 'logo.png', - content: Buffer.from('fake-logo-data'), - contentType: 'image/png', - cid: 'logo' - }, - { - filename: 'product1.jpg', - content: Buffer.from('fake-product1-data'), - contentType: 'image/jpeg', - cid: 'product1' - }, - { - filename: 'product2.jpg', - content: Buffer.from('fake-product2-data'), - contentType: 'image/jpeg', - cid: 'product2' - }, - { - filename: 'product3.jpg', - content: Buffer.from('fake-product3-data'), - contentType: 'image/jpeg', - cid: 'product3' - }, - { - filename: 'divider.gif', - content: Buffer.from('fake-divider-data'), - contentType: 'image/gif', - cid: 'footer-divider' - } - ] - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Complex HTML with multiple inline resources sent successfully'); -}); - -tap.test('CEP-07: HTML with external and inline images mixed', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Mix of inline and external images - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Mixed Image Sources', - html: ` - - -

Mixed Image Sources

-

Inline Image:

- Inline Logo -

External Images:

- External Image 1 - External Image 2 -

Data URI Image:

- Data URI - - - `, - attachments: [ - { - filename: 'logo.png', - content: Buffer.from('logo-data'), - contentType: 'image/png', - cid: 'inline-logo' - } - ] - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Successfully sent email with mixed image sources'); -}); - -tap.test('CEP-07: HTML email responsive design', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Responsive HTML email - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Responsive HTML Email', - html: ` - - - - - - - -
-

Responsive Design Test

-
- Left Column -

Left column content

-
-
- Right Column -

Right column content

-
-

This text is hidden on mobile devices

-
- - - `, - text: 'Responsive Design Test - View in HTML', - attachments: [ - { - filename: 'left.jpg', - content: Buffer.from('left-image-data'), - contentType: 'image/jpeg', - cid: 'left-image' - }, - { - filename: 'right.jpg', - content: Buffer.from('right-image-data'), - contentType: 'image/jpeg', - cid: 'right-image' - } - ] - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Successfully sent responsive HTML email'); -}); - -tap.test('CEP-07: HTML sanitization and security', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Email with potentially dangerous HTML - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'HTML Security Test', - html: ` - - -

Security Test

- - - - Dangerous Link - -
- -
- -

This is safe text content.

- Safe Image - - - `, - text: 'Security Test - Plain text version', - attachments: [ - { - filename: 'safe.png', - content: Buffer.from('safe-image-data'), - contentType: 'image/png', - cid: 'safe-image' - } - ] - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('HTML security test sent successfully'); -}); - -tap.test('CEP-07: Large HTML email with many inline images', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 30000 - }); - - // Create email with many inline images - const imageCount = 10; // Reduced for testing - const attachments: any[] = []; - let htmlContent = '

Performance Test

'; - - for (let i = 0; i < imageCount; i++) { - const cid = `image${i}`; - htmlContent += `Image ${i}`; - - attachments.push({ - filename: `image${i}.png`, - content: Buffer.from(`fake-image-data-${i}`), - contentType: 'image/png', - cid: cid - }); - } - - htmlContent += ''; - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Email with ${imageCount} inline images`, - html: htmlContent, - attachments: attachments - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log(`Performance test with ${imageCount} inline images sent successfully`); -}); - -tap.test('CEP-07: Alternative content for non-HTML clients', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Email with rich HTML and good plain text alternative - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Newsletter - March 2024', - html: ` - - -
- Company Newsletter -
-
-

March Newsletter

-

Featured Articles

- -
-

Special Offer!

-

Get 20% off with code: SPRING20

- Special Offer -
-
-
-

© 2024 Example Corp | Unsubscribe

-
- - - `, - text: `COMPANY NEWSLETTER -March 2024 - -FEATURED ARTICLES -* 10 Tips for Spring Cleaning - https://example.com/article1 -* New Product Launch - https://example.com/article2 -* Customer Success Story - https://example.com/article3 - -SPECIAL OFFER! -Get 20% off with code: SPRING20 - ---- -© 2024 Example Corp -Unsubscribe: https://example.com/unsubscribe`, - attachments: [ - { - filename: 'header.jpg', - content: Buffer.from('header-image'), - contentType: 'image/jpeg', - cid: 'header' - }, - { - filename: 'offer.jpg', - content: Buffer.from('offer-image'), - contentType: 'image/jpeg', - cid: 'offer' - } - ] - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Newsletter with alternative content sent successfully'); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_email-composition/test.cep-08.custom-headers.ts b/test/suite/smtpclient_email-composition/test.cep-08.custom-headers.ts deleted file mode 100644 index 0509464..0000000 --- a/test/suite/smtpclient_email-composition/test.cep-08.custom-headers.ts +++ /dev/null @@ -1,293 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2568, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2568); -}); - -tap.test('CEP-08: Basic custom headers', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Create email with custom headers - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Custom Headers Test', - text: 'Testing custom headers', - headers: { - 'X-Custom-Header': 'Custom Value', - 'X-Campaign-ID': 'CAMP-2024-03', - 'X-Priority': 'High', - 'X-Mailer': 'Custom SMTP Client v1.0' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Basic custom headers test sent successfully'); -}); - -tap.test('CEP-08: Standard headers override protection', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Try to override standard headers via custom headers - const email = new Email({ - from: 'real-sender@example.com', - to: 'real-recipient@example.com', - subject: 'Real Subject', - text: 'Testing header override protection', - headers: { - 'From': 'fake-sender@example.com', // Should not override - 'To': 'fake-recipient@example.com', // Should not override - 'Subject': 'Fake Subject', // Should not override - 'Date': 'Mon, 1 Jan 2000 00:00:00 +0000', // Might be allowed - 'Message-ID': '', // Might be allowed - 'X-Original-From': 'tracking@example.com' // Custom header, should work - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Header override protection test sent successfully'); -}); - -tap.test('CEP-08: Tracking and analytics headers', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Common tracking headers - const email = new Email({ - from: 'marketing@example.com', - to: 'customer@example.com', - subject: 'Special Offer Inside!', - text: 'Check out our special offers', - headers: { - 'X-Campaign-ID': 'SPRING-2024-SALE', - 'X-Customer-ID': 'CUST-12345', - 'X-Segment': 'high-value-customers', - 'X-AB-Test': 'variant-b', - 'X-Send-Time': new Date().toISOString(), - 'X-Template-Version': '2.1.0', - 'List-Unsubscribe': '', - 'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click', - 'Precedence': 'bulk' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Tracking and analytics headers test sent successfully'); -}); - -tap.test('CEP-08: MIME extension headers', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // MIME-related custom headers - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'MIME Extensions Test', - html: '

HTML content

', - text: 'Plain text content', - headers: { - 'MIME-Version': '1.0', // Usually auto-added - 'X-Accept-Language': 'en-US, en;q=0.9, fr;q=0.8', - 'X-Auto-Response-Suppress': 'DR, RN, NRN, OOF', - 'Importance': 'high', - 'X-Priority': '1', - 'X-MSMail-Priority': 'High', - 'Sensitivity': 'Company-Confidential' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('MIME extension headers test sent successfully'); -}); - -tap.test('CEP-08: Email threading headers', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Simulate email thread - const messageId = `<${Date.now()}.${Math.random()}@example.com>`; - const inReplyTo = ''; - const references = ' '; - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Re: Email Threading Test', - text: 'This is a reply in the thread', - headers: { - 'Message-ID': messageId, - 'In-Reply-To': inReplyTo, - 'References': references, - 'Thread-Topic': 'Email Threading Test', - 'Thread-Index': Buffer.from('thread-data').toString('base64') - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Email threading headers test sent successfully'); -}); - -tap.test('CEP-08: Security and authentication headers', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Security-related headers - const email = new Email({ - from: 'secure@example.com', - to: 'recipient@example.com', - subject: 'Security Headers Test', - text: 'Testing security headers', - headers: { - 'X-Originating-IP': '[192.168.1.100]', - 'X-Auth-Result': 'PASS', - 'X-Spam-Score': '0.1', - 'X-Spam-Status': 'No, score=0.1', - 'X-Virus-Scanned': 'ClamAV using ClamSMTP', - 'Authentication-Results': 'example.com; spf=pass smtp.mailfrom=sender@example.com', - 'ARC-Seal': 'i=1; cv=none; d=example.com; s=arc-20240315; t=1710500000;', - 'ARC-Message-Signature': 'i=1; a=rsa-sha256; c=relaxed/relaxed;', - 'ARC-Authentication-Results': 'i=1; example.com; spf=pass' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Security and authentication headers test sent successfully'); -}); - -tap.test('CEP-08: Header folding for long values', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Create headers with long values that need folding - const longValue = 'This is a very long header value that exceeds the recommended 78 character limit per line and should be folded according to RFC 5322 specifications for proper email transmission'; - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Header Folding Test with a very long subject line that should be properly folded', - text: 'Testing header folding', - headers: { - 'X-Long-Header': longValue, - 'X-Multiple-Values': 'value1@example.com, value2@example.com, value3@example.com, value4@example.com, value5@example.com, value6@example.com', - 'References': ' ' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Header folding test sent successfully'); -}); - -tap.test('CEP-08: Custom headers with special characters', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Headers with special characters - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Special Characters in Headers', - text: 'Testing special characters', - headers: { - 'X-Special-Chars': 'Value with special: !@#$%^&*()', - 'X-Quoted-String': '"This is a quoted string"', - 'X-Unicode': 'Unicode: café, naïve, 你好', - 'X-Control-Chars': 'No\ttabs\nor\rnewlines', // Should be sanitized - 'X-Empty': '', - 'X-Spaces': ' trimmed ', - 'X-Semicolon': 'part1; part2; part3' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Special characters test sent successfully'); -}); - -tap.test('CEP-08: Duplicate header handling', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Some headers can appear multiple times - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Duplicate Headers Test', - text: 'Testing duplicate headers', - headers: { - 'Received': 'from server1.example.com', - 'X-Received': 'from server2.example.com', // Workaround for multiple - 'Comments': 'First comment', - 'X-Comments': 'Second comment', // Workaround for multiple - 'X-Tag': 'tag1, tag2, tag3' // String instead of array - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Duplicate header handling test sent successfully'); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_email-composition/test.cep-09.priority-importance.ts b/test/suite/smtpclient_email-composition/test.cep-09.priority-importance.ts deleted file mode 100644 index ba1036f..0000000 --- a/test/suite/smtpclient_email-composition/test.cep-09.priority-importance.ts +++ /dev/null @@ -1,314 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2569, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2569); -}); - -tap.test('CEP-09: Basic priority headers', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Test different priority levels - const priorityLevels = [ - { priority: 'high', headers: { 'X-Priority': '1', 'Importance': 'high' } }, - { priority: 'normal', headers: { 'X-Priority': '3', 'Importance': 'normal' } }, - { priority: 'low', headers: { 'X-Priority': '5', 'Importance': 'low' } } - ]; - - for (const level of priorityLevels) { - console.log(`Testing ${level.priority} priority email...`); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `${level.priority.toUpperCase()} Priority Test`, - text: `This is a ${level.priority} priority message`, - priority: level.priority as 'high' | 'normal' | 'low' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - } - - console.log('Basic priority headers test completed successfully'); -}); - -tap.test('CEP-09: Multiple priority header formats', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Test various priority header combinations - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Multiple Priority Headers Test', - text: 'Testing various priority header formats', - headers: { - 'X-Priority': '1 (Highest)', - 'X-MSMail-Priority': 'High', - 'Importance': 'high', - 'Priority': 'urgent', - 'X-Message-Flag': 'Follow up' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Multiple priority header formats test sent successfully'); -}); - -tap.test('CEP-09: Client-specific priority mappings', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Send test email with comprehensive priority headers - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Cross-client Priority Test', - text: 'This should appear as high priority in all clients', - priority: 'high' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Client-specific priority mappings test sent successfully'); -}); - -tap.test('CEP-09: Sensitivity and confidentiality headers', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Test sensitivity levels - const sensitivityLevels = [ - { level: 'Personal', description: 'Personal information' }, - { level: 'Private', description: 'Private communication' }, - { level: 'Company-Confidential', description: 'Internal use only' }, - { level: 'Normal', description: 'No special handling' } - ]; - - for (const sensitivity of sensitivityLevels) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `${sensitivity.level} Message`, - text: sensitivity.description, - headers: { - 'Sensitivity': sensitivity.level, - 'X-Sensitivity': sensitivity.level - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - } - - console.log('Sensitivity and confidentiality headers test completed successfully'); -}); - -tap.test('CEP-09: Auto-response suppression headers', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Headers to suppress auto-responses (vacation messages, etc.) - const email = new Email({ - from: 'noreply@example.com', - to: 'recipient@example.com', - subject: 'Automated Notification', - text: 'This is an automated message. Please do not reply.', - headers: { - 'X-Auto-Response-Suppress': 'All', // Microsoft - 'Auto-Submitted': 'auto-generated', // RFC 3834 - 'Precedence': 'bulk', // Traditional - 'X-Autoreply': 'no', - 'X-Autorespond': 'no', - 'List-Id': '', // Mailing list header - 'List-Unsubscribe': '' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Auto-response suppression headers test sent successfully'); -}); - -tap.test('CEP-09: Expiration and retention headers', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Set expiration date for the email - const expirationDate = new Date(); - expirationDate.setDate(expirationDate.getDate() + 7); // Expires in 7 days - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Time-sensitive Information', - text: 'This information expires in 7 days', - headers: { - 'Expiry-Date': expirationDate.toUTCString(), - 'X-Message-TTL': '604800', // 7 days in seconds - 'X-Auto-Delete-After': expirationDate.toISOString(), - 'X-Retention-Date': expirationDate.toISOString() - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Expiration and retention headers test sent successfully'); -}); - -tap.test('CEP-09: Message flags and categories', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Test various message flags and categories - const flaggedEmails = [ - { - flag: 'Follow up', - category: 'Action Required', - color: 'red' - }, - { - flag: 'For Your Information', - category: 'Informational', - color: 'blue' - }, - { - flag: 'Review', - category: 'Pending Review', - color: 'yellow' - } - ]; - - for (const flaggedEmail of flaggedEmails) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `${flaggedEmail.flag}: Important Document`, - text: `This email is flagged as: ${flaggedEmail.flag}`, - headers: { - 'X-Message-Flag': flaggedEmail.flag, - 'X-Category': flaggedEmail.category, - 'X-Color-Label': flaggedEmail.color, - 'Keywords': flaggedEmail.flag.replace(' ', '-') - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - } - - console.log('Message flags and categories test completed successfully'); -}); - -tap.test('CEP-09: Priority with delivery timing', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Test deferred delivery with priority - const futureDate = new Date(); - futureDate.setHours(futureDate.getHours() + 2); // Deliver in 2 hours - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Scheduled High Priority Message', - text: 'This high priority message should be delivered at a specific time', - priority: 'high', - headers: { - 'Deferred-Delivery': futureDate.toUTCString(), - 'X-Delay-Until': futureDate.toISOString(), - 'X-Priority': '1', - 'Importance': 'High' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Priority with delivery timing test sent successfully'); -}); - -tap.test('CEP-09: Priority impact on routing', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Test batch of emails with different priorities - const emails = [ - { priority: 'high', subject: 'URGENT: Server Down' }, - { priority: 'high', subject: 'Critical Security Update' }, - { priority: 'normal', subject: 'Weekly Report' }, - { priority: 'low', subject: 'Newsletter' }, - { priority: 'low', subject: 'Promotional Offer' } - ]; - - for (const emailData of emails) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: emailData.subject, - text: `Priority: ${emailData.priority}`, - priority: emailData.priority as 'high' | 'normal' | 'low' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - } - - console.log('Priority impact on routing test completed successfully'); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_email-composition/test.cep-10.receipts-dsn.ts b/test/suite/smtpclient_email-composition/test.cep-10.receipts-dsn.ts deleted file mode 100644 index 27c7668..0000000 --- a/test/suite/smtpclient_email-composition/test.cep-10.receipts-dsn.ts +++ /dev/null @@ -1,411 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2570, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2570); -}); - -tap.test('CEP-10: Read receipt headers', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Create email requesting read receipt - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Important: Please confirm receipt', - text: 'Please confirm you have read this message', - headers: { - 'Disposition-Notification-To': 'sender@example.com', - 'Return-Receipt-To': 'sender@example.com', - 'X-Confirm-Reading-To': 'sender@example.com', - 'X-MS-Receipt-Request': 'sender@example.com' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Read receipt headers test sent successfully'); -}); - -tap.test('CEP-10: DSN (Delivery Status Notification) requests', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Create email with DSN options - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'DSN Test Email', - text: 'Testing delivery status notifications', - headers: { - 'X-DSN-Options': 'notify=SUCCESS,FAILURE,DELAY;return=HEADERS', - 'X-Envelope-ID': `msg-${Date.now()}` - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('DSN requests test sent successfully'); -}); - -tap.test('CEP-10: DSN notify options', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Test different DSN notify combinations - const notifyOptions = [ - { notify: ['SUCCESS'], description: 'Notify on successful delivery only' }, - { notify: ['FAILURE'], description: 'Notify on failure only' }, - { notify: ['DELAY'], description: 'Notify on delays only' }, - { notify: ['SUCCESS', 'FAILURE'], description: 'Notify on success and failure' }, - { notify: ['NEVER'], description: 'Never send notifications' } - ]; - - for (const option of notifyOptions) { - console.log(`Testing DSN: ${option.description}`); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `DSN Test: ${option.description}`, - text: 'Testing DSN notify options', - headers: { - 'X-DSN-Notify': option.notify.join(','), - 'X-DSN-Return': 'HEADERS' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - } - - console.log('DSN notify options test completed successfully'); -}); - -tap.test('CEP-10: DSN return types', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Test different return types - const returnTypes = [ - { type: 'FULL', description: 'Return full message on failure' }, - { type: 'HEADERS', description: 'Return headers only' } - ]; - - for (const returnType of returnTypes) { - console.log(`Testing DSN return type: ${returnType.description}`); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `DSN Return Type: ${returnType.type}`, - text: 'Testing DSN return types', - headers: { - 'X-DSN-Notify': 'FAILURE', - 'X-DSN-Return': returnType.type - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - } - - console.log('DSN return types test completed successfully'); -}); - -tap.test('CEP-10: MDN (Message Disposition Notification)', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Create MDN request email - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Please confirm reading', - text: 'This message requests a read receipt', - headers: { - 'Disposition-Notification-To': 'sender@example.com', - 'Disposition-Notification-Options': 'signed-receipt-protocol=optional,pkcs7-signature', - 'Original-Message-ID': `<${Date.now()}@example.com>` - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - - // Simulate MDN response - const mdnResponse = new Email({ - from: 'recipient@example.com', - to: 'sender@example.com', - subject: 'Read: Please confirm reading', - headers: { - 'Content-Type': 'multipart/report; report-type=disposition-notification', - 'In-Reply-To': `<${Date.now()}@example.com>`, - 'References': `<${Date.now()}@example.com>`, - 'Auto-Submitted': 'auto-replied' - }, - text: 'The message was displayed to the recipient', - attachments: [{ - filename: 'disposition-notification.txt', - content: Buffer.from(`Reporting-UA: mail.example.com; MailClient/1.0 -Original-Recipient: rfc822;recipient@example.com -Final-Recipient: rfc822;recipient@example.com -Original-Message-ID: <${Date.now()}@example.com> -Disposition: automatic-action/MDN-sent-automatically; displayed`), - contentType: 'message/disposition-notification' - }] - }); - - const mdnResult = await smtpClient.sendMail(mdnResponse); - expect(mdnResult.success).toBeTruthy(); - console.log('MDN test completed successfully'); -}); - -tap.test('CEP-10: Multiple recipients with different DSN', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Email with multiple recipients - const emails = [ - { - to: 'important@example.com', - dsn: 'SUCCESS,FAILURE,DELAY' - }, - { - to: 'normal@example.com', - dsn: 'FAILURE' - }, - { - to: 'optional@example.com', - dsn: 'NEVER' - } - ]; - - for (const emailData of emails) { - const email = new Email({ - from: 'sender@example.com', - to: emailData.to, - subject: 'Multi-recipient DSN Test', - text: 'Testing per-recipient DSN options', - headers: { - 'X-DSN-Notify': emailData.dsn, - 'X-DSN-Return': 'HEADERS' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - } - - console.log('Multiple recipients DSN test completed successfully'); -}); - -tap.test('CEP-10: DSN with ORCPT', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Test ORCPT (Original Recipient) parameter - const email = new Email({ - from: 'sender@example.com', - to: 'forwarded@example.com', - subject: 'DSN with ORCPT Test', - text: 'Testing original recipient tracking', - headers: { - 'X-DSN-Notify': 'SUCCESS,FAILURE', - 'X-DSN-Return': 'HEADERS', - 'X-Original-Recipient': 'rfc822;original@example.com' - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('DSN with ORCPT test sent successfully'); -}); - -tap.test('CEP-10: Receipt request formats', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Test various receipt request formats - const receiptFormats = [ - { - name: 'Simple email', - value: 'receipts@example.com' - }, - { - name: 'With display name', - value: '"Receipt Handler" ' - }, - { - name: 'Multiple addresses', - value: 'receipts@example.com, backup@example.com' - }, - { - name: 'With comment', - value: 'receipts@example.com (Automated System)' - } - ]; - - for (const format of receiptFormats) { - console.log(`Testing receipt format: ${format.name}`); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Receipt Format: ${format.name}`, - text: 'Testing receipt address formats', - headers: { - 'Disposition-Notification-To': format.value - } - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - } - - console.log('Receipt request formats test completed successfully'); -}); - -tap.test('CEP-10: Non-delivery reports', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Simulate bounce/NDR structure - const ndrEmail = new Email({ - from: 'MAILER-DAEMON@example.com', - to: 'original-sender@example.com', - subject: 'Undelivered Mail Returned to Sender', - headers: { - 'Auto-Submitted': 'auto-replied', - 'Content-Type': 'multipart/report; report-type=delivery-status', - 'X-Failed-Recipients': 'nonexistent@example.com' - }, - text: 'This is the mail delivery agent at example.com.\n\n' + - 'I was unable to deliver your message to the following addresses:\n\n' + - ': User unknown', - attachments: [ - { - filename: 'delivery-status.txt', - content: Buffer.from(`Reporting-MTA: dns; mail.example.com -X-Queue-ID: 123456789 -Arrival-Date: ${new Date().toUTCString()} - -Final-Recipient: rfc822;nonexistent@example.com -Original-Recipient: rfc822;nonexistent@example.com -Action: failed -Status: 5.1.1 -Diagnostic-Code: smtp; 550 5.1.1 User unknown`), - contentType: 'message/delivery-status' - }, - { - filename: 'original-message.eml', - content: Buffer.from('From: original-sender@example.com\r\n' + - 'To: nonexistent@example.com\r\n' + - 'Subject: Original Subject\r\n\r\n' + - 'Original message content'), - contentType: 'message/rfc822' - } - ] - }); - - const result = await smtpClient.sendMail(ndrEmail); - expect(result.success).toBeTruthy(); - console.log('Non-delivery report test sent successfully'); -}); - -tap.test('CEP-10: Delivery delay notifications', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Simulate delayed delivery notification - const delayNotification = new Email({ - from: 'postmaster@example.com', - to: 'sender@example.com', - subject: 'Delivery Status: Delayed', - headers: { - 'Auto-Submitted': 'auto-replied', - 'Content-Type': 'multipart/report; report-type=delivery-status', - 'X-Delay-Reason': 'Remote server temporarily unavailable' - }, - text: 'This is an automatically generated Delivery Delay Notification.\n\n' + - 'Your message has not been delivered to the following recipients yet:\n\n' + - ' recipient@remote-server.com\n\n' + - 'The server will continue trying to deliver your message for 48 hours.', - attachments: [{ - filename: 'delay-status.txt', - content: Buffer.from(`Reporting-MTA: dns; mail.example.com -Arrival-Date: ${new Date(Date.now() - 3600000).toUTCString()} -Last-Attempt-Date: ${new Date().toUTCString()} - -Final-Recipient: rfc822;recipient@remote-server.com -Action: delayed -Status: 4.4.1 -Will-Retry-Until: ${new Date(Date.now() + 172800000).toUTCString()} -Diagnostic-Code: smtp; 421 4.4.1 Remote server temporarily unavailable`), - contentType: 'message/delivery-status' - }] - }); - - const result = await smtpClient.sendMail(delayNotification); - expect(result.success).toBeTruthy(); - console.log('Delivery delay notification test sent successfully'); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_error-handling/test.cerr-01.4xx-errors.ts b/test/suite/smtpclient_error-handling/test.cerr-01.4xx-errors.ts deleted file mode 100644 index 811d2a1..0000000 --- a/test/suite/smtpclient_error-handling/test.cerr-01.4xx-errors.ts +++ /dev/null @@ -1,232 +0,0 @@ -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 = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Create email with syntactically valid but nonexistent recipient - const email = new Email({ - from: 'test@example.com', - to: 'nonexistent-user@nonexistent-domain-12345.invalid', - subject: 'Testing 4xx Error', - text: 'This should trigger a 4xx error' - }); - - const result = await smtpClient.sendMail(email); - - // Test server may accept or reject - both are valid test outcomes - if (!result.success) { - console.log('✅ Invalid recipient handled:', result.error?.message); - } else { - console.log('ℹ️ Test server accepted recipient (common in test environments)'); - } - - expect(result).toBeTruthy(); -}); - -tap.test('CERR-01: 4xx Errors - should handle mailbox unavailable (450)', async () => { - const email = new Email({ - from: 'test@example.com', - to: 'mailbox-full@example.com', // Valid format but might be unavailable - 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) { - console.log('✅ Mailbox unavailable handled:', result.error?.message); - } else { - // Some test servers accept all recipients - console.log('ℹ️ Test server accepted recipient (common in test environments)'); - } - - expect(result).toBeTruthy(); -}); - -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 = await 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 { - const result = await unauthClient.sendMail(email); - if (!result.success) { - authError = true; - console.log('✅ Authentication required error handled:', result.error?.message); - } - } catch (error) { - authError = true; - console.log('✅ Authentication required error caught:', error.message); - } - - expect(authError).toBeTrue(); - - 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 = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'blocked-sender@blacklisted-domain.invalid', // Might trigger policy rejection - to: 'recipient@example.com', - subject: 'Permanent Error Test', - text: 'Should not retry' - }); - - const result = await trackingClient.sendMail(email); - - // Test completed - whether success or failure, no retries should occur - if (!result.success) { - console.log('✅ Permanent error handled without retry:', result.error?.message); - } else { - console.log('ℹ️ Email accepted (no policy rejection in test server)'); - } - - expect(result).toBeTruthy(); -}); - -tap.test('cleanup - close SMTP client', async () => { - if (smtpClient) { - try { - await smtpClient.close(); - } catch (error) { - console.log('Client already closed or error during close'); - } - } -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_error-handling/test.cerr-02.5xx-errors.ts b/test/suite/smtpclient_error-handling/test.cerr-02.5xx-errors.ts deleted file mode 100644 index 3ccb292..0000000 --- a/test/suite/smtpclient_error-handling/test.cerr-02.5xx-errors.ts +++ /dev/null @@ -1,309 +0,0 @@ -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 5xx error tests', async () => { - testServer = await startTestServer({ - port: 2552, - tlsEnabled: false, - authRequired: false, - maxRecipients: 3 // Low limit to help trigger errors - }); - - expect(testServer.port).toEqual(2552); -}); - -tap.test('CERR-02: 5xx Errors - should handle command not recognized (500)', async () => { - smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // The client should handle standard commands properly - // This tests that the client doesn't send invalid commands - const result = await smtpClient.verify(); - expect(result).toBeTruthy(); - - console.log('✅ Client sends only valid SMTP commands'); -}); - -tap.test('CERR-02: 5xx Errors - should handle syntax error (501)', async () => { - // Test with malformed email that might cause syntax error - let syntaxError = false; - - try { - // The Email class should catch this before sending - const email = new Email({ - from: 'from>@example.com', // Malformed - to: 'recipient@example.com', - subject: 'Syntax Error Test', - text: 'This should fail' - }); - - await smtpClient.sendMail(email); - } catch (error: any) { - syntaxError = true; - expect(error).toBeInstanceOf(Error); - console.log('✅ Syntax error caught:', error.message); - } - - expect(syntaxError).toBeTrue(); -}); - -tap.test('CERR-02: 5xx Errors - should handle command not implemented (502)', async () => { - // Most servers implement all required commands - // This test verifies client doesn't use optional/deprecated commands - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Standard Commands Test', - text: 'Using only standard SMTP commands' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - - console.log('✅ Client uses only widely-implemented commands'); -}); - -tap.test('CERR-02: 5xx Errors - should handle bad sequence (503)', async () => { - // The client should maintain proper command sequence - // This tests internal state management - - // Send multiple emails to ensure sequence is maintained - for (let i = 0; i < 3; i++) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Sequence Test ${i}`, - text: 'Testing command sequence' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - } - - console.log('✅ Client maintains proper command sequence'); -}); - -tap.test('CERR-02: 5xx Errors - should handle authentication failed (535)', async () => { - // Create server requiring authentication - const authServer = await startTestServer({ - port: 2553, - authRequired: true - }); - - let authFailed = false; - - try { - const badAuthClient = await createSmtpClient({ - host: authServer.hostname, - port: authServer.port, - secure: false, - auth: { - user: 'wronguser', - pass: 'wrongpass' - }, - connectionTimeout: 5000 - }); - - const result = await badAuthClient.verify(); - if (!result.success) { - authFailed = true; - console.log('✅ Authentication failure (535) handled:', result.error?.message); - } - } catch (error: any) { - authFailed = true; - console.log('✅ Authentication failure (535) handled:', error.message); - } - - expect(authFailed).toBeTrue(); - - await stopTestServer(authServer); -}); - -tap.test('CERR-02: 5xx Errors - should handle transaction failed (554)', async () => { - // Try to send email that might be rejected - const email = new Email({ - from: 'sender@example.com', - to: 'postmaster@[127.0.0.1]', // IP literal might be rejected - subject: 'Transaction Test', - text: 'Testing transaction failure' - }); - - const result = await smtpClient.sendMail(email); - - // Depending on server configuration - if (!result.success) { - console.log('✅ Transaction failure handled gracefully'); - expect(result.error).toBeInstanceOf(Error); - } else { - console.log('ℹ️ Test server accepted IP literal recipient'); - expect(result.acceptedRecipients.length).toBeGreaterThan(0); - } -}); - -tap.test('CERR-02: 5xx Errors - should not retry permanent 5xx errors', async () => { - // Create a client for testing - const trackingClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Try to send with potentially problematic data - const email = new Email({ - from: 'blocked-user@blacklisted-domain.invalid', - to: 'recipient@example.com', - subject: 'Permanent Error Test', - text: 'Should not retry' - }); - - const result = await trackingClient.sendMail(email); - - // Whether success or failure, permanent errors should not be retried - if (!result.success) { - console.log('✅ Permanent error not retried:', result.error?.message); - } else { - console.log('ℹ️ Email accepted (no permanent rejection in test server)'); - } - - expect(result).toBeTruthy(); -}); - -tap.test('CERR-02: 5xx Errors - should handle server unavailable (550)', async () => { - // Test with recipient that might be rejected - const email = new Email({ - from: 'sender@example.com', - to: 'no-such-user@nonexistent-server.invalid', - subject: 'User Unknown Test', - text: 'Testing unknown user rejection' - }); - - const result = await smtpClient.sendMail(email); - - if (!result.success || result.rejectedRecipients.length > 0) { - console.log('✅ Unknown user (550) rejection handled'); - } else { - // Test server might accept all - console.log('ℹ️ Test server accepted unknown user'); - } - - expect(result).toBeTruthy(); -}); - -tap.test('CERR-02: 5xx Errors - should close connection after fatal error', async () => { - // Test that client properly closes connection after fatal errors - const fatalClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - // Verify connection works - const verifyResult = await fatalClient.verify(); - expect(verifyResult).toBeTruthy(); - - // Simulate a scenario that might cause fatal error - // For this test, we'll just verify the client can handle closure - try { - // The client should handle connection closure gracefully - console.log('✅ Connection properly closed after errors'); - expect(true).toBeTrue(); // Test passed - } catch (error) { - console.log('✅ Fatal error handled properly'); - } -}); - -tap.test('CERR-02: 5xx Errors - should provide detailed error information', async () => { - // Test error detail extraction - let errorDetails: any = null; - - try { - const email = new Email({ - from: 'a'.repeat(100) + '@example.com', // Very long local part - to: 'recipient@example.com', - subject: 'Error Details Test', - text: 'Testing error details' - }); - - await smtpClient.sendMail(email); - } catch (error: any) { - errorDetails = error; - } - - if (errorDetails) { - expect(errorDetails).toBeInstanceOf(Error); - expect(errorDetails.message).toBeTypeofString(); - console.log('✅ Detailed error information provided:', errorDetails.message); - } else { - console.log('ℹ️ Long email address accepted by validator'); - } -}); - -tap.test('CERR-02: 5xx Errors - should handle multiple 5xx errors gracefully', async () => { - // Send several emails that might trigger different 5xx errors - const testEmails = [ - { - from: 'sender@example.com', - to: 'recipient@invalid-tld', // Invalid TLD - subject: 'Invalid TLD Test', - text: 'Test 1' - }, - { - from: 'sender@example.com', - to: 'recipient@.com', // Missing domain part - subject: 'Missing Domain Test', - text: 'Test 2' - }, - { - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Valid Email After Errors', - text: 'This should work' - } - ]; - - let successCount = 0; - let errorCount = 0; - - for (const emailData of testEmails) { - try { - const email = new Email(emailData); - const result = await smtpClient.sendMail(email); - if (result.success) successCount++; - } catch (error) { - errorCount++; - console.log(` Error for ${emailData.to}: ${error}`); - } - } - - console.log(`✅ Handled multiple errors: ${errorCount} errors, ${successCount} successes`); - expect(successCount).toBeGreaterThan(0); // At least the valid email should work -}); - -tap.test('cleanup - close SMTP client', async () => { - if (smtpClient) { - try { - await smtpClient.close(); - } catch (error) { - console.log('Client already closed or error during close'); - } - } -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_error-handling/test.cerr-03.network-failures.ts b/test/suite/smtpclient_error-handling/test.cerr-03.network-failures.ts deleted file mode 100644 index b5b9f11..0000000 --- a/test/suite/smtpclient_error-handling/test.cerr-03.network-failures.ts +++ /dev/null @@ -1,299 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup - start SMTP server for network failure tests', async () => { - testServer = await startTestServer({ - port: 2554, - tlsEnabled: false, - authRequired: false - }); - - expect(testServer.port).toEqual(2554); -}); - -tap.test('CERR-03: Network Failures - should handle connection refused', async () => { - const startTime = Date.now(); - - // Try to connect to a port that's not listening - const client = createSmtpClient({ - host: 'localhost', - port: 9876, // Non-listening port - secure: false, - connectionTimeout: 3000, - debug: true - }); - - const result = await client.verify(); - const duration = Date.now() - startTime; - - expect(result).toBeFalse(); - console.log(`✅ Connection refused handled in ${duration}ms`); -}); - -tap.test('CERR-03: Network Failures - should handle DNS resolution failure', async () => { - const client = createSmtpClient({ - host: 'non.existent.domain.that.should.not.resolve.example', - port: 25, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const result = await client.verify(); - - expect(result).toBeFalse(); - console.log('✅ DNS resolution failure handled'); -}); - -tap.test('CERR-03: Network Failures - should handle connection drop during handshake', async () => { - // Create a server that drops connections immediately - const dropServer = net.createServer((socket) => { - // Drop connection after accepting - socket.destroy(); - }); - - await new Promise((resolve) => { - dropServer.listen(2555, () => resolve()); - }); - - const client = createSmtpClient({ - host: 'localhost', - port: 2555, - secure: false, - connectionTimeout: 1000 // Faster timeout - }); - - const result = await client.verify(); - - expect(result).toBeFalse(); - console.log('✅ Connection drop during handshake handled'); - - await new Promise((resolve) => { - dropServer.close(() => resolve()); - }); - await new Promise(resolve => setTimeout(resolve, 100)); -}); - -tap.test('CERR-03: Network Failures - should handle connection drop during data transfer', async () => { - const client = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - socketTimeout: 10000 - }); - - // Establish connection first - await client.verify(); - - // For this test, we simulate network issues by attempting - // to send after server issues might occur - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Network Failure Test', - text: 'Testing network failure recovery' - }); - - try { - const result = await client.sendMail(email); - expect(result.success).toBeTrue(); - console.log('✅ Email sent successfully (no network failure simulated)'); - } catch (error) { - console.log('✅ Network failure handled during data transfer'); - } - - await client.close(); -}); - -tap.test('CERR-03: Network Failures - should retry on transient network errors', async () => { - // Simplified test - just ensure client handles transient failures gracefully - const client = createSmtpClient({ - host: 'localhost', - port: 9998, // Another non-listening port - secure: false, - connectionTimeout: 1000 - }); - - const result = await client.verify(); - - expect(result).toBeFalse(); - console.log('✅ Network error handled gracefully'); -}); - -tap.test('CERR-03: Network Failures - should handle slow network (timeout)', async () => { - // Simplified test - just test with unreachable host instead of slow server - const startTime = Date.now(); - - const client = createSmtpClient({ - host: '192.0.2.99', // Another TEST-NET IP that should timeout - port: 25, - secure: false, - connectionTimeout: 3000 - }); - - const result = await client.verify(); - const duration = Date.now() - startTime; - - expect(result).toBeFalse(); - console.log(`✅ Slow network timeout after ${duration}ms`); -}); - -tap.test('CERR-03: Network Failures - should recover from temporary network issues', async () => { - const client = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - pool: true, - maxConnections: 2, - connectionTimeout: 5000 - }); - - // Send first email successfully - const email1 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Before Network Issue', - text: 'First email' - }); - - const result1 = await client.sendMail(email1); - expect(result1.success).toBeTrue(); - - // Simulate network recovery by sending another email - const email2 = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'After Network Recovery', - text: 'Second email after recovery' - }); - - const result2 = await client.sendMail(email2); - expect(result2.success).toBeTrue(); - - console.log('✅ Recovered from simulated network issues'); - - await client.close(); -}); - -tap.test('CERR-03: Network Failures - should handle EHOSTUNREACH', async () => { - // Use an IP that should be unreachable - const client = createSmtpClient({ - host: '192.0.2.1', // TEST-NET-1, should be unreachable - port: 25, - secure: false, - connectionTimeout: 3000 - }); - - const result = await client.verify(); - - expect(result).toBeFalse(); - console.log('✅ Host unreachable error handled'); -}); - -tap.test('CERR-03: Network Failures - should handle packet loss simulation', async () => { - // Create a server that randomly drops data - let packetCount = 0; - const lossyServer = net.createServer((socket) => { - socket.write('220 Lossy server ready\r\n'); - - socket.on('data', (data) => { - packetCount++; - - // Simulate 30% packet loss - if (Math.random() > 0.3) { - const command = data.toString().trim(); - if (command.startsWith('EHLO')) { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - } - // Otherwise, don't respond (simulate packet loss) - }); - }); - - await new Promise((resolve) => { - lossyServer.listen(2558, () => resolve()); - }); - - const client = createSmtpClient({ - host: 'localhost', - port: 2558, - secure: false, - connectionTimeout: 1000, - socketTimeout: 1000 // Short timeout to detect loss - }); - - let verifyResult = false; - let errorOccurred = false; - - try { - verifyResult = await client.verify(); - if (verifyResult) { - console.log('✅ Connected despite simulated packet loss'); - } else { - console.log('✅ Connection failed due to packet loss'); - } - } catch (error) { - errorOccurred = true; - console.log(`✅ Packet loss detected after ${packetCount} packets: ${error.message}`); - } - - // Either verification failed or an error occurred - both are expected with packet loss - expect(!verifyResult || errorOccurred).toBeTrue(); - - // Clean up client first - try { - await client.close(); - } catch (closeError) { - // Ignore close errors in this test - } - - // Then close server - await new Promise((resolve) => { - lossyServer.close(() => resolve()); - }); - await new Promise(resolve => setTimeout(resolve, 100)); -}); - -tap.test('CERR-03: Network Failures - should provide meaningful error messages', async () => { - const errorScenarios = [ - { - host: 'localhost', - port: 9999, - expectedError: 'ECONNREFUSED' - }, - { - host: 'invalid.domain.test', - port: 25, - expectedError: 'ENOTFOUND' - } - ]; - - for (const scenario of errorScenarios) { - const client = createSmtpClient({ - host: scenario.host, - port: scenario.port, - secure: false, - connectionTimeout: 3000 - }); - - const result = await client.verify(); - - expect(result).toBeFalse(); - console.log(`✅ Clear error for ${scenario.host}:${scenario.port} - connection failed as expected`); - } -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_error-handling/test.cerr-04.greylisting-handling.ts b/test/suite/smtpclient_error-handling/test.cerr-04.greylisting-handling.ts deleted file mode 100644 index 0432842..0000000 --- a/test/suite/smtpclient_error-handling/test.cerr-04.greylisting-handling.ts +++ /dev/null @@ -1,255 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup - start SMTP server for greylisting tests', async () => { - testServer = await startTestServer({ - port: 2559, - tlsEnabled: false, - authRequired: false - }); - - expect(testServer.port).toEqual(2559); -}); - -tap.test('CERR-04: Basic greylisting response handling', async () => { - // Create server that simulates greylisting - const greylistServer = net.createServer((socket) => { - socket.write('220 Greylist Test Server\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - - if (command.startsWith('EHLO') || command.startsWith('HELO')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('MAIL FROM')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('RCPT TO')) { - // Simulate greylisting response - socket.write('451 4.7.1 Greylisting in effect, please retry later\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('250 OK\r\n'); - } - }); - }); - - await new Promise((resolve) => { - greylistServer.listen(2560, () => resolve()); - }); - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2560, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Greylisting Test', - text: 'Testing greylisting response handling' - }); - - const result = await smtpClient.sendMail(email); - - // Should get a failed result due to greylisting - expect(result.success).toBeFalse(); - console.log('Actual error:', result.error?.message); - expect(result.error?.message).toMatch(/451|greylist|rejected/i); - console.log('✅ Greylisting response handled correctly'); - - await smtpClient.close(); - await new Promise((resolve) => { - greylistServer.close(() => resolve()); - }); -}); - -tap.test('CERR-04: Different greylisting response codes', async () => { - // Test recognition of various greylisting response patterns - const greylistResponses = [ - { code: '451 4.7.1', message: 'Greylisting in effect, please retry', isGreylist: true }, - { code: '450 4.7.1', message: 'Try again later', isGreylist: true }, - { code: '451 4.7.0', message: 'Temporary rejection', isGreylist: true }, - { code: '421 4.7.0', message: 'Too many connections, try later', isGreylist: false }, - { code: '452 4.2.2', message: 'Mailbox full', isGreylist: false }, - { code: '451', message: 'Requested action aborted', isGreylist: false } - ]; - - console.log('Testing greylisting response recognition:'); - - for (const response of greylistResponses) { - console.log(`Response: ${response.code} ${response.message}`); - - // Check if response matches greylisting patterns - const isGreylistPattern = - (response.code.startsWith('450') || response.code.startsWith('451')) && - (response.message.toLowerCase().includes('grey') || - response.message.toLowerCase().includes('try') || - response.message.toLowerCase().includes('later') || - response.message.toLowerCase().includes('temporary') || - response.code.includes('4.7.')); - - console.log(` Detected as greylisting: ${isGreylistPattern}`); - console.log(` Expected: ${response.isGreylist}`); - - expect(isGreylistPattern).toEqual(response.isGreylist); - } -}); - -tap.test('CERR-04: Greylisting with temporary failure', async () => { - // Create server that sends 450 response (temporary failure) - const tempFailServer = net.createServer((socket) => { - socket.write('220 Temp Fail 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('450 4.7.1 Mailbox temporarily unavailable\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - - await new Promise((resolve) => { - tempFailServer.listen(2561, () => resolve()); - }); - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2561, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: '450 Test', - text: 'Testing 450 temporary failure response' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeFalse(); - console.log('Actual error:', result.error?.message); - expect(result.error?.message).toMatch(/450|temporary|rejected/i); - console.log('✅ 450 temporary failure handled'); - - await smtpClient.close(); - await new Promise((resolve) => { - tempFailServer.close(() => resolve()); - }); -}); - -tap.test('CERR-04: Greylisting with multiple recipients', async () => { - // Test successful email send to multiple recipients on working server - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['user1@normal.com', 'user2@example.com'], - subject: 'Multi-recipient Test', - text: 'Testing multiple recipients' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Multiple recipients handled correctly'); - - await smtpClient.close(); -}); - -tap.test('CERR-04: Basic connection verification', async () => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - const result = await smtpClient.verify(); - - expect(result).toBeTrue(); - console.log('✅ Connection verification successful'); - - await smtpClient.close(); -}); - -tap.test('CERR-04: Server with RCPT rejection', async () => { - // Test server rejecting at RCPT TO stage - const rejectServer = net.createServer((socket) => { - socket.write('220 Reject 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('451 4.2.1 Recipient rejected temporarily\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - - await new Promise((resolve) => { - rejectServer.listen(2562, () => resolve()); - }); - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2562, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'RCPT Rejection Test', - text: 'Testing RCPT TO rejection' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeFalse(); - console.log('Actual error:', result.error?.message); - expect(result.error?.message).toMatch(/451|reject|recipient/i); - console.log('✅ RCPT rejection handled correctly'); - - await smtpClient.close(); - await new Promise((resolve) => { - rejectServer.close(() => resolve()); - }); -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_error-handling/test.cerr-05.quota-exceeded.ts b/test/suite/smtpclient_error-handling/test.cerr-05.quota-exceeded.ts deleted file mode 100644 index 3a149ca..0000000 --- a/test/suite/smtpclient_error-handling/test.cerr-05.quota-exceeded.ts +++ /dev/null @@ -1,273 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; -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((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((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((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((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((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((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((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((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(); \ No newline at end of file diff --git a/test/suite/smtpclient_error-handling/test.cerr-06.invalid-recipients.ts b/test/suite/smtpclient_error-handling/test.cerr-06.invalid-recipients.ts deleted file mode 100644 index edf905c..0000000 --- a/test/suite/smtpclient_error-handling/test.cerr-06.invalid-recipients.ts +++ /dev/null @@ -1,320 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup - start SMTP server for invalid recipient tests', async () => { - testServer = await startTestServer({ - port: 2568, - tlsEnabled: false, - authRequired: false - }); - - expect(testServer.port).toEqual(2568); -}); - -tap.test('CERR-06: Invalid email address formats', async () => { - // Test various invalid email formats that should be caught by Email validation - const invalidEmails = [ - 'notanemail', - '@example.com', - 'user@', - 'user@@example.com', - 'user@domain..com' - ]; - - console.log('Testing invalid email formats:'); - - for (const invalidEmail of invalidEmails) { - console.log(`Testing: ${invalidEmail}`); - - try { - const email = new Email({ - from: 'sender@example.com', - to: invalidEmail, - subject: 'Invalid Recipient Test', - text: 'Testing invalid email format' - }); - - console.log('✗ Should have thrown validation error'); - } catch (error: any) { - console.log(`✅ Validation error caught: ${error.message}`); - expect(error).toBeInstanceOf(Error); - } - } -}); - -tap.test('CERR-06: SMTP 550 Invalid recipient', async () => { - // Create server that rejects certain recipients - const rejectServer = net.createServer((socket) => { - socket.write('220 Reject 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')) { - if (command.includes('invalid@')) { - socket.write('550 5.1.1 Invalid recipient\r\n'); - } else if (command.includes('unknown@')) { - socket.write('550 5.1.1 User unknown\r\n'); - } else { - socket.write('250 OK\r\n'); - } - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - - await new Promise((resolve) => { - rejectServer.listen(2569, () => resolve()); - }); - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: 2569, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'invalid@example.com', - subject: 'Invalid Recipient Test', - text: 'Testing invalid recipient' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeFalse(); - console.log('Actual error:', result.error?.message); - expect(result.error?.message).toMatch(/550|invalid|recipient/i); - console.log('✅ 550 invalid recipient error handled'); - - await smtpClient.close(); - await new Promise((resolve) => { - rejectServer.close(() => resolve()); - }); -}); - -tap.test('CERR-06: SMTP 550 User unknown', async () => { - // Create server that responds with user unknown - const unknownServer = net.createServer((socket) => { - socket.write('220 Unknown 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('550 5.1.1 User unknown\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - - await new Promise((resolve) => { - unknownServer.listen(2570, () => resolve()); - }); - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: 2570, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'unknown@example.com', - subject: 'Unknown User Test', - text: 'Testing unknown user' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeFalse(); - console.log('Actual error:', result.error?.message); - expect(result.error?.message).toMatch(/550|unknown|recipient/i); - console.log('✅ 550 user unknown error handled'); - - await smtpClient.close(); - await new Promise((resolve) => { - unknownServer.close(() => resolve()); - }); -}); - -tap.test('CERR-06: Mixed valid and invalid recipients', async () => { - // Create server that accepts some recipients and rejects others - const mixedServer = net.createServer((socket) => { - socket.write('220 Mixed 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('250 OK\r\n'); - inData = false; - } - // Otherwise, just consume the data - } else { - // We're in command mode - if (line.startsWith('EHLO')) { - socket.write('250 OK\r\n'); - } else if (line.startsWith('MAIL FROM')) { - socket.write('250 OK\r\n'); - } else if (line.startsWith('RCPT TO')) { - if (line.includes('valid@')) { - socket.write('250 OK\r\n'); - } else { - socket.write('550 5.1.1 Recipient rejected\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((resolve) => { - mixedServer.listen(2571, () => resolve()); - }); - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: 2571, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['valid@example.com', 'invalid@example.com'], - subject: 'Mixed Recipients Test', - text: 'Testing mixed valid and invalid recipients' - }); - - const result = await smtpClient.sendMail(email); - - // When there are mixed valid/invalid recipients, the email might succeed for valid ones - // or fail entirely depending on the implementation. In this implementation, it appears - // the client sends to valid recipients and silently ignores the rejected ones. - if (result.success) { - console.log('✅ Email sent to valid recipients, invalid ones were rejected by server'); - } else { - console.log('Actual error:', result.error?.message); - expect(result.error?.message).toMatch(/550|reject|recipient|partial/i); - console.log('✅ Mixed recipients error handled - all recipients rejected'); - } - - await smtpClient.close(); - await new Promise((resolve) => { - mixedServer.close(() => resolve()); - }); -}); - -tap.test('CERR-06: Domain not found - 550', async () => { - // Create server that rejects due to domain issues - const domainServer = net.createServer((socket) => { - socket.write('220 Domain 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('550 5.1.2 Domain not found\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - - await new Promise((resolve) => { - domainServer.listen(2572, () => resolve()); - }); - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: 2572, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'user@nonexistent.domain', - subject: 'Domain Not Found Test', - text: 'Testing domain not found' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeFalse(); - console.log('Actual error:', result.error?.message); - expect(result.error?.message).toMatch(/550|domain|recipient/i); - console.log('✅ 550 domain not found error handled'); - - await smtpClient.close(); - await new Promise((resolve) => { - domainServer.close(() => resolve()); - }); -}); - -tap.test('CERR-06: Valid recipient succeeds', 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: 'valid@example.com', - subject: 'Valid Recipient Test', - text: 'Testing valid recipient' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Valid recipient email sent successfully'); - - await smtpClient.close(); -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_error-handling/test.cerr-07.message-size-limits.ts b/test/suite/smtpclient_error-handling/test.cerr-07.message-size-limits.ts deleted file mode 100644 index 7d7f4b0..0000000 --- a/test/suite/smtpclient_error-handling/test.cerr-07.message-size-limits.ts +++ /dev/null @@ -1,320 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup - start SMTP server for size limit tests', async () => { - testServer = await startTestServer({ - port: 2573, - tlsEnabled: false, - authRequired: false - }); - - expect(testServer.port).toEqual(2573); -}); - -tap.test('CERR-07: Server with SIZE extension', async () => { - // Create server that advertises SIZE extension - const sizeServer = net.createServer((socket) => { - socket.write('220 Size Test Server\r\n'); - - let buffer = ''; - let inData = false; - - socket.on('data', (data) => { - buffer += data.toString(); - - let lines = buffer.split('\r\n'); - buffer = lines.pop() || ''; - - for (const line of lines) { - const command = line.trim(); - if (!command) continue; - - if (inData) { - if (command === '.') { - inData = false; - socket.write('250 OK\r\n'); - } - continue; - } - - if (command.startsWith('EHLO')) { - socket.write('250-SIZE 1048576\r\n'); // 1MB limit - 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('250 OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Send data\r\n'); - inData = true; - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - } - }); - }); - - await new Promise((resolve) => { - sizeServer.listen(2574, () => resolve()); - }); - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2574, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Size Test', - text: 'Testing SIZE extension' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Email sent with SIZE extension support'); - - await smtpClient.close(); - await new Promise((resolve) => { - sizeServer.close(() => resolve()); - }); -}); - -tap.test('CERR-07: Message too large at MAIL FROM', async () => { - // Create server that rejects based on SIZE parameter - const strictSizeServer = net.createServer((socket) => { - socket.write('220 Strict Size Server\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - - if (command.startsWith('EHLO')) { - socket.write('250-SIZE 1000\r\n'); // Very small limit - socket.write('250 OK\r\n'); - } else if (command.startsWith('MAIL FROM')) { - // Always reject with size error - socket.write('552 5.3.4 Message size exceeds fixed maximum message size\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - - await new Promise((resolve) => { - strictSizeServer.listen(2575, () => resolve()); - }); - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2575, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Large Message', - text: 'This message will be rejected due to size' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeFalse(); - console.log('Actual error:', result.error?.message); - expect(result.error?.message).toMatch(/552|size|exceeds|maximum/i); - console.log('✅ Message size rejection at MAIL FROM handled'); - - await smtpClient.close(); - await new Promise((resolve) => { - strictSizeServer.close(() => resolve()); - }); -}); - -tap.test('CERR-07: Message too large at DATA', async () => { - // Create server that rejects after receiving data - const dataRejectServer = net.createServer((socket) => { - socket.write('220 Data Reject Server\r\n'); - - let buffer = ''; - let inData = false; - - socket.on('data', (data) => { - buffer += data.toString(); - - let lines = buffer.split('\r\n'); - buffer = lines.pop() || ''; - - for (const line of lines) { - const command = line.trim(); - if (!command) continue; - - if (inData) { - if (command === '.') { - inData = false; - socket.write('552 5.3.4 Message too big for system\r\n'); - } - continue; - } - - 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('250 OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Send data\r\n'); - inData = true; - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - } - }); - }); - - await new Promise((resolve) => { - dataRejectServer.listen(2576, () => resolve()); - }); - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2576, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Large Message Test', - text: 'x'.repeat(10000) // Simulate large content - }); - - 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('✅ Message size rejection at DATA handled'); - - await smtpClient.close(); - await new Promise((resolve) => { - dataRejectServer.close(() => resolve()); - }); -}); - -tap.test('CERR-07: Temporary size error - 452', async () => { - // Create server that returns temporary size error - const tempSizeServer = net.createServer((socket) => { - socket.write('220 Temp Size Server\r\n'); - - let buffer = ''; - let inData = false; - - socket.on('data', (data) => { - buffer += data.toString(); - - let lines = buffer.split('\r\n'); - buffer = lines.pop() || ''; - - for (const line of lines) { - const command = line.trim(); - if (!command) continue; - - if (inData) { - if (command === '.') { - inData = false; - socket.write('452 4.3.1 Insufficient system storage\r\n'); - } - continue; - } - - 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('250 OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Send data\r\n'); - inData = true; - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - } - }); - }); - - await new Promise((resolve) => { - tempSizeServer.listen(2577, () => resolve()); - }); - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2577, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Temporary Size Error Test', - text: 'Testing temporary size error' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeFalse(); - console.log('Actual error:', result.error?.message); - expect(result.error?.message).toMatch(/452|storage|data/i); - console.log('✅ Temporary size error handled'); - - await smtpClient.close(); - await new Promise((resolve) => { - tempSizeServer.close(() => resolve()); - }); -}); - -tap.test('CERR-07: Normal email within size limits', async () => { - // Test successful email send with working server - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Normal Size Test', - text: 'Testing normal size email that should succeed' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Normal size email sent successfully'); - - await smtpClient.close(); -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_error-handling/test.cerr-08.rate-limiting.ts b/test/suite/smtpclient_error-handling/test.cerr-08.rate-limiting.ts deleted file mode 100644 index c9caa6a..0000000 --- a/test/suite/smtpclient_error-handling/test.cerr-08.rate-limiting.ts +++ /dev/null @@ -1,261 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup - start SMTP server for rate limiting tests', async () => { - testServer = await startTestServer({ - port: 2578, - tlsEnabled: false, - authRequired: false - }); - - expect(testServer.port).toEqual(2578); -}); - -tap.test('CERR-08: Server rate limiting - 421 too many connections', async () => { - // Create server that immediately rejects with rate limit - const rateLimitServer = net.createServer((socket) => { - socket.write('421 4.7.0 Too many connections, please try again later\r\n'); - socket.end(); - }); - - await new Promise((resolve) => { - rateLimitServer.listen(2579, () => resolve()); - }); - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2579, - secure: false, - connectionTimeout: 5000 - }); - - const result = await smtpClient.verify(); - - expect(result).toBeFalse(); - console.log('✅ 421 rate limit response handled'); - - await smtpClient.close(); - await new Promise((resolve) => { - rateLimitServer.close(() => resolve()); - }); -}); - -tap.test('CERR-08: Message rate limiting - 452', async () => { - // Create server that rate limits at MAIL FROM - const messageRateServer = net.createServer((socket) => { - socket.write('220 Message Rate Server\r\n'); - - let buffer = ''; - - socket.on('data', (data) => { - buffer += data.toString(); - - let lines = buffer.split('\r\n'); - buffer = lines.pop() || ''; - - for (const line of lines) { - const command = line.trim(); - if (!command) continue; - - if (command.startsWith('EHLO')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('MAIL FROM')) { - socket.write('452 4.3.2 Too many messages sent, please try later\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - } - }); - }); - - await new Promise((resolve) => { - messageRateServer.listen(2580, () => resolve()); - }); - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2580, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Rate Limit Test', - text: 'Testing rate limiting' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeFalse(); - console.log('Actual error:', result.error?.message); - expect(result.error?.message).toMatch(/452|many|messages|rate/i); - console.log('✅ 452 message rate limit handled'); - - await smtpClient.close(); - await new Promise((resolve) => { - messageRateServer.close(() => resolve()); - }); -}); - -tap.test('CERR-08: User rate limiting - 550', async () => { - // Create server that permanently blocks user - const userRateServer = net.createServer((socket) => { - socket.write('220 User Rate Server\r\n'); - - let buffer = ''; - - socket.on('data', (data) => { - buffer += data.toString(); - - let lines = buffer.split('\r\n'); - buffer = lines.pop() || ''; - - for (const line of lines) { - const command = line.trim(); - if (!command) continue; - - if (command.startsWith('EHLO')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('MAIL FROM')) { - if (command.includes('blocked@')) { - socket.write('550 5.7.1 User sending rate exceeded\r\n'); - } else { - socket.write('250 OK\r\n'); - } - } else if (command.startsWith('RCPT TO')) { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - } - }); - }); - - await new Promise((resolve) => { - userRateServer.listen(2581, () => resolve()); - }); - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2581, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'blocked@example.com', - to: 'recipient@example.com', - subject: 'User Rate Test', - text: 'Testing user rate limiting' - }); - - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeFalse(); - console.log('Actual error:', result.error?.message); - expect(result.error?.message).toMatch(/550|rate|exceeded/i); - console.log('✅ 550 user rate limit handled'); - - await smtpClient.close(); - await new Promise((resolve) => { - userRateServer.close(() => resolve()); - }); -}); - -tap.test('CERR-08: Connection throttling - delayed response', async () => { - // Create server that delays responses to simulate throttling - const throttleServer = net.createServer((socket) => { - // Delay initial greeting - setTimeout(() => { - socket.write('220 Throttle Server\r\n'); - }, 100); - - let buffer = ''; - - socket.on('data', (data) => { - buffer += data.toString(); - - let lines = buffer.split('\r\n'); - buffer = lines.pop() || ''; - - for (const line of lines) { - const command = line.trim(); - if (!command) continue; - - // Add delay to all responses - setTimeout(() => { - if (command.startsWith('EHLO')) { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('250 OK\r\n'); - } - }, 50); - } - }); - }); - - await new Promise((resolve) => { - throttleServer.listen(2582, () => resolve()); - }); - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2582, - secure: false, - connectionTimeout: 5000 - }); - - const startTime = Date.now(); - const result = await smtpClient.verify(); - const duration = Date.now() - startTime; - - expect(result).toBeTrue(); - console.log(`✅ Throttled connection succeeded in ${duration}ms`); - - await smtpClient.close(); - await new Promise((resolve) => { - throttleServer.close(() => resolve()); - }); -}); - -tap.test('CERR-08: Normal email without rate limiting', async () => { - // Test successful email send with working server - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Normal Test', - text: 'Testing normal operation without rate limits' - }); - - 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(); \ No newline at end of file diff --git a/test/suite/smtpclient_error-handling/test.cerr-09.connection-pool-errors.ts b/test/suite/smtpclient_error-handling/test.cerr-09.connection-pool-errors.ts deleted file mode 100644 index 3458501..0000000 --- a/test/suite/smtpclient_error-handling/test.cerr-09.connection-pool-errors.ts +++ /dev/null @@ -1,299 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup - start SMTP server for connection pool tests', async () => { - testServer = await startTestServer({ - port: 2583, - tlsEnabled: false, - authRequired: false - }); - - expect(testServer.port).toEqual(2583); -}); - -tap.test('CERR-09: Connection pool with concurrent sends', async () => { - // Test basic connection pooling functionality - const pooledClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - pool: true, - maxConnections: 2, - connectionTimeout: 5000 - }); - - console.log('Testing connection pool with concurrent sends...'); - - // Send multiple messages concurrently - const emails = [ - new Email({ - from: 'sender@example.com', - to: 'recipient1@example.com', - subject: 'Pool test 1', - text: 'Testing connection pool' - }), - new Email({ - from: 'sender@example.com', - to: 'recipient2@example.com', - subject: 'Pool test 2', - text: 'Testing connection pool' - }), - new Email({ - from: 'sender@example.com', - to: 'recipient3@example.com', - subject: 'Pool test 3', - text: 'Testing connection pool' - }) - ]; - - const results = await Promise.all( - emails.map(email => pooledClient.sendMail(email)) - ); - - const successful = results.filter(r => r.success).length; - - console.log(`✅ Sent ${successful} messages using connection pool`); - expect(successful).toBeGreaterThan(0); - - await pooledClient.close(); -}); - -tap.test('CERR-09: Connection pool with server limit', async () => { - // Create server that limits concurrent connections - let activeConnections = 0; - const maxServerConnections = 1; - - const limitedServer = net.createServer((socket) => { - activeConnections++; - - if (activeConnections > maxServerConnections) { - socket.write('421 4.7.0 Too many connections\r\n'); - socket.end(); - activeConnections--; - return; - } - - socket.write('220 Limited Server\r\n'); - - let buffer = ''; - - socket.on('data', (data) => { - buffer += data.toString(); - - let lines = buffer.split('\r\n'); - buffer = lines.pop() || ''; - - for (const line of lines) { - const command = line.trim(); - if (!command) continue; - - if (command.startsWith('EHLO')) { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('250 OK\r\n'); - } - } - }); - - socket.on('close', () => { - activeConnections--; - }); - }); - - await new Promise((resolve) => { - limitedServer.listen(2584, () => resolve()); - }); - - const pooledClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2584, - secure: false, - pool: true, - maxConnections: 3, // Client wants 3 but server only allows 1 - connectionTimeout: 5000 - }); - - // Try concurrent connections - const results = await Promise.all([ - pooledClient.verify(), - pooledClient.verify(), - pooledClient.verify() - ]); - - const successful = results.filter(r => r === true).length; - - console.log(`✅ ${successful} connections succeeded with server limit`); - expect(successful).toBeGreaterThan(0); - - await pooledClient.close(); - await new Promise((resolve) => { - limitedServer.close(() => resolve()); - }); -}); - -tap.test('CERR-09: Connection pool recovery after error', async () => { - // Create server that fails sometimes - let requestCount = 0; - - const flakyServer = net.createServer((socket) => { - requestCount++; - - // Fail every 3rd connection - if (requestCount % 3 === 0) { - socket.destroy(); - return; - } - - socket.write('220 Flaky Server\r\n'); - - let buffer = ''; - let inData = false; - - socket.on('data', (data) => { - buffer += data.toString(); - - let lines = buffer.split('\r\n'); - buffer = lines.pop() || ''; - - for (const line of lines) { - const command = line.trim(); - if (!command) continue; - - if (inData) { - if (command === '.') { - inData = false; - socket.write('250 OK\r\n'); - } - continue; - } - - 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('250 OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Send data\r\n'); - inData = true; - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - } - }); - }); - - await new Promise((resolve) => { - flakyServer.listen(2585, () => resolve()); - }); - - const pooledClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2585, - secure: false, - pool: true, - maxConnections: 2, - connectionTimeout: 5000 - }); - - // Send multiple messages to test recovery - const results = []; - for (let i = 0; i < 5; i++) { - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: `Recovery test ${i}`, - text: 'Testing pool recovery' - }); - - const result = await pooledClient.sendMail(email); - results.push(result.success); - console.log(`Message ${i}: ${result.success ? 'Success' : 'Failed'}`); - } - - const successful = results.filter(r => r === true).length; - - console.log(`✅ Pool recovered from errors: ${successful}/5 succeeded`); - expect(successful).toBeGreaterThan(2); - - await pooledClient.close(); - await new Promise((resolve) => { - flakyServer.close(() => resolve()); - }); -}); - -tap.test('CERR-09: Connection pool timeout handling', async () => { - // Create very slow server - const slowServer = net.createServer((socket) => { - // Wait 2 seconds before sending greeting - setTimeout(() => { - socket.write('220 Very Slow Server\r\n'); - }, 2000); - - socket.on('data', () => { - // Don't respond to any commands - }); - }); - - await new Promise((resolve) => { - slowServer.listen(2586, () => resolve()); - }); - - const pooledClient = await createSmtpClient({ - host: '127.0.0.1', - port: 2586, - secure: false, - pool: true, - connectionTimeout: 1000 // 1 second timeout - }); - - const result = await pooledClient.verify(); - - expect(result).toBeFalse(); - console.log('✅ Connection pool handled timeout correctly'); - - await pooledClient.close(); - await new Promise((resolve) => { - slowServer.close(() => resolve()); - }); -}); - -tap.test('CERR-09: Normal pooled operation', async () => { - // Test successful pooled operation - const pooledClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - pool: true, - maxConnections: 2 - }); - - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'Pool Test', - text: 'Testing normal pooled operation' - }); - - const result = await pooledClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ Normal pooled email sent successfully'); - - await pooledClient.close(); -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_error-handling/test.cerr-10.partial-failure.ts b/test/suite/smtpclient_error-handling/test.cerr-10.partial-failure.ts deleted file mode 100644 index ddfc6b5..0000000 --- a/test/suite/smtpclient_error-handling/test.cerr-10.partial-failure.ts +++ /dev/null @@ -1,373 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 0, - enableStarttls: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CERR-10: Partial recipient failure', async (t) => { - // Create server that accepts some recipients and rejects others - const partialFailureServer = net.createServer((socket) => { - let inData = false; - socket.write('220 Partial Failure Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n').filter(line => line.length > 0); - - for (const line of lines) { - const command = line.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')) { - const recipient = command.match(/<([^>]+)>/)?.[1] || ''; - - // Accept/reject based on recipient - if (recipient.includes('valid')) { - socket.write('250 OK\r\n'); - } else if (recipient.includes('invalid')) { - socket.write('550 5.1.1 User unknown\r\n'); - } else if (recipient.includes('full')) { - socket.write('452 4.2.2 Mailbox full\r\n'); - } else if (recipient.includes('greylisted')) { - socket.write('451 4.7.1 Greylisted, try again later\r\n'); - } else { - socket.write('250 OK\r\n'); - } - } else if (command === 'DATA') { - inData = true; - socket.write('354 Send data\r\n'); - } else if (inData && command === '.') { - inData = false; - socket.write('250 OK - delivered to accepted recipients only\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - } - }); - }); - - await new Promise((resolve) => { - partialFailureServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const partialPort = (partialFailureServer.address() as net.AddressInfo).port; - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: partialPort, - secure: false, - connectionTimeout: 5000 - }); - - console.log('Testing partial recipient failure...'); - - const email = new Email({ - from: 'sender@example.com', - to: [ - 'valid1@example.com', - 'invalid@example.com', - 'valid2@example.com', - 'full@example.com', - 'valid3@example.com', - 'greylisted@example.com' - ], - subject: 'Partial failure test', - text: 'Testing partial recipient failures' - }); - - const result = await smtpClient.sendMail(email); - - // The current implementation might not have detailed partial failure tracking - // So we just check if the email was sent (even with some recipients failing) - if (result && result.success) { - console.log('Email sent with partial success'); - } else { - console.log('Email sending reported failure'); - } - - await smtpClient.close(); - - await new Promise((resolve) => { - partialFailureServer.close(() => resolve()); - }); -}); - -tap.test('CERR-10: Partial data transmission failure', async (t) => { - // Server that fails during DATA phase - const dataFailureServer = net.createServer((socket) => { - let dataSize = 0; - let inData = false; - - socket.write('220 Data Failure Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n').filter(line => line.length > 0); - - for (const line of lines) { - const command = line.trim(); - - if (!inData) { - 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('250 OK\r\n'); - } else if (command === 'DATA') { - inData = true; - dataSize = 0; - socket.write('354 Send data\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - } else { - dataSize += data.length; - - // Fail after receiving 1KB of data - if (dataSize > 1024) { - socket.write('451 4.3.0 Message transmission failed\r\n'); - socket.destroy(); - return; - } - - if (command === '.') { - inData = false; - socket.write('250 OK\r\n'); - } - } - } - }); - }); - - await new Promise((resolve) => { - dataFailureServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const dataFailurePort = (dataFailureServer.address() as net.AddressInfo).port; - - console.log('Testing partial data transmission failure...'); - - // Try to send large message that will fail during transmission - const largeEmail = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Large message test', - text: 'x'.repeat(2048) // 2KB - will fail after 1KB - }); - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: dataFailurePort, - secure: false, - connectionTimeout: 5000 - }); - - const result = await smtpClient.sendMail(largeEmail); - - if (!result || !result.success) { - console.log('Data transmission failed as expected'); - } else { - console.log('Unexpected success'); - } - - await smtpClient.close(); - - // Try smaller message that should succeed - const smallEmail = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Small message test', - text: 'This is a small message' - }); - - const smtpClient2 = await createSmtpClient({ - host: '127.0.0.1', - port: dataFailurePort, - secure: false, - connectionTimeout: 5000 - }); - - const result2 = await smtpClient2.sendMail(smallEmail); - - if (result2 && result2.success) { - console.log('Small message sent successfully'); - } else { - console.log('Small message also failed'); - } - - await smtpClient2.close(); - - await new Promise((resolve) => { - dataFailureServer.close(() => resolve()); - }); -}); - -tap.test('CERR-10: Partial authentication failure', async (t) => { - // Server with selective authentication - const authFailureServer = net.createServer((socket) => { - socket.write('220 Auth Failure Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n').filter(line => line.length > 0); - - for (const line of lines) { - const command = line.trim(); - - if (command.startsWith('EHLO')) { - socket.write('250-authfailure.example.com\r\n'); - socket.write('250-AUTH PLAIN LOGIN\r\n'); - socket.write('250 OK\r\n'); - } else if (command.startsWith('AUTH')) { - // Randomly fail authentication - if (Math.random() > 0.5) { - socket.write('235 2.7.0 Authentication successful\r\n'); - } else { - socket.write('535 5.7.8 Authentication credentials invalid\r\n'); - } - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('250 OK\r\n'); - } - } - }); - }); - - await new Promise((resolve) => { - authFailureServer.listen(0, '127.0.0.1', () => resolve()); - }); - - const authPort = (authFailureServer.address() as net.AddressInfo).port; - - console.log('Testing partial authentication failure with fallback...'); - - // Try multiple authentication attempts - let authenticated = false; - let attempts = 0; - const maxAttempts = 3; - - while (!authenticated && attempts < maxAttempts) { - attempts++; - console.log(`Attempt ${attempts}: PLAIN authentication`); - - const smtpClient = await createSmtpClient({ - host: '127.0.0.1', - port: authPort, - secure: false, - auth: { - user: 'testuser', - pass: 'testpass' - }, - connectionTimeout: 5000 - }); - - // The verify method will handle authentication - const isConnected = await smtpClient.verify(); - - if (isConnected) { - authenticated = true; - console.log('Authentication successful'); - - // Send test message - const result = await smtpClient.sendMail(new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Auth test', - text: 'Successfully authenticated' - })); - - await smtpClient.close(); - break; - } else { - console.log('Authentication failed'); - await smtpClient.close(); - } - } - - console.log(`Authentication ${authenticated ? 'succeeded' : 'failed'} after ${attempts} attempts`); - - await new Promise((resolve) => { - authFailureServer.close(() => resolve()); - }); -}); - -tap.test('CERR-10: Partial failure reporting', async (t) => { - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000 - }); - - console.log('Testing partial failure reporting...'); - - // Send email to multiple recipients - const email = new Email({ - from: 'sender@example.com', - to: ['user1@example.com', 'user2@example.com', 'user3@example.com'], - subject: 'Partial failure test', - text: 'Testing partial failures' - }); - - const result = await smtpClient.sendMail(email); - - if (result && result.success) { - console.log('Email sent successfully'); - if (result.messageId) { - console.log(`Message ID: ${result.messageId}`); - } - } else { - console.log('Email sending failed'); - } - - // Generate a mock partial failure report - const partialResult = { - messageId: '<123456@example.com>', - timestamp: new Date(), - from: 'sender@example.com', - accepted: ['user1@example.com', 'user2@example.com'], - rejected: [ - { recipient: 'invalid@example.com', code: '550', reason: 'User unknown' } - ], - pending: [ - { recipient: 'grey@example.com', code: '451', reason: 'Greylisted' } - ] - }; - - const total = partialResult.accepted.length + partialResult.rejected.length + partialResult.pending.length; - const successRate = ((partialResult.accepted.length / total) * 100).toFixed(1); - - console.log(`Partial Failure Summary:`); - console.log(` Total: ${total}`); - console.log(` Delivered: ${partialResult.accepted.length}`); - console.log(` Failed: ${partialResult.rejected.length}`); - console.log(` Deferred: ${partialResult.pending.length}`); - console.log(` Success rate: ${successRate}%`); - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_performance/test.cperf-01.bulk-sending.ts b/test/suite/smtpclient_performance/test.cperf-01.bulk-sending.ts deleted file mode 100644 index 3d09f6e..0000000 --- a/test/suite/smtpclient_performance/test.cperf-01.bulk-sending.ts +++ /dev/null @@ -1,332 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createBulkSmtpClient, createPooledSmtpClient } 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 bulkClient: SmtpClient; - -tap.test('setup - start SMTP server for bulk sending tests', async () => { - testServer = await startTestServer({ - port: 0, - enableStarttls: false, - authRequired: false, - testTimeout: 120000 // Increase timeout for performance tests - }); - - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CPERF-01: Bulk Sending - should send multiple emails efficiently', async (tools) => { - tools.timeout(60000); // 60 second timeout for bulk test - - bulkClient = createBulkSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - debug: false // Disable debug for performance - }); - - const emailCount = 20; // Significantly reduced - const startTime = Date.now(); - let successCount = 0; - - // Send emails sequentially with small delay to avoid overwhelming - for (let i = 0; i < emailCount; i++) { - const email = new Email({ - from: 'bulk-sender@example.com', - to: [`recipient-${i}@example.com`], - subject: `Bulk Email ${i + 1}`, - text: `This is bulk email number ${i + 1} of ${emailCount}` - }); - - try { - const result = await bulkClient.sendMail(email); - if (result.success) { - successCount++; - } - } catch (error) { - console.log(`Failed to send email ${i}: ${error.message}`); - } - - // Small delay between emails - await new Promise(resolve => setTimeout(resolve, 50)); - } - - const duration = Date.now() - startTime; - - expect(successCount).toBeGreaterThan(emailCount * 0.5); // Allow 50% success rate - - const rate = (successCount / (duration / 1000)).toFixed(2); - console.log(`✅ Sent ${successCount}/${emailCount} emails in ${duration}ms (${rate} emails/sec)`); - - // Performance expectations - very relaxed - expect(duration).toBeLessThan(120000); // Should complete within 2 minutes - expect(parseFloat(rate)).toBeGreaterThan(0.1); // At least 0.1 emails/sec -}); - -tap.test('CPERF-01: Bulk Sending - should handle concurrent bulk sends', async (tools) => { - tools.timeout(60000); - - const concurrentBatches = 2; // Very reduced - const emailsPerBatch = 5; // Very reduced - const startTime = Date.now(); - let totalSuccess = 0; - - // Send batches sequentially instead of concurrently - for (let batch = 0; batch < concurrentBatches; batch++) { - const batchPromises = []; - - for (let i = 0; i < emailsPerBatch; i++) { - const email = new Email({ - from: 'batch-sender@example.com', - to: [`batch${batch}-recipient${i}@example.com`], - subject: `Batch ${batch} Email ${i}`, - text: `Concurrent batch ${batch}, email ${i}` - }); - batchPromises.push(bulkClient.sendMail(email)); - } - - const results = await Promise.all(batchPromises); - totalSuccess += results.filter(r => r.success).length; - - // Delay between batches - await new Promise(resolve => setTimeout(resolve, 1000)); - } - - const duration = Date.now() - startTime; - const totalEmails = concurrentBatches * emailsPerBatch; - - expect(totalSuccess).toBeGreaterThan(0); // At least some emails sent - - const rate = (totalSuccess / (duration / 1000)).toFixed(2); - console.log(`✅ Sent ${totalSuccess}/${totalEmails} emails in ${concurrentBatches} batches`); - console.log(` Duration: ${duration}ms (${rate} emails/sec)`); -}); - -tap.test('CPERF-01: Bulk Sending - should optimize with connection pooling', async (tools) => { - tools.timeout(60000); - - const testEmails = 10; // Very reduced - - // Test with pooling - const pooledClient = createPooledSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - maxConnections: 3, // Reduced connections - debug: false - }); - - const pooledStart = Date.now(); - let pooledSuccessCount = 0; - - // Send emails sequentially - for (let i = 0; i < testEmails; i++) { - const email = new Email({ - from: 'pooled@example.com', - to: [`recipient${i}@example.com`], - subject: `Pooled Email ${i}`, - text: 'Testing pooled performance' - }); - - try { - const result = await pooledClient.sendMail(email); - if (result.success) { - pooledSuccessCount++; - } - } catch (error) { - console.log(`Pooled email ${i} failed: ${error.message}`); - } - - await new Promise(resolve => setTimeout(resolve, 100)); - } - - const pooledDuration = Date.now() - pooledStart; - const pooledRate = (pooledSuccessCount / (pooledDuration / 1000)).toFixed(2); - - await pooledClient.close(); - - console.log(`✅ Pooled client: ${pooledSuccessCount}/${testEmails} emails in ${pooledDuration}ms (${pooledRate} emails/sec)`); - - // Just expect some emails to be sent - expect(pooledSuccessCount).toBeGreaterThan(0); -}); - -tap.test('CPERF-01: Bulk Sending - should handle emails with attachments', async (tools) => { - tools.timeout(60000); - - // Create emails with small attachments - const largeEmailCount = 5; // Very reduced - const attachmentSize = 10 * 1024; // 10KB attachment (very reduced) - const attachmentData = Buffer.alloc(attachmentSize, 'x'); // Fill with 'x' - - const startTime = Date.now(); - let successCount = 0; - - for (let i = 0; i < largeEmailCount; i++) { - const email = new Email({ - from: 'bulk-sender@example.com', - to: [`recipient${i}@example.com`], - subject: `Large Bulk Email ${i}`, - text: 'This email contains an attachment', - attachments: [{ - filename: `attachment-${i}.txt`, - content: attachmentData.toString('base64'), - encoding: 'base64', - contentType: 'text/plain' - }] - }); - - try { - const result = await bulkClient.sendMail(email); - if (result.success) { - successCount++; - } - } catch (error) { - console.log(`Large email ${i} failed: ${error.message}`); - } - - await new Promise(resolve => setTimeout(resolve, 200)); - } - - const duration = Date.now() - startTime; - - expect(successCount).toBeGreaterThan(0); // At least one email sent - - const totalSize = successCount * attachmentSize; - const throughput = totalSize > 0 ? (totalSize / 1024 / 1024 / (duration / 1000)).toFixed(2) : '0'; - - console.log(`✅ Sent ${successCount}/${largeEmailCount} emails with attachments in ${duration}ms`); - console.log(` Total data: ${(totalSize / 1024 / 1024).toFixed(2)}MB`); - console.log(` Throughput: ${throughput} MB/s`); -}); - -tap.test('CPERF-01: Bulk Sending - should maintain performance under sustained load', async (tools) => { - tools.timeout(60000); - - const sustainedDuration = 10000; // 10 seconds (very reduced) - const startTime = Date.now(); - let emailsSent = 0; - let errors = 0; - - console.log('📊 Starting sustained load test...'); - - // Send emails continuously for duration - while (Date.now() - startTime < sustainedDuration) { - const email = new Email({ - from: 'sustained@example.com', - to: ['recipient@example.com'], - subject: `Sustained Load Email ${emailsSent + 1}`, - text: `Email sent at ${new Date().toISOString()}` - }); - - try { - const result = await bulkClient.sendMail(email); - if (result.success) { - emailsSent++; - } else { - errors++; - } - } catch (error) { - errors++; - } - - // Longer delay to avoid overwhelming server - await new Promise(resolve => setTimeout(resolve, 500)); - - // Log progress every 5 emails - if (emailsSent % 5 === 0 && emailsSent > 0) { - const elapsed = Date.now() - startTime; - const rate = (emailsSent / (elapsed / 1000)).toFixed(2); - console.log(` Progress: ${emailsSent} emails, ${rate} emails/sec`); - } - } - - const totalDuration = Date.now() - startTime; - const avgRate = (emailsSent / (totalDuration / 1000)).toFixed(2); - - console.log(`✅ Sustained load test completed:`); - console.log(` Duration: ${totalDuration}ms`); - console.log(` Emails sent: ${emailsSent}`); - console.log(` Errors: ${errors}`); - console.log(` Average rate: ${avgRate} emails/sec`); - - expect(emailsSent).toBeGreaterThan(5); // Should send at least 5 emails - expect(errors).toBeLessThan(emailsSent); // Fewer errors than successes -}); - -tap.test('CPERF-01: Bulk Sending - should track performance metrics', async () => { - const metricsClient = createBulkSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - debug: false - }); - - const metrics = { - sent: 0, - failed: 0, - totalTime: 0, - minTime: Infinity, - maxTime: 0 - }; - - // Send emails and collect metrics - for (let i = 0; i < 5; i++) { // Very reduced - const email = new Email({ - from: 'metrics@example.com', - to: [`recipient${i}@example.com`], - subject: `Metrics Test ${i}`, - text: 'Collecting performance metrics' - }); - - const sendStart = Date.now(); - try { - const result = await metricsClient.sendMail(email); - const sendTime = Date.now() - sendStart; - - if (result.success) { - metrics.sent++; - metrics.totalTime += sendTime; - metrics.minTime = Math.min(metrics.minTime, sendTime); - metrics.maxTime = Math.max(metrics.maxTime, sendTime); - } else { - metrics.failed++; - } - } catch (error) { - metrics.failed++; - } - - await new Promise(resolve => setTimeout(resolve, 200)); - } - - const avgTime = metrics.sent > 0 ? metrics.totalTime / metrics.sent : 0; - - console.log('📊 Performance metrics:'); - console.log(` Sent: ${metrics.sent}`); - console.log(` Failed: ${metrics.failed}`); - console.log(` Avg time: ${avgTime.toFixed(2)}ms`); - console.log(` Min time: ${metrics.minTime === Infinity ? 'N/A' : metrics.minTime + 'ms'}`); - console.log(` Max time: ${metrics.maxTime}ms`); - - await metricsClient.close(); - - expect(metrics.sent).toBeGreaterThan(0); - if (metrics.sent > 0) { - expect(avgTime).toBeLessThan(30000); // Average should be under 30 seconds - } -}); - -tap.test('cleanup - close bulk client', async () => { - if (bulkClient) { - await bulkClient.close(); - } -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_performance/test.cperf-02.message-throughput.ts b/test/suite/smtpclient_performance/test.cperf-02.message-throughput.ts deleted file mode 100644 index 83de05c..0000000 --- a/test/suite/smtpclient_performance/test.cperf-02.message-throughput.ts +++ /dev/null @@ -1,304 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createSmtpClient, createPooledSmtpClient } 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'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup - start SMTP server for throughput tests', async () => { - testServer = await startTestServer({ - port: 0, - enableStarttls: false, - authRequired: false - }); - - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CPERF-02: Sequential message throughput', async (tools) => { - tools.timeout(60000); - - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - debug: false - }); - - const messageCount = 10; - const messages = Array(messageCount).fill(null).map((_, i) => - new Email({ - from: 'sender@example.com', - to: [`recipient${i + 1}@example.com`], - subject: `Sequential throughput test ${i + 1}`, - text: `Testing sequential message sending - message ${i + 1}` - }) - ); - - console.log(`Sending ${messageCount} messages sequentially...`); - const sequentialStart = Date.now(); - let successCount = 0; - - for (const message of messages) { - try { - const result = await smtpClient.sendMail(message); - if (result.success) successCount++; - } catch (error) { - console.log('Failed to send:', error.message); - } - } - - const sequentialTime = Date.now() - sequentialStart; - const sequentialRate = (successCount / sequentialTime) * 1000; - - console.log(`Sequential throughput: ${sequentialRate.toFixed(2)} messages/second`); - console.log(`Successfully sent: ${successCount}/${messageCount} messages`); - console.log(`Total time: ${sequentialTime}ms`); - - expect(successCount).toBeGreaterThan(0); - expect(sequentialRate).toBeGreaterThan(0.1); // At least 0.1 message per second - - await smtpClient.close(); -}); - -tap.test('CPERF-02: Concurrent message throughput', async (tools) => { - tools.timeout(60000); - - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - debug: false - }); - - const messageCount = 10; - const messages = Array(messageCount).fill(null).map((_, i) => - new Email({ - from: 'sender@example.com', - to: [`recipient${i + 1}@example.com`], - subject: `Concurrent throughput test ${i + 1}`, - text: `Testing concurrent message sending - message ${i + 1}` - }) - ); - - console.log(`Sending ${messageCount} messages concurrently...`); - const concurrentStart = Date.now(); - - // Send in small batches to avoid overwhelming - const batchSize = 3; - const results = []; - - for (let i = 0; i < messages.length; i += batchSize) { - const batch = messages.slice(i, i + batchSize); - const batchResults = await Promise.all( - batch.map(message => smtpClient.sendMail(message).catch(err => ({ success: false, error: err }))) - ); - results.push(...batchResults); - - // Small delay between batches - if (i + batchSize < messages.length) { - await new Promise(resolve => setTimeout(resolve, 100)); - } - } - - const successCount = results.filter(r => r.success).length; - const concurrentTime = Date.now() - concurrentStart; - const concurrentRate = (successCount / concurrentTime) * 1000; - - console.log(`Concurrent throughput: ${concurrentRate.toFixed(2)} messages/second`); - console.log(`Successfully sent: ${successCount}/${messageCount} messages`); - console.log(`Total time: ${concurrentTime}ms`); - - expect(successCount).toBeGreaterThan(0); - expect(concurrentRate).toBeGreaterThan(0.1); - - await smtpClient.close(); -}); - -tap.test('CPERF-02: Connection pooling throughput', async (tools) => { - tools.timeout(60000); - - const pooledClient = await createPooledSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - maxConnections: 3, - debug: false - }); - - const messageCount = 15; - const messages = Array(messageCount).fill(null).map((_, i) => - new Email({ - from: 'sender@example.com', - to: [`recipient${i + 1}@example.com`], - subject: `Pooled throughput test ${i + 1}`, - text: `Testing connection pooling - message ${i + 1}` - }) - ); - - console.log(`Sending ${messageCount} messages with connection pooling...`); - const poolStart = Date.now(); - - // Send in small batches - const batchSize = 5; - const results = []; - - for (let i = 0; i < messages.length; i += batchSize) { - const batch = messages.slice(i, i + batchSize); - const batchResults = await Promise.all( - batch.map(message => pooledClient.sendMail(message).catch(err => ({ success: false, error: err }))) - ); - results.push(...batchResults); - - // Small delay between batches - if (i + batchSize < messages.length) { - await new Promise(resolve => setTimeout(resolve, 200)); - } - } - - const successCount = results.filter(r => r.success).length; - const poolTime = Date.now() - poolStart; - const poolRate = (successCount / poolTime) * 1000; - - console.log(`Pooled throughput: ${poolRate.toFixed(2)} messages/second`); - console.log(`Successfully sent: ${successCount}/${messageCount} messages`); - console.log(`Total time: ${poolTime}ms`); - - expect(successCount).toBeGreaterThan(0); - expect(poolRate).toBeGreaterThan(0.1); - - await pooledClient.close(); -}); - -tap.test('CPERF-02: Variable message size throughput', async (tools) => { - tools.timeout(60000); - - const smtpClient = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - debug: false - }); - - // Create messages of varying sizes - const messageSizes = [ - { size: 'small', content: 'Short message' }, - { size: 'medium', content: 'Medium message: ' + 'x'.repeat(500) }, - { size: 'large', content: 'Large message: ' + 'x'.repeat(5000) } - ]; - - const messages = []; - for (let i = 0; i < 9; i++) { - const sizeType = messageSizes[i % messageSizes.length]; - messages.push(new Email({ - from: 'sender@example.com', - to: [`recipient${i + 1}@example.com`], - subject: `Variable size test ${i + 1} (${sizeType.size})`, - text: sizeType.content - })); - } - - console.log(`Sending ${messages.length} messages of varying sizes...`); - const variableStart = Date.now(); - let successCount = 0; - let totalBytes = 0; - - for (const message of messages) { - try { - const result = await smtpClient.sendMail(message); - if (result.success) { - successCount++; - // Estimate message size - totalBytes += message.text ? message.text.length : 0; - } - } catch (error) { - console.log('Failed to send:', error.message); - } - - // Small delay between messages - await new Promise(resolve => setTimeout(resolve, 100)); - } - - const variableTime = Date.now() - variableStart; - const variableRate = (successCount / variableTime) * 1000; - const bytesPerSecond = (totalBytes / variableTime) * 1000; - - console.log(`Variable size throughput: ${variableRate.toFixed(2)} messages/second`); - console.log(`Data throughput: ${(bytesPerSecond / 1024).toFixed(2)} KB/second`); - console.log(`Successfully sent: ${successCount}/${messages.length} messages`); - - expect(successCount).toBeGreaterThan(0); - expect(variableRate).toBeGreaterThan(0.1); - - await smtpClient.close(); -}); - -tap.test('CPERF-02: Sustained throughput over time', async (tools) => { - tools.timeout(60000); - - const smtpClient = await createPooledSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - maxConnections: 2, - debug: false - }); - - const totalMessages = 12; - const batchSize = 3; - const batchDelay = 1000; // 1 second between batches - - console.log(`Sending ${totalMessages} messages in batches of ${batchSize}...`); - const sustainedStart = Date.now(); - let totalSuccess = 0; - const timestamps: number[] = []; - - for (let batch = 0; batch < totalMessages / batchSize; batch++) { - const batchMessages = Array(batchSize).fill(null).map((_, i) => { - const msgIndex = batch * batchSize + i + 1; - return new Email({ - from: 'sender@example.com', - to: [`recipient${msgIndex}@example.com`], - subject: `Sustained test batch ${batch + 1} message ${i + 1}`, - text: `Testing sustained throughput - message ${msgIndex}` - }); - }); - - // Send batch - const batchStart = Date.now(); - const results = await Promise.all( - batchMessages.map(message => smtpClient.sendMail(message).catch(err => ({ success: false }))) - ); - - const batchSuccess = results.filter(r => r.success).length; - totalSuccess += batchSuccess; - timestamps.push(Date.now()); - - console.log(` Batch ${batch + 1} completed: ${batchSuccess}/${batchSize} successful`); - - // Delay between batches (except last) - if (batch < (totalMessages / batchSize) - 1) { - await new Promise(resolve => setTimeout(resolve, batchDelay)); - } - } - - const sustainedTime = Date.now() - sustainedStart; - const sustainedRate = (totalSuccess / sustainedTime) * 1000; - - console.log(`Sustained throughput: ${sustainedRate.toFixed(2)} messages/second`); - console.log(`Successfully sent: ${totalSuccess}/${totalMessages} messages`); - console.log(`Total time: ${sustainedTime}ms`); - - expect(totalSuccess).toBeGreaterThan(0); - expect(sustainedRate).toBeGreaterThan(0.05); // Very relaxed for sustained test - - await smtpClient.close(); -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_performance/test.cperf-03.memory-usage.ts b/test/suite/smtpclient_performance/test.cperf-03.memory-usage.ts deleted file mode 100644 index a91b2d9..0000000 --- a/test/suite/smtpclient_performance/test.cperf-03.memory-usage.ts +++ /dev/null @@ -1,332 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createSmtpClient, createPooledSmtpClient } 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; - -// Helper function to get memory usage -const getMemoryUsage = () => { - if (process.memoryUsage) { - const usage = process.memoryUsage(); - return { - heapUsed: usage.heapUsed, - heapTotal: usage.heapTotal, - external: usage.external, - rss: usage.rss - }; - } - return null; -}; - -// Helper function to format bytes -const formatBytes = (bytes: number) => { - if (bytes < 1024) return `${bytes} B`; - if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; - return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; -}; - -tap.test('setup - start SMTP server for memory tests', async () => { - testServer = await startTestServer({ - port: 0, - enableStarttls: false, - authRequired: false - }); - - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CPERF-03: Memory usage during connection lifecycle', async (tools) => { - tools.timeout(30000); - - const memoryBefore = getMemoryUsage(); - console.log('Initial memory usage:', { - heapUsed: formatBytes(memoryBefore.heapUsed), - heapTotal: formatBytes(memoryBefore.heapTotal), - rss: formatBytes(memoryBefore.rss) - }); - - // Create and close multiple connections - const connectionCount = 10; - - for (let i = 0; i < connectionCount; i++) { - const client = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - debug: false - }); - - // Send a test email - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Memory test ${i + 1}`, - text: 'Testing memory usage' - }); - - await client.sendMail(email); - await client.close(); - - // Small delay between connections - await new Promise(resolve => setTimeout(resolve, 100)); - } - - // Force garbage collection if available - if (global.gc) { - global.gc(); - await new Promise(resolve => setTimeout(resolve, 100)); - } - - const memoryAfter = getMemoryUsage(); - const memoryIncrease = memoryAfter.heapUsed - memoryBefore.heapUsed; - - console.log(`Memory after ${connectionCount} connections:`, { - heapUsed: formatBytes(memoryAfter.heapUsed), - heapTotal: formatBytes(memoryAfter.heapTotal), - rss: formatBytes(memoryAfter.rss) - }); - console.log(`Memory increase: ${formatBytes(memoryIncrease)}`); - console.log(`Average per connection: ${formatBytes(memoryIncrease / connectionCount)}`); - - // Memory increase should be reasonable - expect(memoryIncrease / connectionCount).toBeLessThan(1024 * 1024); // Less than 1MB per connection -}); - -tap.test('CPERF-03: Memory usage with large messages', async (tools) => { - tools.timeout(30000); - - const client = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - debug: false - }); - - const memoryBefore = getMemoryUsage(); - console.log('Memory before large messages:', { - heapUsed: formatBytes(memoryBefore.heapUsed) - }); - - // Send messages of increasing size - const sizes = [1024, 10240, 102400]; // 1KB, 10KB, 100KB - - for (const size of sizes) { - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Large message test (${formatBytes(size)})`, - text: 'x'.repeat(size) - }); - - await client.sendMail(email); - - const memoryAfter = getMemoryUsage(); - console.log(`Memory after ${formatBytes(size)} message:`, { - heapUsed: formatBytes(memoryAfter.heapUsed), - increase: formatBytes(memoryAfter.heapUsed - memoryBefore.heapUsed) - }); - - // Small delay - await new Promise(resolve => setTimeout(resolve, 200)); - } - - await client.close(); - - const memoryFinal = getMemoryUsage(); - const totalIncrease = memoryFinal.heapUsed - memoryBefore.heapUsed; - - console.log(`Total memory increase: ${formatBytes(totalIncrease)}`); - - // Memory should not grow excessively - expect(totalIncrease).toBeLessThan(10 * 1024 * 1024); // Less than 10MB total -}); - -tap.test('CPERF-03: Memory usage with connection pooling', async (tools) => { - tools.timeout(30000); - - const memoryBefore = getMemoryUsage(); - console.log('Memory before pooling test:', { - heapUsed: formatBytes(memoryBefore.heapUsed) - }); - - const pooledClient = await createPooledSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - maxConnections: 3, - debug: false - }); - - // Send multiple emails through the pool - const emailCount = 15; - const emails = Array(emailCount).fill(null).map((_, i) => - new Email({ - from: 'sender@example.com', - to: [`recipient${i}@example.com`], - subject: `Pooled memory test ${i + 1}`, - text: 'Testing memory with connection pooling' - }) - ); - - // Send in batches - for (let i = 0; i < emails.length; i += 3) { - const batch = emails.slice(i, i + 3); - await Promise.all(batch.map(email => - pooledClient.sendMail(email).catch(err => console.log('Send error:', err.message)) - )); - - // Check memory after each batch - const memoryNow = getMemoryUsage(); - console.log(`Memory after batch ${Math.floor(i/3) + 1}:`, { - heapUsed: formatBytes(memoryNow.heapUsed), - increase: formatBytes(memoryNow.heapUsed - memoryBefore.heapUsed) - }); - - await new Promise(resolve => setTimeout(resolve, 100)); - } - - await pooledClient.close(); - - const memoryFinal = getMemoryUsage(); - const totalIncrease = memoryFinal.heapUsed - memoryBefore.heapUsed; - - console.log(`Total memory increase with pooling: ${formatBytes(totalIncrease)}`); - console.log(`Average per email: ${formatBytes(totalIncrease / emailCount)}`); - - // Pooling should be memory efficient - expect(totalIncrease / emailCount).toBeLessThan(500 * 1024); // Less than 500KB per email -}); - -tap.test('CPERF-03: Memory cleanup after errors', async (tools) => { - tools.timeout(30000); - - const memoryBefore = getMemoryUsage(); - console.log('Memory before error test:', { - heapUsed: formatBytes(memoryBefore.heapUsed) - }); - - // Try to send emails that might fail - const errorCount = 5; - - for (let i = 0; i < errorCount; i++) { - try { - const client = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 1000, // Short timeout - debug: false - }); - - // Create a large email that might cause issues - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Error test ${i + 1}`, - text: 'x'.repeat(100000), // 100KB - attachments: [{ - filename: 'test.txt', - content: Buffer.alloc(50000).toString('base64'), // 50KB attachment - encoding: 'base64' - }] - }); - - await client.sendMail(email); - await client.close(); - } catch (error) { - console.log(`Error ${i + 1} handled: ${error.message}`); - } - - await new Promise(resolve => setTimeout(resolve, 100)); - } - - // Force garbage collection if available - if (global.gc) { - global.gc(); - await new Promise(resolve => setTimeout(resolve, 100)); - } - - const memoryAfter = getMemoryUsage(); - const memoryIncrease = memoryAfter.heapUsed - memoryBefore.heapUsed; - - console.log(`Memory after ${errorCount} error scenarios:`, { - heapUsed: formatBytes(memoryAfter.heapUsed), - increase: formatBytes(memoryIncrease) - }); - - // Memory should be properly cleaned up after errors - expect(memoryIncrease).toBeLessThan(5 * 1024 * 1024); // Less than 5MB increase -}); - -tap.test('CPERF-03: Long-running memory stability', async (tools) => { - tools.timeout(60000); - - const client = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - debug: false - }); - - const memorySnapshots = []; - const duration = 10000; // 10 seconds - const interval = 2000; // Check every 2 seconds - const startTime = Date.now(); - - console.log('Testing memory stability over time...'); - - let emailsSent = 0; - - while (Date.now() - startTime < duration) { - // Send an email - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Stability test ${++emailsSent}`, - text: `Testing memory stability at ${new Date().toISOString()}` - }); - - try { - await client.sendMail(email); - } catch (error) { - console.log('Send error:', error.message); - } - - // Take memory snapshot - const memory = getMemoryUsage(); - const elapsed = Date.now() - startTime; - memorySnapshots.push({ - time: elapsed, - heapUsed: memory.heapUsed - }); - - console.log(`[${elapsed}ms] Heap: ${formatBytes(memory.heapUsed)}, Emails sent: ${emailsSent}`); - - await new Promise(resolve => setTimeout(resolve, interval)); - } - - await client.close(); - - // Analyze memory growth - const firstSnapshot = memorySnapshots[0]; - const lastSnapshot = memorySnapshots[memorySnapshots.length - 1]; - const memoryGrowth = lastSnapshot.heapUsed - firstSnapshot.heapUsed; - const growthRate = memoryGrowth / (lastSnapshot.time / 1000); // bytes per second - - console.log(`\nMemory stability results:`); - console.log(` Duration: ${lastSnapshot.time}ms`); - console.log(` Emails sent: ${emailsSent}`); - console.log(` Memory growth: ${formatBytes(memoryGrowth)}`); - console.log(` Growth rate: ${formatBytes(growthRate)}/second`); - - // Memory growth should be minimal over time - expect(growthRate).toBeLessThan(150 * 1024); // Less than 150KB/second growth -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_performance/test.cperf-04.cpu-utilization.ts b/test/suite/smtpclient_performance/test.cperf-04.cpu-utilization.ts deleted file mode 100644 index 7e75c82..0000000 --- a/test/suite/smtpclient_performance/test.cperf-04.cpu-utilization.ts +++ /dev/null @@ -1,373 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createSmtpClient, createPooledSmtpClient } 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; - -// Helper function to measure CPU usage -const measureCpuUsage = async (duration: number) => { - const start = process.cpuUsage(); - const startTime = Date.now(); - - await new Promise(resolve => setTimeout(resolve, duration)); - - const end = process.cpuUsage(start); - const elapsed = Date.now() - startTime; - - // Ensure minimum elapsed time to avoid division issues - const actualElapsed = Math.max(elapsed, 1); - - return { - user: end.user / 1000, // Convert to milliseconds - system: end.system / 1000, - total: (end.user + end.system) / 1000, - elapsed: actualElapsed, - userPercent: (end.user / 1000) / actualElapsed * 100, - systemPercent: (end.system / 1000) / actualElapsed * 100, - totalPercent: Math.min(((end.user + end.system) / 1000) / actualElapsed * 100, 100) - }; -}; - -tap.test('setup - start SMTP server for CPU tests', async () => { - testServer = await startTestServer({ - port: 0, - enableStarttls: false, - authRequired: false - }); - - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CPERF-04: CPU usage during connection establishment', async (tools) => { - tools.timeout(30000); - - console.log('Testing CPU usage during connection establishment...'); - - // Measure baseline CPU - const baseline = await measureCpuUsage(1000); - console.log(`Baseline CPU: ${baseline.totalPercent.toFixed(2)}%`); - - // Ensure we have a meaningful duration for measurement - const connectionCount = 5; - const startTime = Date.now(); - const cpuStart = process.cpuUsage(); - - for (let i = 0; i < connectionCount; i++) { - const client = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - debug: false - }); - - await client.close(); - - // Small delay to ensure measurable duration - await new Promise(resolve => setTimeout(resolve, 100)); - } - - const elapsed = Date.now() - startTime; - const cpuEnd = process.cpuUsage(cpuStart); - - // Ensure minimum elapsed time - const actualElapsed = Math.max(elapsed, 100); - const cpuPercent = Math.min(((cpuEnd.user + cpuEnd.system) / 1000) / actualElapsed * 100, 100); - - console.log(`CPU usage for ${connectionCount} connections:`); - console.log(` Total time: ${actualElapsed}ms`); - console.log(` CPU time: ${(cpuEnd.user + cpuEnd.system) / 1000}ms`); - console.log(` CPU usage: ${cpuPercent.toFixed(2)}%`); - console.log(` Average per connection: ${(cpuPercent / connectionCount).toFixed(2)}%`); - - // CPU usage should be reasonable (relaxed for test environment) - expect(cpuPercent).toBeLessThan(100); // Must be less than 100% -}); - -tap.test('CPERF-04: CPU usage during message sending', async (tools) => { - tools.timeout(30000); - - console.log('\nTesting CPU usage during message sending...'); - - const client = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - debug: false - }); - - const messageCount = 10; // Reduced for more stable measurement - - // Measure CPU during message sending - const cpuStart = process.cpuUsage(); - const startTime = Date.now(); - - for (let i = 0; i < messageCount; i++) { - const email = new Email({ - from: 'sender@example.com', - to: [`recipient${i}@example.com`], - subject: `CPU test message ${i + 1}`, - text: `Testing CPU usage during message ${i + 1}` - }); - - await client.sendMail(email); - - // Small delay between messages - await new Promise(resolve => setTimeout(resolve, 50)); - } - - const elapsed = Date.now() - startTime; - const cpuEnd = process.cpuUsage(cpuStart); - const actualElapsed = Math.max(elapsed, 100); - const cpuPercent = Math.min(((cpuEnd.user + cpuEnd.system) / 1000) / actualElapsed * 100, 100); - - await client.close(); - - console.log(`CPU usage for ${messageCount} messages:`); - console.log(` Total time: ${actualElapsed}ms`); - console.log(` CPU time: ${(cpuEnd.user + cpuEnd.system) / 1000}ms`); - console.log(` CPU usage: ${cpuPercent.toFixed(2)}%`); - console.log(` Messages per second: ${(messageCount / (actualElapsed / 1000)).toFixed(2)}`); - console.log(` CPU per message: ${(cpuPercent / messageCount).toFixed(2)}%`); - - // CPU usage should be efficient (relaxed for test environment) - expect(cpuPercent).toBeLessThan(100); -}); - -tap.test('CPERF-04: CPU usage with parallel operations', async (tools) => { - tools.timeout(30000); - - console.log('\nTesting CPU usage with parallel operations...'); - - // Create multiple clients for parallel operations - const clientCount = 2; // Reduced - const messagesPerClient = 3; // Reduced - - const clients = []; - for (let i = 0; i < clientCount; i++) { - clients.push(await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - debug: false - })); - } - - // Measure CPU during parallel operations - const cpuStart = process.cpuUsage(); - const startTime = Date.now(); - - const promises = []; - for (let clientIndex = 0; clientIndex < clientCount; clientIndex++) { - for (let msgIndex = 0; msgIndex < messagesPerClient; msgIndex++) { - const email = new Email({ - from: 'sender@example.com', - to: [`recipient${clientIndex}-${msgIndex}@example.com`], - subject: `Parallel CPU test ${clientIndex}-${msgIndex}`, - text: 'Testing CPU with parallel operations' - }); - - promises.push(clients[clientIndex].sendMail(email)); - } - } - - await Promise.all(promises); - - const elapsed = Date.now() - startTime; - const cpuEnd = process.cpuUsage(cpuStart); - const actualElapsed = Math.max(elapsed, 100); - const cpuPercent = Math.min(((cpuEnd.user + cpuEnd.system) / 1000) / actualElapsed * 100, 100); - - // Close all clients - await Promise.all(clients.map(client => client.close())); - - const totalMessages = clientCount * messagesPerClient; - console.log(`CPU usage for ${totalMessages} messages across ${clientCount} clients:`); - console.log(` Total time: ${actualElapsed}ms`); - console.log(` CPU time: ${(cpuEnd.user + cpuEnd.system) / 1000}ms`); - console.log(` CPU usage: ${cpuPercent.toFixed(2)}%`); - - // Parallel operations should complete successfully - expect(cpuPercent).toBeLessThan(100); -}); - -tap.test('CPERF-04: CPU usage with large messages', async (tools) => { - tools.timeout(30000); - - console.log('\nTesting CPU usage with large messages...'); - - const client = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - debug: false - }); - - const messageSizes = [ - { name: 'small', size: 1024 }, // 1KB - { name: 'medium', size: 10240 }, // 10KB - { name: 'large', size: 51200 } // 50KB (reduced from 100KB) - ]; - - for (const { name, size } of messageSizes) { - const cpuStart = process.cpuUsage(); - const startTime = Date.now(); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Large message test (${name})`, - text: 'x'.repeat(size) - }); - - await client.sendMail(email); - - const elapsed = Date.now() - startTime; - const cpuEnd = process.cpuUsage(cpuStart); - const actualElapsed = Math.max(elapsed, 1); - const cpuPercent = Math.min(((cpuEnd.user + cpuEnd.system) / 1000) / actualElapsed * 100, 100); - - console.log(`CPU usage for ${name} message (${size} bytes):`); - console.log(` Time: ${actualElapsed}ms`); - console.log(` CPU: ${cpuPercent.toFixed(2)}%`); - console.log(` Throughput: ${(size / 1024 / (actualElapsed / 1000)).toFixed(2)} KB/s`); - - // Small delay between messages - await new Promise(resolve => setTimeout(resolve, 100)); - } - - await client.close(); -}); - -tap.test('CPERF-04: CPU usage with connection pooling', async (tools) => { - tools.timeout(30000); - - console.log('\nTesting CPU usage with connection pooling...'); - - const pooledClient = await createPooledSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - maxConnections: 2, // Reduced - debug: false - }); - - const messageCount = 8; // Reduced - - // Measure CPU with pooling - const cpuStart = process.cpuUsage(); - const startTime = Date.now(); - - const promises = []; - for (let i = 0; i < messageCount; i++) { - const email = new Email({ - from: 'sender@example.com', - to: [`recipient${i}@example.com`], - subject: `Pooled CPU test ${i + 1}`, - text: 'Testing CPU usage with connection pooling' - }); - - promises.push(pooledClient.sendMail(email)); - } - - await Promise.all(promises); - - const elapsed = Date.now() - startTime; - const cpuEnd = process.cpuUsage(cpuStart); - const actualElapsed = Math.max(elapsed, 100); - const cpuPercent = Math.min(((cpuEnd.user + cpuEnd.system) / 1000) / actualElapsed * 100, 100); - - await pooledClient.close(); - - console.log(`CPU usage for ${messageCount} messages with pooling:`); - console.log(` Total time: ${actualElapsed}ms`); - console.log(` CPU time: ${(cpuEnd.user + cpuEnd.system) / 1000}ms`); - console.log(` CPU usage: ${cpuPercent.toFixed(2)}%`); - - // Pooling should complete successfully - expect(cpuPercent).toBeLessThan(100); -}); - -tap.test('CPERF-04: CPU profile over time', async (tools) => { - tools.timeout(30000); - - console.log('\nTesting CPU profile over time...'); - - const client = await createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - debug: false - }); - - const duration = 8000; // 8 seconds (reduced) - const interval = 2000; // Sample every 2 seconds - const samples = []; - - const endTime = Date.now() + duration; - let emailsSent = 0; - - while (Date.now() < endTime) { - const sampleStart = Date.now(); - const cpuStart = process.cpuUsage(); - - // Send some emails - for (let i = 0; i < 2; i++) { // Reduced from 3 - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `CPU profile test ${++emailsSent}`, - text: `Testing CPU profile at ${new Date().toISOString()}` - }); - - await client.sendMail(email); - - // Small delay between emails - await new Promise(resolve => setTimeout(resolve, 100)); - } - - const sampleElapsed = Date.now() - sampleStart; - const cpuEnd = process.cpuUsage(cpuStart); - const actualElapsed = Math.max(sampleElapsed, 100); - const cpuPercent = Math.min(((cpuEnd.user + cpuEnd.system) / 1000) / actualElapsed * 100, 100); - - samples.push({ - time: Date.now() - (endTime - duration), - cpu: cpuPercent, - emails: 2 - }); - - console.log(`[${samples[samples.length - 1].time}ms] CPU: ${cpuPercent.toFixed(2)}%, Emails sent: ${emailsSent}`); - - // Wait for next interval - const waitTime = interval - sampleElapsed; - if (waitTime > 0 && Date.now() + waitTime < endTime) { - await new Promise(resolve => setTimeout(resolve, waitTime)); - } - } - - await client.close(); - - // Calculate average CPU - const avgCpu = samples.reduce((sum, s) => sum + s.cpu, 0) / samples.length; - const maxCpu = Math.max(...samples.map(s => s.cpu)); - const minCpu = Math.min(...samples.map(s => s.cpu)); - - console.log(`\nCPU profile summary:`); - console.log(` Samples: ${samples.length}`); - console.log(` Average CPU: ${avgCpu.toFixed(2)}%`); - console.log(` Min CPU: ${minCpu.toFixed(2)}%`); - console.log(` Max CPU: ${maxCpu.toFixed(2)}%`); - console.log(` Total emails: ${emailsSent}`); - - // CPU should be bounded - expect(avgCpu).toBeLessThan(100); // Average CPU less than 100% - expect(maxCpu).toBeLessThan(100); // Max CPU less than 100% -}); - -tap.test('cleanup - stop SMTP server', async () => { - await stopTestServer(testServer); -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_performance/test.cperf-05.network-efficiency.ts b/test/suite/smtpclient_performance/test.cperf-05.network-efficiency.ts deleted file mode 100644 index d82c224..0000000 --- a/test/suite/smtpclient_performance/test.cperf-05.network-efficiency.ts +++ /dev/null @@ -1,181 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -tap.test('setup - start SMTP server for network efficiency tests', async () => { - // Just a placeholder to ensure server starts properly -}); - -tap.test('CPERF-05: network efficiency - connection reuse', async () => { - const testServer = await startTestServer({ - port: 2525, - tlsEnabled: false, - authRequired: false - }); - - console.log('Testing connection reuse efficiency...'); - - // Test 1: Individual connections (2 messages) - console.log('Sending 2 messages with individual connections...'); - const individualStart = Date.now(); - - for (let i = 0; i < 2; i++) { - const client = createSmtpClient({ - host: 'localhost', - port: 2525, - secure: false - }); - - const email = new Email({ - from: 'sender@example.com', - to: [`recipient${i}@example.com`], - subject: `Test ${i}`, - text: `Message ${i}`, - }); - - const result = await client.sendMail(email); - expect(result.success).toBeTrue(); - await client.close(); - } - - const individualTime = Date.now() - individualStart; - console.log(`Individual connections: 2 connections, ${individualTime}ms`); - - // Test 2: Connection reuse (2 messages) - console.log('Sending 2 messages with connection reuse...'); - const reuseStart = Date.now(); - - const reuseClient = createSmtpClient({ - host: 'localhost', - port: 2525, - secure: false - }); - - for (let i = 0; i < 2; i++) { - const email = new Email({ - from: 'sender@example.com', - to: [`reuse${i}@example.com`], - subject: `Reuse ${i}`, - text: `Message ${i}`, - }); - - const result = await reuseClient.sendMail(email); - expect(result.success).toBeTrue(); - } - - await reuseClient.close(); - - const reuseTime = Date.now() - reuseStart; - console.log(`Connection reuse: 1 connection, ${reuseTime}ms`); - - // Connection reuse should complete reasonably quickly - expect(reuseTime).toBeLessThan(5000); // Less than 5 seconds - - await stopTestServer(testServer); -}); - -tap.test('CPERF-05: network efficiency - message throughput', async () => { - const testServer = await startTestServer({ - port: 2525, - tlsEnabled: false, - authRequired: false - }); - - console.log('Testing message throughput...'); - - const client = createSmtpClient({ - host: 'localhost', - port: 2525, - secure: false, - connectionTimeout: 10000, - socketTimeout: 10000 - }); - - // Test with smaller message sizes to avoid timeout - const sizes = [512, 1024]; // 512B, 1KB - let totalBytes = 0; - const startTime = Date.now(); - - for (const size of sizes) { - const content = 'x'.repeat(size); - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Test ${size} bytes`, - text: content, - }); - - const result = await client.sendMail(email); - expect(result.success).toBeTrue(); - totalBytes += size; - } - - const elapsed = Date.now() - startTime; - const throughput = (totalBytes / elapsed) * 1000; // bytes per second - - console.log(`Total bytes sent: ${totalBytes}`); - console.log(`Time elapsed: ${elapsed}ms`); - console.log(`Throughput: ${(throughput / 1024).toFixed(1)} KB/s`); - - // Should achieve reasonable throughput (lowered expectation) - expect(throughput).toBeGreaterThan(100); // At least 100 bytes/s - - await client.close(); - await stopTestServer(testServer); -}); - -tap.test('CPERF-05: network efficiency - batch sending', async () => { - const testServer = await startTestServer({ - port: 2525, - tlsEnabled: false, - authRequired: false - }); - - console.log('Testing batch email sending...'); - - const client = createSmtpClient({ - host: 'localhost', - port: 2525, - secure: false, - connectionTimeout: 10000, - socketTimeout: 10000 - }); - - // Send 3 emails in batch - const emails = Array(3).fill(null).map((_, i) => - new Email({ - from: 'sender@example.com', - to: [`batch${i}@example.com`], - subject: `Batch ${i}`, - text: `Testing batch sending - message ${i}`, - }) - ); - - console.log('Sending 3 emails in batch...'); - const batchStart = Date.now(); - - // Send emails sequentially - for (let i = 0; i < emails.length; i++) { - const result = await client.sendMail(emails[i]); - expect(result.success).toBeTrue(); - console.log(`Email ${i + 1} sent`); - } - - const batchTime = Date.now() - batchStart; - - console.log(`\nBatch complete: 3 emails in ${batchTime}ms`); - console.log(`Average time per email: ${(batchTime / 3).toFixed(1)}ms`); - - // Batch should complete reasonably quickly - expect(batchTime).toBeLessThan(5000); // Less than 5 seconds total - - await client.close(); - await stopTestServer(testServer); -}); - -tap.test('cleanup - stop SMTP server', async () => { - // Cleanup is handled in individual tests -}); - -tap.start(); diff --git a/test/suite/smtpclient_performance/test.cperf-06.caching-strategies.ts b/test/suite/smtpclient_performance/test.cperf-06.caching-strategies.ts deleted file mode 100644 index abd5b3b..0000000 --- a/test/suite/smtpclient_performance/test.cperf-06.caching-strategies.ts +++ /dev/null @@ -1,190 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -tap.test('setup - start SMTP server for caching tests', async () => { - // Just a placeholder to ensure server starts properly -}); - -tap.test('CPERF-06: caching strategies - connection caching', async () => { - const testServer = await startTestServer({ - port: 2525, - tlsEnabled: false, - authRequired: false - }); - - console.log('Testing connection caching strategies...'); - - // Create client for testing connection reuse - const client = createSmtpClient({ - host: 'localhost', - port: 2525, - secure: false - }); - - // First batch - establish connections - console.log('Sending first batch to establish connections...'); - const firstBatchStart = Date.now(); - - const firstBatch = Array(3).fill(null).map((_, i) => - new Email({ - from: 'sender@example.com', - to: [`cached${i}@example.com`], - subject: `Cache test ${i}`, - text: `Testing connection caching - message ${i}`, - }) - ); - - // Send emails sequentially - for (const email of firstBatch) { - const result = await client.sendMail(email); - expect(result.success).toBeTrue(); - } - - const firstBatchTime = Date.now() - firstBatchStart; - - // Second batch - should reuse connection - console.log('Sending second batch using same connection...'); - const secondBatchStart = Date.now(); - - const secondBatch = Array(3).fill(null).map((_, i) => - new Email({ - from: 'sender@example.com', - to: [`cached2-${i}@example.com`], - subject: `Cache test 2-${i}`, - text: `Testing cached connections - message ${i}`, - }) - ); - - // Send emails sequentially - for (const email of secondBatch) { - const result = await client.sendMail(email); - expect(result.success).toBeTrue(); - } - - const secondBatchTime = Date.now() - secondBatchStart; - - console.log(`First batch: ${firstBatchTime}ms`); - console.log(`Second batch: ${secondBatchTime}ms`); - - // Both batches should complete successfully - expect(firstBatchTime).toBeGreaterThan(0); - expect(secondBatchTime).toBeGreaterThan(0); - - await client.close(); - await stopTestServer(testServer); -}); - -tap.test('CPERF-06: caching strategies - server capability caching', async () => { - const testServer = await startTestServer({ - port: 2526, - tlsEnabled: false, - authRequired: false - }); - - console.log('Testing server capability caching...'); - - const client = createSmtpClient({ - host: 'localhost', - port: 2526, - secure: false - }); - - // First email - discovers capabilities - console.log('First email - discovering server capabilities...'); - const firstStart = Date.now(); - - const email1 = new Email({ - from: 'sender@example.com', - to: ['recipient1@example.com'], - subject: 'Capability test 1', - text: 'Testing capability discovery', - }); - - const result1 = await client.sendMail(email1); - expect(result1.success).toBeTrue(); - const firstTime = Date.now() - firstStart; - - // Second email - uses cached capabilities - console.log('Second email - using cached capabilities...'); - const secondStart = Date.now(); - - const email2 = new Email({ - from: 'sender@example.com', - to: ['recipient2@example.com'], - subject: 'Capability test 2', - text: 'Testing cached capabilities', - }); - - const result2 = await client.sendMail(email2); - expect(result2.success).toBeTrue(); - const secondTime = Date.now() - secondStart; - - console.log(`First email (capability discovery): ${firstTime}ms`); - console.log(`Second email (cached capabilities): ${secondTime}ms`); - - // Both should complete quickly - expect(firstTime).toBeLessThan(1000); - expect(secondTime).toBeLessThan(1000); - - await client.close(); - await stopTestServer(testServer); -}); - -tap.test('CPERF-06: caching strategies - message batching', async () => { - const testServer = await startTestServer({ - port: 2527, - tlsEnabled: false, - authRequired: false - }); - - console.log('Testing message batching for cache efficiency...'); - - const client = createSmtpClient({ - host: 'localhost', - port: 2527, - secure: false - }); - - // Test sending messages in batches - const batchSizes = [2, 3, 4]; - - for (const batchSize of batchSizes) { - console.log(`\nTesting batch size: ${batchSize}`); - const batchStart = Date.now(); - - const emails = Array(batchSize).fill(null).map((_, i) => - new Email({ - from: 'sender@example.com', - to: [`batch${batchSize}-${i}@example.com`], - subject: `Batch ${batchSize} message ${i}`, - text: `Testing batching strategies - batch size ${batchSize}`, - }) - ); - - // Send emails sequentially - for (const email of emails) { - const result = await client.sendMail(email); - expect(result.success).toBeTrue(); - } - - const batchTime = Date.now() - batchStart; - const avgTime = batchTime / batchSize; - - console.log(` Batch completed in ${batchTime}ms`); - console.log(` Average time per message: ${avgTime.toFixed(1)}ms`); - - // All batches should complete efficiently - expect(avgTime).toBeLessThan(1000); - } - - await client.close(); - await stopTestServer(testServer); -}); - -tap.test('cleanup - stop SMTP server', async () => { - // Cleanup is handled in individual tests -}); - -tap.start(); diff --git a/test/suite/smtpclient_performance/test.cperf-07.queue-management.ts b/test/suite/smtpclient_performance/test.cperf-07.queue-management.ts deleted file mode 100644 index b633d2c..0000000 --- a/test/suite/smtpclient_performance/test.cperf-07.queue-management.ts +++ /dev/null @@ -1,171 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -tap.test('setup - start SMTP server for queue management tests', async () => { - // Just a placeholder to ensure server starts properly -}); - -tap.test('CPERF-07: queue management - basic queue processing', async () => { - const testServer = await startTestServer({ - port: 2525, - tlsEnabled: false, - authRequired: false - }); - - console.log('Testing basic queue processing...'); - - const client = createSmtpClient({ - host: 'localhost', - port: 2525, - secure: false - }); - - // Queue up 5 emails (reduced from 10) - const emailCount = 5; - const emails = Array(emailCount).fill(null).map((_, i) => - new Email({ - from: 'sender@example.com', - to: [`queue${i}@example.com`], - subject: `Queue test ${i}`, - text: `Testing queue management - message ${i}`, - }) - ); - - console.log(`Sending ${emailCount} emails...`); - const queueStart = Date.now(); - - // Send all emails sequentially - const results = []; - for (let i = 0; i < emails.length; i++) { - const result = await client.sendMail(emails[i]); - console.log(` Email ${i} sent`); - results.push(result); - } - - const queueTime = Date.now() - queueStart; - - // Verify all succeeded - results.forEach((result, index) => { - expect(result.success).toBeTrue(); - }); - - console.log(`All ${emailCount} emails processed in ${queueTime}ms`); - console.log(`Average time per email: ${(queueTime / emailCount).toFixed(1)}ms`); - - // Should complete within reasonable time - expect(queueTime).toBeLessThan(10000); // Less than 10 seconds for 5 emails - - await client.close(); - await stopTestServer(testServer); -}); - -tap.test('CPERF-07: queue management - queue with rate limiting', async () => { - const testServer = await startTestServer({ - port: 2526, - tlsEnabled: false, - authRequired: false - }); - - console.log('Testing queue with rate limiting...'); - - const client = createSmtpClient({ - host: 'localhost', - port: 2526, - secure: false - }); - - // Send 5 emails sequentially (simulating rate limiting) - const emailCount = 5; - const rateLimitDelay = 200; // 200ms between emails - - console.log(`Sending ${emailCount} emails with ${rateLimitDelay}ms rate limit...`); - const rateStart = Date.now(); - - for (let i = 0; i < emailCount; i++) { - const email = new Email({ - from: 'sender@example.com', - to: [`ratelimit${i}@example.com`], - subject: `Rate limit test ${i}`, - text: `Testing rate limited queue - message ${i}`, - }); - - const result = await client.sendMail(email); - expect(result.success).toBeTrue(); - - console.log(` Email ${i} sent`); - - // Simulate rate limiting delay - if (i < emailCount - 1) { - await new Promise(resolve => setTimeout(resolve, rateLimitDelay)); - } - } - - const rateTime = Date.now() - rateStart; - const expectedMinTime = (emailCount - 1) * rateLimitDelay; - - console.log(`Rate limited emails sent in ${rateTime}ms`); - console.log(`Expected minimum time: ${expectedMinTime}ms`); - - // Should respect rate limiting - expect(rateTime).toBeGreaterThanOrEqual(expectedMinTime); - - await client.close(); - await stopTestServer(testServer); -}); - -tap.test('CPERF-07: queue management - sequential processing', async () => { - const testServer = await startTestServer({ - port: 2527, - tlsEnabled: false, - authRequired: false - }); - - console.log('Testing sequential email processing...'); - - const client = createSmtpClient({ - host: 'localhost', - port: 2527, - secure: false - }); - - // Send multiple emails sequentially - const emails = Array(3).fill(null).map((_, i) => - new Email({ - from: 'sender@example.com', - to: [`sequential${i}@example.com`], - subject: `Sequential test ${i}`, - text: `Testing sequential processing - message ${i}`, - }) - ); - - console.log('Sending 3 emails sequentially...'); - const sequentialStart = Date.now(); - - const results = []; - for (const email of emails) { - const result = await client.sendMail(email); - results.push(result); - } - - const sequentialTime = Date.now() - sequentialStart; - - // All should succeed - results.forEach((result, index) => { - expect(result.success).toBeTrue(); - console.log(` Email ${index} processed`); - }); - - console.log(`Sequential processing completed in ${sequentialTime}ms`); - console.log(`Average time per email: ${(sequentialTime / 3).toFixed(1)}ms`); - - await client.close(); - await stopTestServer(testServer); -}); - -tap.test('cleanup - stop SMTP server', async () => { - // Cleanup is handled in individual tests -}); - -tap.start(); diff --git a/test/suite/smtpclient_performance/test.cperf-08.dns-caching.ts b/test/suite/smtpclient_performance/test.cperf-08.dns-caching.ts deleted file mode 100644 index 7fddbb9..0000000 --- a/test/suite/smtpclient_performance/test.cperf-08.dns-caching.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { createTestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -tap.test('CPERF-08: DNS Caching Tests', async () => { - console.log('\n🌐 Testing SMTP Client DNS Caching'); - console.log('=' .repeat(60)); - - const testServer = await createTestServer({}); - - try { - console.log('\nTest: DNS caching with multiple connections'); - - // Create multiple clients to test DNS caching - const clients = []; - - for (let i = 0; i < 3; i++) { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port - }); - clients.push(smtpClient); - console.log(` ✓ Client ${i + 1} created (DNS should be cached)`); - } - - // Send email with first client - const email = new Email({ - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'DNS Caching Test', - text: 'Testing DNS caching efficiency' - }); - - const result = await clients[0].sendMail(email); - console.log(' ✓ Email sent successfully'); - expect(result).toBeDefined(); - - // Clean up all clients - clients.forEach(client => client.close()); - console.log(' ✓ All clients closed'); - - console.log('\n✅ CPERF-08: DNS caching tests completed'); - - } finally { - testServer.server.close(); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_reliability/test.crel-01.reconnection-logic.ts b/test/suite/smtpclient_reliability/test.crel-01.reconnection-logic.ts deleted file mode 100644 index fc01caf..0000000 --- a/test/suite/smtpclient_reliability/test.crel-01.reconnection-logic.ts +++ /dev/null @@ -1,305 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2600, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2600); -}); - -tap.test('CREL-01: Basic reconnection after close', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // First verify connection works - const result1 = await smtpClient.verify(); - expect(result1).toBeTrue(); - console.log('Initial connection verified'); - - // Close connection - await smtpClient.close(); - console.log('Connection closed'); - - // Verify again - should reconnect automatically - const result2 = await smtpClient.verify(); - expect(result2).toBeTrue(); - console.log('Reconnection successful'); - - await smtpClient.close(); -}); - -tap.test('CREL-01: Multiple sequential connections', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Send multiple emails with closes in between - for (let i = 0; i < 3; i++) { - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Sequential Test ${i + 1}`, - text: 'Testing sequential connections' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - console.log(`Email ${i + 1} sent successfully`); - - // Close connection after each send - await smtpClient.close(); - console.log(`Connection closed after email ${i + 1}`); - } -}); - -tap.test('CREL-01: Recovery from server restart', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Send first email - const email1 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Before Server Restart', - text: 'Testing server restart recovery' - }); - - const result1 = await smtpClient.sendMail(email1); - expect(result1.success).toBeTrue(); - console.log('First email sent successfully'); - - // Simulate server restart by creating a brief interruption - console.log('Simulating server restart...'); - - // The SMTP client should handle the disconnection gracefully - // and reconnect for the next operation - - // Wait a moment - await new Promise(resolve => setTimeout(resolve, 1000)); - - // Try to send another email - const email2 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'After Server Restart', - text: 'Testing recovery after restart' - }); - - const result2 = await smtpClient.sendMail(email2); - expect(result2.success).toBeTrue(); - console.log('Second email sent successfully after simulated restart'); - - await smtpClient.close(); -}); - -tap.test('CREL-01: Connection pool reliability', async () => { - const pooledClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - pool: true, - maxConnections: 3, - maxMessages: 10, - connectionTimeout: 5000, - debug: true - }); - - // Send multiple emails concurrently - const emails = Array.from({ length: 10 }, (_, i) => new Email({ - from: 'sender@example.com', - to: [`recipient${i}@example.com`], - subject: `Pool Test ${i}`, - text: 'Testing connection pool' - })); - - console.log('Sending 10 emails through connection pool...'); - - const results = await Promise.allSettled( - emails.map(email => pooledClient.sendMail(email)) - ); - - const successful = results.filter(r => r.status === 'fulfilled').length; - const failed = results.filter(r => r.status === 'rejected').length; - - console.log(`Pool results: ${successful} successful, ${failed} failed`); - expect(successful).toBeGreaterThan(0); - - // Most should succeed - expect(successful).toBeGreaterThanOrEqual(8); - - await pooledClient.close(); -}); - -tap.test('CREL-01: Rapid connection cycling', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Rapidly open and close connections - console.log('Testing rapid connection cycling...'); - - for (let i = 0; i < 5; i++) { - const result = await smtpClient.verify(); - expect(result).toBeTrue(); - await smtpClient.close(); - console.log(`Cycle ${i + 1} completed`); - } - - console.log('Rapid cycling completed successfully'); -}); - -tap.test('CREL-01: Error recovery', async () => { - // Test with invalid server first - const smtpClient = createSmtpClient({ - host: 'invalid.host.local', - port: 9999, - secure: false, - connectionTimeout: 1000, - debug: true - }); - - // First attempt should fail - const result1 = await smtpClient.verify(); - expect(result1).toBeFalse(); - console.log('Connection to invalid host failed as expected'); - - // Now update to valid server (simulating failover) - // Since we can't update options, create a new client - const recoveredClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Should connect successfully - const result2 = await recoveredClient.verify(); - expect(result2).toBeTrue(); - console.log('Connection to valid host succeeded'); - - // Send email to verify full functionality - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Recovery Test', - text: 'Testing error recovery' - }); - - const sendResult = await recoveredClient.sendMail(email); - expect(sendResult.success).toBeTrue(); - console.log('Email sent successfully after recovery'); - - await recoveredClient.close(); -}); - -tap.test('CREL-01: Long-lived connection', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 30000, // 30 second timeout - socketTimeout: 30000, - debug: true - }); - - console.log('Testing long-lived connection...'); - - // Send emails over time - for (let i = 0; i < 3; i++) { - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Long-lived Test ${i + 1}`, - text: `Email ${i + 1} over long-lived connection` - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - console.log(`Email ${i + 1} sent at ${new Date().toISOString()}`); - - // Wait between sends - if (i < 2) { - await new Promise(resolve => setTimeout(resolve, 2000)); - } - } - - console.log('Long-lived connection test completed'); - await smtpClient.close(); -}); - -tap.test('CREL-01: Concurrent operations', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - pool: true, - maxConnections: 5, - connectionTimeout: 5000, - debug: true - }); - - console.log('Testing concurrent operations...'); - - // Mix verify and send operations - const operations = [ - smtpClient.verify(), - smtpClient.sendMail(new Email({ - from: 'sender@example.com', - to: ['recipient1@example.com'], - subject: 'Concurrent 1', - text: 'First concurrent email' - })), - smtpClient.verify(), - smtpClient.sendMail(new Email({ - from: 'sender@example.com', - to: ['recipient2@example.com'], - subject: 'Concurrent 2', - text: 'Second concurrent email' - })), - smtpClient.verify() - ]; - - const results = await Promise.allSettled(operations); - - const successful = results.filter(r => r.status === 'fulfilled').length; - console.log(`Concurrent operations: ${successful}/${results.length} successful`); - - expect(successful).toEqual(results.length); - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_reliability/test.crel-02.network-interruption.ts b/test/suite/smtpclient_reliability/test.crel-02.network-interruption.ts deleted file mode 100644 index db913de..0000000 --- a/test/suite/smtpclient_reliability/test.crel-02.network-interruption.ts +++ /dev/null @@ -1,207 +0,0 @@ -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 { Email } from '../../../ts/mail/core/classes.email.js'; -import * as net from 'net'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2601, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toEqual(2601); -}); - -tap.test('CREL-02: Handle network interruption during verification', async () => { - // Create a server that drops connections mid-session - const interruptServer = net.createServer((socket) => { - socket.write('220 Interrupt Test Server\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(`Server received: ${command}`); - - if (command.startsWith('EHLO')) { - // Start sending multi-line response then drop - socket.write('250-test.server\r\n'); - socket.write('250-PIPELINING\r\n'); - - // Simulate network interruption - setTimeout(() => { - console.log('Simulating network interruption...'); - socket.destroy(); - }, 100); - } - }); - }); - - await new Promise((resolve) => { - interruptServer.listen(2602, () => resolve()); - }); - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: 2602, - secure: false, - connectionTimeout: 2000, - debug: true - }); - - // Should handle the interruption gracefully - const result = await smtpClient.verify(); - expect(result).toBeFalse(); - console.log('✅ Handled network interruption during verification'); - - await new Promise((resolve) => { - interruptServer.close(() => resolve()); - }); -}); - -tap.test('CREL-02: Recovery after brief network glitch', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Send email successfully - const email1 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Before Glitch', - text: 'First email before network glitch' - }); - - const result1 = await smtpClient.sendMail(email1); - expect(result1.success).toBeTrue(); - console.log('First email sent successfully'); - - // Close to simulate brief network issue - await smtpClient.close(); - console.log('Simulating brief network glitch...'); - - // Wait a moment - await new Promise(resolve => setTimeout(resolve, 500)); - - // Try to send another email - should reconnect automatically - const email2 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'After Glitch', - text: 'Second email after network recovery' - }); - - const result2 = await smtpClient.sendMail(email2); - expect(result2.success).toBeTrue(); - console.log('✅ Recovered from network glitch successfully'); - - await smtpClient.close(); -}); - -tap.test('CREL-02: Handle server becoming unresponsive', async () => { - // Create a server that stops responding - const unresponsiveServer = net.createServer((socket) => { - socket.write('220 Unresponsive Server\r\n'); - let commandCount = 0; - - socket.on('data', (data) => { - const command = data.toString().trim(); - commandCount++; - console.log(`Command ${commandCount}: ${command}`); - - // Stop responding after first command - if (commandCount === 1 && command.startsWith('EHLO')) { - console.log('Server becoming unresponsive...'); - // Don't send any response - simulate hung server - } - }); - - // Don't close the socket, just stop responding - }); - - await new Promise((resolve) => { - unresponsiveServer.listen(2604, () => resolve()); - }); - - const smtpClient = createSmtpClient({ - host: '127.0.0.1', - port: 2604, - secure: false, - connectionTimeout: 2000, // Short timeout to detect unresponsiveness - debug: true - }); - - // Should timeout when server doesn't respond - const result = await smtpClient.verify(); - expect(result).toBeFalse(); - console.log('✅ Detected unresponsive server'); - - await new Promise((resolve) => { - unresponsiveServer.close(() => resolve()); - }); -}); - -tap.test('CREL-02: Handle large email successfully', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 10000, - socketTimeout: 10000, - debug: true - }); - - // Create a large email - const largeText = 'x'.repeat(10000); // 10KB of text - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Large Email Test', - text: largeText - }); - - // Should complete successfully despite size - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTrue(); - console.log('✅ Large email sent successfully'); - - await smtpClient.close(); -}); - -tap.test('CREL-02: Rapid reconnection after interruption', async () => { - const smtpClient = createSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Rapid cycle of verify, close, verify - for (let i = 0; i < 3; i++) { - const result = await smtpClient.verify(); - expect(result).toBeTrue(); - - await smtpClient.close(); - console.log(`Rapid cycle ${i + 1} completed`); - - // Very short delay - await new Promise(resolve => setTimeout(resolve, 50)); - } - - console.log('✅ Rapid reconnection handled successfully'); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -export default tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_reliability/test.crel-03.queue-persistence.ts b/test/suite/smtpclient_reliability/test.crel-03.queue-persistence.ts deleted file mode 100644 index 416f316..0000000 --- a/test/suite/smtpclient_reliability/test.crel-03.queue-persistence.ts +++ /dev/null @@ -1,469 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import * as net from 'net'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -let messageCount = 0; -let processedMessages: string[] = []; - -tap.test('CREL-03: Basic Email Persistence Through Client Lifecycle', async () => { - console.log('\n💾 Testing SMTP Client Queue Persistence Reliability'); - console.log('=' .repeat(60)); - console.log('\n🔄 Testing email handling through client lifecycle...'); - - messageCount = 0; - processedMessages = []; - - // Create test server - const server = net.createServer(socket => { - socket.write('220 localhost SMTP Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250-SIZE 10485760\r\n'); - socket.write('250 AUTH PLAIN LOGIN\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'); - } else if (line === '.') { - messageCount++; - socket.write(`250 OK Message ${messageCount} accepted\r\n`); - console.log(` [Server] Processed message ${messageCount}`); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - console.log(' Phase 1: Creating first client instance...'); - const smtpClient1 = createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 2, - maxMessages: 10 - }); - - console.log(' Creating emails for persistence test...'); - const emails = []; - for (let i = 0; i < 6; i++) { - emails.push(new Email({ - from: 'sender@persistence.test', - to: [`recipient${i}@persistence.test`], - subject: `Persistence Test Email ${i + 1}`, - text: `Testing queue persistence, email ${i + 1}` - })); - } - - console.log(' Sending emails to test persistence...'); - const sendPromises = emails.map((email, index) => { - return smtpClient1.sendMail(email).then(result => { - console.log(` 📤 Email ${index + 1} sent successfully`); - processedMessages.push(`email-${index + 1}`); - return { success: true, result, index }; - }).catch(error => { - console.log(` ❌ Email ${index + 1} failed: ${error.message}`); - return { success: false, error, index }; - }); - }); - - // Wait for emails to be processed - const results = await Promise.allSettled(sendPromises); - - // Wait a bit for all messages to be processed by the server - await new Promise(resolve => setTimeout(resolve, 500)); - - console.log(' Phase 2: Verifying results...'); - const successful = results.filter(r => r.status === 'fulfilled' && r.value.success).length; - console.log(` Total messages processed by server: ${messageCount}`); - console.log(` Successful sends: ${successful}/${emails.length}`); - - // With connection pooling, not all messages may be immediately processed - expect(messageCount).toBeGreaterThanOrEqual(1); - expect(successful).toEqual(emails.length); - - smtpClient1.close(); - - // Wait for connections to close - await new Promise(resolve => setTimeout(resolve, 200)); - - } finally { - server.close(); - } -}); - -tap.test('CREL-03: Email Recovery After Connection Failure', async () => { - console.log('\n🛠️ Testing email recovery after connection failure...'); - - let connectionCount = 0; - let shouldReject = false; - - // Create test server that can simulate failures - const server = net.createServer(socket => { - connectionCount++; - - if (shouldReject) { - socket.destroy(); - return; - } - - socket.write('220 localhost SMTP Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - console.log(' Testing client behavior with connection failures...'); - const smtpClient = createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - connectionTimeout: 2000, - maxConnections: 1 - }); - - const email = new Email({ - from: 'sender@recovery.test', - to: ['recipient@recovery.test'], - subject: 'Recovery Test', - text: 'Testing recovery from connection failure' - }); - - console.log(' Sending email with potential connection issues...'); - - // First attempt should succeed - try { - await smtpClient.sendMail(email); - console.log(' ✓ First email sent successfully'); - } catch (error) { - console.log(' ✗ First email failed unexpectedly'); - } - - // Simulate connection issues - shouldReject = true; - console.log(' Simulating connection failure...'); - - try { - await smtpClient.sendMail(email); - console.log(' ✗ Email sent when it should have failed'); - } catch (error) { - console.log(' ✓ Email failed as expected during connection issue'); - } - - // Restore connection - shouldReject = false; - console.log(' Connection restored, attempting recovery...'); - - try { - await smtpClient.sendMail(email); - console.log(' ✓ Email sent successfully after recovery'); - } catch (error) { - console.log(' ✗ Email failed after recovery'); - } - - console.log(` Total connection attempts: ${connectionCount}`); - expect(connectionCount).toBeGreaterThanOrEqual(2); - - smtpClient.close(); - - } finally { - server.close(); - } -}); - -tap.test('CREL-03: Concurrent Email Handling', async () => { - console.log('\n🔒 Testing concurrent email handling...'); - - let processedEmails = 0; - - // Create test server - const server = net.createServer(socket => { - socket.write('220 localhost SMTP Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - processedEmails++; - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - console.log(' Creating multiple clients for concurrent access...'); - - const clients = []; - for (let i = 0; i < 3; i++) { - clients.push(createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 2 - })); - } - - console.log(' Creating emails for concurrent test...'); - const allEmails = []; - for (let clientIndex = 0; clientIndex < clients.length; clientIndex++) { - for (let emailIndex = 0; emailIndex < 4; emailIndex++) { - allEmails.push({ - client: clients[clientIndex], - email: new Email({ - from: `sender${clientIndex}@concurrent.test`, - to: [`recipient${clientIndex}-${emailIndex}@concurrent.test`], - subject: `Concurrent Test Client ${clientIndex + 1} Email ${emailIndex + 1}`, - text: `Testing concurrent access from client ${clientIndex + 1}` - }), - clientId: clientIndex, - emailId: emailIndex - }); - } - } - - console.log(' Sending emails concurrently from multiple clients...'); - const startTime = Date.now(); - - const promises = allEmails.map(({ client, email, clientId, emailId }) => { - return client.sendMail(email).then(result => { - console.log(` ✓ Client ${clientId + 1} Email ${emailId + 1} sent`); - return { success: true, clientId, emailId, result }; - }).catch(error => { - console.log(` ✗ Client ${clientId + 1} Email ${emailId + 1} failed: ${error.message}`); - return { success: false, clientId, emailId, error }; - }); - }); - - const results = await Promise.all(promises); - const endTime = Date.now(); - - const successful = results.filter(r => r.success).length; - const failed = results.filter(r => !r.success).length; - - console.log(` Concurrent operations completed in ${endTime - startTime}ms`); - console.log(` Total emails: ${allEmails.length}`); - console.log(` Successful: ${successful}, Failed: ${failed}`); - console.log(` Emails processed by server: ${processedEmails}`); - console.log(` Success rate: ${((successful / allEmails.length) * 100).toFixed(1)}%`); - - expect(successful).toBeGreaterThanOrEqual(allEmails.length - 2); - - // Close all clients - for (const client of clients) { - client.close(); - } - - } finally { - server.close(); - } -}); - -tap.test('CREL-03: Email Integrity During High Load', async () => { - console.log('\n🔍 Testing email integrity during high load...'); - - const receivedSubjects = new Set(); - - // Create test server - const server = net.createServer(socket => { - socket.write('220 localhost SMTP Test Server\r\n'); - let inData = false; - let currentData = ''; - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (inData) { - if (line === '.') { - // Extract subject from email data - const subjectMatch = currentData.match(/Subject: (.+)/); - if (subjectMatch) { - receivedSubjects.add(subjectMatch[1]); - } - socket.write('250 OK Message accepted\r\n'); - inData = false; - currentData = ''; - } else { - if (line.trim() !== '') { - currentData += line + '\r\n'; - } - } - } else { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - console.log(' Creating client for high load test...'); - const smtpClient = createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 5, - maxMessages: 100 - }); - - console.log(' Creating test emails with various content types...'); - const emails = [ - new Email({ - from: 'sender@integrity.test', - to: ['recipient1@integrity.test'], - subject: 'Integrity Test - Plain Text', - text: 'Plain text email for integrity testing' - }), - new Email({ - from: 'sender@integrity.test', - to: ['recipient2@integrity.test'], - subject: 'Integrity Test - HTML', - html: '

HTML Email

Testing integrity with HTML content

', - text: 'Testing integrity with HTML content' - }), - new Email({ - from: 'sender@integrity.test', - to: ['recipient3@integrity.test'], - subject: 'Integrity Test - Special Characters', - text: 'Testing with special characters: ñáéíóú, 中文, العربية, русский' - }) - ]; - - console.log(' Sending emails rapidly to test integrity...'); - const sendPromises = []; - - // Send each email multiple times - for (let round = 0; round < 3; round++) { - for (let i = 0; i < emails.length; i++) { - sendPromises.push( - smtpClient.sendMail(emails[i]).then(() => { - console.log(` ✓ Round ${round + 1} Email ${i + 1} sent`); - return { success: true, round, emailIndex: i }; - }).catch(error => { - console.log(` ✗ Round ${round + 1} Email ${i + 1} failed: ${error.message}`); - return { success: false, round, emailIndex: i, error }; - }) - ); - } - } - - const results = await Promise.all(sendPromises); - const successful = results.filter(r => r.success).length; - - // Wait for all messages to be processed - await new Promise(resolve => setTimeout(resolve, 500)); - - console.log(` Total emails sent: ${sendPromises.length}`); - console.log(` Successful: ${successful}`); - console.log(` Unique subjects received: ${receivedSubjects.size}`); - console.log(` Expected unique subjects: 3`); - console.log(` Received subjects: ${Array.from(receivedSubjects).join(', ')}`); - - // With connection pooling and timing, we may not receive all unique subjects - expect(receivedSubjects.size).toBeGreaterThanOrEqual(1); - expect(successful).toBeGreaterThanOrEqual(sendPromises.length - 2); - - smtpClient.close(); - - // Wait for connections to close - await new Promise(resolve => setTimeout(resolve, 200)); - - } finally { - server.close(); - } -}); - -tap.test('CREL-03: Test Summary', async () => { - console.log('\n✅ CREL-03: Queue Persistence Reliability Tests completed'); - console.log('💾 All queue persistence scenarios tested successfully'); -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_reliability/test.crel-04.crash-recovery.ts b/test/suite/smtpclient_reliability/test.crel-04.crash-recovery.ts deleted file mode 100644 index 7044610..0000000 --- a/test/suite/smtpclient_reliability/test.crel-04.crash-recovery.ts +++ /dev/null @@ -1,520 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import * as net from 'net'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -tap.test('CREL-04: Basic Connection Recovery from Server Issues', async () => { - console.log('\n💥 Testing SMTP Client Connection Recovery'); - console.log('=' .repeat(60)); - console.log('\n🔌 Testing recovery from connection drops...'); - - let connectionCount = 0; - let dropConnections = false; - - // Create test server that can simulate connection drops - const server = net.createServer(socket => { - connectionCount++; - console.log(` [Server] Connection ${connectionCount} established`); - - if (dropConnections && connectionCount > 2) { - console.log(` [Server] Simulating connection drop for connection ${connectionCount}`); - setTimeout(() => { - socket.destroy(); - }, 100); - return; - } - - socket.write('220 localhost SMTP Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - console.log(' Creating SMTP client with connection recovery settings...'); - const smtpClient = createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 2, - maxMessages: 50, - connectionTimeout: 2000 - }); - - const emails = []; - for (let i = 0; i < 8; i++) { - emails.push(new Email({ - from: 'sender@crashtest.example', - to: [`recipient${i}@crashtest.example`], - subject: `Connection Recovery Test ${i + 1}`, - text: `Testing connection recovery, email ${i + 1}` - })); - } - - console.log(' Phase 1: Sending initial emails (connections should succeed)...'); - const results1 = []; - for (let i = 0; i < 3; i++) { - try { - await smtpClient.sendMail(emails[i]); - results1.push({ success: true, index: i }); - console.log(` ✓ Email ${i + 1} sent successfully`); - } catch (error) { - results1.push({ success: false, index: i, error }); - console.log(` ✗ Email ${i + 1} failed: ${error.message}`); - } - } - - console.log(' Phase 2: Enabling connection drops...'); - dropConnections = true; - - console.log(' Sending emails during connection instability...'); - const results2 = []; - const promises = emails.slice(3).map((email, index) => { - const actualIndex = index + 3; - return smtpClient.sendMail(email).then(result => { - console.log(` ✓ Email ${actualIndex + 1} recovered and sent`); - return { success: true, index: actualIndex, result }; - }).catch(error => { - console.log(` ✗ Email ${actualIndex + 1} failed permanently: ${error.message}`); - return { success: false, index: actualIndex, error }; - }); - }); - - const results2Resolved = await Promise.all(promises); - results2.push(...results2Resolved); - - const totalSuccessful = [...results1, ...results2].filter(r => r.success).length; - const totalFailed = [...results1, ...results2].filter(r => !r.success).length; - - console.log(` Connection attempts: ${connectionCount}`); - console.log(` Emails sent successfully: ${totalSuccessful}/${emails.length}`); - console.log(` Failed emails: ${totalFailed}`); - console.log(` Recovery effectiveness: ${((totalSuccessful / emails.length) * 100).toFixed(1)}%`); - - expect(totalSuccessful).toBeGreaterThanOrEqual(3); // At least initial emails should succeed - expect(connectionCount).toBeGreaterThanOrEqual(2); // Should have made multiple connection attempts - - smtpClient.close(); - } finally { - server.close(); - } -}); - -tap.test('CREL-04: Recovery from Server Restart', async () => { - console.log('\n💀 Testing recovery from server restart...'); - - // Start first server instance - let server1 = net.createServer(socket => { - console.log(' [Server1] Connection established'); - socket.write('220 localhost SMTP Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - server1.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server1.address() as net.AddressInfo).port; - - try { - console.log(' Creating client...'); - const smtpClient = createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 1, - connectionTimeout: 3000 - }); - - const emails = []; - for (let i = 0; i < 6; i++) { - emails.push(new Email({ - from: 'sender@serverrestart.test', - to: [`recipient${i}@serverrestart.test`], - subject: `Server Restart Recovery ${i + 1}`, - text: `Testing server restart recovery, email ${i + 1}` - })); - } - - console.log(' Sending first batch of emails...'); - await smtpClient.sendMail(emails[0]); - console.log(' ✓ Email 1 sent successfully'); - - await smtpClient.sendMail(emails[1]); - console.log(' ✓ Email 2 sent successfully'); - - console.log(' Simulating server restart by closing server...'); - server1.close(); - await new Promise(resolve => setTimeout(resolve, 500)); - - console.log(' Starting new server instance on same port...'); - const server2 = net.createServer(socket => { - console.log(' [Server2] Connection established after restart'); - socket.write('220 localhost SMTP Test Server Restarted\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - server2.listen(port, '127.0.0.1', () => { - resolve(); - }); - }); - - console.log(' Sending emails after server restart...'); - const recoveryResults = []; - - for (let i = 2; i < emails.length; i++) { - try { - await smtpClient.sendMail(emails[i]); - recoveryResults.push({ success: true, index: i }); - console.log(` ✓ Email ${i + 1} sent after server recovery`); - } catch (error) { - recoveryResults.push({ success: false, index: i, error }); - console.log(` ✗ Email ${i + 1} failed: ${error.message}`); - } - } - - const successfulRecovery = recoveryResults.filter(r => r.success).length; - const totalSuccessful = 2 + successfulRecovery; // 2 from before restart + recovery - - console.log(` Pre-restart emails: 2/2 successful`); - console.log(` Post-restart emails: ${successfulRecovery}/${recoveryResults.length} successful`); - console.log(` Overall success rate: ${((totalSuccessful / emails.length) * 100).toFixed(1)}%`); - console.log(` Server restart recovery: ${successfulRecovery > 0 ? 'Successful' : 'Failed'}`); - - expect(successfulRecovery).toBeGreaterThanOrEqual(1); // At least some emails should work after restart - - smtpClient.close(); - server2.close(); - } finally { - // Ensure cleanup - try { - server1.close(); - } catch (e) { /* Already closed */ } - } -}); - -tap.test('CREL-04: Error Recovery and State Management', async () => { - console.log('\n⚠️ Testing error recovery and state management...'); - - let errorInjectionEnabled = false; - const server = net.createServer(socket => { - socket.write('220 localhost SMTP Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (errorInjectionEnabled && line.startsWith('MAIL FROM')) { - console.log(' [Server] Injecting error response'); - socket.write('550 Simulated server error\r\n'); - return; - } - - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else if (line === 'RSET') { - socket.write('250 OK\r\n'); - } - }); - }); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - console.log(' Creating client with error handling...'); - const smtpClient = createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 1, - connectionTimeout: 3000 - }); - - const emails = []; - for (let i = 0; i < 6; i++) { - emails.push(new Email({ - from: 'sender@exception.test', - to: [`recipient${i}@exception.test`], - subject: `Error Recovery Test ${i + 1}`, - text: `Testing error recovery, email ${i + 1}` - })); - } - - console.log(' Phase 1: Sending emails normally...'); - await smtpClient.sendMail(emails[0]); - console.log(' ✓ Email 1 sent successfully'); - - await smtpClient.sendMail(emails[1]); - console.log(' ✓ Email 2 sent successfully'); - - console.log(' Phase 2: Enabling error injection...'); - errorInjectionEnabled = true; - - console.log(' Sending emails with error injection...'); - const recoveryResults = []; - - for (let i = 2; i < 4; i++) { - try { - await smtpClient.sendMail(emails[i]); - recoveryResults.push({ success: true, index: i }); - console.log(` ✓ Email ${i + 1} sent despite errors`); - } catch (error) { - recoveryResults.push({ success: false, index: i, error }); - console.log(` ✗ Email ${i + 1} failed: ${error.message}`); - } - } - - console.log(' Phase 3: Disabling error injection...'); - errorInjectionEnabled = false; - - console.log(' Sending final emails (recovery validation)...'); - for (let i = 4; i < emails.length; i++) { - try { - await smtpClient.sendMail(emails[i]); - recoveryResults.push({ success: true, index: i }); - console.log(` ✓ Email ${i + 1} sent after recovery`); - } catch (error) { - recoveryResults.push({ success: false, index: i, error }); - console.log(` ✗ Email ${i + 1} failed: ${error.message}`); - } - } - - const successful = recoveryResults.filter(r => r.success).length; - const totalSuccessful = 2 + successful; // 2 initial + recovery phase - - console.log(` Pre-error emails: 2/2 successful`); - console.log(` Error/recovery phase emails: ${successful}/${recoveryResults.length} successful`); - console.log(` Total success rate: ${((totalSuccessful / emails.length) * 100).toFixed(1)}%`); - console.log(` Error recovery: ${successful >= recoveryResults.length - 2 ? 'Effective' : 'Partial'}`); - - expect(totalSuccessful).toBeGreaterThanOrEqual(4); // At least initial + some recovery - - smtpClient.close(); - } finally { - server.close(); - } -}); - -tap.test('CREL-04: Resource Management During Issues', async () => { - console.log('\n🧠 Testing resource management during connection issues...'); - - let memoryBefore = process.memoryUsage(); - - const server = net.createServer(socket => { - socket.write('220 localhost SMTP Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - console.log(' Creating client for resource management test...'); - const smtpClient = createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 5, - maxMessages: 100 - }); - - console.log(' Creating emails with various content types...'); - const emails = [ - new Email({ - from: 'sender@resource.test', - to: ['recipient1@resource.test'], - subject: 'Resource Test - Normal', - text: 'Normal email content' - }), - new Email({ - from: 'sender@resource.test', - to: ['recipient2@resource.test'], - subject: 'Resource Test - Large Content', - text: 'X'.repeat(50000) // Large content - }), - new Email({ - from: 'sender@resource.test', - to: ['recipient3@resource.test'], - subject: 'Resource Test - Unicode', - text: '🎭🎪🎨🎯🎲🎸🎺🎻🎼🎵🎶🎷'.repeat(100) - }) - ]; - - console.log(' Sending emails and monitoring resource usage...'); - const results = []; - - for (let i = 0; i < emails.length; i++) { - console.log(` Testing email ${i + 1} (${emails[i].subject.split(' - ')[1]})...`); - - try { - // Monitor memory usage before sending - const memBefore = process.memoryUsage(); - console.log(` Memory before: ${Math.round(memBefore.heapUsed / 1024 / 1024)}MB`); - - await smtpClient.sendMail(emails[i]); - - const memAfter = process.memoryUsage(); - console.log(` Memory after: ${Math.round(memAfter.heapUsed / 1024 / 1024)}MB`); - - const memIncrease = memAfter.heapUsed - memBefore.heapUsed; - console.log(` Memory increase: ${Math.round(memIncrease / 1024)}KB`); - - results.push({ - success: true, - index: i, - memoryIncrease: memIncrease - }); - console.log(` ✓ Email ${i + 1} sent successfully`); - - } catch (error) { - results.push({ success: false, index: i, error }); - console.log(` ✗ Email ${i + 1} failed: ${error.message}`); - } - - // Force garbage collection if available - if (global.gc) { - global.gc(); - } - - await new Promise(resolve => setTimeout(resolve, 100)); - } - - const successful = results.filter(r => r.success).length; - const totalMemoryIncrease = results.reduce((sum, r) => sum + (r.memoryIncrease || 0), 0); - - console.log(` Resource management: ${successful}/${emails.length} emails processed`); - console.log(` Total memory increase: ${Math.round(totalMemoryIncrease / 1024)}KB`); - console.log(` Resource efficiency: ${((successful / emails.length) * 100).toFixed(1)}%`); - - expect(successful).toBeGreaterThanOrEqual(2); // Most emails should succeed - expect(totalMemoryIncrease).toBeLessThan(100 * 1024 * 1024); // Less than 100MB increase - - smtpClient.close(); - } finally { - server.close(); - } -}); - -tap.test('CREL-04: Test Summary', async () => { - console.log('\n✅ CREL-04: Crash Recovery Reliability Tests completed'); - console.log('💥 All connection recovery scenarios tested successfully'); -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_reliability/test.crel-05.memory-leaks.ts b/test/suite/smtpclient_reliability/test.crel-05.memory-leaks.ts deleted file mode 100644 index a369642..0000000 --- a/test/suite/smtpclient_reliability/test.crel-05.memory-leaks.ts +++ /dev/null @@ -1,503 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import * as net from 'net'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -// Helper function to get memory usage -const getMemoryUsage = () => { - const usage = process.memoryUsage(); - return { - heapUsed: Math.round(usage.heapUsed / 1024 / 1024 * 100) / 100, // MB - heapTotal: Math.round(usage.heapTotal / 1024 / 1024 * 100) / 100, // MB - external: Math.round(usage.external / 1024 / 1024 * 100) / 100, // MB - rss: Math.round(usage.rss / 1024 / 1024 * 100) / 100 // MB - }; -}; - -// Force garbage collection if available -const forceGC = () => { - if (global.gc) { - global.gc(); - global.gc(); // Run twice for thoroughness - } -}; - -tap.test('CREL-05: Connection Pool Memory Management', async () => { - console.log('\n🧠 Testing SMTP Client Memory Leak Prevention'); - console.log('=' .repeat(60)); - console.log('\n🏊 Testing connection pool memory management...'); - - // Create test server - const server = net.createServer(socket => { - socket.write('220 localhost SMTP Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - const initialMemory = getMemoryUsage(); - console.log(` Initial memory: ${initialMemory.heapUsed}MB heap, ${initialMemory.rss}MB RSS`); - - console.log(' Phase 1: Creating and using multiple connection pools...'); - const memorySnapshots = []; - - for (let poolIndex = 0; poolIndex < 5; poolIndex++) { - console.log(` Creating connection pool ${poolIndex + 1}...`); - - const smtpClient = createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 3, - maxMessages: 20, - connectionTimeout: 1000 - }); - - // Send emails through this pool - const emails = []; - for (let i = 0; i < 6; i++) { - emails.push(new Email({ - from: `sender${poolIndex}@memoryleak.test`, - to: [`recipient${i}@memoryleak.test`], - subject: `Memory Pool Test ${poolIndex + 1}-${i + 1}`, - text: `Testing memory management in pool ${poolIndex + 1}, email ${i + 1}` - })); - } - - // Send emails concurrently - const promises = emails.map((email, index) => { - return smtpClient.sendMail(email).then(result => { - return { success: true, result }; - }).catch(error => { - return { success: false, error }; - }); - }); - - const results = await Promise.all(promises); - const successful = results.filter(r => r.success).length; - console.log(` Pool ${poolIndex + 1}: ${successful}/${emails.length} emails sent`); - - // Close the pool - smtpClient.close(); - console.log(` Pool ${poolIndex + 1} closed`); - - // Force garbage collection and measure memory - forceGC(); - await new Promise(resolve => setTimeout(resolve, 100)); - - const currentMemory = getMemoryUsage(); - memorySnapshots.push({ - pool: poolIndex + 1, - heap: currentMemory.heapUsed, - rss: currentMemory.rss, - external: currentMemory.external - }); - - console.log(` Memory after pool ${poolIndex + 1}: ${currentMemory.heapUsed}MB heap`); - } - - console.log('\n Memory analysis:'); - memorySnapshots.forEach((snapshot, index) => { - const memoryIncrease = snapshot.heap - initialMemory.heapUsed; - console.log(` Pool ${snapshot.pool}: +${memoryIncrease.toFixed(2)}MB heap increase`); - }); - - // Check for memory leaks (memory should not continuously increase) - const firstIncrease = memorySnapshots[0].heap - initialMemory.heapUsed; - const lastIncrease = memorySnapshots[memorySnapshots.length - 1].heap - initialMemory.heapUsed; - const leakGrowth = lastIncrease - firstIncrease; - - console.log(` Memory leak assessment:`); - console.log(` First pool increase: +${firstIncrease.toFixed(2)}MB`); - console.log(` Final memory increase: +${lastIncrease.toFixed(2)}MB`); - console.log(` Memory growth across pools: +${leakGrowth.toFixed(2)}MB`); - console.log(` Memory management: ${leakGrowth < 3.0 ? 'Good (< 3MB growth)' : 'Potential leak detected'}`); - - expect(leakGrowth).toBeLessThan(5.0); // Allow some memory growth but detect major leaks - - } finally { - server.close(); - } -}); - -tap.test('CREL-05: Email Object Memory Lifecycle', async () => { - console.log('\n📧 Testing email object memory lifecycle...'); - - // Create test server - const server = net.createServer(socket => { - socket.write('220 localhost SMTP Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - const smtpClient = createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 2 - }); - - const initialMemory = getMemoryUsage(); - console.log(` Initial memory: ${initialMemory.heapUsed}MB heap`); - - console.log(' Phase 1: Creating large batches of email objects...'); - const batchSizes = [50, 100, 150, 100, 50]; // Varying batch sizes - const memorySnapshots = []; - - for (let batchIndex = 0; batchIndex < batchSizes.length; batchIndex++) { - const batchSize = batchSizes[batchIndex]; - console.log(` Creating batch ${batchIndex + 1} with ${batchSize} emails...`); - - const emails = []; - for (let i = 0; i < batchSize; i++) { - emails.push(new Email({ - from: 'sender@emailmemory.test', - to: [`recipient${i}@emailmemory.test`], - subject: `Memory Lifecycle Test Batch ${batchIndex + 1} Email ${i + 1}`, - text: `Testing email object memory lifecycle. This is a moderately long email body to test memory usage patterns. Email ${i + 1} in batch ${batchIndex + 1} of ${batchSize} emails.`, - html: `

Email ${i + 1}

Testing memory patterns with HTML content. Batch ${batchIndex + 1}.

` - })); - } - - console.log(` Sending batch ${batchIndex + 1}...`); - const promises = emails.map((email, index) => { - return smtpClient.sendMail(email).then(result => { - return { success: true }; - }).catch(error => { - return { success: false, error }; - }); - }); - - const results = await Promise.all(promises); - const successful = results.filter(r => r.success).length; - console.log(` Batch ${batchIndex + 1}: ${successful}/${batchSize} emails sent`); - - // Clear email references - emails.length = 0; - - // Force garbage collection - forceGC(); - await new Promise(resolve => setTimeout(resolve, 100)); - - const currentMemory = getMemoryUsage(); - memorySnapshots.push({ - batch: batchIndex + 1, - size: batchSize, - heap: currentMemory.heapUsed, - external: currentMemory.external - }); - - console.log(` Memory after batch ${batchIndex + 1}: ${currentMemory.heapUsed}MB heap`); - } - - console.log('\n Email object memory analysis:'); - memorySnapshots.forEach((snapshot, index) => { - const memoryIncrease = snapshot.heap - initialMemory.heapUsed; - console.log(` Batch ${snapshot.batch} (${snapshot.size} emails): +${memoryIncrease.toFixed(2)}MB`); - }); - - // Check if memory scales reasonably with email batch size - const maxMemoryIncrease = Math.max(...memorySnapshots.map(s => s.heap - initialMemory.heapUsed)); - const avgBatchSize = batchSizes.reduce((a, b) => a + b, 0) / batchSizes.length; - - console.log(` Maximum memory increase: +${maxMemoryIncrease.toFixed(2)}MB`); - console.log(` Average batch size: ${avgBatchSize} emails`); - console.log(` Memory per email: ~${(maxMemoryIncrease / avgBatchSize * 1024).toFixed(1)}KB`); - console.log(` Email object lifecycle: ${maxMemoryIncrease < 10 ? 'Efficient' : 'Needs optimization'}`); - - expect(maxMemoryIncrease).toBeLessThan(15); // Allow reasonable memory usage - - smtpClient.close(); - } finally { - server.close(); - } -}); - -tap.test('CREL-05: Long-Running Client Memory Stability', async () => { - console.log('\n⏱️ Testing long-running client memory stability...'); - - // Create test server - const server = net.createServer(socket => { - socket.write('220 localhost SMTP Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - const smtpClient = createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 2, - maxMessages: 1000 - }); - - const initialMemory = getMemoryUsage(); - console.log(` Initial memory: ${initialMemory.heapUsed}MB heap`); - - console.log(' Starting sustained email sending operation...'); - const memoryMeasurements = []; - const totalEmails = 100; // Reduced for test efficiency - const measurementInterval = 20; // Measure every 20 emails - - let emailsSent = 0; - let emailsFailed = 0; - - for (let i = 0; i < totalEmails; i++) { - const email = new Email({ - from: 'sender@longrunning.test', - to: [`recipient${i}@longrunning.test`], - subject: `Long Running Test ${i + 1}`, - text: `Sustained operation test email ${i + 1}` - }); - - try { - await smtpClient.sendMail(email); - emailsSent++; - } catch (error) { - emailsFailed++; - } - - // Measure memory at intervals - if ((i + 1) % measurementInterval === 0) { - forceGC(); - const currentMemory = getMemoryUsage(); - memoryMeasurements.push({ - emailCount: i + 1, - heap: currentMemory.heapUsed, - rss: currentMemory.rss, - timestamp: Date.now() - }); - - console.log(` ${i + 1}/${totalEmails} emails: ${currentMemory.heapUsed}MB heap`); - } - } - - console.log('\n Long-running memory analysis:'); - console.log(` Emails sent: ${emailsSent}, Failed: ${emailsFailed}`); - - memoryMeasurements.forEach((measurement, index) => { - const memoryIncrease = measurement.heap - initialMemory.heapUsed; - console.log(` After ${measurement.emailCount} emails: +${memoryIncrease.toFixed(2)}MB heap`); - }); - - // Analyze memory growth trend - if (memoryMeasurements.length >= 2) { - const firstMeasurement = memoryMeasurements[0]; - const lastMeasurement = memoryMeasurements[memoryMeasurements.length - 1]; - - const memoryGrowth = lastMeasurement.heap - firstMeasurement.heap; - const emailsProcessed = lastMeasurement.emailCount - firstMeasurement.emailCount; - const growthRate = (memoryGrowth / emailsProcessed) * 1000; // KB per email - - console.log(` Memory growth over operation: +${memoryGrowth.toFixed(2)}MB`); - console.log(` Growth rate: ~${growthRate.toFixed(2)}KB per email`); - console.log(` Memory stability: ${growthRate < 10 ? 'Excellent' : growthRate < 25 ? 'Good' : 'Concerning'}`); - - expect(growthRate).toBeLessThan(50); // Allow reasonable growth but detect major leaks - } - - expect(emailsSent).toBeGreaterThanOrEqual(totalEmails - 5); // Most emails should succeed - - smtpClient.close(); - } finally { - server.close(); - } -}); - -tap.test('CREL-05: Large Content Memory Management', async () => { - console.log('\n🌊 Testing large content memory management...'); - - // Create test server - const server = net.createServer(socket => { - socket.write('220 localhost SMTP Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - const smtpClient = createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 1 - }); - - const initialMemory = getMemoryUsage(); - console.log(` Initial memory: ${initialMemory.heapUsed}MB heap`); - - console.log(' Testing with various content sizes...'); - const contentSizes = [ - { size: 1024, name: '1KB' }, - { size: 10240, name: '10KB' }, - { size: 102400, name: '100KB' }, - { size: 256000, name: '250KB' } - ]; - - for (const contentTest of contentSizes) { - console.log(` Testing ${contentTest.name} content size...`); - - const beforeMemory = getMemoryUsage(); - - // Create large text content - const largeText = 'X'.repeat(contentTest.size); - - const email = new Email({ - from: 'sender@largemem.test', - to: ['recipient@largemem.test'], - subject: `Large Content Test - ${contentTest.name}`, - text: largeText - }); - - try { - await smtpClient.sendMail(email); - console.log(` ✓ ${contentTest.name} email sent successfully`); - } catch (error) { - console.log(` ✗ ${contentTest.name} email failed: ${error.message}`); - } - - // Force cleanup - forceGC(); - await new Promise(resolve => setTimeout(resolve, 100)); - - const afterMemory = getMemoryUsage(); - const memoryDiff = afterMemory.heapUsed - beforeMemory.heapUsed; - - console.log(` Memory impact: ${memoryDiff > 0 ? '+' : ''}${memoryDiff.toFixed(2)}MB`); - console.log(` Efficiency: ${Math.abs(memoryDiff) < (contentTest.size / 1024 / 1024) * 2 ? 'Good' : 'High memory usage'}`); - } - - const finalMemory = getMemoryUsage(); - const totalMemoryIncrease = finalMemory.heapUsed - initialMemory.heapUsed; - - console.log(`\n Large content memory summary:`); - console.log(` Total memory increase: +${totalMemoryIncrease.toFixed(2)}MB`); - console.log(` Memory management efficiency: ${totalMemoryIncrease < 5 ? 'Excellent' : 'Needs optimization'}`); - - expect(totalMemoryIncrease).toBeLessThan(20); // Allow reasonable memory usage for large content - - smtpClient.close(); - } finally { - server.close(); - } -}); - -tap.test('CREL-05: Test Summary', async () => { - console.log('\n✅ CREL-05: Memory Leak Prevention Reliability Tests completed'); - console.log('🧠 All memory management scenarios tested successfully'); -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_reliability/test.crel-06.concurrency-safety.ts b/test/suite/smtpclient_reliability/test.crel-06.concurrency-safety.ts deleted file mode 100644 index 8e363c7..0000000 --- a/test/suite/smtpclient_reliability/test.crel-06.concurrency-safety.ts +++ /dev/null @@ -1,558 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import * as net from 'net'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -tap.test('CREL-06: Simultaneous Connection Management', async () => { - console.log('\n⚡ Testing SMTP Client Concurrent Operation Safety'); - console.log('=' .repeat(60)); - console.log('\n🔗 Testing simultaneous connection management safety...'); - - let connectionCount = 0; - let activeConnections = 0; - const connectionLog: string[] = []; - - // Create test server that tracks connections - const server = net.createServer(socket => { - connectionCount++; - activeConnections++; - const connId = `CONN-${connectionCount}`; - connectionLog.push(`${new Date().toISOString()}: ${connId} OPENED (active: ${activeConnections})`); - console.log(` [Server] ${connId} opened (total: ${connectionCount}, active: ${activeConnections})`); - - socket.on('close', () => { - activeConnections--; - connectionLog.push(`${new Date().toISOString()}: ${connId} CLOSED (active: ${activeConnections})`); - console.log(` [Server] ${connId} closed (active: ${activeConnections})`); - }); - - socket.write('220 localhost SMTP Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - console.log(' Creating multiple SMTP clients with shared connection pool settings...'); - const clients = []; - - for (let i = 0; i < 5; i++) { - clients.push(createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 3, // Allow up to 3 connections - maxMessages: 10, - connectionTimeout: 2000 - })); - } - - console.log(' Launching concurrent email sending operations...'); - const emailBatches = clients.map((client, clientIndex) => { - return Array.from({ length: 8 }, (_, emailIndex) => { - return new Email({ - from: `sender${clientIndex}@concurrent.test`, - to: [`recipient${clientIndex}-${emailIndex}@concurrent.test`], - subject: `Concurrent Safety Test Client ${clientIndex + 1} Email ${emailIndex + 1}`, - text: `Testing concurrent operation safety from client ${clientIndex + 1}, email ${emailIndex + 1}` - }); - }); - }); - - const startTime = Date.now(); - const allPromises: Promise[] = []; - - // Launch all email operations simultaneously - emailBatches.forEach((emails, clientIndex) => { - emails.forEach((email, emailIndex) => { - const promise = clients[clientIndex].sendMail(email).then(result => { - console.log(` ✓ Client ${clientIndex + 1} Email ${emailIndex + 1} sent`); - return { success: true, clientIndex, emailIndex, result }; - }).catch(error => { - console.log(` ✗ Client ${clientIndex + 1} Email ${emailIndex + 1} failed: ${error.message}`); - return { success: false, clientIndex, emailIndex, error }; - }); - allPromises.push(promise); - }); - }); - - const results = await Promise.all(allPromises); - const endTime = Date.now(); - - // Close all clients - clients.forEach(client => client.close()); - - // Wait for connections to close - await new Promise(resolve => setTimeout(resolve, 500)); - - const successful = results.filter(r => r.success).length; - const failed = results.filter(r => !r.success).length; - const totalEmails = emailBatches.flat().length; - - console.log(`\n Concurrent operation results:`); - console.log(` Total operations: ${totalEmails}`); - console.log(` Successful: ${successful}, Failed: ${failed}`); - console.log(` Success rate: ${((successful / totalEmails) * 100).toFixed(1)}%`); - console.log(` Execution time: ${endTime - startTime}ms`); - console.log(` Peak connections: ${Math.max(...connectionLog.map(log => { - const match = log.match(/active: (\d+)/); - return match ? parseInt(match[1]) : 0; - }))}`); - console.log(` Connection management: ${activeConnections === 0 ? 'Clean' : 'Connections remaining'}`); - - expect(successful).toBeGreaterThanOrEqual(totalEmails - 5); // Allow some failures - expect(activeConnections).toEqual(0); // All connections should be closed - - } finally { - server.close(); - } -}); - -tap.test('CREL-06: Concurrent Queue Operations', async () => { - console.log('\n🔒 Testing concurrent queue operations...'); - - let messageProcessingOrder: string[] = []; - - // Create test server that tracks message processing order - const server = net.createServer(socket => { - socket.write('220 localhost SMTP Test Server\r\n'); - let inData = false; - let currentData = ''; - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (inData) { - if (line === '.') { - // Extract Message-ID from email data - const messageIdMatch = currentData.match(/Message-ID:\s*<([^>]+)>/); - if (messageIdMatch) { - messageProcessingOrder.push(messageIdMatch[1]); - console.log(` [Server] Processing: ${messageIdMatch[1]}`); - } - socket.write('250 OK Message accepted\r\n'); - inData = false; - currentData = ''; - } else { - currentData += line + '\r\n'; - } - } else { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - console.log(' Creating SMTP client for concurrent queue operations...'); - const smtpClient = createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 2, - maxMessages: 50 - }); - - console.log(' Launching concurrent queue operations...'); - const operations: Promise[] = []; - const emailGroups = ['A', 'B', 'C', 'D']; - - // Create concurrent operations that use the queue - emailGroups.forEach((group, groupIndex) => { - // Add multiple emails per group concurrently - for (let i = 0; i < 6; i++) { - const email = new Email({ - from: `sender${group}@queuetest.example`, - to: [`recipient${group}${i}@queuetest.example`], - subject: `Queue Safety Test Group ${group} Email ${i + 1}`, - text: `Testing queue safety for group ${group}, email ${i + 1}` - }); - - const operation = smtpClient.sendMail(email).then(result => { - return { - success: true, - group, - index: i, - messageId: result.messageId, - timestamp: Date.now() - }; - }).catch(error => { - return { - success: false, - group, - index: i, - error: error.message - }; - }); - - operations.push(operation); - } - }); - - const startTime = Date.now(); - const results = await Promise.all(operations); - const endTime = Date.now(); - - // Wait for all processing to complete - await new Promise(resolve => setTimeout(resolve, 300)); - - const successful = results.filter(r => r.success).length; - const failed = results.filter(r => !r.success).length; - - console.log(`\n Queue safety results:`); - console.log(` Total queue operations: ${operations.length}`); - console.log(` Successful: ${successful}, Failed: ${failed}`); - console.log(` Success rate: ${((successful / operations.length) * 100).toFixed(1)}%`); - console.log(` Processing time: ${endTime - startTime}ms`); - - // Analyze processing order - const groupCounts = emailGroups.reduce((acc, group) => { - acc[group] = messageProcessingOrder.filter(id => id && id.includes(`${group}`)).length; - return acc; - }, {} as Record); - - console.log(` Processing distribution:`); - Object.entries(groupCounts).forEach(([group, count]) => { - console.log(` Group ${group}: ${count} emails processed`); - }); - - const totalProcessed = Object.values(groupCounts).reduce((a, b) => a + b, 0); - console.log(` Queue integrity: ${totalProcessed === successful ? 'Maintained' : 'Some messages lost'}`); - - expect(successful).toBeGreaterThanOrEqual(operations.length - 2); // Allow minimal failures - - smtpClient.close(); - } finally { - server.close(); - } -}); - -tap.test('CREL-06: Concurrent Error Handling', async () => { - console.log('\n❌ Testing concurrent error handling safety...'); - - let errorInjectionPhase = false; - let connectionAttempts = 0; - - // Create test server that can inject errors - const server = net.createServer(socket => { - connectionAttempts++; - console.log(` [Server] Connection attempt ${connectionAttempts}`); - - if (errorInjectionPhase && Math.random() < 0.4) { - console.log(` [Server] Injecting connection error ${connectionAttempts}`); - socket.destroy(); - return; - } - - socket.write('220 localhost SMTP Test Server\r\n'); - - socket.on('data', (data) => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (errorInjectionPhase && line.startsWith('MAIL FROM') && Math.random() < 0.3) { - console.log(' [Server] Injecting SMTP error'); - socket.write('450 Temporary failure, please retry\r\n'); - return; - } - - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - console.log(' Creating multiple clients for concurrent error testing...'); - const clients = []; - - for (let i = 0; i < 4; i++) { - clients.push(createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 2, - connectionTimeout: 3000 - })); - } - - const emails = []; - for (let clientIndex = 0; clientIndex < clients.length; clientIndex++) { - for (let emailIndex = 0; emailIndex < 5; emailIndex++) { - emails.push({ - client: clients[clientIndex], - email: new Email({ - from: `sender${clientIndex}@errortest.example`, - to: [`recipient${clientIndex}-${emailIndex}@errortest.example`], - subject: `Concurrent Error Test Client ${clientIndex + 1} Email ${emailIndex + 1}`, - text: `Testing concurrent error handling ${clientIndex + 1}-${emailIndex + 1}` - }), - clientIndex, - emailIndex - }); - } - } - - console.log(' Phase 1: Normal operation...'); - const phase1Results = []; - const phase1Emails = emails.slice(0, 8); // First 8 emails - - const phase1Promises = phase1Emails.map(({ client, email, clientIndex, emailIndex }) => { - return client.sendMail(email).then(result => { - console.log(` ✓ Phase 1: Client ${clientIndex + 1} Email ${emailIndex + 1} sent`); - return { success: true, phase: 1, clientIndex, emailIndex }; - }).catch(error => { - console.log(` ✗ Phase 1: Client ${clientIndex + 1} Email ${emailIndex + 1} failed`); - return { success: false, phase: 1, clientIndex, emailIndex, error: error.message }; - }); - }); - - const phase1Resolved = await Promise.all(phase1Promises); - phase1Results.push(...phase1Resolved); - - console.log(' Phase 2: Error injection enabled...'); - errorInjectionPhase = true; - - const phase2Results = []; - const phase2Emails = emails.slice(8); // Remaining emails - - const phase2Promises = phase2Emails.map(({ client, email, clientIndex, emailIndex }) => { - return client.sendMail(email).then(result => { - console.log(` ✓ Phase 2: Client ${clientIndex + 1} Email ${emailIndex + 1} recovered`); - return { success: true, phase: 2, clientIndex, emailIndex }; - }).catch(error => { - console.log(` ✗ Phase 2: Client ${clientIndex + 1} Email ${emailIndex + 1} failed permanently`); - return { success: false, phase: 2, clientIndex, emailIndex, error: error.message }; - }); - }); - - const phase2Resolved = await Promise.all(phase2Promises); - phase2Results.push(...phase2Resolved); - - // Close all clients - clients.forEach(client => client.close()); - - const phase1Success = phase1Results.filter(r => r.success).length; - const phase2Success = phase2Results.filter(r => r.success).length; - const totalSuccess = phase1Success + phase2Success; - const totalEmails = emails.length; - - console.log(`\n Concurrent error handling results:`); - console.log(` Phase 1 (normal): ${phase1Success}/${phase1Results.length} successful`); - console.log(` Phase 2 (errors): ${phase2Success}/${phase2Results.length} successful`); - console.log(` Overall success: ${totalSuccess}/${totalEmails} (${((totalSuccess / totalEmails) * 100).toFixed(1)}%)`); - console.log(` Error resilience: ${phase2Success > 0 ? 'Good' : 'Poor'}`); - console.log(` Concurrent error safety: ${phase1Success === phase1Results.length ? 'Maintained' : 'Some failures'}`); - - expect(phase1Success).toBeGreaterThanOrEqual(phase1Results.length - 1); // Most should succeed - expect(phase2Success).toBeGreaterThanOrEqual(1); // Some should succeed despite errors - - } finally { - server.close(); - } -}); - -tap.test('CREL-06: Resource Contention Management', async () => { - console.log('\n🏁 Testing resource contention management...'); - - // Create test server with limited capacity - const server = net.createServer(socket => { - console.log(' [Server] New connection established'); - - socket.write('220 localhost SMTP Test Server\r\n'); - - // Add some delay to simulate slow server - socket.on('data', (data) => { - setTimeout(() => { - const lines = data.toString().split('\r\n'); - - lines.forEach(line => { - if (line.startsWith('EHLO') || line.startsWith('HELO')) { - socket.write('250-localhost\r\n'); - socket.write('250 SIZE 10485760\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'); - } else if (line === '.') { - socket.write('250 OK Message accepted\r\n'); - } else if (line === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - }, 20); // Add 20ms delay to responses - }); - }); - - server.maxConnections = 3; // Limit server connections - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - resolve(); - }); - }); - - const port = (server.address() as net.AddressInfo).port; - - try { - console.log(' Creating high-contention scenario with limited resources...'); - const clients = []; - - // Create more clients than server can handle simultaneously - for (let i = 0; i < 8; i++) { - clients.push(createTestSmtpClient({ - host: '127.0.0.1', - port: port, - secure: false, - maxConnections: 1, // Force contention - maxMessages: 10, - connectionTimeout: 3000 - })); - } - - const emails = []; - clients.forEach((client, clientIndex) => { - for (let emailIndex = 0; emailIndex < 4; emailIndex++) { - emails.push({ - client, - email: new Email({ - from: `sender${clientIndex}@contention.test`, - to: [`recipient${clientIndex}-${emailIndex}@contention.test`], - subject: `Resource Contention Test ${clientIndex + 1}-${emailIndex + 1}`, - text: `Testing resource contention management ${clientIndex + 1}-${emailIndex + 1}` - }), - clientIndex, - emailIndex - }); - } - }); - - console.log(' Launching high-contention operations...'); - const startTime = Date.now(); - const promises = emails.map(({ client, email, clientIndex, emailIndex }) => { - return client.sendMail(email).then(result => { - console.log(` ✓ Client ${clientIndex + 1} Email ${emailIndex + 1} sent`); - return { - success: true, - clientIndex, - emailIndex, - completionTime: Date.now() - startTime - }; - }).catch(error => { - console.log(` ✗ Client ${clientIndex + 1} Email ${emailIndex + 1} failed: ${error.message}`); - return { - success: false, - clientIndex, - emailIndex, - error: error.message, - completionTime: Date.now() - startTime - }; - }); - }); - - const results = await Promise.all(promises); - const endTime = Date.now(); - - // Close all clients - clients.forEach(client => client.close()); - - const successful = results.filter(r => r.success).length; - const failed = results.filter(r => !r.success).length; - const avgCompletionTime = results - .filter(r => r.success) - .reduce((sum, r) => sum + r.completionTime, 0) / successful || 0; - - console.log(`\n Resource contention results:`); - console.log(` Total operations: ${emails.length}`); - console.log(` Successful: ${successful}, Failed: ${failed}`); - console.log(` Success rate: ${((successful / emails.length) * 100).toFixed(1)}%`); - console.log(` Total execution time: ${endTime - startTime}ms`); - console.log(` Average completion time: ${avgCompletionTime.toFixed(0)}ms`); - console.log(` Resource management: ${successful > emails.length * 0.8 ? 'Effective' : 'Needs improvement'}`); - - expect(successful).toBeGreaterThanOrEqual(emails.length * 0.7); // At least 70% should succeed - - } finally { - server.close(); - } -}); - -tap.test('CREL-06: Test Summary', async () => { - console.log('\n✅ CREL-06: Concurrent Operation Safety Reliability Tests completed'); - console.log('⚡ All concurrency safety scenarios tested successfully'); -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_reliability/test.crel-07.resource-cleanup.ts b/test/suite/smtpclient_reliability/test.crel-07.resource-cleanup.ts deleted file mode 100644 index 9b4a93f..0000000 --- a/test/suite/smtpclient_reliability/test.crel-07.resource-cleanup.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { createTestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; - -tap.test('CREL-07: Resource Cleanup Tests', async () => { - console.log('\n🧹 Testing SMTP Client Resource Cleanup'); - console.log('=' .repeat(60)); - - const testServer = await createTestServer({}); - - try { - console.log('\nTest 1: Basic client creation and cleanup'); - - // Create a client - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port - }); - console.log(' ✓ Client created'); - - // Verify connection - try { - const verifyResult = await smtpClient.verify(); - console.log(' ✓ Connection verified:', verifyResult); - } catch (error) { - console.log(' ⚠️ Verify failed:', error.message); - } - - // Close the client - smtpClient.close(); - console.log(' ✓ Client closed'); - - console.log('\nTest 2: Multiple close calls'); - const testClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port - }); - - // Close multiple times - should not throw - testClient.close(); - testClient.close(); - testClient.close(); - console.log(' ✓ Multiple close calls handled safely'); - - console.log('\n✅ CREL-07: Resource cleanup tests completed'); - - } finally { - testServer.server.close(); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_rfc-compliance/test.crfc-01.rfc5321-client.ts b/test/suite/smtpclient_rfc-compliance/test.crfc-01.rfc5321-client.ts deleted file mode 100644 index cefb395..0000000 --- a/test/suite/smtpclient_rfc-compliance/test.crfc-01.rfc5321-client.ts +++ /dev/null @@ -1,283 +0,0 @@ -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: - 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: - 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 . termination sequence' - }); - - // Client MUST terminate DATA with . - const result = await smtpClient.sendMail(email); - - expect(result.success).toBeTrue(); - console.log('✅ RFC 5321 §4.1.1.9: DATA terminated with .'); -}); - -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(); \ No newline at end of file diff --git a/test/suite/smtpclient_rfc-compliance/test.crfc-02.esmtp-compliance.ts b/test/suite/smtpclient_rfc-compliance/test.crfc-02.esmtp-compliance.ts deleted file mode 100644 index 562244f..0000000 --- a/test/suite/smtpclient_rfc-compliance/test.crfc-02.esmtp-compliance.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { expect, tap } from '@git.zone/tstest/tapbundle'; -import { createTestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -tap.test('CRFC-02: Basic ESMTP Compliance', async () => { - console.log('\n📧 Testing SMTP Client ESMTP Compliance'); - console.log('=' .repeat(60)); - - const testServer = await createTestServer({}); - - try { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port - }); - - console.log('\nTest 1: Basic EHLO negotiation'); - const email1 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'ESMTP test', - text: 'Testing ESMTP' - }); - - const result1 = await smtpClient.sendMail(email1); - console.log(' ✓ EHLO negotiation successful'); - expect(result1).toBeDefined(); - - console.log('\nTest 2: Multiple recipients'); - const email2 = new Email({ - from: 'sender@example.com', - to: ['recipient1@example.com', 'recipient2@example.com'], - cc: ['cc@example.com'], - bcc: ['bcc@example.com'], - subject: 'Multiple recipients', - text: 'Testing multiple recipients' - }); - - const result2 = await smtpClient.sendMail(email2); - console.log(' ✓ Multiple recipients handled'); - expect(result2).toBeDefined(); - - console.log('\nTest 3: UTF-8 content'); - const email3 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'UTF-8: café ☕ 测试', - text: 'International text: émojis 🎉, 日本語', - html: '

HTML: Zürich

' - }); - - const result3 = await smtpClient.sendMail(email3); - console.log(' ✓ UTF-8 content accepted'); - expect(result3).toBeDefined(); - - console.log('\nTest 4: Long headers'); - const longSubject = 'This is a very long subject line that exceeds 78 characters and should be properly folded according to RFC 2822'; - const email4 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: longSubject, - text: 'Testing header folding' - }); - - const result4 = await smtpClient.sendMail(email4); - console.log(' ✓ Long headers handled'); - expect(result4).toBeDefined(); - - console.log('\n✅ CRFC-02: ESMTP compliance tests completed'); - - } finally { - testServer.server.close(); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_rfc-compliance/test.crfc-03.command-syntax.ts b/test/suite/smtpclient_rfc-compliance/test.crfc-03.command-syntax.ts deleted file mode 100644 index 5ff633a..0000000 --- a/test/suite/smtpclient_rfc-compliance/test.crfc-03.command-syntax.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { expect, tap } from '@git.zone/tstest/tapbundle'; -import { createTestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -tap.test('CRFC-03: SMTP Command Syntax Compliance', async () => { - console.log('\n📧 Testing SMTP Client Command Syntax Compliance'); - console.log('=' .repeat(60)); - - const testServer = await createTestServer({}); - - try { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port - }); - - console.log('\nTest 1: Valid email addresses'); - const email1 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Valid email test', - text: 'Testing valid email addresses' - }); - - const result1 = await smtpClient.sendMail(email1); - console.log(' ✓ Valid email addresses accepted'); - expect(result1).toBeDefined(); - - console.log('\nTest 2: Email with display names'); - const email2 = new Email({ - from: 'Test Sender ', - to: ['Test Recipient '], - subject: 'Display name test', - text: 'Testing email addresses with display names' - }); - - const result2 = await smtpClient.sendMail(email2); - console.log(' ✓ Display names handled correctly'); - expect(result2).toBeDefined(); - - console.log('\nTest 3: Multiple recipients'); - const email3 = new Email({ - from: 'sender@example.com', - to: ['user1@example.com', 'user2@example.com'], - cc: ['cc@example.com'], - subject: 'Multiple recipients test', - text: 'Testing RCPT TO command with multiple recipients' - }); - - const result3 = await smtpClient.sendMail(email3); - console.log(' ✓ Multiple RCPT TO commands sent correctly'); - expect(result3).toBeDefined(); - - console.log('\nTest 4: Connection test (HELO/EHLO)'); - const verified = await smtpClient.verify(); - console.log(' ✓ HELO/EHLO command syntax correct'); - expect(verified).toBeDefined(); - - console.log('\n✅ CRFC-03: Command syntax compliance tests completed'); - - } finally { - testServer.server.close(); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_rfc-compliance/test.crfc-04.response-codes.ts b/test/suite/smtpclient_rfc-compliance/test.crfc-04.response-codes.ts deleted file mode 100644 index e4c3db7..0000000 --- a/test/suite/smtpclient_rfc-compliance/test.crfc-04.response-codes.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { expect, tap } from '@git.zone/tstest/tapbundle'; -import { createTestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -tap.test('CRFC-04: SMTP Response Code Handling', async () => { - console.log('\n📧 Testing SMTP Client Response Code Handling'); - console.log('=' .repeat(60)); - - const testServer = await createTestServer({}); - - try { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port - }); - - console.log('\nTest 1: Successful email (2xx responses)'); - const email1 = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Success test', - text: 'Testing successful response codes' - }); - - const result1 = await smtpClient.sendMail(email1); - console.log(' ✓ 2xx response codes handled correctly'); - expect(result1).toBeDefined(); - - console.log('\nTest 2: Verify connection'); - const verified = await smtpClient.verify(); - console.log(' ✓ Connection verification successful'); - expect(verified).toBeDefined(); - - console.log('\nTest 3: Multiple recipients (multiple 250 responses)'); - const email2 = new Email({ - from: 'sender@example.com', - to: ['user1@example.com', 'user2@example.com', 'user3@example.com'], - subject: 'Multiple recipients', - text: 'Testing multiple positive responses' - }); - - const result2 = await smtpClient.sendMail(email2); - console.log(' ✓ Multiple positive responses handled'); - expect(result2).toBeDefined(); - - console.log('\n✅ CRFC-04: Response code handling tests completed'); - - } finally { - testServer.server.close(); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_rfc-compliance/test.crfc-05.state-machine.ts b/test/suite/smtpclient_rfc-compliance/test.crfc-05.state-machine.ts deleted file mode 100644 index 6d0caba..0000000 --- a/test/suite/smtpclient_rfc-compliance/test.crfc-05.state-machine.ts +++ /dev/null @@ -1,703 +0,0 @@ -import { expect, tap } from '@git.zone/tstest/tapbundle'; -import { createTestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/index.js'; - -tap.test('CRFC-05: should comply with SMTP state machine (RFC 5321)', async (tools) => { - const testId = 'CRFC-05-state-machine'; - console.log(`\n${testId}: Testing SMTP state machine compliance...`); - - let scenarioCount = 0; - - // Scenario 1: Initial state and greeting - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing initial state and greeting`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected - Initial state'); - - let state = 'initial'; - - // Send greeting immediately upon connection - socket.write('220 statemachine.example.com ESMTP Service ready\r\n'); - state = 'greeting-sent'; - console.log(' [Server] State: initial -> greeting-sent'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] State: ${state}, Received: ${command}`); - - if (state === 'greeting-sent') { - if (command.startsWith('EHLO') || command.startsWith('HELO')) { - socket.write('250 statemachine.example.com\r\n'); - state = 'ready'; - console.log(' [Server] State: greeting-sent -> ready'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('503 5.5.1 Bad sequence of commands\r\n'); - } - } else if (state === 'ready') { - if (command.startsWith('MAIL FROM:')) { - socket.write('250 OK\r\n'); - state = 'mail'; - console.log(' [Server] State: ready -> mail'); - } else if (command.startsWith('EHLO') || command.startsWith('HELO')) { - socket.write('250 statemachine.example.com\r\n'); - // Stay in ready state - } else if (command === 'RSET' || command === 'NOOP') { - socket.write('250 OK\r\n'); - // Stay in ready state - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('503 5.5.1 Bad sequence of commands\r\n'); - } - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Just establish connection and send EHLO - try { - await smtpClient.verify(); - console.log(' Initial state transition (connect -> EHLO) successful'); - } catch (error) { - console.log(` Connection/EHLO failed: ${error.message}`); - } - - await testServer.server.close(); - })(); - - // Scenario 2: Transaction state machine - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing transaction state machine`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 statemachine.example.com ESMTP\r\n'); - - let state = 'ready'; - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] State: ${state}, Command: ${command}`); - - switch (state) { - case 'ready': - if (command.startsWith('EHLO')) { - socket.write('250 statemachine.example.com\r\n'); - // Stay in ready - } else if (command.startsWith('MAIL FROM:')) { - socket.write('250 OK\r\n'); - state = 'mail'; - console.log(' [Server] State: ready -> mail'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('503 5.5.1 Bad sequence of commands\r\n'); - } - break; - - case 'mail': - if (command.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - state = 'rcpt'; - console.log(' [Server] State: mail -> rcpt'); - } else if (command === 'RSET') { - socket.write('250 OK\r\n'); - state = 'ready'; - console.log(' [Server] State: mail -> ready (RSET)'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('503 5.5.1 Bad sequence of commands\r\n'); - } - break; - - case 'rcpt': - if (command.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - // Stay in rcpt (can have multiple recipients) - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - state = 'data'; - console.log(' [Server] State: rcpt -> data'); - } else if (command === 'RSET') { - socket.write('250 OK\r\n'); - state = 'ready'; - console.log(' [Server] State: rcpt -> ready (RSET)'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('503 5.5.1 Bad sequence of commands\r\n'); - } - break; - - case 'data': - if (command === '.') { - socket.write('250 OK\r\n'); - state = 'ready'; - console.log(' [Server] State: data -> ready (message complete)'); - } else if (command === 'QUIT') { - // QUIT is not allowed during DATA - socket.write('503 5.5.1 Bad sequence of commands\r\n'); - } - // All other input during DATA is message content - break; - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient1@example.com', 'recipient2@example.com'], - subject: 'State machine test', - text: 'Testing SMTP transaction state machine' - }); - - const result = await smtpClient.sendMail(email); - console.log(' Complete transaction state sequence successful'); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - await testServer.server.close(); - })(); - - // Scenario 3: Invalid state transitions - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing invalid state transitions`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 statemachine.example.com ESMTP\r\n'); - - let state = 'ready'; - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] State: ${state}, Command: ${command}`); - - // Strictly enforce state machine - switch (state) { - case 'ready': - if (command.startsWith('EHLO') || command.startsWith('HELO')) { - socket.write('250 statemachine.example.com\r\n'); - } else if (command.startsWith('MAIL FROM:')) { - socket.write('250 OK\r\n'); - state = 'mail'; - } else if (command === 'RSET' || command === 'NOOP') { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else if (command.startsWith('RCPT TO:')) { - console.log(' [Server] RCPT TO without MAIL FROM'); - socket.write('503 5.5.1 Need MAIL command first\r\n'); - } else if (command === 'DATA') { - console.log(' [Server] DATA without MAIL FROM and RCPT TO'); - socket.write('503 5.5.1 Need MAIL and RCPT commands first\r\n'); - } else { - socket.write('503 5.5.1 Bad sequence of commands\r\n'); - } - break; - - case 'mail': - if (command.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - state = 'rcpt'; - } else if (command.startsWith('MAIL FROM:')) { - console.log(' [Server] Second MAIL FROM without RSET'); - socket.write('503 5.5.1 Sender already specified\r\n'); - } else if (command === 'DATA') { - console.log(' [Server] DATA without RCPT TO'); - socket.write('503 5.5.1 Need RCPT command first\r\n'); - } else if (command === 'RSET') { - socket.write('250 OK\r\n'); - state = 'ready'; - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('503 5.5.1 Bad sequence of commands\r\n'); - } - break; - - case 'rcpt': - if (command.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - state = 'data'; - } else if (command.startsWith('MAIL FROM:')) { - console.log(' [Server] MAIL FROM after RCPT TO without RSET'); - socket.write('503 5.5.1 Sender already specified\r\n'); - } else if (command === 'RSET') { - socket.write('250 OK\r\n'); - state = 'ready'; - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('503 5.5.1 Bad sequence of commands\r\n'); - } - break; - - case 'data': - if (command === '.') { - socket.write('250 OK\r\n'); - state = 'ready'; - } else if (command.startsWith('MAIL FROM:') || - command.startsWith('RCPT TO:') || - command === 'RSET') { - console.log(' [Server] SMTP command during DATA mode'); - socket.write('503 5.5.1 Commands not allowed during data transfer\r\n'); - } - // During DATA, most input is treated as message content - break; - } - }); - } - }); - - // We'll create a custom client to send invalid command sequences - const testCases = [ - { - name: 'RCPT without MAIL', - commands: ['EHLO client.example.com', 'RCPT TO:'], - expectError: true - }, - { - name: 'DATA without RCPT', - commands: ['EHLO client.example.com', 'MAIL FROM:', 'DATA'], - expectError: true - }, - { - name: 'Double MAIL FROM', - commands: ['EHLO client.example.com', 'MAIL FROM:', 'MAIL FROM:'], - expectError: true - } - ]; - - for (const testCase of testCases) { - console.log(` Testing: ${testCase.name}`); - - try { - // Create simple socket connection for manual command testing - const net = await import('net'); - const client = net.createConnection(testServer.port, testServer.hostname); - - let responseCount = 0; - let errorReceived = false; - - client.on('data', (data) => { - const response = data.toString(); - console.log(` Response: ${response.trim()}`); - - if (response.startsWith('5')) { - errorReceived = true; - } - - responseCount++; - - if (responseCount <= testCase.commands.length) { - const command = testCase.commands[responseCount - 1]; - if (command) { - setTimeout(() => { - console.log(` Sending: ${command}`); - client.write(command + '\r\n'); - }, 100); - } - } else { - client.write('QUIT\r\n'); - client.end(); - } - }); - - await new Promise((resolve, reject) => { - client.on('end', () => { - if (testCase.expectError && errorReceived) { - console.log(` ✓ Expected error received`); - } else if (!testCase.expectError && !errorReceived) { - console.log(` ✓ No error as expected`); - } else { - console.log(` ✗ Unexpected result`); - } - resolve(void 0); - }); - - client.on('error', reject); - - // Start with greeting response - setTimeout(() => { - if (testCase.commands.length > 0) { - console.log(` Sending: ${testCase.commands[0]}`); - client.write(testCase.commands[0] + '\r\n'); - } - }, 100); - }); - - } catch (error) { - console.log(` Error testing ${testCase.name}: ${error.message}`); - } - } - - await testServer.server.close(); - })(); - - // Scenario 4: RSET command state transitions - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing RSET command state transitions`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 statemachine.example.com ESMTP\r\n'); - - let state = 'ready'; - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] State: ${state}, Command: ${command}`); - - if (command.startsWith('EHLO')) { - socket.write('250 statemachine.example.com\r\n'); - state = 'ready'; - } else if (command.startsWith('MAIL FROM:')) { - socket.write('250 OK\r\n'); - state = 'mail'; - } else if (command.startsWith('RCPT TO:')) { - if (state === 'mail' || state === 'rcpt') { - socket.write('250 OK\r\n'); - state = 'rcpt'; - } else { - socket.write('503 5.5.1 Bad sequence of commands\r\n'); - } - } else if (command === 'RSET') { - console.log(` [Server] RSET from state: ${state} -> ready`); - socket.write('250 OK\r\n'); - state = 'ready'; - } else if (command === 'DATA') { - if (state === 'rcpt') { - socket.write('354 Start mail input\r\n'); - state = 'data'; - } else { - socket.write('503 5.5.1 Bad sequence of commands\r\n'); - } - } else if (command === '.') { - if (state === 'data') { - socket.write('250 OK\r\n'); - state = 'ready'; - } - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else if (command === 'NOOP') { - socket.write('250 OK\r\n'); - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test RSET at various points in transaction - console.log(' Testing RSET from different states...'); - - // We'll manually test RSET behavior - const net = await import('net'); - const client = net.createConnection(testServer.port, testServer.hostname); - - const commands = [ - 'EHLO client.example.com', // -> ready - 'MAIL FROM:', // -> mail - 'RSET', // -> ready (reset from mail state) - 'MAIL FROM:', // -> mail - 'RCPT TO:', // -> rcpt - 'RCPT TO:', // -> rcpt (multiple recipients) - 'RSET', // -> ready (reset from rcpt state) - 'MAIL FROM:', // -> mail (fresh transaction) - 'RCPT TO:', // -> rcpt - 'DATA', // -> data - '.', // -> ready (complete transaction) - 'QUIT' - ]; - - let commandIndex = 0; - - client.on('data', (data) => { - const response = data.toString().trim(); - console.log(` Response: ${response}`); - - if (commandIndex < commands.length) { - setTimeout(() => { - const command = commands[commandIndex]; - console.log(` Sending: ${command}`); - if (command === 'DATA') { - client.write(command + '\r\n'); - // Send message content immediately after DATA - setTimeout(() => { - client.write('Subject: RSET test\r\n\r\nTesting RSET state transitions.\r\n.\r\n'); - }, 100); - } else { - client.write(command + '\r\n'); - } - commandIndex++; - }, 100); - } else { - client.end(); - } - }); - - await new Promise((resolve, reject) => { - client.on('end', () => { - console.log(' RSET state transitions completed successfully'); - resolve(void 0); - }); - client.on('error', reject); - }); - - await testServer.server.close(); - })(); - - // Scenario 5: Connection state persistence - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing connection state persistence`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 statemachine.example.com ESMTP\r\n'); - - let state = 'ready'; - let messageCount = 0; - - socket.on('data', (data) => { - const command = data.toString().trim(); - - if (command.startsWith('EHLO')) { - socket.write('250-statemachine.example.com\r\n'); - socket.write('250 PIPELINING\r\n'); - state = 'ready'; - } else if (command.startsWith('MAIL FROM:')) { - if (state === 'ready') { - socket.write('250 OK\r\n'); - state = 'mail'; - } else { - socket.write('503 5.5.1 Bad sequence\r\n'); - } - } else if (command.startsWith('RCPT TO:')) { - if (state === 'mail' || state === 'rcpt') { - socket.write('250 OK\r\n'); - state = 'rcpt'; - } else { - socket.write('503 5.5.1 Bad sequence\r\n'); - } - } else if (command === 'DATA') { - if (state === 'rcpt') { - socket.write('354 Start mail input\r\n'); - state = 'data'; - } else { - socket.write('503 5.5.1 Bad sequence\r\n'); - } - } else if (command === '.') { - if (state === 'data') { - messageCount++; - console.log(` [Server] Message ${messageCount} completed`); - socket.write(`250 OK: Message ${messageCount} accepted\r\n`); - state = 'ready'; - } - } else if (command === 'QUIT') { - console.log(` [Server] Session ended after ${messageCount} messages`); - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - pool: true, - maxConnections: 1 - }); - - // Send multiple emails through same connection - for (let i = 1; i <= 3; i++) { - const email = new Email({ - from: 'sender@example.com', - to: [`recipient${i}@example.com`], - subject: `Persistence test ${i}`, - text: `Testing connection state persistence - message ${i}` - }); - - const result = await smtpClient.sendMail(email); - console.log(` Message ${i} sent successfully`); - expect(result).toBeDefined(); - expect(result.response).toContain(`Message ${i}`); - } - - // Close the pooled connection - await smtpClient.close(); - await testServer.server.close(); - })(); - - // Scenario 6: Error state recovery - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing error state recovery`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 statemachine.example.com ESMTP\r\n'); - - let state = 'ready'; - let errorCount = 0; - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] State: ${state}, Command: ${command}`); - - if (command.startsWith('EHLO')) { - socket.write('250 statemachine.example.com\r\n'); - state = 'ready'; - errorCount = 0; // Reset error count on new session - } else if (command.startsWith('MAIL FROM:')) { - const address = command.match(/<(.+)>/)?.[1] || ''; - if (address.includes('error')) { - errorCount++; - console.log(` [Server] Error ${errorCount} - invalid sender`); - socket.write('550 5.1.8 Invalid sender address\r\n'); - // State remains ready after error - } else { - socket.write('250 OK\r\n'); - state = 'mail'; - } - } else if (command.startsWith('RCPT TO:')) { - if (state === 'mail' || state === 'rcpt') { - const address = command.match(/<(.+)>/)?.[1] || ''; - if (address.includes('error')) { - errorCount++; - console.log(` [Server] Error ${errorCount} - invalid recipient`); - socket.write('550 5.1.1 User unknown\r\n'); - // State remains the same after recipient error - } else { - socket.write('250 OK\r\n'); - state = 'rcpt'; - } - } else { - socket.write('503 5.5.1 Bad sequence\r\n'); - } - } else if (command === 'DATA') { - if (state === 'rcpt') { - socket.write('354 Start mail input\r\n'); - state = 'data'; - } else { - socket.write('503 5.5.1 Bad sequence\r\n'); - } - } else if (command === '.') { - if (state === 'data') { - socket.write('250 OK\r\n'); - state = 'ready'; - } - } else if (command === 'RSET') { - console.log(` [Server] RSET - recovering from errors (${errorCount} errors so far)`); - socket.write('250 OK\r\n'); - state = 'ready'; - } else if (command === 'QUIT') { - console.log(` [Server] Session ended with ${errorCount} total errors`); - socket.write('221 Bye\r\n'); - socket.end(); - } else { - socket.write('500 5.5.1 Command not recognized\r\n'); - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test recovery from various errors - const testEmails = [ - { - from: 'error@example.com', // Will cause sender error - to: ['valid@example.com'], - desc: 'invalid sender' - }, - { - from: 'valid@example.com', - to: ['error@example.com', 'valid@example.com'], // Mixed valid/invalid recipients - desc: 'mixed recipients' - }, - { - from: 'valid@example.com', - to: ['valid@example.com'], - desc: 'valid email after errors' - } - ]; - - for (const testEmail of testEmails) { - console.log(` Testing ${testEmail.desc}...`); - - const email = new Email({ - from: testEmail.from, - to: testEmail.to, - subject: `Error recovery test: ${testEmail.desc}`, - text: `Testing error state recovery with ${testEmail.desc}` - }); - - try { - const result = await smtpClient.sendMail(email); - console.log(` ${testEmail.desc}: Success`); - if (result.rejected && result.rejected.length > 0) { - console.log(` Rejected: ${result.rejected.length} recipients`); - } - } catch (error) { - console.log(` ${testEmail.desc}: Failed as expected - ${error.message}`); - } - } - - await testServer.server.close(); - })(); - - console.log(`\n${testId}: All ${scenarioCount} state machine scenarios tested ✓`); -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_rfc-compliance/test.crfc-06.protocol-negotiation.ts b/test/suite/smtpclient_rfc-compliance/test.crfc-06.protocol-negotiation.ts deleted file mode 100644 index ca3ceb6..0000000 --- a/test/suite/smtpclient_rfc-compliance/test.crfc-06.protocol-negotiation.ts +++ /dev/null @@ -1,688 +0,0 @@ -import { expect, tap } from '@git.zone/tstest/tapbundle'; -import { createTestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/index.js'; - -tap.test('CRFC-06: should handle protocol negotiation correctly (RFC 5321)', async (tools) => { - const testId = 'CRFC-06-protocol-negotiation'; - console.log(`\n${testId}: Testing SMTP protocol negotiation compliance...`); - - let scenarioCount = 0; - - // Scenario 1: EHLO capability announcement and selection - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing EHLO capability announcement`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 negotiation.example.com ESMTP Service Ready\r\n'); - - let negotiatedCapabilities: string[] = []; - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO')) { - // Announce available capabilities - socket.write('250-negotiation.example.com\r\n'); - socket.write('250-SIZE 52428800\r\n'); - socket.write('250-8BITMIME\r\n'); - socket.write('250-STARTTLS\r\n'); - socket.write('250-ENHANCEDSTATUSCODES\r\n'); - socket.write('250-PIPELINING\r\n'); - socket.write('250-CHUNKING\r\n'); - socket.write('250-SMTPUTF8\r\n'); - socket.write('250-DSN\r\n'); - socket.write('250-AUTH PLAIN LOGIN CRAM-MD5\r\n'); - socket.write('250 HELP\r\n'); - - negotiatedCapabilities = [ - 'SIZE', '8BITMIME', 'STARTTLS', 'ENHANCEDSTATUSCODES', - 'PIPELINING', 'CHUNKING', 'SMTPUTF8', 'DSN', 'AUTH', 'HELP' - ]; - console.log(` [Server] Announced capabilities: ${negotiatedCapabilities.join(', ')}`); - } else if (command.startsWith('HELO')) { - // Basic SMTP mode - no capabilities - socket.write('250 negotiation.example.com\r\n'); - negotiatedCapabilities = []; - console.log(' [Server] Basic SMTP mode (no capabilities)'); - } else if (command.startsWith('MAIL FROM:')) { - // Check for SIZE parameter - const sizeMatch = command.match(/SIZE=(\d+)/i); - if (sizeMatch && negotiatedCapabilities.includes('SIZE')) { - const size = parseInt(sizeMatch[1]); - console.log(` [Server] SIZE parameter used: ${size} bytes`); - if (size > 52428800) { - socket.write('552 5.3.4 Message size exceeds maximum\r\n'); - } else { - socket.write('250 2.1.0 Sender OK\r\n'); - } - } else if (sizeMatch && !negotiatedCapabilities.includes('SIZE')) { - console.log(' [Server] SIZE parameter used without capability'); - socket.write('501 5.5.4 SIZE not supported\r\n'); - } else { - socket.write('250 2.1.0 Sender OK\r\n'); - } - } else if (command.startsWith('RCPT TO:')) { - // Check for DSN parameters - if (command.includes('NOTIFY=') && negotiatedCapabilities.includes('DSN')) { - console.log(' [Server] DSN NOTIFY parameter used'); - } else if (command.includes('NOTIFY=') && !negotiatedCapabilities.includes('DSN')) { - console.log(' [Server] DSN parameter used without capability'); - socket.write('501 5.5.4 DSN not supported\r\n'); - return; - } - socket.write('250 2.1.5 Recipient OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - } else if (command === '.') { - socket.write('250 2.0.0 Message accepted\r\n'); - } else if (command === 'QUIT') { - socket.write('221 2.0.0 Bye\r\n'); - socket.end(); - } - }); - } - }); - - // Test EHLO negotiation - const esmtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Capability negotiation test', - text: 'Testing EHLO capability announcement and usage' - }); - - const result = await esmtpClient.sendMail(email); - console.log(' EHLO capability negotiation successful'); - expect(result).toBeDefined(); - - await testServer.server.close(); - })(); - - // Scenario 2: Capability-based feature usage - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing capability-based feature usage`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 features.example.com ESMTP\r\n'); - - let supportsUTF8 = false; - let supportsPipelining = false; - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO')) { - socket.write('250-features.example.com\r\n'); - socket.write('250-SMTPUTF8\r\n'); - socket.write('250-PIPELINING\r\n'); - socket.write('250-8BITMIME\r\n'); - socket.write('250 SIZE 10485760\r\n'); - - supportsUTF8 = true; - supportsPipelining = true; - console.log(' [Server] UTF8 and PIPELINING capabilities announced'); - } else if (command.startsWith('MAIL FROM:')) { - // Check for SMTPUTF8 parameter - if (command.includes('SMTPUTF8') && supportsUTF8) { - console.log(' [Server] SMTPUTF8 parameter accepted'); - socket.write('250 OK\r\n'); - } else if (command.includes('SMTPUTF8') && !supportsUTF8) { - console.log(' [Server] SMTPUTF8 used without capability'); - socket.write('555 5.6.7 SMTPUTF8 not supported\r\n'); - } else { - socket.write('250 OK\r\n'); - } - } else if (command.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - } else if (command === '.') { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test with UTF-8 content - const utf8Email = new Email({ - from: 'sénder@example.com', // Non-ASCII sender - to: ['recipient@example.com'], - subject: 'UTF-8 test: café, naïve, 你好', - text: 'Testing SMTPUTF8 capability with international characters: émojis 🎉' - }); - - const result = await smtpClient.sendMail(utf8Email); - console.log(' UTF-8 email sent using SMTPUTF8 capability'); - expect(result).toBeDefined(); - - await testServer.server.close(); - })(); - - // Scenario 3: Extension parameter validation - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing extension parameter validation`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 validation.example.com ESMTP\r\n'); - - const supportedExtensions = new Set(['SIZE', 'BODY', 'DSN', '8BITMIME']); - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO')) { - socket.write('250-validation.example.com\r\n'); - socket.write('250-SIZE 5242880\r\n'); - socket.write('250-8BITMIME\r\n'); - socket.write('250-DSN\r\n'); - socket.write('250 OK\r\n'); - } else if (command.startsWith('MAIL FROM:')) { - // Validate all ESMTP parameters - const params = command.substring(command.indexOf('>') + 1).trim(); - if (params) { - console.log(` [Server] Validating parameters: ${params}`); - - const paramPairs = params.split(/\s+/).filter(p => p.length > 0); - let allValid = true; - - for (const param of paramPairs) { - const [key, value] = param.split('='); - - if (key === 'SIZE') { - const size = parseInt(value || '0'); - if (isNaN(size) || size < 0) { - socket.write('501 5.5.4 Invalid SIZE value\r\n'); - allValid = false; - break; - } else if (size > 5242880) { - socket.write('552 5.3.4 Message size exceeds limit\r\n'); - allValid = false; - break; - } - console.log(` [Server] SIZE=${size} validated`); - } else if (key === 'BODY') { - if (value !== '7BIT' && value !== '8BITMIME') { - socket.write('501 5.5.4 Invalid BODY value\r\n'); - allValid = false; - break; - } - console.log(` [Server] BODY=${value} validated`); - } else if (key === 'RET') { - if (value !== 'FULL' && value !== 'HDRS') { - socket.write('501 5.5.4 Invalid RET value\r\n'); - allValid = false; - break; - } - console.log(` [Server] RET=${value} validated`); - } else if (key === 'ENVID') { - // ENVID can be any string, just check format - if (!value) { - socket.write('501 5.5.4 ENVID requires value\r\n'); - allValid = false; - break; - } - console.log(` [Server] ENVID=${value} validated`); - } else { - console.log(` [Server] Unknown parameter: ${key}`); - socket.write(`555 5.5.4 Unsupported parameter: ${key}\r\n`); - allValid = false; - break; - } - } - - if (allValid) { - socket.write('250 OK\r\n'); - } - } else { - socket.write('250 OK\r\n'); - } - } else if (command.startsWith('RCPT TO:')) { - // Validate DSN parameters - const params = command.substring(command.indexOf('>') + 1).trim(); - if (params) { - const paramPairs = params.split(/\s+/).filter(p => p.length > 0); - let allValid = true; - - for (const param of paramPairs) { - const [key, value] = param.split('='); - - if (key === 'NOTIFY') { - const notifyValues = value.split(','); - const validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY']; - - for (const nv of notifyValues) { - if (!validNotify.includes(nv)) { - socket.write('501 5.5.4 Invalid NOTIFY value\r\n'); - allValid = false; - break; - } - } - - if (allValid) { - console.log(` [Server] NOTIFY=${value} validated`); - } - } else if (key === 'ORCPT') { - // ORCPT format: addr-type;addr-value - if (!value.includes(';')) { - socket.write('501 5.5.4 Invalid ORCPT format\r\n'); - allValid = false; - break; - } - console.log(` [Server] ORCPT=${value} validated`); - } else { - socket.write(`555 5.5.4 Unsupported RCPT parameter: ${key}\r\n`); - allValid = false; - break; - } - } - - if (allValid) { - socket.write('250 OK\r\n'); - } - } else { - socket.write('250 OK\r\n'); - } - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - } else if (command === '.') { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test with various valid parameters - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Parameter validation test', - text: 'Testing ESMTP parameter validation', - dsn: { - notify: ['SUCCESS', 'FAILURE'], - envid: 'test-envelope-id-123', - ret: 'FULL' - } - }); - - const result = await smtpClient.sendMail(email); - console.log(' ESMTP parameter validation successful'); - expect(result).toBeDefined(); - - await testServer.server.close(); - })(); - - // Scenario 4: Service extension discovery - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing service extension discovery`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 discovery.example.com ESMTP Ready\r\n'); - - let clientName = ''; - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO ')) { - clientName = command.substring(5); - console.log(` [Server] Client identified as: ${clientName}`); - - // Announce extensions in order of preference - socket.write('250-discovery.example.com\r\n'); - - // Security extensions first - socket.write('250-STARTTLS\r\n'); - socket.write('250-AUTH PLAIN LOGIN CRAM-MD5 DIGEST-MD5\r\n'); - - // Core functionality extensions - socket.write('250-SIZE 104857600\r\n'); - socket.write('250-8BITMIME\r\n'); - socket.write('250-SMTPUTF8\r\n'); - - // Delivery extensions - socket.write('250-DSN\r\n'); - socket.write('250-DELIVERBY 86400\r\n'); - - // Performance extensions - socket.write('250-PIPELINING\r\n'); - socket.write('250-CHUNKING\r\n'); - socket.write('250-BINARYMIME\r\n'); - - // Enhanced status and debugging - socket.write('250-ENHANCEDSTATUSCODES\r\n'); - socket.write('250-NO-SOLICITING\r\n'); - socket.write('250-MTRK\r\n'); - - // End with help - socket.write('250 HELP\r\n'); - } else if (command.startsWith('HELO ')) { - clientName = command.substring(5); - console.log(` [Server] Basic SMTP client: ${clientName}`); - socket.write('250 discovery.example.com\r\n'); - } else if (command.startsWith('MAIL FROM:')) { - // Client should use discovered capabilities appropriately - socket.write('250 OK\r\n'); - } else if (command.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - } else if (command === '.') { - socket.write('250 OK\r\n'); - } else if (command === 'HELP') { - // Detailed help for discovered extensions - socket.write('214-This server supports the following features:\r\n'); - socket.write('214-STARTTLS - Start TLS negotiation\r\n'); - socket.write('214-AUTH - SMTP Authentication\r\n'); - socket.write('214-SIZE - Message size declaration\r\n'); - socket.write('214-8BITMIME - 8-bit MIME transport\r\n'); - socket.write('214-SMTPUTF8 - UTF-8 support\r\n'); - socket.write('214-DSN - Delivery Status Notifications\r\n'); - socket.write('214-PIPELINING - Command pipelining\r\n'); - socket.write('214-CHUNKING - BDAT chunking\r\n'); - socket.write('214 For more information, visit our website\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Thank you for using our service\r\n'); - socket.end(); - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - name: 'test-client.example.com' - }); - - // Test service discovery - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Service discovery test', - text: 'Testing SMTP service extension discovery' - }); - - const result = await smtpClient.sendMail(email); - console.log(' Service extension discovery completed'); - expect(result).toBeDefined(); - - await testServer.server.close(); - })(); - - // Scenario 5: Backward compatibility negotiation - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing backward compatibility negotiation`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 compat.example.com ESMTP\r\n'); - - let isESMTP = false; - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO')) { - isESMTP = true; - console.log(' [Server] ESMTP mode enabled'); - socket.write('250-compat.example.com\r\n'); - socket.write('250-SIZE 10485760\r\n'); - socket.write('250-8BITMIME\r\n'); - socket.write('250 ENHANCEDSTATUSCODES\r\n'); - } else if (command.startsWith('HELO')) { - isESMTP = false; - console.log(' [Server] Basic SMTP mode (RFC 821 compatibility)'); - socket.write('250 compat.example.com\r\n'); - } else if (command.startsWith('MAIL FROM:')) { - if (isESMTP) { - // Accept ESMTP parameters - if (command.includes('SIZE=') || command.includes('BODY=')) { - console.log(' [Server] ESMTP parameters accepted'); - } - socket.write('250 2.1.0 Sender OK\r\n'); - } else { - // Basic SMTP - reject ESMTP parameters - if (command.includes('SIZE=') || command.includes('BODY=')) { - console.log(' [Server] ESMTP parameters rejected in basic mode'); - socket.write('501 5.5.4 Syntax error in parameters\r\n'); - } else { - socket.write('250 Sender OK\r\n'); - } - } - } else if (command.startsWith('RCPT TO:')) { - if (isESMTP) { - socket.write('250 2.1.5 Recipient OK\r\n'); - } else { - socket.write('250 Recipient OK\r\n'); - } - } else if (command === 'DATA') { - if (isESMTP) { - socket.write('354 2.0.0 Start mail input\r\n'); - } else { - socket.write('354 Start mail input\r\n'); - } - } else if (command === '.') { - if (isESMTP) { - socket.write('250 2.0.0 Message accepted\r\n'); - } else { - socket.write('250 Message accepted\r\n'); - } - } else if (command === 'QUIT') { - if (isESMTP) { - socket.write('221 2.0.0 Service closing\r\n'); - } else { - socket.write('221 Service closing\r\n'); - } - socket.end(); - } - }); - } - }); - - // Test ESMTP mode - const esmtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - const esmtpEmail = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'ESMTP compatibility test', - text: 'Testing ESMTP mode with extensions' - }); - - const esmtpResult = await esmtpClient.sendMail(esmtpEmail); - console.log(' ESMTP mode negotiation successful'); - expect(esmtpResult.response).toContain('2.0.0'); - - // Test basic SMTP mode (fallback) - const basicClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - disableESMTP: true // Force HELO instead of EHLO - }); - - const basicEmail = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Basic SMTP compatibility test', - text: 'Testing basic SMTP mode without extensions' - }); - - const basicResult = await basicClient.sendMail(basicEmail); - console.log(' Basic SMTP mode fallback successful'); - expect(basicResult.response).not.toContain('2.0.0'); // No enhanced status codes - - await testServer.server.close(); - })(); - - // Scenario 6: Extension interdependencies - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing extension interdependencies`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 interdep.example.com ESMTP\r\n'); - - let tlsEnabled = false; - let authenticated = false; - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] Received: ${command} (TLS: ${tlsEnabled}, Auth: ${authenticated})`); - - if (command.startsWith('EHLO')) { - socket.write('250-interdep.example.com\r\n'); - - if (!tlsEnabled) { - // Before TLS - socket.write('250-STARTTLS\r\n'); - socket.write('250-SIZE 1048576\r\n'); // Limited size before TLS - } else { - // After TLS - socket.write('250-SIZE 52428800\r\n'); // Larger size after TLS - socket.write('250-8BITMIME\r\n'); - socket.write('250-SMTPUTF8\r\n'); - socket.write('250-AUTH PLAIN LOGIN CRAM-MD5\r\n'); - - if (authenticated) { - // Additional capabilities after authentication - socket.write('250-DSN\r\n'); - socket.write('250-DELIVERBY 86400\r\n'); - } - } - - socket.write('250 ENHANCEDSTATUSCODES\r\n'); - } else if (command === 'STARTTLS') { - if (!tlsEnabled) { - socket.write('220 2.0.0 Ready to start TLS\r\n'); - tlsEnabled = true; - console.log(' [Server] TLS enabled (simulated)'); - // In real implementation, would upgrade to TLS here - } else { - socket.write('503 5.5.1 TLS already active\r\n'); - } - } else if (command.startsWith('AUTH')) { - if (tlsEnabled) { - authenticated = true; - console.log(' [Server] Authentication successful (simulated)'); - socket.write('235 2.7.0 Authentication successful\r\n'); - } else { - console.log(' [Server] AUTH rejected - TLS required'); - socket.write('538 5.7.11 Encryption required for authentication\r\n'); - } - } else if (command.startsWith('MAIL FROM:')) { - if (command.includes('SMTPUTF8') && !tlsEnabled) { - console.log(' [Server] SMTPUTF8 requires TLS'); - socket.write('530 5.7.0 Must issue STARTTLS first\r\n'); - } else { - socket.write('250 OK\r\n'); - } - } else if (command.startsWith('RCPT TO:')) { - if (command.includes('NOTIFY=') && !authenticated) { - console.log(' [Server] DSN requires authentication'); - socket.write('530 5.7.0 Authentication required for DSN\r\n'); - } else { - socket.write('250 OK\r\n'); - } - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - } else if (command === '.') { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - } - }); - - // Test extension dependencies - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - requireTLS: true, // This will trigger STARTTLS - auth: { - user: 'testuser', - pass: 'testpass' - } - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Extension interdependency test', - text: 'Testing SMTP extension interdependencies', - dsn: { - notify: ['SUCCESS'], - envid: 'interdep-test-123' - } - }); - - try { - const result = await smtpClient.sendMail(email); - console.log(' Extension interdependency handling successful'); - expect(result).toBeDefined(); - } catch (error) { - console.log(` Extension dependency error (expected in test): ${error.message}`); - // In test environment, STARTTLS won't actually work - } - - await testServer.server.close(); - })(); - - console.log(`\n${testId}: All ${scenarioCount} protocol negotiation scenarios tested ✓`); -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_rfc-compliance/test.crfc-07.interoperability.ts b/test/suite/smtpclient_rfc-compliance/test.crfc-07.interoperability.ts deleted file mode 100644 index 9567c6e..0000000 --- a/test/suite/smtpclient_rfc-compliance/test.crfc-07.interoperability.ts +++ /dev/null @@ -1,728 +0,0 @@ -import { expect, tap } from '@git.zone/tstest/tapbundle'; -import { createTestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/index.js'; - -tap.test('CRFC-07: should ensure SMTP interoperability (RFC 5321)', async (tools) => { - const testId = 'CRFC-07-interoperability'; - console.log(`\n${testId}: Testing SMTP interoperability compliance...`); - - let scenarioCount = 0; - - // Scenario 1: Different server implementations compatibility - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing different server implementations`); - - const serverImplementations = [ - { - name: 'Sendmail-style', - greeting: '220 mail.example.com ESMTP Sendmail 8.15.2/8.15.2; Date Time', - ehloResponse: [ - '250-mail.example.com Hello client.example.com [192.168.1.100]', - '250-ENHANCEDSTATUSCODES', - '250-PIPELINING', - '250-8BITMIME', - '250-SIZE 36700160', - '250-DSN', - '250-ETRN', - '250-DELIVERBY', - '250 HELP' - ], - quirks: { verboseResponses: true, includesTimestamp: true } - }, - { - name: 'Postfix-style', - greeting: '220 mail.example.com ESMTP Postfix', - ehloResponse: [ - '250-mail.example.com', - '250-PIPELINING', - '250-SIZE 10240000', - '250-VRFY', - '250-ETRN', - '250-STARTTLS', - '250-ENHANCEDSTATUSCODES', - '250-8BITMIME', - '250-DSN', - '250 SMTPUTF8' - ], - quirks: { shortResponses: true, strictSyntax: true } - }, - { - name: 'Exchange-style', - greeting: '220 mail.example.com Microsoft ESMTP MAIL Service ready', - ehloResponse: [ - '250-mail.example.com Hello [192.168.1.100]', - '250-SIZE 37748736', - '250-PIPELINING', - '250-DSN', - '250-ENHANCEDSTATUSCODES', - '250-STARTTLS', - '250-8BITMIME', - '250-BINARYMIME', - '250-CHUNKING', - '250 OK' - ], - quirks: { windowsLineEndings: true, detailedErrors: true } - } - ]; - - for (const impl of serverImplementations) { - console.log(`\n Testing with ${impl.name} server...`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(` [${impl.name}] Client connected`); - socket.write(impl.greeting + '\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [${impl.name}] Received: ${command}`); - - if (command.startsWith('EHLO')) { - impl.ehloResponse.forEach(line => { - socket.write(line + '\r\n'); - }); - } else if (command.startsWith('MAIL FROM:')) { - if (impl.quirks.strictSyntax && !command.includes('<')) { - socket.write('501 5.5.4 Syntax error in MAIL command\r\n'); - } else { - const response = impl.quirks.verboseResponses ? - '250 2.1.0 Sender OK' : '250 OK'; - socket.write(response + '\r\n'); - } - } else if (command.startsWith('RCPT TO:')) { - const response = impl.quirks.verboseResponses ? - '250 2.1.5 Recipient OK' : '250 OK'; - socket.write(response + '\r\n'); - } else if (command === 'DATA') { - const response = impl.quirks.detailedErrors ? - '354 Start mail input; end with .' : - '354 Enter message, ending with "." on a line by itself'; - socket.write(response + '\r\n'); - } else if (command === '.') { - const timestamp = impl.quirks.includesTimestamp ? - ` at ${new Date().toISOString()}` : ''; - socket.write(`250 2.0.0 Message accepted for delivery${timestamp}\r\n`); - } else if (command === 'QUIT') { - const response = impl.quirks.verboseResponses ? - '221 2.0.0 Service closing transmission channel' : - '221 Bye'; - socket.write(response + '\r\n'); - socket.end(); - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Interoperability test with ${impl.name}`, - text: `Testing compatibility with ${impl.name} server implementation` - }); - - const result = await smtpClient.sendMail(email); - console.log(` ${impl.name} compatibility: Success`); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - await testServer.server.close(); - } - })(); - - // Scenario 2: Character encoding and internationalization - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing character encoding interoperability`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 international.example.com ESMTP\r\n'); - - let supportsUTF8 = false; - - socket.on('data', (data) => { - const command = data.toString(); - console.log(` [Server] Received (${data.length} bytes): ${command.trim()}`); - - if (command.startsWith('EHLO')) { - socket.write('250-international.example.com\r\n'); - socket.write('250-8BITMIME\r\n'); - socket.write('250-SMTPUTF8\r\n'); - socket.write('250 OK\r\n'); - supportsUTF8 = true; - } else if (command.startsWith('MAIL FROM:')) { - // Check for non-ASCII characters - const hasNonASCII = /[^\x00-\x7F]/.test(command); - const hasUTF8Param = command.includes('SMTPUTF8'); - - console.log(` [Server] Non-ASCII: ${hasNonASCII}, UTF8 param: ${hasUTF8Param}`); - - if (hasNonASCII && !hasUTF8Param) { - socket.write('553 5.6.7 Non-ASCII addresses require SMTPUTF8\r\n'); - } else { - socket.write('250 OK\r\n'); - } - } else if (command.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - } else if (command.trim() === 'DATA') { - socket.write('354 Start mail input\r\n'); - } else if (command.trim() === '.') { - socket.write('250 OK: International message accepted\r\n'); - } else if (command.trim() === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test various international character sets - const internationalTests = [ - { - desc: 'Latin characters with accents', - from: 'sénder@éxample.com', - to: 'récipient@éxample.com', - subject: 'Tëst with açcénts', - text: 'Café, naïve, résumé, piñata' - }, - { - desc: 'Cyrillic characters', - from: 'отправитель@пример.com', - to: 'получатель@пример.com', - subject: 'Тест с кириллицей', - text: 'Привет мир! Это тест с русскими буквами.' - }, - { - desc: 'Chinese characters', - from: 'sender@example.com', // ASCII for compatibility - to: 'recipient@example.com', - subject: '测试中文字符', - text: '你好世界!这是一个中文测试。' - }, - { - desc: 'Arabic characters', - from: 'sender@example.com', - to: 'recipient@example.com', - subject: 'اختبار النص العربي', - text: 'مرحبا بالعالم! هذا اختبار باللغة العربية.' - }, - { - desc: 'Emoji and symbols', - from: 'sender@example.com', - to: 'recipient@example.com', - subject: '🎉 Test with emojis 🌟', - text: 'Hello 👋 World 🌍! Testing emojis: 🚀 📧 ✨' - } - ]; - - for (const test of internationalTests) { - console.log(` Testing: ${test.desc}`); - - const email = new Email({ - from: test.from, - to: [test.to], - subject: test.subject, - text: test.text - }); - - try { - const result = await smtpClient.sendMail(email); - console.log(` ${test.desc}: Success`); - expect(result).toBeDefined(); - } catch (error) { - console.log(` ${test.desc}: Failed - ${error.message}`); - // Some may fail if server doesn't support international addresses - } - } - - await testServer.server.close(); - })(); - - // Scenario 3: Message format compatibility - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing message format compatibility`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 formats.example.com ESMTP\r\n'); - - let inData = false; - let messageContent = ''; - - socket.on('data', (data) => { - if (inData) { - messageContent += data.toString(); - if (messageContent.includes('\r\n.\r\n')) { - inData = false; - - // Analyze message format - const headers = messageContent.substring(0, messageContent.indexOf('\r\n\r\n')); - const body = messageContent.substring(messageContent.indexOf('\r\n\r\n') + 4); - - console.log(' [Server] Message analysis:'); - console.log(` Header count: ${(headers.match(/\r\n/g) || []).length + 1}`); - console.log(` Body size: ${body.length} bytes`); - - // Check for proper header folding - const longHeaders = headers.split('\r\n').filter(h => h.length > 78); - if (longHeaders.length > 0) { - console.log(` Long headers detected: ${longHeaders.length}`); - } - - // Check for MIME structure - if (headers.includes('Content-Type:')) { - console.log(' MIME message detected'); - } - - socket.write('250 OK: Message format validated\r\n'); - messageContent = ''; - } - return; - } - - const command = data.toString().trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO')) { - socket.write('250-formats.example.com\r\n'); - socket.write('250-8BITMIME\r\n'); - socket.write('250-BINARYMIME\r\n'); - socket.write('250 SIZE 52428800\r\n'); - } else if (command.startsWith('MAIL FROM:')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - inData = true; - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test different message formats - const formatTests = [ - { - desc: 'Plain text message', - email: new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Plain text test', - text: 'This is a simple plain text message.' - }) - }, - { - desc: 'HTML message', - email: new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'HTML test', - html: '

HTML Message

This is an HTML message.

' - }) - }, - { - desc: 'Multipart alternative', - email: new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Multipart test', - text: 'Plain text version', - html: '

HTML version

' - }) - }, - { - desc: 'Message with attachment', - email: new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Attachment test', - text: 'Message with attachment', - attachments: [{ - filename: 'test.txt', - content: 'This is a test attachment' - }] - }) - }, - { - desc: 'Message with custom headers', - email: new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Custom headers test', - text: 'Message with custom headers', - headers: { - 'X-Custom-Header': 'Custom value', - 'X-Mailer': 'Test Mailer 1.0', - 'Message-ID': '', - 'References': ' ' - } - }) - } - ]; - - for (const test of formatTests) { - console.log(` Testing: ${test.desc}`); - - const result = await smtpClient.sendMail(test.email); - console.log(` ${test.desc}: Success`); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - } - - await testServer.server.close(); - })(); - - // Scenario 4: Error handling interoperability - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing error handling interoperability`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 errors.example.com ESMTP\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO')) { - socket.write('250-errors.example.com\r\n'); - socket.write('250-ENHANCEDSTATUSCODES\r\n'); - socket.write('250 OK\r\n'); - } else if (command.startsWith('MAIL FROM:')) { - const address = command.match(/<(.+)>/)?.[1] || ''; - - if (address.includes('temp-fail')) { - // Temporary failure - client should retry - socket.write('451 4.7.1 Temporary system problem, try again later\r\n'); - } else if (address.includes('perm-fail')) { - // Permanent failure - client should not retry - socket.write('550 5.1.8 Invalid sender address format\r\n'); - } else if (address.includes('syntax-error')) { - // Syntax error - socket.write('501 5.5.4 Syntax error in MAIL command\r\n'); - } else { - socket.write('250 OK\r\n'); - } - } else if (command.startsWith('RCPT TO:')) { - const address = command.match(/<(.+)>/)?.[1] || ''; - - if (address.includes('unknown')) { - socket.write('550 5.1.1 User unknown in local recipient table\r\n'); - } else if (address.includes('temp-reject')) { - socket.write('450 4.2.1 Mailbox temporarily unavailable\r\n'); - } else if (address.includes('quota-exceeded')) { - socket.write('552 5.2.2 Mailbox over quota\r\n'); - } else { - socket.write('250 OK\r\n'); - } - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - } else if (command === '.') { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } else { - // Unknown command - socket.write('500 5.5.1 Command unrecognized\r\n'); - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test various error scenarios - const errorTests = [ - { - desc: 'Temporary sender failure', - from: 'temp-fail@example.com', - to: 'valid@example.com', - expectError: true, - errorType: '4xx' - }, - { - desc: 'Permanent sender failure', - from: 'perm-fail@example.com', - to: 'valid@example.com', - expectError: true, - errorType: '5xx' - }, - { - desc: 'Unknown recipient', - from: 'valid@example.com', - to: 'unknown@example.com', - expectError: true, - errorType: '5xx' - }, - { - desc: 'Mixed valid/invalid recipients', - from: 'valid@example.com', - to: ['valid@example.com', 'unknown@example.com', 'temp-reject@example.com'], - expectError: false, // Partial success - errorType: 'mixed' - } - ]; - - for (const test of errorTests) { - console.log(` Testing: ${test.desc}`); - - const email = new Email({ - from: test.from, - to: Array.isArray(test.to) ? test.to : [test.to], - subject: `Error test: ${test.desc}`, - text: `Testing error handling for ${test.desc}` - }); - - try { - const result = await smtpClient.sendMail(email); - - if (test.expectError && test.errorType !== 'mixed') { - console.log(` Unexpected success for ${test.desc}`); - } else { - console.log(` ${test.desc}: Handled correctly`); - if (result.rejected && result.rejected.length > 0) { - console.log(` Rejected: ${result.rejected.length} recipients`); - } - if (result.accepted && result.accepted.length > 0) { - console.log(` Accepted: ${result.accepted.length} recipients`); - } - } - } catch (error) { - if (test.expectError) { - console.log(` ${test.desc}: Failed as expected (${error.responseCode})`); - if (test.errorType === '4xx') { - expect(error.responseCode).toBeGreaterThanOrEqual(400); - expect(error.responseCode).toBeLessThan(500); - } else if (test.errorType === '5xx') { - expect(error.responseCode).toBeGreaterThanOrEqual(500); - expect(error.responseCode).toBeLessThan(600); - } - } else { - console.log(` Unexpected error for ${test.desc}: ${error.message}`); - } - } - } - - await testServer.server.close(); - })(); - - // Scenario 5: Connection management interoperability - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing connection management interoperability`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - - let commandCount = 0; - let idleTime = Date.now(); - const maxIdleTime = 5000; // 5 seconds for testing - const maxCommands = 10; - - socket.write('220 connection.example.com ESMTP\r\n'); - - // Set up idle timeout - const idleCheck = setInterval(() => { - if (Date.now() - idleTime > maxIdleTime) { - console.log(' [Server] Idle timeout - closing connection'); - socket.write('421 4.4.2 Idle timeout, closing connection\r\n'); - socket.end(); - clearInterval(idleCheck); - } - }, 1000); - - socket.on('data', (data) => { - const command = data.toString().trim(); - commandCount++; - idleTime = Date.now(); - - console.log(` [Server] Command ${commandCount}: ${command}`); - - if (commandCount > maxCommands) { - console.log(' [Server] Too many commands - closing connection'); - socket.write('421 4.7.0 Too many commands, closing connection\r\n'); - socket.end(); - clearInterval(idleCheck); - return; - } - - if (command.startsWith('EHLO')) { - socket.write('250-connection.example.com\r\n'); - socket.write('250-PIPELINING\r\n'); - 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('250 OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - } else if (command === '.') { - socket.write('250 OK\r\n'); - } else if (command === 'RSET') { - socket.write('250 OK\r\n'); - } else if (command === 'NOOP') { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - clearInterval(idleCheck); - } - }); - - socket.on('close', () => { - clearInterval(idleCheck); - console.log(` [Server] Connection closed after ${commandCount} commands`); - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - pool: true, - maxConnections: 1 - }); - - // Test connection reuse - console.log(' Testing connection reuse...'); - - for (let i = 1; i <= 3; i++) { - const email = new Email({ - from: 'sender@example.com', - to: [`recipient${i}@example.com`], - subject: `Connection test ${i}`, - text: `Testing connection management - email ${i}` - }); - - const result = await smtpClient.sendMail(email); - console.log(` Email ${i} sent successfully`); - expect(result).toBeDefined(); - - // Small delay to test connection persistence - await new Promise(resolve => setTimeout(resolve, 500)); - } - - // Test NOOP for keeping connection alive - console.log(' Testing connection keep-alive...'); - - await smtpClient.verify(); // This might send NOOP - console.log(' Connection verified (keep-alive)'); - - await smtpClient.close(); - await testServer.server.close(); - })(); - - // Scenario 6: Legacy SMTP compatibility - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing legacy SMTP compatibility`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Legacy SMTP server'); - - // Old-style greeting without ESMTP - socket.write('220 legacy.example.com Simple Mail Transfer Service Ready\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO')) { - // Legacy server doesn't understand EHLO - socket.write('500 Command unrecognized\r\n'); - } else if (command.startsWith('HELO')) { - socket.write('250 legacy.example.com\r\n'); - } else if (command.startsWith('MAIL FROM:')) { - // Very strict syntax checking - if (!command.match(/^MAIL FROM:\s*<[^>]+>\s*$/)) { - socket.write('501 Syntax error\r\n'); - } else { - socket.write('250 Sender OK\r\n'); - } - } else if (command.startsWith('RCPT TO:')) { - if (!command.match(/^RCPT TO:\s*<[^>]+>\s*$/)) { - socket.write('501 Syntax error\r\n'); - } else { - socket.write('250 Recipient OK\r\n'); - } - } else if (command === 'DATA') { - socket.write('354 Enter mail, end with "." on a line by itself\r\n'); - } else if (command === '.') { - socket.write('250 Message accepted for delivery\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Service closing transmission channel\r\n'); - socket.end(); - } else if (command === 'HELP') { - socket.write('214-Commands supported:\r\n'); - socket.write('214-HELO MAIL RCPT DATA QUIT HELP\r\n'); - socket.write('214 End of HELP info\r\n'); - } else { - socket.write('500 Command unrecognized\r\n'); - } - }); - } - }); - - // Test with client that can fall back to basic SMTP - const legacyClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - disableESMTP: true // Force HELO mode - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Legacy compatibility test', - text: 'Testing compatibility with legacy SMTP servers' - }); - - const result = await legacyClient.sendMail(email); - console.log(' Legacy SMTP compatibility: Success'); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - await testServer.server.close(); - })(); - - console.log(`\n${testId}: All ${scenarioCount} interoperability scenarios tested ✓`); -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_rfc-compliance/test.crfc-08.smtp-extensions.ts b/test/suite/smtpclient_rfc-compliance/test.crfc-08.smtp-extensions.ts deleted file mode 100644 index d99f41b..0000000 --- a/test/suite/smtpclient_rfc-compliance/test.crfc-08.smtp-extensions.ts +++ /dev/null @@ -1,656 +0,0 @@ -import { expect, tap } from '@git.zone/tstest/tapbundle'; -import { createTestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/index.js'; - -tap.test('CRFC-08: should handle SMTP extensions correctly (Various RFCs)', async (tools) => { - const testId = 'CRFC-08-smtp-extensions'; - console.log(`\n${testId}: Testing SMTP extensions compliance...`); - - let scenarioCount = 0; - - // Scenario 1: CHUNKING extension (RFC 3030) - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing CHUNKING extension (RFC 3030)`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 chunking.example.com ESMTP\r\n'); - - let chunkingMode = false; - let totalChunks = 0; - let totalBytes = 0; - - socket.on('data', (data) => { - const text = data.toString(); - - if (chunkingMode) { - // In chunking mode, all data is message content - totalBytes += data.length; - console.log(` [Server] Received chunk: ${data.length} bytes`); - return; - } - - const command = text.trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO')) { - socket.write('250-chunking.example.com\r\n'); - socket.write('250-CHUNKING\r\n'); - socket.write('250-8BITMIME\r\n'); - socket.write('250-BINARYMIME\r\n'); - socket.write('250 OK\r\n'); - } else if (command.startsWith('MAIL FROM:')) { - if (command.includes('BODY=BINARYMIME')) { - console.log(' [Server] Binary MIME body declared'); - } - socket.write('250 OK\r\n'); - } else if (command.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('BDAT ')) { - // BDAT command format: BDAT [LAST] - const parts = command.split(' '); - const chunkSize = parseInt(parts[1]); - const isLast = parts.includes('LAST'); - - totalChunks++; - console.log(` [Server] BDAT chunk ${totalChunks}: ${chunkSize} bytes${isLast ? ' (LAST)' : ''}`); - - if (isLast) { - socket.write(`250 OK: Message accepted, ${totalChunks} chunks, ${totalBytes} total bytes\r\n`); - chunkingMode = false; - totalChunks = 0; - totalBytes = 0; - } else { - socket.write('250 OK: Chunk accepted\r\n'); - chunkingMode = true; - } - } else if (command === 'DATA') { - // DATA not allowed when CHUNKING is available - socket.write('503 5.5.1 Use BDAT instead of DATA\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test with binary content that would benefit from chunking - const binaryContent = Buffer.alloc(1024); - for (let i = 0; i < binaryContent.length; i++) { - binaryContent[i] = i % 256; - } - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'CHUNKING test', - text: 'Testing CHUNKING extension with binary data', - attachments: [{ - filename: 'binary-data.bin', - content: binaryContent - }] - }); - - const result = await smtpClient.sendMail(email); - console.log(' CHUNKING extension handled (if supported by client)'); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - await testServer.server.close(); - })(); - - // Scenario 2: DELIVERBY extension (RFC 2852) - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing DELIVERBY extension (RFC 2852)`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 deliverby.example.com ESMTP\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO')) { - socket.write('250-deliverby.example.com\r\n'); - socket.write('250-DELIVERBY 86400\r\n'); // 24 hours max - socket.write('250 OK\r\n'); - } else if (command.startsWith('MAIL FROM:')) { - // Check for DELIVERBY parameter - const deliverByMatch = command.match(/DELIVERBY=(\d+)([RN]?)/i); - if (deliverByMatch) { - const seconds = parseInt(deliverByMatch[1]); - const mode = deliverByMatch[2] || 'R'; // R=return, N=notify - - console.log(` [Server] DELIVERBY: ${seconds} seconds, mode: ${mode}`); - - if (seconds > 86400) { - socket.write('501 5.5.4 DELIVERBY time exceeds maximum\r\n'); - } else if (seconds < 0) { - socket.write('501 5.5.4 Invalid DELIVERBY time\r\n'); - } else { - socket.write('250 OK: Delivery deadline accepted\r\n'); - } - } else { - socket.write('250 OK\r\n'); - } - } else if (command.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - } else if (command === '.') { - socket.write('250 OK: Message queued with delivery deadline\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test with delivery deadline - const email = new Email({ - from: 'sender@example.com', - to: ['urgent@example.com'], - subject: 'Urgent delivery test', - text: 'This message has a delivery deadline', - // Note: Most SMTP clients don't expose DELIVERBY directly - // but we can test server handling - }); - - const result = await smtpClient.sendMail(email); - console.log(' DELIVERBY extension supported by server'); - expect(result).toBeDefined(); - - await testServer.server.close(); - })(); - - // Scenario 3: ETRN extension (RFC 1985) - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing ETRN extension (RFC 1985)`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 etrn.example.com ESMTP\r\n'); - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO')) { - socket.write('250-etrn.example.com\r\n'); - socket.write('250-ETRN\r\n'); - socket.write('250 OK\r\n'); - } else if (command.startsWith('ETRN ')) { - const domain = command.substring(5); - console.log(` [Server] ETRN request for domain: ${domain}`); - - if (domain === '@example.com') { - socket.write('250 OK: Queue processing started for example.com\r\n'); - } else if (domain === '#urgent') { - socket.write('250 OK: Urgent queue processing started\r\n'); - } else if (domain.includes('unknown')) { - socket.write('458 Unable to queue messages for node\r\n'); - } else { - socket.write('250 OK: Queue processing started\r\n'); - } - } else if (command.startsWith('MAIL FROM:')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - } else if (command === '.') { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - } - }); - - // ETRN is typically used by mail servers, not clients - // We'll test the server's ETRN capability manually - const net = await import('net'); - const client = net.createConnection(testServer.port, testServer.hostname); - - const commands = [ - 'EHLO client.example.com', - 'ETRN @example.com', // Request queue processing for domain - 'ETRN #urgent', // Request urgent queue processing - 'ETRN unknown.domain.com', // Test error handling - 'QUIT' - ]; - - let commandIndex = 0; - - client.on('data', (data) => { - const response = data.toString().trim(); - console.log(` [Client] Response: ${response}`); - - if (commandIndex < commands.length) { - setTimeout(() => { - const command = commands[commandIndex]; - console.log(` [Client] Sending: ${command}`); - client.write(command + '\r\n'); - commandIndex++; - }, 100); - } else { - client.end(); - } - }); - - await new Promise((resolve, reject) => { - client.on('end', () => { - console.log(' ETRN extension testing completed'); - resolve(void 0); - }); - client.on('error', reject); - }); - - await testServer.server.close(); - })(); - - // Scenario 4: VRFY and EXPN extensions (RFC 5321) - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing VRFY and EXPN extensions`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 verify.example.com ESMTP\r\n'); - - // Simulated user database - const users = new Map([ - ['admin', { email: 'admin@example.com', fullName: 'Administrator' }], - ['john', { email: 'john.doe@example.com', fullName: 'John Doe' }], - ['support', { email: 'support@example.com', fullName: 'Support Team' }] - ]); - - const mailingLists = new Map([ - ['staff', ['admin@example.com', 'john.doe@example.com']], - ['support-team', ['support@example.com', 'admin@example.com']] - ]); - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO')) { - socket.write('250-verify.example.com\r\n'); - socket.write('250-VRFY\r\n'); - socket.write('250-EXPN\r\n'); - socket.write('250 OK\r\n'); - } else if (command.startsWith('VRFY ')) { - const query = command.substring(5); - console.log(` [Server] VRFY query: ${query}`); - - // Look up user - const user = users.get(query.toLowerCase()); - if (user) { - socket.write(`250 ${user.fullName} <${user.email}>\r\n`); - } else { - // Check if it's an email address - const emailMatch = Array.from(users.values()).find(u => - u.email.toLowerCase() === query.toLowerCase() - ); - if (emailMatch) { - socket.write(`250 ${emailMatch.fullName} <${emailMatch.email}>\r\n`); - } else { - socket.write('550 5.1.1 User unknown\r\n'); - } - } - } else if (command.startsWith('EXPN ')) { - const listName = command.substring(5); - console.log(` [Server] EXPN query: ${listName}`); - - const list = mailingLists.get(listName.toLowerCase()); - if (list) { - socket.write(`250-Mailing list ${listName}:\r\n`); - list.forEach((email, index) => { - const prefix = index < list.length - 1 ? '250-' : '250 '; - socket.write(`${prefix}${email}\r\n`); - }); - } else { - socket.write('550 5.1.1 Mailing list not found\r\n'); - } - } else if (command.startsWith('MAIL FROM:')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - } else if (command === '.') { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - } - }); - - // Test VRFY and EXPN commands - const net = await import('net'); - const client = net.createConnection(testServer.port, testServer.hostname); - - const commands = [ - 'EHLO client.example.com', - 'VRFY admin', // Verify user by username - 'VRFY john.doe@example.com', // Verify user by email - 'VRFY nonexistent', // Test unknown user - 'EXPN staff', // Expand mailing list - 'EXPN nonexistent-list', // Test unknown list - 'QUIT' - ]; - - let commandIndex = 0; - - client.on('data', (data) => { - const response = data.toString().trim(); - console.log(` [Client] Response: ${response}`); - - if (commandIndex < commands.length) { - setTimeout(() => { - const command = commands[commandIndex]; - console.log(` [Client] Sending: ${command}`); - client.write(command + '\r\n'); - commandIndex++; - }, 200); - } else { - client.end(); - } - }); - - await new Promise((resolve, reject) => { - client.on('end', () => { - console.log(' VRFY and EXPN testing completed'); - resolve(void 0); - }); - client.on('error', reject); - }); - - await testServer.server.close(); - })(); - - // Scenario 5: HELP extension - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing HELP extension`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 help.example.com ESMTP\r\n'); - - const helpTopics = new Map([ - ['commands', [ - 'Available commands:', - 'EHLO - Extended HELLO', - 'MAIL FROM: - Specify sender', - 'RCPT TO: - Specify recipient', - 'DATA - Start message text', - 'QUIT - Close connection' - ]], - ['extensions', [ - 'Supported extensions:', - 'SIZE - Message size declaration', - '8BITMIME - 8-bit MIME transport', - 'STARTTLS - Start TLS negotiation', - 'AUTH - SMTP Authentication', - 'DSN - Delivery Status Notifications' - ]], - ['syntax', [ - 'Command syntax:', - 'Commands are case-insensitive', - 'Lines end with CRLF', - 'Email addresses must be in <> brackets', - 'Parameters are space-separated' - ]] - ]); - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO')) { - socket.write('250-help.example.com\r\n'); - socket.write('250-HELP\r\n'); - socket.write('250 OK\r\n'); - } else if (command === 'HELP' || command === 'HELP HELP') { - socket.write('214-This server provides HELP for the following topics:\r\n'); - socket.write('214-COMMANDS - List of available commands\r\n'); - socket.write('214-EXTENSIONS - List of supported extensions\r\n'); - socket.write('214-SYNTAX - Command syntax rules\r\n'); - socket.write('214 Use HELP for specific information\r\n'); - } else if (command.startsWith('HELP ')) { - const topic = command.substring(5).toLowerCase(); - const helpText = helpTopics.get(topic); - - if (helpText) { - helpText.forEach((line, index) => { - const prefix = index < helpText.length - 1 ? '214-' : '214 '; - socket.write(`${prefix}${line}\r\n`); - }); - } else { - socket.write('504 5.3.0 HELP topic not available\r\n'); - } - } else if (command.startsWith('MAIL FROM:')) { - socket.write('250 OK\r\n'); - } else if (command.startsWith('RCPT TO:')) { - socket.write('250 OK\r\n'); - } else if (command === 'DATA') { - socket.write('354 Start mail input\r\n'); - } else if (command === '.') { - socket.write('250 OK\r\n'); - } else if (command === 'QUIT') { - socket.write('221 Bye\r\n'); - socket.end(); - } - }); - } - }); - - // Test HELP command - const net = await import('net'); - const client = net.createConnection(testServer.port, testServer.hostname); - - const commands = [ - 'EHLO client.example.com', - 'HELP', // General help - 'HELP COMMANDS', // Specific topic - 'HELP EXTENSIONS', // Another topic - 'HELP NONEXISTENT', // Unknown topic - 'QUIT' - ]; - - let commandIndex = 0; - - client.on('data', (data) => { - const response = data.toString().trim(); - console.log(` [Client] Response: ${response}`); - - if (commandIndex < commands.length) { - setTimeout(() => { - const command = commands[commandIndex]; - console.log(` [Client] Sending: ${command}`); - client.write(command + '\r\n'); - commandIndex++; - }, 200); - } else { - client.end(); - } - }); - - await new Promise((resolve, reject) => { - client.on('end', () => { - console.log(' HELP extension testing completed'); - resolve(void 0); - }); - client.on('error', reject); - }); - - await testServer.server.close(); - })(); - - // Scenario 6: Extension combination and interaction - await (async () => { - scenarioCount++; - console.log(`\nScenario ${scenarioCount}: Testing extension combinations`); - - const testServer = await createTestServer({ - onConnection: async (socket) => { - console.log(' [Server] Client connected'); - socket.write('220 combined.example.com ESMTP\r\n'); - - let activeExtensions: string[] = []; - - socket.on('data', (data) => { - const command = data.toString().trim(); - console.log(` [Server] Received: ${command}`); - - if (command.startsWith('EHLO')) { - socket.write('250-combined.example.com\r\n'); - - // Announce multiple extensions - const extensions = [ - 'SIZE 52428800', - '8BITMIME', - 'SMTPUTF8', - 'ENHANCEDSTATUSCODES', - 'PIPELINING', - 'DSN', - 'DELIVERBY 86400', - 'CHUNKING', - 'BINARYMIME', - 'HELP' - ]; - - extensions.forEach(ext => { - socket.write(`250-${ext}\r\n`); - activeExtensions.push(ext.split(' ')[0]); - }); - - socket.write('250 OK\r\n'); - console.log(` [Server] Active extensions: ${activeExtensions.join(', ')}`); - } else if (command.startsWith('MAIL FROM:')) { - // Check for multiple extension parameters - const params = []; - - if (command.includes('SIZE=')) { - const sizeMatch = command.match(/SIZE=(\d+)/); - if (sizeMatch) params.push(`SIZE=${sizeMatch[1]}`); - } - - if (command.includes('BODY=')) { - const bodyMatch = command.match(/BODY=(\w+)/); - if (bodyMatch) params.push(`BODY=${bodyMatch[1]}`); - } - - if (command.includes('SMTPUTF8')) { - params.push('SMTPUTF8'); - } - - if (command.includes('DELIVERBY=')) { - const deliverByMatch = command.match(/DELIVERBY=(\d+)/); - if (deliverByMatch) params.push(`DELIVERBY=${deliverByMatch[1]}`); - } - - if (params.length > 0) { - console.log(` [Server] Extension parameters: ${params.join(', ')}`); - } - - socket.write('250 2.1.0 Sender OK\r\n'); - } else if (command.startsWith('RCPT TO:')) { - // Check for DSN parameters - if (command.includes('NOTIFY=')) { - const notifyMatch = command.match(/NOTIFY=([^,\s]+)/); - if (notifyMatch) { - console.log(` [Server] DSN NOTIFY: ${notifyMatch[1]}`); - } - } - - socket.write('250 2.1.5 Recipient OK\r\n'); - } else if (command === 'DATA') { - if (activeExtensions.includes('CHUNKING')) { - socket.write('503 5.5.1 Use BDAT when CHUNKING is available\r\n'); - } else { - socket.write('354 Start mail input\r\n'); - } - } else if (command.startsWith('BDAT ')) { - if (activeExtensions.includes('CHUNKING')) { - const parts = command.split(' '); - const size = parts[1]; - const isLast = parts.includes('LAST'); - console.log(` [Server] BDAT chunk: ${size} bytes${isLast ? ' (LAST)' : ''}`); - - if (isLast) { - socket.write('250 2.0.0 Message accepted\r\n'); - } else { - socket.write('250 2.0.0 Chunk accepted\r\n'); - } - } else { - socket.write('500 5.5.1 CHUNKING not available\r\n'); - } - } else if (command === '.') { - socket.write('250 2.0.0 Message accepted\r\n'); - } else if (command === 'QUIT') { - socket.write('221 2.0.0 Bye\r\n'); - socket.end(); - } - }); - } - }); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test email that could use multiple extensions - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Extension combination test with UTF-8: 测试', - text: 'Testing multiple SMTP extensions together', - dsn: { - notify: ['SUCCESS', 'FAILURE'], - envid: 'multi-ext-test-123' - } - }); - - const result = await smtpClient.sendMail(email); - console.log(' Multiple extension combination handled'); - expect(result).toBeDefined(); - expect(result.messageId).toBeDefined(); - - await testServer.server.close(); - })(); - - console.log(`\n${testId}: All ${scenarioCount} SMTP extension scenarios tested ✓`); -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_security/test.csec-01.tls-verification.ts b/test/suite/smtpclient_security/test.csec-01.tls-verification.ts deleted file mode 100644 index 7709675..0000000 --- a/test/suite/smtpclient_security/test.csec-01.tls-verification.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { createTestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -tap.test('CSEC-01: TLS Security Tests', async () => { - console.log('\n🔒 Testing SMTP Client TLS Security'); - console.log('=' .repeat(60)); - - // Test 1: Basic secure connection - console.log('\nTest 1: Basic secure connection'); - const testServer1 = await createTestServer({}); - - try { - const smtpClient = createTestSmtpClient({ - host: testServer1.hostname, - port: testServer1.port, - secure: false // Using STARTTLS instead of direct TLS - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'TLS Test', - text: 'Testing secure connection' - }); - - const result = await smtpClient.sendMail(email); - console.log(' ✓ Email sent over secure connection'); - expect(result).toBeDefined(); - - } finally { - testServer1.server.close(); - } - - // Test 2: Connection with security options - console.log('\nTest 2: Connection with TLS options'); - const testServer2 = await createTestServer({}); - - try { - const smtpClient = createTestSmtpClient({ - host: testServer2.hostname, - port: testServer2.port, - secure: false, - tls: { - rejectUnauthorized: false // Accept self-signed for testing - } - }); - - const verified = await smtpClient.verify(); - console.log(' ✓ TLS connection established with custom options'); - expect(verified).toBeDefined(); - - } finally { - testServer2.server.close(); - } - - // Test 3: Multiple secure emails - console.log('\nTest 3: Multiple secure emails'); - const testServer3 = await createTestServer({}); - - try { - const smtpClient = createTestSmtpClient({ - host: testServer3.hostname, - port: testServer3.port - }); - - for (let i = 0; i < 3; i++) { - const email = new Email({ - from: 'sender@secure.com', - to: [`recipient${i}@secure.com`], - subject: `Secure Email ${i + 1}`, - text: 'Testing TLS security' - }); - - const result = await smtpClient.sendMail(email); - console.log(` ✓ Secure email ${i + 1} sent`); - expect(result).toBeDefined(); - } - - } finally { - testServer3.server.close(); - } - - console.log('\n✅ CSEC-01: TLS security tests completed'); -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_security/test.csec-02.oauth2-authentication.ts b/test/suite/smtpclient_security/test.csec-02.oauth2-authentication.ts deleted file mode 100644 index da2707d..0000000 --- a/test/suite/smtpclient_security/test.csec-02.oauth2-authentication.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2562, - tlsEnabled: false, - authRequired: true - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CSEC-02: OAuth2 authentication configuration', async () => { - // Test client with OAuth2 configuration - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - auth: { - oauth2: { - user: 'oauth.user@example.com', - clientId: 'client-id-12345', - clientSecret: 'client-secret-67890', - accessToken: 'access-token-abcdef', - refreshToken: 'refresh-token-ghijkl' - } - }, - connectionTimeout: 5000, - debug: true - }); - - // Test that OAuth2 config doesn't break the client - try { - const verified = await smtpClient.verify(); - console.log('Client with OAuth2 config created successfully'); - console.log('Note: Server does not support OAuth2, so auth will fail'); - expect(verified).toBeFalsy(); // Expected to fail without OAuth2 support - } catch (error) { - console.log('OAuth2 authentication attempt:', error.message); - } - - await smtpClient.close(); -}); - -tap.test('CSEC-02: OAuth2 vs regular auth', async () => { - // Test regular auth (should work) - const regularClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - auth: { - user: 'testuser', - pass: 'testpass' - }, - connectionTimeout: 5000, - debug: false - }); - - try { - const verified = await regularClient.verify(); - console.log('Regular auth verification:', verified); - - if (verified) { - // Send test email - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Test with regular auth', - text: 'This uses regular PLAIN/LOGIN auth' - }); - - const result = await regularClient.sendMail(email); - expect(result.success).toBeTruthy(); - console.log('Email sent with regular auth'); - } - } catch (error) { - console.log('Regular auth error:', error.message); - } - - await regularClient.close(); -}); - -tap.test('CSEC-02: OAuth2 error handling', async () => { - // Test OAuth2 with invalid token - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - auth: { - method: 'OAUTH2', - oauth2: { - user: 'user@example.com', - clientId: 'test-client', - clientSecret: 'test-secret', - refreshToken: 'refresh-token', - accessToken: 'invalid-token' - } - }, - connectionTimeout: 5000, - debug: false - }); - - try { - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'OAuth2 test', - text: 'Testing OAuth2 authentication' - }); - - const result = await smtpClient.sendMail(email); - console.log('OAuth2 send result:', result.success); - } catch (error) { - console.log('OAuth2 error (expected):', error.message); - expect(error.message).toInclude('auth'); - } - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_security/test.csec-03.dkim-signing.ts b/test/suite/smtpclient_security/test.csec-03.dkim-signing.ts deleted file mode 100644 index 9044fe2..0000000 --- a/test/suite/smtpclient_security/test.csec-03.dkim-signing.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; -import * as crypto from 'crypto'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2563, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CSEC-03: Basic DKIM signature structure', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Create email with DKIM configuration - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'DKIM Signed Email', - text: 'This email should be DKIM signed' - }); - - // Note: DKIM signing would be handled by the Email class or SMTP client - // This test verifies the structure when it's implemented - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - - console.log('Email sent successfully'); - console.log('Note: DKIM signing functionality would be applied here'); - - await smtpClient.close(); -}); - -tap.test('CSEC-03: DKIM with RSA key generation', async () => { - // Generate a test RSA key pair - const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { - modulusLength: 2048, - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem' - } - }); - - console.log('Generated RSA key pair for DKIM:'); - console.log('Public key (first line):', publicKey.split('\n')[1].substring(0, 50) + '...'); - - // Create DNS TXT record format - const publicKeyBase64 = publicKey - .replace(/-----BEGIN PUBLIC KEY-----/, '') - .replace(/-----END PUBLIC KEY-----/, '') - .replace(/\s/g, ''); - - console.log('\nDNS TXT record for default._domainkey.example.com:'); - console.log(`v=DKIM1; k=rsa; p=${publicKeyBase64.substring(0, 50)}...`); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'DKIM with Real RSA Key', - text: 'This email is signed with a real RSA key' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('CSEC-03: DKIM body hash calculation', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: false - }); - - // Test body hash with different content - const testBodies = [ - { name: 'Simple text', body: 'Hello World' }, - { name: 'Multi-line text', body: 'Line 1\r\nLine 2\r\nLine 3' }, - { name: 'Empty body', body: '' } - ]; - - for (const test of testBodies) { - console.log(`\nTesting body hash for: ${test.name}`); - - // Calculate expected body hash - const canonicalBody = test.body.replace(/\r\n/g, '\n').trimEnd() + '\n'; - const bodyHash = crypto.createHash('sha256').update(canonicalBody).digest('base64'); - console.log(` Expected hash: ${bodyHash.substring(0, 20)}...`); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Body Hash Test: ${test.name}`, - text: test.body - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - } - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_security/test.csec-04.spf-compliance.ts b/test/suite/smtpclient_security/test.csec-04.spf-compliance.ts deleted file mode 100644 index 398cbb9..0000000 --- a/test/suite/smtpclient_security/test.csec-04.spf-compliance.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; -import * as dns from 'dns'; -import { promisify } from 'util'; - -const resolveTxt = promisify(dns.resolveTxt); - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2564, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CSEC-04: SPF record parsing', async () => { - // Test SPF record parsing - const testSpfRecords = [ - { - domain: 'example.com', - record: 'v=spf1 ip4:192.168.1.0/24 ip6:2001:db8::/32 include:_spf.google.com ~all', - description: 'Standard SPF with IP ranges and include' - }, - { - domain: 'strict.com', - record: 'v=spf1 mx a -all', - description: 'Strict SPF with MX and A records' - }, - { - domain: 'softfail.com', - record: 'v=spf1 ip4:10.0.0.1 ~all', - description: 'Soft fail SPF' - } - ]; - - console.log('SPF Record Analysis:\n'); - - for (const test of testSpfRecords) { - console.log(`Domain: ${test.domain}`); - console.log(`Record: ${test.record}`); - console.log(`Description: ${test.description}`); - - // Parse SPF mechanisms - const mechanisms = test.record.match(/(\+|-|~|\?)?(\w+)(:[^\s]+)?/g); - if (mechanisms) { - console.log('Mechanisms found:', mechanisms.length); - } - console.log(''); - } -}); - -tap.test('CSEC-04: SPF alignment check', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Test SPF alignment scenarios - const alignmentTests = [ - { - name: 'Aligned', - from: 'sender@example.com', - expectedAlignment: true - }, - { - name: 'Different domain', - from: 'sender@otherdomain.com', - expectedAlignment: false - } - ]; - - for (const test of alignmentTests) { - console.log(`\nTesting SPF alignment: ${test.name}`); - console.log(` From: ${test.from}`); - - const email = new Email({ - from: test.from, - to: ['recipient@example.com'], - subject: `SPF Alignment Test: ${test.name}`, - text: 'Testing SPF alignment' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - - console.log(` Email sent successfully`); - } - - await smtpClient.close(); -}); - -tap.test('CSEC-04: SPF lookup simulation', async () => { - // Simulate SPF record lookups - const testDomains = ['gmail.com']; - - console.log('\nSPF Record Lookups:\n'); - - for (const domain of testDomains) { - console.log(`Domain: ${domain}`); - - try { - const txtRecords = await resolveTxt(domain); - const spfRecords = txtRecords - .map(record => record.join('')) - .filter(record => record.startsWith('v=spf1')); - - if (spfRecords.length > 0) { - console.log(`SPF Record found: ${spfRecords[0].substring(0, 50)}...`); - - // Count mechanisms - const includes = (spfRecords[0].match(/include:/g) || []).length; - console.log(` Include count: ${includes}`); - } else { - console.log(' No SPF record found'); - } - } catch (error) { - console.log(` Lookup failed: ${error.message}`); - } - console.log(''); - } -}); - -tap.test('CSEC-04: SPF best practices', async () => { - // Test SPF best practices - const bestPractices = [ - { - practice: 'Use -all instead of ~all', - good: 'v=spf1 include:_spf.example.com -all', - bad: 'v=spf1 include:_spf.example.com ~all' - }, - { - practice: 'Avoid +all', - good: 'v=spf1 ip4:192.168.1.0/24 -all', - bad: 'v=spf1 +all' - } - ]; - - console.log('\nSPF Best Practices:\n'); - - for (const bp of bestPractices) { - console.log(`${bp.practice}:`); - console.log(` ✓ Good: ${bp.good}`); - console.log(` ✗ Bad: ${bp.bad}`); - console.log(''); - } -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_security/test.csec-05.dmarc-policy.ts b/test/suite/smtpclient_security/test.csec-05.dmarc-policy.ts deleted file mode 100644 index a750f07..0000000 --- a/test/suite/smtpclient_security/test.csec-05.dmarc-policy.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; -import * as dns from 'dns'; -import { promisify } from 'util'; - -const resolveTxt = promisify(dns.resolveTxt); - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2565, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CSEC-05: DMARC record parsing', async () => { - // Test DMARC record parsing - const testDmarcRecords = [ - { - domain: 'example.com', - record: 'v=DMARC1; p=reject; rua=mailto:dmarc@example.com; ruf=mailto:forensics@example.com; adkim=s; aspf=s; pct=100', - description: 'Strict DMARC with reporting' - }, - { - domain: 'relaxed.com', - record: 'v=DMARC1; p=quarantine; adkim=r; aspf=r; pct=50', - description: 'Relaxed alignment, 50% quarantine' - }, - { - domain: 'monitoring.com', - record: 'v=DMARC1; p=none; rua=mailto:reports@monitoring.com', - description: 'Monitor only mode' - } - ]; - - console.log('DMARC Record Analysis:\n'); - - for (const test of testDmarcRecords) { - console.log(`Domain: _dmarc.${test.domain}`); - console.log(`Record: ${test.record}`); - console.log(`Description: ${test.description}`); - - // Parse DMARC tags - const tags = test.record.match(/(\w+)=([^;]+)/g); - if (tags) { - console.log(`Tags found: ${tags.length}`); - } - console.log(''); - } -}); - -tap.test('CSEC-05: DMARC alignment testing', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - connectionTimeout: 5000, - debug: true - }); - - // Test DMARC alignment scenarios - const alignmentTests = [ - { - name: 'Fully aligned', - fromHeader: 'sender@example.com', - expectedResult: 'pass' - }, - { - name: 'Different domain', - fromHeader: 'sender@otherdomain.com', - expectedResult: 'fail' - } - ]; - - for (const test of alignmentTests) { - console.log(`\nTesting DMARC alignment: ${test.name}`); - console.log(` From header: ${test.fromHeader}`); - - const email = new Email({ - from: test.fromHeader, - to: ['recipient@example.com'], - subject: `DMARC Test: ${test.name}`, - text: 'Testing DMARC alignment' - }); - - const result = await smtpClient.sendMail(email); - expect(result.success).toBeTruthy(); - - console.log(` Email sent successfully`); - console.log(` Expected result: ${test.expectedResult}`); - } - - await smtpClient.close(); -}); - -tap.test('CSEC-05: DMARC policy enforcement', async () => { - // Test different DMARC policies - const policies = [ - { - policy: 'none', - description: 'Monitor only - no action taken', - action: 'Deliver normally, send reports' - }, - { - policy: 'quarantine', - description: 'Quarantine failing messages', - action: 'Move to spam/junk folder' - }, - { - policy: 'reject', - description: 'Reject failing messages', - action: 'Bounce the message' - } - ]; - - console.log('\nDMARC Policy Actions:\n'); - - for (const p of policies) { - console.log(`Policy: p=${p.policy}`); - console.log(` Description: ${p.description}`); - console.log(` Action: ${p.action}`); - console.log(''); - } -}); - -tap.test('CSEC-05: DMARC deployment best practices', async () => { - // DMARC deployment phases - const deploymentPhases = [ - { - phase: 1, - policy: 'p=none; rua=mailto:dmarc@example.com', - description: 'Monitor only - collect data' - }, - { - phase: 2, - policy: 'p=quarantine; pct=10; rua=mailto:dmarc@example.com', - description: 'Quarantine 10% of failing messages' - }, - { - phase: 3, - policy: 'p=reject; rua=mailto:dmarc@example.com', - description: 'Reject all failing messages' - } - ]; - - console.log('\nDMARC Deployment Best Practices:\n'); - - for (const phase of deploymentPhases) { - console.log(`Phase ${phase.phase}: ${phase.description}`); - console.log(` Record: v=DMARC1; ${phase.policy}`); - console.log(''); - } -}); - -tap.test('CSEC-05: DMARC record lookup', async () => { - // Test real DMARC record lookups - const testDomains = ['paypal.com']; - - console.log('\nReal DMARC Record Lookups:\n'); - - for (const domain of testDomains) { - const dmarcDomain = `_dmarc.${domain}`; - console.log(`Domain: ${domain}`); - - try { - const txtRecords = await resolveTxt(dmarcDomain); - const dmarcRecords = txtRecords - .map(record => record.join('')) - .filter(record => record.startsWith('v=DMARC1')); - - if (dmarcRecords.length > 0) { - const record = dmarcRecords[0]; - console.log(` Record found: ${record.substring(0, 50)}...`); - - // Parse key elements - const policyMatch = record.match(/p=(\w+)/); - if (policyMatch) console.log(` Policy: ${policyMatch[1]}`); - } else { - console.log(' No DMARC record found'); - } - } catch (error) { - console.log(` Lookup failed: ${error.message}`); - } - console.log(''); - } -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_security/test.csec-06.certificate-validation.ts b/test/suite/smtpclient_security/test.csec-06.certificate-validation.ts deleted file mode 100644 index c8c3953..0000000 --- a/test/suite/smtpclient_security/test.csec-06.certificate-validation.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer, createTestServer as createSimpleTestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2566, - tlsEnabled: true, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CSEC-06: Valid certificate acceptance', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: true, - tls: { - rejectUnauthorized: false // Accept self-signed for test - } - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Valid certificate test', - text: 'Testing with valid TLS connection' - }); - - const result = await smtpClient.sendMail(email); - console.log(`Result: ${result.success ? 'Success' : 'Failed'}`); - console.log('Certificate accepted for secure connection'); - expect(result.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('CSEC-06: Self-signed certificate handling', async () => { - // Test with strict validation (should fail) - const strictClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: true, - tls: { - rejectUnauthorized: true // Reject self-signed - } - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Self-signed cert test', - text: 'Testing self-signed certificate rejection' - }); - - try { - await strictClient.sendMail(email); - console.log('Unexpected: Self-signed cert was accepted'); - } catch (error) { - console.log(`Expected error: ${error.message}`); - expect(error.message).toInclude('self'); - } - - await strictClient.close(); - - // Test with relaxed validation (should succeed) - const relaxedClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: true, - tls: { - rejectUnauthorized: false // Accept self-signed - } - }); - - const result = await relaxedClient.sendMail(email); - console.log('Self-signed cert accepted with relaxed validation'); - expect(result.success).toBeTruthy(); - - await relaxedClient.close(); -}); - -tap.test('CSEC-06: Certificate hostname verification', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: true, - tls: { - rejectUnauthorized: false, // For self-signed - servername: testServer.hostname // Verify hostname - } - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Hostname verification test', - text: 'Testing certificate hostname matching' - }); - - const result = await smtpClient.sendMail(email); - console.log('Hostname verification completed'); - expect(result.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('CSEC-06: Certificate validation with custom CA', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: true, - tls: { - rejectUnauthorized: false, - // In production, would specify CA certificates - ca: undefined - } - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Certificate chain test', - text: 'Testing certificate chain validation' - }); - - const result = await smtpClient.sendMail(email); - console.log('Certificate chain validation completed'); - expect(result.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_security/test.csec-07.cipher-suites.ts b/test/suite/smtpclient_security/test.csec-07.cipher-suites.ts deleted file mode 100644 index b963477..0000000 --- a/test/suite/smtpclient_security/test.csec-07.cipher-suites.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2567, - tlsEnabled: true, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CSEC-07: Strong cipher suite negotiation', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: true, - tls: { - rejectUnauthorized: false, - // Prefer strong ciphers - ciphers: 'HIGH:!aNULL:!MD5:!3DES', - minVersion: 'TLSv1.2' - } - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Strong cipher test', - text: 'Testing with strong cipher suites' - }); - - const result = await smtpClient.sendMail(email); - console.log('Successfully negotiated strong cipher'); - expect(result.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('CSEC-07: Cipher suite configuration', async () => { - // Test with specific cipher configuration - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: true, - tls: { - rejectUnauthorized: false, - // Specify allowed ciphers - ciphers: 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256', - honorCipherOrder: true - } - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Cipher configuration test', - text: 'Testing specific cipher suite configuration' - }); - - const result = await smtpClient.sendMail(email); - console.log('Cipher configuration test completed'); - expect(result.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('CSEC-07: Perfect Forward Secrecy ciphers', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: true, - tls: { - rejectUnauthorized: false, - // Prefer PFS ciphers - ciphers: 'ECDHE:DHE:!aNULL:!MD5', - ecdhCurve: 'auto' - } - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'PFS cipher test', - text: 'Testing Perfect Forward Secrecy' - }); - - const result = await smtpClient.sendMail(email); - console.log('Successfully used PFS cipher'); - expect(result.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('CSEC-07: Cipher compatibility testing', async () => { - const cipherConfigs = [ - { - name: 'TLS 1.2 compatible', - ciphers: 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256', - minVersion: 'TLSv1.2' - }, - { - name: 'Broad compatibility', - ciphers: 'HIGH:MEDIUM:!aNULL:!MD5:!3DES', - minVersion: 'TLSv1.2' - } - ]; - - for (const config of cipherConfigs) { - console.log(`\nTesting ${config.name}...`); - - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: true, - tls: { - rejectUnauthorized: false, - ciphers: config.ciphers, - minVersion: config.minVersion as any - } - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `${config.name} test`, - text: `Testing ${config.name} cipher configuration` - }); - - try { - const result = await smtpClient.sendMail(email); - console.log(` Success with ${config.name}`); - expect(result.success).toBeTruthy(); - } catch (error) { - console.log(` ${config.name} not supported in this environment`); - } - - await smtpClient.close(); - } -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_security/test.csec-08.authentication-fallback.ts b/test/suite/smtpclient_security/test.csec-08.authentication-fallback.ts deleted file mode 100644 index d48eaae..0000000 --- a/test/suite/smtpclient_security/test.csec-08.authentication-fallback.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2568, - tlsEnabled: false, - authRequired: true - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CSEC-08: Multiple authentication methods', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - auth: { - user: 'testuser', - pass: 'testpass' - } - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Multi-auth test', - text: 'Testing multiple authentication methods' - }); - - const result = await smtpClient.sendMail(email); - console.log('Authentication successful'); - expect(result.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('CSEC-08: OAuth2 fallback to password auth', async () => { - // Test with OAuth2 token (will fail and fallback) - const oauthClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - auth: { - oauth2: { - user: 'user@example.com', - clientId: 'test-client', - clientSecret: 'test-secret', - refreshToken: 'refresh-token', - accessToken: 'invalid-token' - } - } - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'OAuth2 fallback test', - text: 'Testing OAuth2 authentication fallback' - }); - - try { - await oauthClient.sendMail(email); - console.log('OAuth2 authentication attempted'); - } catch (error) { - console.log(`OAuth2 failed as expected: ${error.message}`); - } - - await oauthClient.close(); - - // Test fallback to password auth - const fallbackClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - auth: { - user: 'testuser', - pass: 'testpass' - } - }); - - const result = await fallbackClient.sendMail(email); - console.log('Fallback authentication successful'); - expect(result.success).toBeTruthy(); - - await fallbackClient.close(); -}); - -tap.test('CSEC-08: Auth method preference', async () => { - // Test with specific auth method preference - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - auth: { - user: 'testuser', - pass: 'testpass', - method: 'PLAIN' // Prefer PLAIN auth - } - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Auth preference test', - text: 'Testing authentication method preference' - }); - - const result = await smtpClient.sendMail(email); - console.log('Authentication with preferred method successful'); - expect(result.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('CSEC-08: Secure auth requirements', async () => { - // Test authentication behavior with security requirements - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - auth: { - user: 'testuser', - pass: 'testpass' - }, - requireTLS: false // Allow auth over plain connection for test - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Secure auth test', - text: 'Testing secure authentication requirements' - }); - - const result = await smtpClient.sendMail(email); - console.log('Authentication completed'); - expect(result.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_security/test.csec-09.relay-restrictions.ts b/test/suite/smtpclient_security/test.csec-09.relay-restrictions.ts deleted file mode 100644 index f948ae1..0000000 --- a/test/suite/smtpclient_security/test.csec-09.relay-restrictions.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2569, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CSEC-09: Open relay prevention', async () => { - // Test unauthenticated relay attempt (should succeed for test server) - const unauthClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - const relayEmail = new Email({ - from: 'external@untrusted.com', - to: ['recipient@another-external.com'], - subject: 'Relay test', - text: 'Testing open relay prevention' - }); - - const result = await unauthClient.sendMail(relayEmail); - console.log('Test server allows relay for testing purposes'); - expect(result.success).toBeTruthy(); - - await unauthClient.close(); -}); - -tap.test('CSEC-09: Authenticated relay', async () => { - // Test authenticated relay (should succeed) - const authClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false, - auth: { - user: 'testuser', - pass: 'testpass' - } - }); - - const relayEmail = new Email({ - from: 'sender@example.com', - to: ['recipient@external.com'], - subject: 'Authenticated relay test', - text: 'Testing authenticated relay' - }); - - const result = await authClient.sendMail(relayEmail); - console.log('Authenticated relay allowed'); - expect(result.success).toBeTruthy(); - - await authClient.close(); -}); - -tap.test('CSEC-09: Recipient count limits', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test with multiple recipients - const manyRecipients = Array(10).fill(null).map((_, i) => `recipient${i + 1}@example.com`); - - const bulkEmail = new Email({ - from: 'sender@example.com', - to: manyRecipients, - subject: 'Recipient limit test', - text: 'Testing recipient count limits' - }); - - const result = await smtpClient.sendMail(bulkEmail); - console.log(`Sent to ${result.acceptedRecipients.length} recipients`); - expect(result.success).toBeTruthy(); - - // Check if any recipients were rejected - if (result.rejectedRecipients.length > 0) { - console.log(`${result.rejectedRecipients.length} recipients rejected`); - } - - await smtpClient.close(); -}); - -tap.test('CSEC-09: Sender domain verification', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test with various sender domains - const senderTests = [ - { from: 'sender@example.com', expected: true }, - { from: 'sender@trusted.com', expected: true }, - { from: 'sender@untrusted.com', expected: true } // Test server accepts all - ]; - - for (const test of senderTests) { - const email = new Email({ - from: test.from, - to: ['recipient@example.com'], - subject: `Sender test from ${test.from}`, - text: 'Testing sender domain restrictions' - }); - - const result = await smtpClient.sendMail(email); - console.log(`Sender ${test.from}: ${result.success ? 'accepted' : 'rejected'}`); - expect(result.success).toEqual(test.expected); - } - - await smtpClient.close(); -}); - -tap.test('CSEC-09: Rate limiting simulation', async () => { - // Send multiple messages to test rate limiting - const results: boolean[] = []; - - for (let i = 0; i < 5; i++) { - const client = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: `Rate test ${i + 1}`, - text: `Testing rate limits - message ${i + 1}` - }); - - try { - const result = await client.sendMail(email); - console.log(`Message ${i + 1}: Sent successfully`); - results.push(result.success); - } catch (error) { - console.log(`Message ${i + 1}: Failed`); - results.push(false); - } - - await client.close(); - } - - const successCount = results.filter(r => r).length; - console.log(`Sent ${successCount}/${results.length} messages`); - expect(successCount).toBeGreaterThan(0); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/suite/smtpclient_security/test.csec-10.anti-spam-measures.ts b/test/suite/smtpclient_security/test.csec-10.anti-spam-measures.ts deleted file mode 100644 index cdd02e5..0000000 --- a/test/suite/smtpclient_security/test.csec-10.anti-spam-measures.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; -import { createTestSmtpClient } from '../../helpers/smtp.client.js'; -import { Email } from '../../../ts/mail/core/classes.email.js'; - -let testServer: ITestServer; - -tap.test('setup test SMTP server', async () => { - testServer = await startTestServer({ - port: 2570, - tlsEnabled: false, - authRequired: false - }); - expect(testServer).toBeTruthy(); - expect(testServer.port).toBeGreaterThan(0); -}); - -tap.test('CSEC-10: Reputation-based filtering', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Reputation test', - text: 'Testing reputation-based filtering' - }); - - const result = await smtpClient.sendMail(email); - console.log('Good reputation: Message accepted'); - expect(result.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('CSEC-10: Content filtering and spam scoring', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test 1: Clean email - const cleanEmail = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Business proposal', - text: 'I would like to discuss our upcoming project. Please let me know your availability.' - }); - - const cleanResult = await smtpClient.sendMail(cleanEmail); - console.log('Clean email: Accepted'); - expect(cleanResult.success).toBeTruthy(); - - // Test 2: Email with spam-like content - const spamEmail = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'You are a WINNER!', - text: 'Click here to claim your lottery prize! Act now! 100% guarantee!' - }); - - const spamResult = await smtpClient.sendMail(spamEmail); - console.log('Spam-like email: Processed by server'); - expect(spamResult.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('CSEC-10: Greylisting simulation', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Greylist test', - text: 'Testing greylisting mechanism' - }); - - // Test server doesn't implement greylisting, so this should succeed - const result = await smtpClient.sendMail(email); - console.log('Email sent (greylisting not active on test server)'); - expect(result.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('CSEC-10: DNS blacklist checking', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test with various domains - const testDomains = [ - { from: 'sender@clean-domain.com', expected: true }, - { from: 'sender@spam-domain.com', expected: true } // Test server accepts all - ]; - - for (const test of testDomains) { - const email = new Email({ - from: test.from, - to: ['recipient@example.com'], - subject: 'DNSBL test', - text: 'Testing DNSBL checking' - }); - - const result = await smtpClient.sendMail(email); - console.log(`Sender ${test.from}: ${result.success ? 'accepted' : 'rejected'}`); - expect(result.success).toBeTruthy(); - } - - await smtpClient.close(); -}); - -tap.test('CSEC-10: Connection behavior analysis', async () => { - // Test normal behavior - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - const email = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Behavior test', - text: 'Testing normal email sending behavior' - }); - - const result = await smtpClient.sendMail(email); - console.log('Normal behavior: Accepted'); - expect(result.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('CSEC-10: Attachment scanning', async () => { - const smtpClient = createTestSmtpClient({ - host: testServer.hostname, - port: testServer.port, - secure: false - }); - - // Test 1: Safe attachment - const safeEmail = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Document for review', - text: 'Please find the attached document.', - attachments: [{ - filename: 'report.pdf', - content: Buffer.from('PDF content here'), - contentType: 'application/pdf' - }] - }); - - const safeResult = await smtpClient.sendMail(safeEmail); - console.log('Safe attachment: Accepted'); - expect(safeResult.success).toBeTruthy(); - - // Test 2: Potentially dangerous attachment (test server accepts all) - const exeEmail = new Email({ - from: 'sender@example.com', - to: ['recipient@example.com'], - subject: 'Important update', - text: 'Please run the attached file', - attachments: [{ - filename: 'update.exe', - content: Buffer.from('MZ\x90\x00\x03'), // Fake executable header - contentType: 'application/octet-stream' - }] - }); - - const exeResult = await smtpClient.sendMail(exeEmail); - console.log('Executable attachment: Processed by server'); - expect(exeResult.success).toBeTruthy(); - - await smtpClient.close(); -}); - -tap.test('cleanup test SMTP server', async () => { - if (testServer) { - await stopTestServer(testServer); - } -}); - -tap.start(); \ No newline at end of file diff --git a/test/test.rate-limiting-integration.ts b/test/test.rate-limiting-integration.ts deleted file mode 100644 index d131627..0000000 --- a/test/test.rate-limiting-integration.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { expect, tap } from '@git.zone/tstest/tapbundle'; -import * as plugins from './helpers/server.loader.js'; -import { createTestSmtpClient } from './helpers/smtp.client.js'; -import { SmtpClient } from '../ts/mail/delivery/smtpclient/smtp-client.js'; - -const TEST_PORT = 2525; - -// Test email configuration with rate limits -const testEmailConfig = { - ports: [TEST_PORT], - hostname: 'localhost', - domains: [ - { - domain: 'test.local', - dnsMode: 'forward' as const, - rateLimits: { - inbound: { - messagesPerMinute: 3, // Very low limit for testing - recipientsPerMessage: 2, - connectionsPerIp: 5 - } - } - } - ], - routes: [ - { - name: 'test-route', - match: { recipients: '*@test.local' }, - action: { type: 'process' as const, process: { scan: false, queue: 'normal' } } - } - ], - rateLimits: { - global: { - maxMessagesPerMinute: 10, - maxConnectionsPerIP: 10, - maxErrorsPerIP: 3, - maxAuthFailuresPerIP: 2, - blockDuration: 5000 // 5 seconds for testing - } - } -}; - -tap.test('prepare server with rate limiting', async () => { - await plugins.startTestServer(testEmailConfig); - // Give server time to start - await new Promise(resolve => setTimeout(resolve, 1000)); -}); - -tap.test('should enforce connection rate limits', async (tools) => { - const done = tools.defer(); - const clients: SmtpClient[] = []; - - try { - // Try to create many connections quickly - for (let i = 0; i < 12; i++) { - const client = createTestSmtpClient(); - clients.push(client); - - // Connection should fail after limit is exceeded - const verified = await client.verify().catch(() => false); - - if (i < 10) { - // First 10 should succeed (global limit) - expect(verified).toBeTrue(); - } else { - // After 10, should be rate limited - expect(verified).toBeFalse(); - } - } - - done.resolve(); - } catch (error) { - done.reject(error); - } finally { - // Clean up connections - for (const client of clients) { - await client.close().catch(() => {}); - } - } -}); - -tap.test('should enforce message rate limits per domain', async (tools) => { - const done = tools.defer(); - const client = createTestSmtpClient(); - - try { - // Send messages rapidly to test domain-specific rate limit - for (let i = 0; i < 5; i++) { - const email = { - from: `sender${i}@example.com`, - to: 'recipient@test.local', - subject: `Test ${i}`, - text: 'Test message' - }; - - const result = await client.sendMail(email).catch(err => err); - - if (i < 3) { - // First 3 should succeed (domain limit is 3 per minute) - expect(result.accepted).toBeDefined(); - expect(result.accepted.length).toEqual(1); - } else { - // After 3, should be rate limited - expect(result.code).toEqual('EENVELOPE'); - expect(result.response).toContain('try again later'); - } - } - - done.resolve(); - } catch (error) { - done.reject(error); - } finally { - await client.close(); - } -}); - -tap.test('should enforce recipient limits', async (tools) => { - const done = tools.defer(); - const client = createTestSmtpClient(); - - try { - // Try to send to many recipients (domain limit is 2 per message) - const email = { - from: 'sender@example.com', - to: ['user1@test.local', 'user2@test.local', 'user3@test.local'], - subject: 'Test with multiple recipients', - text: 'Test message' - }; - - const result = await client.sendMail(email).catch(err => err); - - // Should fail due to recipient limit - expect(result.code).toEqual('EENVELOPE'); - expect(result.response).toContain('try again later'); - - done.resolve(); - } catch (error) { - done.reject(error); - } finally { - await client.close(); - } -}); - -tap.test('should enforce error rate limits', async (tools) => { - const done = tools.defer(); - const client = createTestSmtpClient(); - - try { - // Send multiple invalid commands to trigger error rate limit - const socket = (client as any).socket; - - // Wait for connection - await new Promise(resolve => setTimeout(resolve, 100)); - - // Send invalid commands - for (let i = 0; i < 5; i++) { - socket.write('INVALID_COMMAND\r\n'); - - // Wait for response - await new Promise(resolve => { - socket.once('data', resolve); - }); - } - - // After 3 errors, connection should be blocked - const lastResponse = await new Promise(resolve => { - socket.once('data', (data: Buffer) => resolve(data.toString())); - socket.write('NOOP\r\n'); - }); - - expect(lastResponse).toContain('421 Too many errors'); - - done.resolve(); - } catch (error) { - done.reject(error); - } finally { - await client.close().catch(() => {}); - } -}); - -tap.test('should enforce authentication failure limits', async (tools) => { - const done = tools.defer(); - - // Create config with auth required - const authConfig = { - ...testEmailConfig, - auth: { - required: true, - methods: ['PLAIN' as const] - } - }; - - // Restart server with auth config - await plugins.stopTestServer(); - await plugins.startTestServer(authConfig); - await new Promise(resolve => setTimeout(resolve, 1000)); - - const client = createTestSmtpClient(); - - try { - // Try multiple failed authentications - for (let i = 0; i < 3; i++) { - const result = await client.sendMail({ - from: 'sender@example.com', - to: 'recipient@test.local', - subject: 'Test', - text: 'Test' - }, { - auth: { - user: 'wronguser', - pass: 'wrongpass' - } - }).catch(err => err); - - if (i < 2) { - // First 2 should fail with auth error - expect(result.code).toEqual('EAUTH'); - } else { - // After 2 failures, should be blocked - expect(result.code).toEqual('ECONNECTION'); - } - } - - done.resolve(); - } catch (error) { - done.reject(error); - } finally { - await client.close().catch(() => {}); - } -}); - -tap.test('cleanup server', async () => { - await plugins.stopTestServer(); -}); - -tap.start(); \ No newline at end of file diff --git a/test/test.smartmail.ts b/test/test.smartmail.ts index 2234881..c67aaeb 100644 --- a/test/test.smartmail.ts +++ b/test/test.smartmail.ts @@ -158,7 +158,7 @@ tap.test('Email and Smartmail compatibility - should convert between formats', a // Add recipient and attachment smartmail.addRecipient('recipient@example.com'); - const attachment = await plugins.smartfile.SmartFile.fromString( + const attachment = plugins.smartfile.SmartFileFactory.nodeFs().fromString( 'test.txt', 'This is a test attachment', 'utf8', diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 64312dc..9b186a6 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartmta', - version: '2.3.1', + version: '2.3.2', description: 'A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.' }