system-mail.test.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  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. assert.match(message.text, /登录页面查看账号状态/);
  21. assert.doesNotMatch(message.text, /管理员审核/);
  22. });
  23. test('builds password reset email with reset url', () => {
  24. const message = buildPasswordResetEmail({
  25. appBaseUrl: 'https://mail.example.com',
  26. to: 'alice@example.com',
  27. token: 'reset-token',
  28. fromEmail: 'notify@example.com',
  29. fromName: ''
  30. });
  31. assert.equal(message.from, 'notify@example.com');
  32. assert.match(message.subject, /重置密码/);
  33. assert.match(message.text, /https:\/\/mail\.example\.com\/reset-password\?token=reset-token/);
  34. });
  35. test('sends system email through smtp without returning secrets', async () => {
  36. let sentPayload;
  37. const result = await sendSystemEmail({
  38. host: 'smtp.example.com',
  39. port: 465,
  40. secure: true,
  41. username: 'mailer@example.com',
  42. password: 'smtp-password-123',
  43. helo: 'mail.example.com',
  44. fromEmail: 'notify@example.com',
  45. fromName: 'MailHub Notify'
  46. }, buildVerificationEmail({
  47. appBaseUrl: 'https://mail.example.com',
  48. to: 'alice@example.com',
  49. token: 'verify-token',
  50. fromEmail: 'notify@example.com',
  51. fromName: 'MailHub Notify'
  52. }), {
  53. sendViaSmtp: async (payload) => {
  54. sentPayload = payload;
  55. return {
  56. code: 250,
  57. message: '2.0.0 queued as ABC123',
  58. queueId: 'ABC123',
  59. deliveryLog: [{
  60. phase: 'auth',
  61. direction: 'client',
  62. command: 'AUTH PLAIN <redacted>'
  63. }]
  64. };
  65. }
  66. });
  67. assert.equal(sentPayload.host, 'smtp.example.com');
  68. assert.equal(sentPayload.port, 465);
  69. assert.equal(sentPayload.secure, true);
  70. assert.equal(sentPayload.username, 'mailer@example.com');
  71. assert.equal(sentPayload.password, 'smtp-password-123');
  72. assert.equal(sentPayload.helo, 'mail.example.com');
  73. assert.equal(sentPayload.mailFrom, 'notify@example.com');
  74. assert.deepEqual(sentPayload.recipients, ['alice@example.com']);
  75. assert.match(sentPayload.rawMessage, /^From: "MailHub Notify" <notify@example.com>/);
  76. assert.deepEqual(result, {
  77. ok: true,
  78. code: 250,
  79. message: '2.0.0 queued as ABC123',
  80. queueId: 'ABC123'
  81. });
  82. assert.equal(JSON.stringify(result).includes('smtp-password-123'), false);
  83. assert.equal(JSON.stringify(result).includes('verify-token'), false);
  84. });
  85. test('normalizes array recipients before building smtp payload', async () => {
  86. let sentPayload;
  87. await sendSystemEmail({
  88. host: 'smtp.example.com',
  89. port: 25,
  90. secure: false,
  91. username: '',
  92. password: '',
  93. helo: 'mail.example.com',
  94. fromEmail: 'notify@example.com',
  95. fromName: 'MailHub Notify'
  96. }, {
  97. from: '"MailHub Notify" <notify@example.com>',
  98. to: ['Alice <alice@example.com>', 'bad\r\nRCPT TO:<evil@example.com>'],
  99. subject: '安全测试',
  100. text: 'Hello'
  101. }, {
  102. sendViaSmtp: async (payload) => {
  103. sentPayload = payload;
  104. return { code: 250, message: 'queued', queueId: 'SAFE' };
  105. }
  106. });
  107. assert.deepEqual(sentPayload.recipients, ['alice@example.com']);
  108. assert.match(sentPayload.rawMessage, /^To: alice@example.com$/m);
  109. assert.doesNotMatch(sentPayload.rawMessage, /^Bcc:/m);
  110. assert.doesNotMatch(sentPayload.rawMessage, /RCPT TO/i);
  111. });