system-mail.test.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import assert from 'node:assert/strict';
  2. import { test } from 'node:test';
  3. import {
  4. buildPasswordResetEmail,
  5. buildVerificationEmail,
  6. sendSystemEmail
  7. } from '../src/system-mail.js';
  8. test('builds verification email with configured sender and verification url', () => {
  9. const message = buildVerificationEmail({
  10. appBaseUrl: 'https://mail.example.com/',
  11. to: 'alice@example.com',
  12. token: 'verify-token',
  13. fromEmail: 'notify@example.com',
  14. fromName: 'MailHub Notify'
  15. });
  16. assert.equal(message.from, '"MailHub Notify" <notify@example.com>');
  17. assert.equal(message.to, 'alice@example.com');
  18. assert.match(message.subject, /验证邮箱/);
  19. assert.match(message.text, /https:\/\/mail\.example\.com\/api\/auth\/verify-email\?token=verify-token/);
  20. });
  21. test('builds password reset email with reset url', () => {
  22. const message = buildPasswordResetEmail({
  23. appBaseUrl: 'https://mail.example.com',
  24. to: 'alice@example.com',
  25. token: 'reset-token',
  26. fromEmail: 'notify@example.com',
  27. fromName: ''
  28. });
  29. assert.equal(message.from, 'notify@example.com');
  30. assert.match(message.subject, /重置密码/);
  31. assert.match(message.text, /https:\/\/mail\.example\.com\/reset-password\?token=reset-token/);
  32. });
  33. test('sends system email through smtp without returning secrets', async () => {
  34. let sentPayload;
  35. const result = await sendSystemEmail({
  36. host: 'smtp.example.com',
  37. port: 465,
  38. secure: true,
  39. username: 'mailer@example.com',
  40. password: 'smtp-password-123',
  41. helo: 'mail.example.com',
  42. fromEmail: 'notify@example.com',
  43. fromName: 'MailHub Notify'
  44. }, buildVerificationEmail({
  45. appBaseUrl: 'https://mail.example.com',
  46. to: 'alice@example.com',
  47. token: 'verify-token',
  48. fromEmail: 'notify@example.com',
  49. fromName: 'MailHub Notify'
  50. }), {
  51. sendViaSmtp: async (payload) => {
  52. sentPayload = payload;
  53. return {
  54. code: 250,
  55. message: '2.0.0 queued as ABC123',
  56. queueId: 'ABC123',
  57. deliveryLog: [{
  58. phase: 'auth',
  59. direction: 'client',
  60. command: 'AUTH PLAIN <redacted>'
  61. }]
  62. };
  63. }
  64. });
  65. assert.equal(sentPayload.host, 'smtp.example.com');
  66. assert.equal(sentPayload.port, 465);
  67. assert.equal(sentPayload.secure, true);
  68. assert.equal(sentPayload.username, 'mailer@example.com');
  69. assert.equal(sentPayload.password, 'smtp-password-123');
  70. assert.equal(sentPayload.helo, 'mail.example.com');
  71. assert.equal(sentPayload.mailFrom, 'notify@example.com');
  72. assert.deepEqual(sentPayload.recipients, ['alice@example.com']);
  73. assert.match(sentPayload.rawMessage, /^From: "MailHub Notify" <notify@example.com>/);
  74. assert.deepEqual(result, {
  75. ok: true,
  76. code: 250,
  77. message: '2.0.0 queued as ABC123',
  78. queueId: 'ABC123'
  79. });
  80. assert.equal(JSON.stringify(result).includes('smtp-password-123'), false);
  81. assert.equal(JSON.stringify(result).includes('verify-token'), false);
  82. });
  83. test('normalizes array recipients before building smtp payload', async () => {
  84. let sentPayload;
  85. await sendSystemEmail({
  86. host: 'smtp.example.com',
  87. port: 25,
  88. secure: false,
  89. username: '',
  90. password: '',
  91. helo: 'mail.example.com',
  92. fromEmail: 'notify@example.com',
  93. fromName: 'MailHub Notify'
  94. }, {
  95. from: '"MailHub Notify" <notify@example.com>',
  96. to: ['Alice <alice@example.com>', 'bad\r\nRCPT TO:<evil@example.com>'],
  97. subject: '安全测试',
  98. text: 'Hello'
  99. }, {
  100. sendViaSmtp: async (payload) => {
  101. sentPayload = payload;
  102. return { code: 250, message: 'queued', queueId: 'SAFE' };
  103. }
  104. });
  105. assert.deepEqual(sentPayload.recipients, ['alice@example.com']);
  106. assert.match(sentPayload.rawMessage, /^To: alice@example.com$/m);
  107. assert.doesNotMatch(sentPayload.rawMessage, /^Bcc:/m);
  108. assert.doesNotMatch(sentPayload.rawMessage, /RCPT TO/i);
  109. });