mailer-delivery-log.test.js 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import assert from 'node:assert/strict';
  2. import net from 'node:net';
  3. import { test } from 'node:test';
  4. import { sendViaSmtp } from '../src/mailer.js';
  5. test('records a sanitized SMTP delivery log without storing credentials or message body', async () => {
  6. const capturedCommands = [];
  7. const server = net.createServer((socket) => {
  8. socket.setEncoding('utf8');
  9. socket.write('220 relay.test ESMTP ready\r\n');
  10. let buffer = '';
  11. let dataMode = false;
  12. socket.on('data', (chunk) => {
  13. buffer += chunk;
  14. let index;
  15. while ((index = buffer.indexOf('\n')) !== -1) {
  16. const line = buffer.slice(0, index).replace(/\r$/, '');
  17. buffer = buffer.slice(index + 1);
  18. if (dataMode) {
  19. if (line === '.') {
  20. dataMode = false;
  21. socket.write('250 2.0.0 queued as ABC123\r\n');
  22. }
  23. continue;
  24. }
  25. capturedCommands.push(line);
  26. if (line.startsWith('EHLO')) {
  27. socket.write('250-relay.test\r\n250 AUTH PLAIN\r\n');
  28. } else if (line.startsWith('AUTH PLAIN')) {
  29. socket.write('235 2.7.0 authentication successful\r\n');
  30. } else if (line.startsWith('MAIL FROM')) {
  31. socket.write('250 2.1.0 sender ok\r\n');
  32. } else if (line.startsWith('RCPT TO')) {
  33. socket.write('250 2.1.5 recipient ok\r\n');
  34. } else if (line === 'DATA') {
  35. dataMode = true;
  36. socket.write('354 end with dot\r\n');
  37. } else if (line === 'QUIT') {
  38. socket.write('221 bye\r\n');
  39. socket.end();
  40. }
  41. }
  42. });
  43. });
  44. await listen(server);
  45. try {
  46. const { port } = server.address();
  47. const result = await sendViaSmtp({
  48. host: '127.0.0.1',
  49. port,
  50. secure: false,
  51. username: 'smtp-user',
  52. password: 'super-secret',
  53. helo: 'mail.example.com',
  54. mailFrom: 'sender@example.com',
  55. recipients: ['rcpt@example.net'],
  56. rawMessage: 'Subject: Private\r\n\r\nThis body must not be logged'
  57. });
  58. assert.equal(result.code, 250);
  59. assert.ok(Array.isArray(result.deliveryLog));
  60. assert.ok(result.deliveryLog.length >= 10);
  61. const serialized = JSON.stringify(result.deliveryLog);
  62. assert.match(serialized, /EHLO mail\.example\.com/);
  63. assert.match(serialized, /AUTH PLAIN <redacted>/);
  64. assert.match(serialized, /MAIL FROM:<sender@example\.com>/);
  65. assert.match(serialized, /RCPT TO:<rcpt@example\.net>/);
  66. assert.match(serialized, /DATA/);
  67. assert.match(serialized, /queued as ABC123/);
  68. assert.match(serialized, /messageBytes/);
  69. assert.doesNotMatch(serialized, /super-secret/);
  70. assert.doesNotMatch(serialized, /This body must not be logged/);
  71. assert.ok(capturedCommands.some((command) => command.startsWith('AUTH PLAIN ')));
  72. } finally {
  73. await close(server);
  74. }
  75. });
  76. function listen(server) {
  77. return new Promise((resolve, reject) => {
  78. server.once('error', reject);
  79. server.listen(0, '127.0.0.1', () => {
  80. server.off('error', reject);
  81. resolve();
  82. });
  83. });
  84. }
  85. function close(server) {
  86. return new Promise((resolve) => server.close(resolve));
  87. }