microsoft-email.test.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. const test = require('node:test');
  2. const assert = require('node:assert/strict');
  3. const {
  4. extractVerificationCodeFromMessages,
  5. fetchMicrosoftMailboxMessages,
  6. fetchMicrosoftVerificationCode,
  7. normalizeMailboxId,
  8. } = require('../microsoft-email.js');
  9. test('extractVerificationCodeFromMessages 支持显式过滤条件并跳过排除的验证码', () => {
  10. const result = extractVerificationCodeFromMessages([
  11. {
  12. From: { EmailAddress: { Address: 'noreply@openai.com' } },
  13. Subject: 'Your code is 112233',
  14. BodyPreview: '112233',
  15. ReceivedDateTime: '2026-04-14T09:00:00.000Z',
  16. Id: 'too-old',
  17. },
  18. {
  19. From: { EmailAddress: { Address: 'alerts@example.com' } },
  20. Subject: 'Your code is 223344',
  21. BodyPreview: '223344',
  22. ReceivedDateTime: '2026-04-14T10:00:00.000Z',
  23. Id: 'wrong-sender',
  24. },
  25. {
  26. From: { EmailAddress: { Address: 'account-security@openai.com' } },
  27. Subject: 'OpenAI verification',
  28. BodyPreview: 'Use 334455 to continue',
  29. ReceivedDateTime: '2026-04-14T10:05:00.000Z',
  30. Id: 'matched',
  31. },
  32. ], {
  33. filterAfterTimestamp: Date.UTC(2026, 3, 14, 9, 30, 0),
  34. senderFilters: ['openai'],
  35. subjectFilters: ['verification'],
  36. excludeCodes: ['112233'],
  37. });
  38. assert.deepEqual(result, {
  39. code: '334455',
  40. emailTimestamp: Date.UTC(2026, 3, 14, 10, 5, 0),
  41. messageId: 'matched',
  42. sender: 'account-security@openai.com',
  43. subject: 'OpenAI verification',
  44. mailbox: 'INBOX',
  45. message: {
  46. mailbox: 'INBOX',
  47. from: {
  48. emailAddress: {
  49. address: 'account-security@openai.com',
  50. name: '',
  51. },
  52. },
  53. subject: 'OpenAI verification',
  54. receivedDateTime: '2026-04-14T10:05:00.000Z',
  55. bodyPreview: 'Use 334455 to continue',
  56. body: {
  57. content: '',
  58. },
  59. id: 'matched',
  60. },
  61. });
  62. });
  63. test('normalizeMailboxId 将 Junk 归一为微软邮箱夹 ID', () => {
  64. assert.equal(normalizeMailboxId('INBOX'), 'inbox');
  65. assert.equal(normalizeMailboxId('junk'), 'junkemail');
  66. assert.equal(normalizeMailboxId('Junk Email'), 'junkemail');
  67. });
  68. test('fetchMicrosoftMailboxMessages 会回退到可用的 token 策略并保留邮箱夹信息', async () => {
  69. const requests = [];
  70. const fetchImpl = async (url, options = {}) => {
  71. requests.push({ url, options });
  72. if (String(url).includes('/oauth2/v2.0/token')) {
  73. const params = new URLSearchParams(String(options.body || ''));
  74. if (String(url).includes('/common/') && params.get('scope')?.includes('Mail.Read')) {
  75. return {
  76. ok: false,
  77. status: 400,
  78. statusText: 'Bad Request',
  79. text: async () => JSON.stringify({ error_description: 'common delegated failed' }),
  80. };
  81. }
  82. return {
  83. ok: true,
  84. json: async () => ({
  85. access_token: 'access-token-1',
  86. refresh_token: 'refresh-token-next',
  87. }),
  88. };
  89. }
  90. assert.match(String(url), /graph\.microsoft\.com\/v1\.0\/me\/mailFolders\/junkemail\/messages/);
  91. return {
  92. ok: true,
  93. json: async () => ({
  94. value: [
  95. {
  96. from: { emailAddress: { address: 'noreply@openai.com' } },
  97. subject: 'OpenAI verification',
  98. bodyPreview: 'Use 445566 to continue',
  99. receivedDateTime: '2026-04-14T10:06:00.000Z',
  100. id: 'mail-1',
  101. },
  102. ],
  103. }),
  104. };
  105. };
  106. const result = await fetchMicrosoftMailboxMessages({
  107. clientId: 'client-1',
  108. refreshToken: 'refresh-token-1',
  109. mailbox: 'Junk',
  110. top: 5,
  111. fetchImpl,
  112. });
  113. assert.equal(requests.length, 3);
  114. assert.equal(result.nextRefreshToken, 'refresh-token-next');
  115. assert.equal(result.tokenStrategy, 'entra-consumers-delegated');
  116. assert.equal(result.transport, 'graph');
  117. assert.equal(result.messages.length, 1);
  118. assert.equal(result.messages[0].id, 'mail-1');
  119. assert.equal(result.messages[0].mailbox, 'Junk');
  120. });
  121. test('fetchMicrosoftVerificationCode 会按邮箱夹轮询并在 Junk 中命中最新验证码', async () => {
  122. const mailboxRequests = {
  123. inbox: 0,
  124. junkemail: 0,
  125. };
  126. const logs = [];
  127. const fetchImpl = async (url) => {
  128. if (String(url).includes('/oauth2/v2.0/token')) {
  129. return {
  130. ok: true,
  131. json: async () => ({
  132. access_token: 'access-token-2',
  133. refresh_token: 'refresh-token-next-2',
  134. }),
  135. };
  136. }
  137. const urlString = String(url);
  138. if (urlString.includes('/mailFolders/inbox/messages')) {
  139. mailboxRequests.inbox += 1;
  140. return {
  141. ok: true,
  142. json: async () => ({
  143. value: [{
  144. From: { EmailAddress: { Address: 'alerts@example.com' } },
  145. Subject: 'Nothing useful',
  146. BodyPreview: 'No code',
  147. ReceivedDateTime: '2026-04-14T10:00:00.000Z',
  148. Id: 'mail-ignore',
  149. }],
  150. }),
  151. };
  152. }
  153. assert.match(urlString, /mailFolders\/junkemail\/messages/);
  154. mailboxRequests.junkemail += 1;
  155. if (mailboxRequests.junkemail === 1) {
  156. return {
  157. ok: true,
  158. json: async () => ({
  159. value: [{
  160. from: { emailAddress: { address: 'no-reply@example.com' } },
  161. subject: 'Nothing useful',
  162. bodyPreview: 'Still no code',
  163. receivedDateTime: '2026-04-14T10:05:00.000Z',
  164. id: 'mail-ignore-2',
  165. }],
  166. }),
  167. };
  168. }
  169. return {
  170. ok: true,
  171. json: async () => ({
  172. value: [{
  173. from: { emailAddress: { address: 'account-security@openai.com' } },
  174. Subject: 'Your verification code',
  175. BodyPreview: '667788',
  176. ReceivedDateTime: '2026-04-14T10:10:00.000Z',
  177. Id: 'mail-hit',
  178. }],
  179. }),
  180. };
  181. };
  182. const result = await fetchMicrosoftVerificationCode({
  183. token: 'refresh-token-2',
  184. clientId: 'client-2',
  185. maxRetries: 2,
  186. retryDelayMs: 0,
  187. mailboxes: ['INBOX', 'Junk'],
  188. fetchImpl,
  189. log: (message) => logs.push(message),
  190. filterAfterTimestamp: Date.UTC(2026, 3, 14, 9, 0, 0),
  191. senderFilters: ['openai'],
  192. subjectFilters: ['verification'],
  193. });
  194. assert.equal(result.code, '667788');
  195. assert.equal(result.messageId, 'mail-hit');
  196. assert.equal(result.nextRefreshToken, 'refresh-token-next-2');
  197. assert.equal(result.mailbox, 'Junk');
  198. assert.equal(mailboxRequests.inbox, 2);
  199. assert.equal(mailboxRequests.junkemail, 2);
  200. assert.equal(logs.some((message) => /retrying/i.test(message)), true);
  201. });