background-signup-step2-branching.test.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. const test = require('node:test');
  2. const assert = require('node:assert/strict');
  3. const fs = require('node:fs');
  4. const step2Source = fs.readFileSync('background/steps/submit-signup-email.js', 'utf8');
  5. const step2GlobalScope = {};
  6. const step2Api = new Function('self', `${step2Source}; return self.MultiPageBackgroundStep2;`)(step2GlobalScope);
  7. const signupFlowSource = fs.readFileSync('background/signup-flow-helpers.js', 'utf8');
  8. const signupFlowGlobalScope = {};
  9. const signupFlowApi = new Function('self', `${signupFlowSource}; return self.MultiPageSignupFlowHelpers;`)(signupFlowGlobalScope);
  10. test('step 2 completes with password step skipped when landing on email verification page', async () => {
  11. const completedPayloads = [];
  12. const executor = step2Api.createStep2Executor({
  13. addLog: async () => {},
  14. chrome: { tabs: { update: async () => {} } },
  15. completeStepFromBackground: async (step, payload) => {
  16. completedPayloads.push({ step, payload });
  17. },
  18. ensureContentScriptReadyOnTab: async () => {},
  19. ensureSignupEntryPageReady: async () => ({ tabId: 11 }),
  20. ensureSignupPostEmailPageReadyInTab: async () => ({
  21. state: 'verification_page',
  22. url: 'https://auth.openai.com/email-verification',
  23. }),
  24. getTabId: async () => 11,
  25. isTabAlive: async () => true,
  26. resolveSignupEmailForFlow: async () => 'user@example.com',
  27. sendToContentScriptResilient: async () => ({ submitted: true }),
  28. SIGNUP_PAGE_INJECT_FILES: [],
  29. });
  30. await executor.executeStep2({ email: 'user@example.com' });
  31. assert.deepStrictEqual(completedPayloads, [
  32. {
  33. step: 2,
  34. payload: {
  35. email: 'user@example.com',
  36. nextSignupState: 'verification_page',
  37. nextSignupUrl: 'https://auth.openai.com/email-verification',
  38. skippedPasswordStep: true,
  39. },
  40. },
  41. ]);
  42. });
  43. test('step 2 keeps password flow when landing on password page', async () => {
  44. const completedPayloads = [];
  45. const executor = step2Api.createStep2Executor({
  46. addLog: async () => {},
  47. chrome: { tabs: { update: async () => {} } },
  48. completeStepFromBackground: async (step, payload) => {
  49. completedPayloads.push({ step, payload });
  50. },
  51. ensureContentScriptReadyOnTab: async () => {},
  52. ensureSignupEntryPageReady: async () => ({ tabId: 12 }),
  53. ensureSignupPostEmailPageReadyInTab: async () => ({
  54. state: 'password_page',
  55. url: 'https://auth.openai.com/create-account/password',
  56. }),
  57. getTabId: async () => 12,
  58. isTabAlive: async () => true,
  59. resolveSignupEmailForFlow: async () => 'user@example.com',
  60. sendToContentScriptResilient: async () => ({ submitted: true }),
  61. SIGNUP_PAGE_INJECT_FILES: [],
  62. });
  63. await executor.executeStep2({ email: 'user@example.com' });
  64. assert.deepStrictEqual(completedPayloads, [
  65. {
  66. step: 2,
  67. payload: {
  68. email: 'user@example.com',
  69. nextSignupState: 'password_page',
  70. nextSignupUrl: 'https://auth.openai.com/create-account/password',
  71. skippedPasswordStep: false,
  72. },
  73. },
  74. ]);
  75. });
  76. test('step 2 retries once after post-email landing check fails', async () => {
  77. const completedPayloads = [];
  78. let entryReadyCalls = 0;
  79. const entryReadyOptions = [];
  80. let sendCalls = 0;
  81. let landingCalls = 0;
  82. const executor = step2Api.createStep2Executor({
  83. addLog: async () => {},
  84. chrome: { tabs: { update: async () => {} } },
  85. completeStepFromBackground: async (step, payload) => {
  86. completedPayloads.push({ step, payload });
  87. },
  88. ensureContentScriptReadyOnTab: async () => {},
  89. ensureSignupEntryPageReady: async (_step, options) => {
  90. entryReadyCalls += 1;
  91. entryReadyOptions.push(options || null);
  92. return { tabId: 13 };
  93. },
  94. ensureSignupPostEmailPageReadyInTab: async () => {
  95. landingCalls += 1;
  96. if (landingCalls === 1) {
  97. throw new Error('等待邮箱提交后的页面跳转超时,请检查页面是否仍停留在邮箱输入页。');
  98. }
  99. return {
  100. state: 'password_page',
  101. url: 'https://auth.openai.com/create-account/password',
  102. };
  103. },
  104. getTabId: async () => 13,
  105. isTabAlive: async () => true,
  106. resolveSignupEmailForFlow: async () => 'user@example.com',
  107. sendToContentScriptResilient: async () => {
  108. sendCalls += 1;
  109. return { submitted: true };
  110. },
  111. SIGNUP_PAGE_INJECT_FILES: [],
  112. });
  113. await executor.executeStep2({ email: 'user@example.com' });
  114. assert.equal(entryReadyCalls, 1);
  115. assert.deepStrictEqual(entryReadyOptions, [{ reloadIfSameUrl: true }]);
  116. assert.equal(sendCalls, 2);
  117. assert.equal(landingCalls, 2);
  118. assert.deepStrictEqual(completedPayloads, [
  119. {
  120. step: 2,
  121. payload: {
  122. email: 'user@example.com',
  123. nextSignupState: 'password_page',
  124. nextSignupUrl: 'https://auth.openai.com/create-account/password',
  125. skippedPasswordStep: false,
  126. },
  127. },
  128. ]);
  129. });
  130. test('signup flow helper recognizes email verification page as post-email landing page', async () => {
  131. let ensureCalls = 0;
  132. let passwordReadyChecks = 0;
  133. const helpers = signupFlowApi.createSignupFlowHelpers({
  134. buildGeneratedAliasEmail: () => '',
  135. chrome: {
  136. tabs: {
  137. get: async () => ({
  138. id: 21,
  139. url: 'https://auth.openai.com/email-verification',
  140. }),
  141. },
  142. },
  143. ensureContentScriptReadyOnTab: async () => {
  144. ensureCalls += 1;
  145. },
  146. ensureHotmailAccountForFlow: async () => ({}),
  147. ensureLuckmailPurchaseForFlow: async () => ({}),
  148. isGeneratedAliasProvider: () => false,
  149. isHotmailProvider: () => false,
  150. isLuckmailProvider: () => false,
  151. isSignupEmailVerificationPageUrl: (url) => /\/email-verification(?:[/?#]|$)/i.test(url || ''),
  152. isSignupPasswordPageUrl: (url) => /\/create-account\/password(?:[/?#]|$)/i.test(url || ''),
  153. reuseOrCreateTab: async () => 21,
  154. sendToContentScriptResilient: async () => {
  155. passwordReadyChecks += 1;
  156. return {};
  157. },
  158. setEmailState: async () => {},
  159. SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
  160. SIGNUP_PAGE_INJECT_FILES: [],
  161. waitForTabUrlMatch: async () => ({
  162. id: 21,
  163. url: 'https://auth.openai.com/email-verification',
  164. }),
  165. });
  166. const result = await helpers.ensureSignupPostEmailPageReadyInTab(21, 2);
  167. assert.deepStrictEqual(result, {
  168. ready: true,
  169. state: 'verification_page',
  170. url: 'https://auth.openai.com/email-verification',
  171. });
  172. assert.equal(ensureCalls, 1);
  173. assert.equal(passwordReadyChecks, 0);
  174. });
  175. test('signup flow helper reuses existing managed alias email when it is still compatible', async () => {
  176. let buildCalls = 0;
  177. let setEmailCalls = 0;
  178. const helpers = signupFlowApi.createSignupFlowHelpers({
  179. buildGeneratedAliasEmail: () => {
  180. buildCalls += 1;
  181. return 'demo+fresh@gmail.com';
  182. },
  183. chrome: { tabs: { get: async () => ({ id: 21, url: 'https://auth.openai.com/create-account/password' }) } },
  184. ensureContentScriptReadyOnTab: async () => {},
  185. ensureHotmailAccountForFlow: async () => ({}),
  186. ensureLuckmailPurchaseForFlow: async () => ({}),
  187. isGeneratedAliasProvider: () => true,
  188. isReusableGeneratedAliasEmail: (_state, email) => email === 'demo+saved@gmail.com',
  189. isHotmailProvider: () => false,
  190. isLuckmailProvider: () => false,
  191. isSignupEmailVerificationPageUrl: () => false,
  192. isSignupPasswordPageUrl: () => true,
  193. reuseOrCreateTab: async () => 21,
  194. sendToContentScriptResilient: async () => ({}),
  195. setEmailState: async () => {
  196. setEmailCalls += 1;
  197. },
  198. SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
  199. SIGNUP_PAGE_INJECT_FILES: [],
  200. waitForTabUrlMatch: async () => null,
  201. });
  202. const email = await helpers.resolveSignupEmailForFlow({
  203. mailProvider: 'gmail',
  204. email: 'demo+saved@gmail.com',
  205. });
  206. assert.equal(email, 'demo+saved@gmail.com');
  207. assert.equal(buildCalls, 0);
  208. assert.equal(setEmailCalls, 0);
  209. });
  210. test('signup flow helper finalizes step 3 submit by reusing signup verification preparation', async () => {
  211. let ensureCalls = 0;
  212. const messages = [];
  213. const sendOptions = [];
  214. const helpers = signupFlowApi.createSignupFlowHelpers({
  215. buildGeneratedAliasEmail: () => '',
  216. chrome: { tabs: { get: async () => ({ id: 31, url: 'https://auth.openai.com/create-account/password' }) } },
  217. ensureContentScriptReadyOnTab: async (...args) => {
  218. ensureCalls += 1;
  219. messages.push({ type: 'ensure', args });
  220. },
  221. ensureHotmailAccountForFlow: async () => ({}),
  222. ensureLuckmailPurchaseForFlow: async () => ({}),
  223. isGeneratedAliasProvider: () => false,
  224. isReusableGeneratedAliasEmail: () => false,
  225. isHotmailProvider: () => false,
  226. isLuckmailProvider: () => false,
  227. isSignupEmailVerificationPageUrl: () => false,
  228. isSignupPasswordPageUrl: () => true,
  229. reuseOrCreateTab: async () => 31,
  230. sendToContentScriptResilient: async (_source, message, options) => {
  231. messages.push({ type: 'send', message });
  232. sendOptions.push(options);
  233. return { ready: true, retried: 1 };
  234. },
  235. setEmailState: async () => {},
  236. SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
  237. SIGNUP_PAGE_INJECT_FILES: ['content/utils.js', 'content/signup-page.js'],
  238. waitForTabUrlMatch: async () => null,
  239. });
  240. const result = await helpers.finalizeSignupPasswordSubmitInTab(31, 'Secret123!', 3);
  241. assert.deepStrictEqual(result, { ready: true, retried: 1 });
  242. assert.equal(ensureCalls, 1);
  243. assert.deepStrictEqual(messages.find((item) => item.type === 'send')?.message, {
  244. type: 'PREPARE_SIGNUP_VERIFICATION',
  245. step: 3,
  246. source: 'background',
  247. payload: {
  248. password: 'Secret123!',
  249. prepareSource: 'step3_finalize',
  250. prepareLogLabel: '步骤 3 收尾',
  251. },
  252. });
  253. assert.equal(sendOptions[0]?.timeoutMs, 65000);
  254. });