background-message-router-step2-skip.test.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. const test = require('node:test');
  2. const assert = require('node:assert/strict');
  3. const fs = require('node:fs');
  4. const source = fs.readFileSync('background/message-router.js', 'utf8');
  5. const globalScope = {};
  6. const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
  7. function createRouter(overrides = {}) {
  8. const events = {
  9. logs: [],
  10. stepStatuses: [],
  11. emailStates: [],
  12. finalizePayloads: [],
  13. notifyCompletions: [],
  14. notifyErrors: [],
  15. };
  16. const router = api.createMessageRouter({
  17. addLog: async (message, level) => {
  18. events.logs.push({ message, level });
  19. },
  20. appendAccountRunRecord: async () => null,
  21. batchUpdateLuckmailPurchases: async () => {},
  22. buildLocalhostCleanupPrefix: () => '',
  23. buildLuckmailSessionSettingsPayload: () => ({}),
  24. buildPersistentSettingsPayload: () => ({}),
  25. broadcastDataUpdate: () => {},
  26. cancelScheduledAutoRun: async () => {},
  27. checkIcloudSession: async () => {},
  28. clearAutoRunTimerAlarm: async () => {},
  29. clearLuckmailRuntimeState: async () => {},
  30. clearStopRequest: () => {},
  31. closeLocalhostCallbackTabs: async () => {},
  32. closeTabsByUrlPrefix: async () => {},
  33. deleteHotmailAccount: async () => {},
  34. deleteHotmailAccounts: async () => {},
  35. deleteIcloudAlias: async () => {},
  36. deleteUsedIcloudAliases: async () => {},
  37. disableUsedLuckmailPurchases: async () => {},
  38. doesStepUseCompletionSignal: () => false,
  39. ensureManualInteractionAllowed: async () => ({}),
  40. executeStep: async () => {},
  41. executeStepViaCompletionSignal: async () => {},
  42. exportSettingsBundle: async () => ({}),
  43. fetchGeneratedEmail: async () => '',
  44. finalizeStep3Completion: overrides.finalizeStep3Completion || (async (payload) => {
  45. events.finalizePayloads.push(payload);
  46. }),
  47. finalizeIcloudAliasAfterSuccessfulFlow: async () => {},
  48. findHotmailAccount: async () => null,
  49. flushCommand: async () => {},
  50. getCurrentLuckmailPurchase: () => null,
  51. getPendingAutoRunTimerPlan: () => null,
  52. getSourceLabel: () => '',
  53. getState: async () => overrides.state || { stepStatuses: { 3: 'pending' } },
  54. getStopRequested: () => false,
  55. handleAutoRunLoopUnhandledError: async () => {},
  56. importSettingsBundle: async () => {},
  57. invalidateDownstreamAfterStepRestart: async () => {},
  58. isAutoRunLockedState: () => false,
  59. isHotmailProvider: () => false,
  60. isLocalhostOAuthCallbackUrl: () => true,
  61. isLuckmailProvider: () => false,
  62. isStopError: () => false,
  63. launchAutoRunTimerPlan: async () => {},
  64. listIcloudAliases: async () => [],
  65. listLuckmailPurchasesForManagement: async () => [],
  66. normalizeHotmailAccounts: (items) => items,
  67. normalizeRunCount: (value) => value,
  68. AUTO_RUN_TIMER_KIND_SCHEDULED_START: 'scheduled',
  69. notifyStepComplete: (step, payload) => {
  70. events.notifyCompletions.push({ step, payload });
  71. },
  72. notifyStepError: (step, error) => {
  73. events.notifyErrors.push({ step, error });
  74. },
  75. patchHotmailAccount: async () => {},
  76. registerTab: async () => {},
  77. requestStop: async () => {},
  78. resetState: async () => {},
  79. resumeAutoRun: async () => {},
  80. scheduleAutoRun: async () => {},
  81. selectLuckmailPurchase: async () => {},
  82. setCurrentHotmailAccount: async () => {},
  83. setEmailState: async (email) => {
  84. events.emailStates.push(email);
  85. },
  86. setEmailStateSilently: async () => {},
  87. setIcloudAliasPreservedState: async () => {},
  88. setIcloudAliasUsedState: async () => {},
  89. setLuckmailPurchaseDisabledState: async () => {},
  90. setLuckmailPurchasePreservedState: async () => {},
  91. setLuckmailPurchaseUsedState: async () => {},
  92. setPersistentSettings: async () => {},
  93. setState: async () => {},
  94. setStepStatus: async (step, status) => {
  95. events.stepStatuses.push({ step, status });
  96. },
  97. skipAutoRunCountdown: async () => false,
  98. skipStep: async () => {},
  99. startAutoRunLoop: async () => {},
  100. syncHotmailAccounts: async () => {},
  101. testHotmailAccountMailAccess: async () => {},
  102. upsertHotmailAccount: async () => {},
  103. verifyHotmailAccount: async () => {},
  104. });
  105. return { router, events };
  106. }
  107. test('message router skips step 3 when step 2 lands on verification page', async () => {
  108. const { router, events } = createRouter({
  109. state: { stepStatuses: { 3: 'pending' } },
  110. });
  111. await router.handleStepData(2, {
  112. email: 'user@example.com',
  113. skippedPasswordStep: true,
  114. });
  115. assert.deepStrictEqual(events.emailStates, ['user@example.com']);
  116. assert.deepStrictEqual(events.stepStatuses, [{ step: 3, status: 'skipped' }]);
  117. assert.equal(events.logs[0]?.message, '步骤 2:提交邮箱后页面直接进入邮箱验证码页,已自动跳过步骤 3。');
  118. });
  119. test('message router does not overwrite a completed step 3 when step 2 is replayed', async () => {
  120. const { router, events } = createRouter({
  121. state: { stepStatuses: { 3: 'completed' } },
  122. });
  123. await router.handleStepData(2, {
  124. skippedPasswordStep: true,
  125. });
  126. assert.deepStrictEqual(events.stepStatuses, []);
  127. });
  128. test('message router finalizes step 3 before marking it completed', async () => {
  129. const { router, events } = createRouter();
  130. const response = await router.handleMessage({
  131. type: 'STEP_COMPLETE',
  132. step: 3,
  133. source: 'signup-page',
  134. payload: {
  135. email: 'user@example.com',
  136. signupVerificationRequestedAt: 123,
  137. },
  138. }, {});
  139. assert.deepStrictEqual(events.finalizePayloads, [
  140. {
  141. email: 'user@example.com',
  142. signupVerificationRequestedAt: 123,
  143. },
  144. ]);
  145. assert.deepStrictEqual(events.stepStatuses, [{ step: 3, status: 'completed' }]);
  146. assert.deepStrictEqual(events.emailStates, ['user@example.com']);
  147. assert.deepStrictEqual(events.notifyCompletions, [
  148. {
  149. step: 3,
  150. payload: {
  151. email: 'user@example.com',
  152. signupVerificationRequestedAt: 123,
  153. },
  154. },
  155. ]);
  156. assert.deepStrictEqual(response, { ok: true });
  157. });
  158. test('message router marks step 3 failed when post-submit finalize fails', async () => {
  159. const { router, events } = createRouter({
  160. finalizeStep3Completion: async () => {
  161. throw new Error('步骤 3 提交后仍停留在密码页。');
  162. },
  163. });
  164. const response = await router.handleMessage({
  165. type: 'STEP_COMPLETE',
  166. step: 3,
  167. source: 'signup-page',
  168. payload: {
  169. email: 'user@example.com',
  170. },
  171. }, {});
  172. assert.deepStrictEqual(events.stepStatuses, [{ step: 3, status: 'failed' }]);
  173. assert.deepStrictEqual(events.notifyErrors, [
  174. {
  175. step: 3,
  176. error: '步骤 3 提交后仍停留在密码页。',
  177. },
  178. ]);
  179. assert.equal(events.logs.some(({ message }) => /步骤 3 失败:步骤 3 提交后仍停留在密码页。/.test(message)), true);
  180. assert.deepStrictEqual(response, { ok: true, error: '步骤 3 提交后仍停留在密码页。' });
  181. });
  182. test('message router suppresses duplicate content ready logs for same source tab', async () => {
  183. const { router, events } = createRouter();
  184. await router.handleMessage({
  185. type: 'CONTENT_SCRIPT_READY',
  186. source: 'checkout-stripe',
  187. }, { tab: { id: 662543926 } });
  188. await router.handleMessage({
  189. type: 'CONTENT_SCRIPT_READY',
  190. source: 'checkout-stripe',
  191. }, { tab: { id: 662543926 } });
  192. assert.deepStrictEqual(
  193. events.logs.filter((entry) => /内容脚本已就绪/.test(entry.message)).map((entry) => entry.message),
  194. ['内容脚本已就绪:(标签页 662543926)']
  195. );
  196. });