inbound-db.test.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import assert from 'node:assert/strict';
  2. import { mkdtempSync } from 'node:fs';
  3. import { tmpdir } from 'node:os';
  4. import path from 'node:path';
  5. import { test } from 'node:test';
  6. import {
  7. createDomain,
  8. createInboundMailbox,
  9. createInboundMessage,
  10. createUser,
  11. getInboundMailboxByAddress,
  12. getInboundMessage,
  13. initDatabase,
  14. listInboundMailboxes,
  15. listInboundMessages,
  16. markInboundMessageRead,
  17. resolveInboundRecipient,
  18. updateDomain,
  19. verifySmtpCredential
  20. } from '../src/db.js';
  21. test('users can create inbound mailboxes and read received messages', () => {
  22. initDatabase(mkdtempSync(path.join(tmpdir(), 'mailhub-inbound-db-')), 'inbound-secret');
  23. const user = createUser({ username: 'inbound-user', email: 'inbound-user@example.com', password: 'password123' });
  24. createDomain(user.id, {
  25. domain: 'inbound.example',
  26. selector: 'mh',
  27. verificationToken: 'verify',
  28. dkimPublic: 'public',
  29. dkimPrivate: 'private',
  30. senderHost: 'mail.inbound.example',
  31. sendingIp: '192.0.2.10',
  32. spfExtra: '',
  33. dmarcPolicy: 'none',
  34. dmarcRua: ''
  35. });
  36. const mailbox = createInboundMailbox(user.id, {
  37. address: 'Support@Inbound.Example',
  38. displayName: 'Support',
  39. password: 'mailbox-pass-123',
  40. aliases: 'help desk',
  41. forwardTo: 'archive@example.net, ops@example.net',
  42. keepForwarded: false,
  43. quotaMb: 512
  44. });
  45. assert.equal(mailbox.address, 'support@inbound.example');
  46. assert.equal(mailbox.displayName, 'Support');
  47. assert.equal(mailbox.passwordSet, true);
  48. assert.deepEqual(mailbox.aliases, ['help', 'desk']);
  49. assert.deepEqual(mailbox.forwardTo, ['archive@example.net', 'ops@example.net']);
  50. assert.equal(mailbox.keepForwarded, false);
  51. assert.equal(mailbox.quotaMb, 512);
  52. assert.equal(mailbox.messageCount, 0);
  53. assert.equal(mailbox.unreadCount, 0);
  54. const resolved = getInboundMailboxByAddress('SUPPORT@INBOUND.EXAMPLE');
  55. assert.equal(resolved.id, mailbox.id);
  56. assert.equal(resolved.userId, user.id);
  57. assert.equal(verifySmtpCredential('support@inbound.example', 'mailbox-pass-123').user.id, user.id);
  58. assert.equal(verifySmtpCredential('support@inbound.example', 'wrong-password'), null);
  59. assert.equal(resolveInboundRecipient('help@inbound.example').mailbox.id, mailbox.id);
  60. const message = createInboundMessage(resolved, {
  61. sender: 'alice@example.net',
  62. recipients: ['support@inbound.example'],
  63. subject: 'Hello inbound',
  64. messageId: '<hello@example.net>',
  65. rawMessage: 'From: alice@example.net\r\nTo: support@inbound.example\r\nSubject: Hello inbound\r\n\r\nHello MailHub',
  66. textBody: 'Hello MailHub',
  67. htmlBody: ''
  68. });
  69. assert.equal(message.mailboxId, mailbox.id);
  70. assert.equal(message.read, false);
  71. const mailboxes = listInboundMailboxes(user.id);
  72. assert.equal(mailboxes.length, 1);
  73. assert.equal(mailboxes[0].messageCount, 1);
  74. assert.equal(mailboxes[0].unreadCount, 1);
  75. const messages = listInboundMessages(user.id);
  76. assert.equal(messages.length, 1);
  77. assert.equal(messages[0].subject, 'Hello inbound');
  78. assert.equal(messages[0].preview, 'Hello MailHub');
  79. assert.equal('rawMessage' in messages[0], false);
  80. const detail = getInboundMessage(user.id, message.id);
  81. assert.equal(detail.rawMessage.includes('Hello MailHub'), true);
  82. assert.equal(detail.textBody, 'Hello MailHub');
  83. const readMessage = markInboundMessageRead(user.id, message.id, true);
  84. assert.equal(readMessage.read, true);
  85. assert.equal(listInboundMailboxes(user.id)[0].unreadCount, 0);
  86. assert.equal(markInboundMessageRead(999999, message.id, true), null);
  87. });
  88. test('domains can route unknown inbound recipients to catch-all targets', () => {
  89. initDatabase(mkdtempSync(path.join(tmpdir(), 'mailhub-inbound-catchall-')), 'inbound-secret');
  90. const user = createUser({ username: 'catch-user', email: 'catch@example.com', password: 'password123' });
  91. const domain = createDomain(user.id, {
  92. domain: 'catch.example',
  93. selector: 'mh',
  94. verificationToken: 'verify',
  95. dkimPublic: 'public',
  96. dkimPrivate: 'private',
  97. senderHost: 'mail.catch.example',
  98. sendingIp: '192.0.2.20',
  99. spfExtra: '',
  100. dmarcPolicy: 'none',
  101. dmarcRua: ''
  102. });
  103. const mailbox = createInboundMailbox(user.id, { address: 'share@catch.example' });
  104. const updated = updateDomain(domain.id, user.id, { catchAllAddress: 'share@catch.example' });
  105. assert.equal(updated.catchAllAddress, 'share@catch.example');
  106. const localRoute = resolveInboundRecipient('missing@catch.example');
  107. assert.equal(localRoute.catchAll, true);
  108. assert.equal(localRoute.mailbox.id, mailbox.id);
  109. assert.equal(localRoute.recipient, 'missing@catch.example');
  110. updateDomain(domain.id, user.id, { catchAllAddress: '/dev/null' });
  111. const dropRoute = resolveInboundRecipient('drop@catch.example');
  112. assert.equal(dropRoute.drop, true);
  113. assert.equal(dropRoute.mailbox, null);
  114. updateDomain(domain.id, user.id, { catchAllAddress: 'external@example.net' });
  115. const forwardRoute = resolveInboundRecipient('forward@catch.example');
  116. assert.deepEqual(forwardRoute.forwardTo, ['external@example.net']);
  117. assert.equal(forwardRoute.mailbox, null);
  118. });
  119. test('inbound mailboxes must belong to a domain owned by the user', () => {
  120. initDatabase(mkdtempSync(path.join(tmpdir(), 'mailhub-inbound-db-scope-')), 'inbound-secret');
  121. const owner = createUser({ username: 'owner-user', email: 'owner@example.com', password: 'password123' });
  122. const other = createUser({ username: 'other-user', email: 'other@example.com', password: 'password123' });
  123. createDomain(owner.id, {
  124. domain: 'owned.example',
  125. selector: 'mh',
  126. verificationToken: 'verify',
  127. dkimPublic: 'public',
  128. dkimPrivate: 'private',
  129. senderHost: 'mail.owned.example',
  130. sendingIp: '192.0.2.11',
  131. spfExtra: '',
  132. dmarcPolicy: 'none',
  133. dmarcRua: ''
  134. });
  135. assert.throws(
  136. () => createInboundMailbox(other.id, { address: 'support@owned.example' }),
  137. /收信域名不存在/
  138. );
  139. });