dns-guide.test.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import assert from 'node:assert/strict';
  2. import dns from 'node:dns';
  3. import { test } from 'node:test';
  4. test('includes live current values for verification and dkim records', async () => {
  5. const originalResolver = dns.promises.Resolver;
  6. const lookup = new Map([
  7. ['example.com', ['v=spf1 ip4:192.0.2.10 a:mail.example.com ~all']],
  8. ['_mailhub.example.com', ['mailhub-verification=verify-token']],
  9. ['mh202607._domainkey.example.com', ['v=DKIM1; k=rsa; p=dkim-public']],
  10. ['_dmarc.example.com', ['v=DMARC1; p=none; rua=mailto:dmarc@example.com; adkim=s; aspf=s; pct=100']]
  11. ]);
  12. dns.promises.Resolver = class FakeResolver {
  13. setServers() {}
  14. async resolveTxt(name) {
  15. const value = lookup.get(name);
  16. if (!value) {
  17. const error = new Error('not found');
  18. error.code = 'ENODATA';
  19. throw error;
  20. }
  21. return value.map((record) => [record]);
  22. }
  23. async resolve4(name) {
  24. return name === 'mail.example.com' ? ['192.0.2.10'] : [];
  25. }
  26. async reverse(ip) {
  27. return ip === '192.0.2.10' ? ['mail.example.com'] : [];
  28. }
  29. };
  30. try {
  31. const { buildDnsGuide } = await import(`../src/dns-guide.js?test=${Date.now()}`);
  32. const guide = await buildDnsGuide({
  33. domain: 'example.com',
  34. selector: 'mh202607',
  35. verificationToken: 'verify-token',
  36. dkimPublic: 'dkim-public',
  37. senderHost: 'mail.example.com',
  38. sendingIp: '192.0.2.10',
  39. spfExtra: '',
  40. dmarcPolicy: 'none',
  41. dmarcRua: 'mailto:dmarc@example.com'
  42. });
  43. const verification = guide.records.find((record) => record.key === 'verification');
  44. const dkim = guide.records.find((record) => record.key === 'dkim');
  45. const spf = guide.records.find((record) => record.key === 'spf');
  46. assert.equal(guide.records.some((record) => record.key === 'ptr'), false);
  47. assert.deepEqual(verification.current, ['mailhub-verification=verify-token']);
  48. assert.deepEqual(dkim.current, ['v=DKIM1; k=rsa; p=dkim-public']);
  49. assert.deepEqual(guide.requiredSpf, ['a:mail.example.com']);
  50. assert.equal(spf.value, 'v=spf1 a:mail.example.com ~all');
  51. assert.equal(spf.value.includes('ip4:'), false);
  52. } finally {
  53. dns.promises.Resolver = originalResolver;
  54. }
  55. });
  56. test('removes the managed direct IP while preserving configured SPF extensions', async () => {
  57. const originalResolver = dns.promises.Resolver;
  58. dns.promises.Resolver = class FakeResolver {
  59. setServers() {}
  60. async resolveTxt(name) {
  61. if (name === 'example.com') {
  62. return [['v=spf1 ip4:192.0.2.10 a:mail.example.com include:spf.mailjet.com ~all']];
  63. }
  64. const error = new Error('not found');
  65. error.code = 'ENODATA';
  66. throw error;
  67. }
  68. async resolve4() {
  69. return ['192.0.2.20'];
  70. }
  71. };
  72. try {
  73. const { buildDnsGuide } = await import(`../src/dns-guide.js?test=${Date.now()}-spf-policy`);
  74. const guide = await buildDnsGuide({
  75. domain: 'example.com',
  76. selector: 'mailhub',
  77. verificationToken: 'verify-token',
  78. dkimPublic: 'dkim-public',
  79. senderHost: 'mail.example.com',
  80. sendingIp: '192.0.2.10',
  81. spfExtra: 'ip4:192.0.2.10 include:spf.mailjet.com',
  82. dmarcPolicy: 'none',
  83. dmarcRua: ''
  84. });
  85. const spf = guide.records.find((record) => record.key === 'spf');
  86. assert.equal(spf.value, 'v=spf1 a:mail.example.com include:spf.mailjet.com ~all');
  87. assert.match(spf.warnings.join('\n'), /直接 IP 授权/);
  88. } finally {
  89. dns.promises.Resolver = originalResolver;
  90. }
  91. });
  92. test('removes stale direct IP mechanisms after the managed sending IP changes', async () => {
  93. const originalResolver = dns.promises.Resolver;
  94. dns.promises.Resolver = class FakeResolver {
  95. setServers() {}
  96. async resolveTxt(name) {
  97. if (name === 'example.com') {
  98. return [['v=spf1 ip4:192.0.2.10 ip4:198.51.100.25 a:mail.example.com ~all']];
  99. }
  100. const error = new Error('not found');
  101. error.code = 'ENODATA';
  102. throw error;
  103. }
  104. async resolve4() {
  105. return ['192.0.2.10'];
  106. }
  107. };
  108. try {
  109. const { buildDnsGuide } = await import(`../src/dns-guide.js?test=${Date.now()}-spf-preserve`);
  110. const guide = await buildDnsGuide({
  111. domain: 'example.com',
  112. selector: 'mailhub',
  113. verificationToken: 'verify-token',
  114. dkimPublic: 'dkim-public',
  115. senderHost: 'mail.example.com',
  116. sendingIp: '192.0.2.20',
  117. spfExtra: '',
  118. dmarcPolicy: 'none',
  119. dmarcRua: ''
  120. });
  121. const spf = guide.records.find((record) => record.key === 'spf');
  122. assert.equal(spf.value, 'v=spf1 a:mail.example.com ~all');
  123. assert.match(spf.warnings.join('\n'), /直接 IP 授权/);
  124. } finally {
  125. dns.promises.Resolver = originalResolver;
  126. }
  127. });
  128. test('preserves direct IP mechanisms explicitly configured as SPF extensions', async () => {
  129. const originalResolver = dns.promises.Resolver;
  130. dns.promises.Resolver = class FakeResolver {
  131. setServers() {}
  132. async resolveTxt(name) {
  133. if (name === 'example.com') {
  134. return [['v=spf1 ip4:192.0.2.10 ip4:198.51.100.25 include:spf.mailjet.com ~all']];
  135. }
  136. const error = new Error('not found');
  137. error.code = 'ENODATA';
  138. throw error;
  139. }
  140. async resolve4() {
  141. return ['192.0.2.20'];
  142. }
  143. };
  144. try {
  145. const { buildDnsGuide } = await import(`../src/dns-guide.js?test=${Date.now()}-spf-explicit-ip`);
  146. const guide = await buildDnsGuide({
  147. domain: 'example.com',
  148. selector: 'mailhub',
  149. verificationToken: 'verify-token',
  150. dkimPublic: 'dkim-public',
  151. senderHost: 'mail.example.com',
  152. sendingIp: '192.0.2.20',
  153. spfExtra: 'ip4:198.51.100.25 include:spf.mailjet.com',
  154. dmarcPolicy: 'none',
  155. dmarcRua: ''
  156. });
  157. const spf = guide.records.find((record) => record.key === 'spf');
  158. assert.equal(
  159. spf.value,
  160. 'v=spf1 ip4:198.51.100.25 include:spf.mailjet.com a:mail.example.com ~all'
  161. );
  162. } finally {
  163. dns.promises.Resolver = originalResolver;
  164. }
  165. });
  166. test('builds PTR as a system delivery check', async () => {
  167. const originalResolver = dns.promises.Resolver;
  168. dns.promises.Resolver = class FakeResolver {
  169. setServers() {}
  170. async reverse(ip) {
  171. return ip === '192.0.2.10' ? ['mail.example.com'] : [];
  172. }
  173. };
  174. try {
  175. const { buildSystemDnsChecks } = await import(`../src/dns-guide.js?test=${Date.now()}-system`);
  176. const checks = await buildSystemDnsChecks({
  177. mailHostname: 'mail.example.com',
  178. sendingIp: '192.0.2.10'
  179. });
  180. assert.equal(checks.ptr.key, 'ptr');
  181. assert.equal(checks.ptr.host, '192.0.2.10');
  182. assert.equal(checks.ptr.value, 'mail.example.com');
  183. assert.equal(checks.ptr.status, 'ok');
  184. assert.deepEqual(checks.ptr.current, ['mail.example.com']);
  185. } finally {
  186. dns.promises.Resolver = originalResolver;
  187. }
  188. });