luckmail-utils.test.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. const test = require('node:test');
  2. const assert = require('node:assert/strict');
  3. const {
  4. DEFAULT_LUCKMAIL_BASE_URL,
  5. DEFAULT_LUCKMAIL_EMAIL_TYPE,
  6. DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  7. buildLuckmailBaselineCursor,
  8. buildLuckmailMailCursor,
  9. extractLuckmailVerificationCode,
  10. filterReusableLuckmailPurchases,
  11. isLuckmailMailNewerThanCursor,
  12. isLuckmailPurchaseForProject,
  13. normalizeLuckmailBaseUrl,
  14. normalizeLuckmailEmailType,
  15. normalizeLuckmailProjectName,
  16. normalizeLuckmailPurchaseListPage,
  17. normalizeLuckmailTags,
  18. normalizeLuckmailUsedPurchases,
  19. normalizeTimestamp,
  20. normalizeLuckmailTokenCode,
  21. normalizeLuckmailTokenMail,
  22. pickReusableLuckmailPurchase,
  23. pickLuckmailVerificationMail,
  24. } = require('../luckmail-utils.js');
  25. test('normalizeLuckmailEmailType keeps supported values and falls back to ms_graph', () => {
  26. assert.equal(normalizeLuckmailEmailType('self_built'), 'self_built');
  27. assert.equal(normalizeLuckmailEmailType('ms_imap'), 'ms_imap');
  28. assert.equal(normalizeLuckmailEmailType('ms_graph'), 'ms_graph');
  29. assert.equal(normalizeLuckmailEmailType('google_variant'), 'google_variant');
  30. assert.equal(normalizeLuckmailEmailType(''), DEFAULT_LUCKMAIL_EMAIL_TYPE);
  31. assert.equal(normalizeLuckmailEmailType('unknown'), DEFAULT_LUCKMAIL_EMAIL_TYPE);
  32. });
  33. test('normalizeLuckmailBaseUrl trims invalid input to default base url', () => {
  34. assert.equal(normalizeLuckmailBaseUrl(''), DEFAULT_LUCKMAIL_BASE_URL);
  35. assert.equal(normalizeLuckmailBaseUrl('https://mails.luckyous.com/'), DEFAULT_LUCKMAIL_BASE_URL);
  36. assert.equal(normalizeLuckmailBaseUrl('https://demo.example.com/api/'), 'https://demo.example.com/api');
  37. assert.equal(normalizeLuckmailBaseUrl('notaurl'), DEFAULT_LUCKMAIL_BASE_URL);
  38. });
  39. test('normalizeLuckmailTokenCode and normalizeLuckmailTokenMail extract verification code', () => {
  40. const tokenCode = normalizeLuckmailTokenCode({
  41. email_address: 'demo@outlook.com',
  42. project: 'openai',
  43. has_new_mail: true,
  44. verification_code: '123456',
  45. mail: {
  46. message_id: 'mail-1',
  47. from: 'noreply@openai.com',
  48. subject: 'Your ChatGPT code is 123456',
  49. received_at: '2026-04-14T10:00:00Z',
  50. },
  51. });
  52. assert.equal(tokenCode.verification_code, '123456');
  53. assert.equal(tokenCode.mail.message_id, 'mail-1');
  54. assert.equal(tokenCode.mail.verification_code, '123456');
  55. const normalizedMail = normalizeLuckmailTokenMail({
  56. message_id: 'mail-2',
  57. from: 'noreply@openai.com',
  58. subject: 'OpenAI security message',
  59. body: 'Your verification code is 654321.',
  60. received_at: '2026-04-14T10:01:00Z',
  61. });
  62. assert.equal(normalizedMail.verification_code, '654321');
  63. assert.equal(extractLuckmailVerificationCode('你的验证码为 778899'), '778899');
  64. });
  65. test('normalizeLuckmailProjectName and isLuckmailPurchaseForProject match openai case-insensitively', () => {
  66. assert.equal(normalizeLuckmailProjectName(' OpenAi '), 'openai');
  67. assert.equal(isLuckmailPurchaseForProject({
  68. id: 1,
  69. email_address: 'demo@outlook.com',
  70. token: 'tok-1',
  71. project_name: 'OpenAi',
  72. }, 'openai'), true);
  73. assert.equal(isLuckmailPurchaseForProject({
  74. id: 2,
  75. email_address: 'other@example.com',
  76. token: 'tok-2',
  77. project: 'OtherProject',
  78. }, 'openai'), false);
  79. });
  80. test('normalizeLuckmailPurchaseListPage and normalizeLuckmailTags normalize list payloads', () => {
  81. const page = normalizeLuckmailPurchaseListPage({
  82. list: [{
  83. id: 3,
  84. email_address: 'demo@outlook.com',
  85. token: 'tok-3',
  86. project_name: 'OpenAi',
  87. }],
  88. total: 4,
  89. page: 2,
  90. page_size: 1,
  91. });
  92. assert.equal(page.total, 4);
  93. assert.equal(page.page, 2);
  94. assert.equal(page.page_size, 1);
  95. assert.equal(page.list[0].project_code, 'openai');
  96. const tags = normalizeLuckmailTags([{
  97. id: 9,
  98. name: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  99. limit_type: 0,
  100. }]);
  101. assert.deepEqual(tags[0], {
  102. id: 9,
  103. name: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  104. remark: '',
  105. limit_type: 0,
  106. purchase_count: 0,
  107. created_at: null,
  108. });
  109. });
  110. test('normalizeLuckmailUsedPurchases keeps positive numeric keys only', () => {
  111. assert.deepEqual(normalizeLuckmailUsedPurchases({
  112. 1: true,
  113. foo: true,
  114. '-2': true,
  115. 3: false,
  116. }), {
  117. 1: true,
  118. 3: false,
  119. });
  120. });
  121. test('pickReusableLuckmailPurchase only returns reusable openai purchase', () => {
  122. const purchases = [{
  123. id: 10,
  124. email_address: 'used@outlook.com',
  125. token: 'tok-used',
  126. project_name: 'openai',
  127. }, {
  128. id: 11,
  129. email_address: 'preserved@outlook.com',
  130. token: 'tok-preserved',
  131. project_name: 'OpenAi',
  132. tag_name: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  133. }, {
  134. id: 12,
  135. email_address: 'disabled@outlook.com',
  136. token: 'tok-disabled',
  137. project_name: 'openai',
  138. user_disabled: 1,
  139. }, {
  140. id: 13,
  141. email_address: 'expired@outlook.com',
  142. token: 'tok-expired',
  143. project_name: 'openai',
  144. warranty_until: '2026-04-14T09:00:00Z',
  145. }, {
  146. id: 14,
  147. email_address: 'other@example.com',
  148. token: 'tok-other',
  149. project_name: 'other',
  150. }, {
  151. id: 15,
  152. email_address: 'ready@outlook.com',
  153. token: 'tok-ready',
  154. project_name: 'OpenAi',
  155. warranty_until: '2026-04-15T09:00:00Z',
  156. }];
  157. const reusable = filterReusableLuckmailPurchases(purchases, {
  158. projectCode: 'openai',
  159. usedPurchases: { 10: true },
  160. preserveTagName: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  161. now: Date.parse('2026-04-14T10:00:00Z'),
  162. });
  163. assert.deepEqual(reusable.map((purchase) => purchase.id), [15]);
  164. assert.equal(pickReusableLuckmailPurchase(purchases, {
  165. projectCode: 'openai',
  166. usedPurchases: { 10: true },
  167. preserveTagName: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
  168. now: Date.parse('2026-04-14T10:00:00Z'),
  169. }).id, 15);
  170. });
  171. test('pickLuckmailVerificationMail respects sender filters, time filters, and excluded codes', () => {
  172. const match = pickLuckmailVerificationMail([
  173. {
  174. message_id: 'old-mail',
  175. from: 'noreply@openai.com',
  176. subject: 'Your code is 111111',
  177. received_at: '2026-04-14T09:59:00Z',
  178. },
  179. {
  180. message_id: 'new-mail',
  181. from: 'noreply@openai.com',
  182. subject: 'Your code is 222222',
  183. received_at: '2026-04-14T10:05:00Z',
  184. },
  185. ], {
  186. senderFilters: ['openai'],
  187. subjectFilters: ['code'],
  188. excludeCodes: ['111111'],
  189. afterTimestamp: Date.parse('2026-04-14T10:00:00Z'),
  190. });
  191. assert.equal(match.code, '222222');
  192. assert.equal(match.mail.message_id, 'new-mail');
  193. });
  194. test('isLuckmailMailNewerThanCursor compares message id and timestamp safely', () => {
  195. const cursor = buildLuckmailMailCursor({
  196. message_id: 'mail-1',
  197. received_at: '2026-04-14T10:00:00Z',
  198. });
  199. assert.equal(isLuckmailMailNewerThanCursor({
  200. message_id: 'mail-1',
  201. received_at: '2026-04-14T10:00:00Z',
  202. }, cursor), false);
  203. assert.equal(isLuckmailMailNewerThanCursor({
  204. message_id: 'mail-2',
  205. received_at: '2026-04-14T10:01:00Z',
  206. }, cursor), true);
  207. });
  208. test('normalizeLuckmailMailCursor tolerates null cursor input', () => {
  209. const { normalizeLuckmailMailCursor } = require('../luckmail-utils.js');
  210. assert.deepEqual(normalizeLuckmailMailCursor(null), {
  211. messageId: '',
  212. receivedAt: '',
  213. });
  214. });
  215. test('normalizeTimestamp treats LuckMail naive datetime strings as UTC', () => {
  216. assert.equal(
  217. normalizeTimestamp('2026-04-14 13:32:05'),
  218. Date.UTC(2026, 3, 14, 13, 32, 5, 0)
  219. );
  220. });
  221. test('buildLuckmailBaselineCursor tracks newest existing mail as baseline', () => {
  222. const cursor = buildLuckmailBaselineCursor([
  223. {
  224. message_id: 'mail-old',
  225. received_at: '2026-04-14 13:31:15',
  226. subject: '你的 ChatGPT 代码为 111111',
  227. },
  228. {
  229. message_id: 'mail-new',
  230. received_at: '2026-04-14 13:32:05',
  231. subject: '你的 ChatGPT 代码为 222222',
  232. },
  233. ]);
  234. assert.deepEqual(cursor, {
  235. messageId: 'mail-new',
  236. receivedAt: '2026-04-14 13:32:05',
  237. });
  238. });