mail-2925-content.test.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. const test = require('node:test');
  2. const assert = require('node:assert/strict');
  3. const fs = require('node:fs');
  4. const source = fs.readFileSync('content/mail-2925.js', 'utf8');
  5. function extractFunction(name) {
  6. const markers = [`async function ${name}(`, `function ${name}(`];
  7. const start = markers
  8. .map((marker) => source.indexOf(marker))
  9. .find((index) => index >= 0);
  10. if (start < 0) {
  11. throw new Error(`missing function ${name}`);
  12. }
  13. let parenDepth = 0;
  14. let signatureEnded = false;
  15. let braceStart = -1;
  16. for (let i = start; i < source.length; i += 1) {
  17. const ch = source[i];
  18. if (ch === '(') {
  19. parenDepth += 1;
  20. } else if (ch === ')') {
  21. parenDepth -= 1;
  22. if (parenDepth === 0) {
  23. signatureEnded = true;
  24. }
  25. } else if (ch === '{' && signatureEnded) {
  26. braceStart = i;
  27. break;
  28. }
  29. }
  30. if (braceStart < 0) {
  31. throw new Error(`missing body for function ${name}`);
  32. }
  33. let depth = 0;
  34. let end = braceStart;
  35. for (; end < source.length; end += 1) {
  36. const ch = source[end];
  37. if (ch === '{') depth += 1;
  38. if (ch === '}') {
  39. depth -= 1;
  40. if (depth === 0) {
  41. end += 1;
  42. break;
  43. }
  44. }
  45. }
  46. return source.slice(start, end);
  47. }
  48. test('handlePollEmail returns to inbox before initial refresh when 2925 opens on a detail page', async () => {
  49. const bundle = extractFunction('handlePollEmail');
  50. const api = new Function(`
  51. let detailMode = true;
  52. const clickOrder = [];
  53. const seenCodes = new Set();
  54. const mailItem = { text: 'OpenAI verification code 654321' };
  55. function findMailItems() {
  56. return detailMode ? [] : [mailItem];
  57. }
  58. function getMailItemId() {
  59. return 'mail-1';
  60. }
  61. function getCurrentMailIds(items = []) {
  62. return new Set(items.map(() => 'mail-1'));
  63. }
  64. function normalizeMinuteTimestamp(value) {
  65. return Number(value) || 0;
  66. }
  67. function parseMailItemTimestamp() {
  68. return Date.now();
  69. }
  70. function matchesMailFilters() {
  71. return true;
  72. }
  73. function getMailItemText(item) {
  74. return item.text;
  75. }
  76. function extractVerificationCode(text) {
  77. const match = String(text || '').match(/(\\d{6})/);
  78. return match ? match[1] : null;
  79. }
  80. function extractEmails() {
  81. return [];
  82. }
  83. function emailMatchesTarget() {
  84. return true;
  85. }
  86. function getTargetEmailMatchState() {
  87. return { matches: true, hasExplicitEmail: false };
  88. }
  89. async function sleep() {}
  90. async function sleepRandom() {}
  91. async function returnToInbox() {
  92. clickOrder.push('inbox');
  93. detailMode = false;
  94. return true;
  95. }
  96. async function refreshInbox() {
  97. clickOrder.push('refresh');
  98. }
  99. function persistSeenCodes() {}
  100. function log() {}
  101. ${bundle}
  102. return {
  103. handlePollEmail,
  104. getClickOrder() {
  105. return clickOrder.slice();
  106. },
  107. };
  108. `)();
  109. const result = await api.handlePollEmail(4, {
  110. senderFilters: ['openai'],
  111. subjectFilters: ['verification'],
  112. maxAttempts: 1,
  113. intervalMs: 1,
  114. filterAfterTimestamp: Date.now(),
  115. });
  116. assert.equal(result.code, '654321');
  117. assert.deepEqual(api.getClickOrder(), ['inbox', 'refresh']);
  118. });
  119. test('openMailAndGetMessageText always returns to inbox after opening a 2925 message', async () => {
  120. const bundle = [
  121. extractFunction('findInboxLink'),
  122. extractFunction('returnToInbox'),
  123. extractFunction('openMailAndGetMessageText'),
  124. ].join('\n');
  125. const api = new Function(`
  126. const MAIL_INBOX_SELECTORS = [
  127. 'a[href*="mailList"]',
  128. '[class*="inbox"]',
  129. '[class*="Inbox"]',
  130. '[title*="鏀朵欢绠?]',
  131. ];
  132. const clickOrder = [];
  133. const mailItem = { kind: 'mail' };
  134. const inboxLink = { kind: 'inbox' };
  135. let listVisible = true;
  136. let bodyText = '';
  137. const document = {
  138. body: {
  139. get textContent() {
  140. return bodyText;
  141. },
  142. },
  143. querySelector(selector) {
  144. if (selector.includes('mailList') || selector.includes('inbox') || selector.includes('Inbox')) {
  145. return inboxLink;
  146. }
  147. return null;
  148. },
  149. };
  150. function findMailItems() {
  151. return listVisible ? [mailItem] : [];
  152. }
  153. function simulateClick(node) {
  154. if (node === mailItem) {
  155. clickOrder.push('mail');
  156. listVisible = false;
  157. bodyText = 'Your ChatGPT code is 731091';
  158. return;
  159. }
  160. if (node === inboxLink) {
  161. clickOrder.push('inbox');
  162. listVisible = true;
  163. return;
  164. }
  165. throw new Error('unexpected node');
  166. }
  167. async function sleep() {}
  168. async function sleepRandom() {}
  169. ${bundle}
  170. return {
  171. mailItem,
  172. openMailAndGetMessageText,
  173. getClickOrder() {
  174. return clickOrder.slice();
  175. },
  176. isListVisible() {
  177. return listVisible;
  178. },
  179. };
  180. `)();
  181. const text = await api.openMailAndGetMessageText(api.mailItem);
  182. assert.match(text, /731091/);
  183. assert.deepEqual(api.getClickOrder(), ['mail', 'inbox']);
  184. assert.equal(api.isListVisible(), true);
  185. });