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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  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. let sendCalls = 0;
  80. let landingCalls = 0;
  81. const executor = step2Api.createStep2Executor({
  82. addLog: async () => {},
  83. chrome: { tabs: { update: async () => {} } },
  84. completeStepFromBackground: async (step, payload) => {
  85. completedPayloads.push({ step, payload });
  86. },
  87. ensureContentScriptReadyOnTab: async () => {},
  88. ensureSignupEntryPageReady: async () => {
  89. entryReadyCalls += 1;
  90. return { tabId: 13 };
  91. },
  92. ensureSignupPostEmailPageReadyInTab: async () => {
  93. landingCalls += 1;
  94. if (landingCalls === 1) {
  95. throw new Error('等待邮箱提交后的页面跳转超时,请检查页面是否仍停留在邮箱输入页。');
  96. }
  97. return {
  98. state: 'password_page',
  99. url: 'https://auth.openai.com/create-account/password',
  100. };
  101. },
  102. getTabId: async () => 13,
  103. isTabAlive: async () => true,
  104. resolveSignupEmailForFlow: async () => 'user@example.com',
  105. sendToContentScriptResilient: async () => {
  106. sendCalls += 1;
  107. return { submitted: true };
  108. },
  109. SIGNUP_PAGE_INJECT_FILES: [],
  110. });
  111. await executor.executeStep2({ email: 'user@example.com' });
  112. assert.equal(entryReadyCalls, 1);
  113. assert.equal(sendCalls, 2);
  114. assert.equal(landingCalls, 2);
  115. assert.deepStrictEqual(completedPayloads, [
  116. {
  117. step: 2,
  118. payload: {
  119. email: 'user@example.com',
  120. nextSignupState: 'password_page',
  121. nextSignupUrl: 'https://auth.openai.com/create-account/password',
  122. skippedPasswordStep: false,
  123. },
  124. },
  125. ]);
  126. });
  127. test('signup flow helper recognizes email verification page as post-email landing page', async () => {
  128. let ensureCalls = 0;
  129. let passwordReadyChecks = 0;
  130. const helpers = signupFlowApi.createSignupFlowHelpers({
  131. buildGeneratedAliasEmail: () => '',
  132. chrome: {
  133. tabs: {
  134. get: async () => ({
  135. id: 21,
  136. url: 'https://auth.openai.com/email-verification',
  137. }),
  138. },
  139. },
  140. ensureContentScriptReadyOnTab: async () => {
  141. ensureCalls += 1;
  142. },
  143. ensureHotmailAccountForFlow: async () => ({}),
  144. ensureLuckmailPurchaseForFlow: async () => ({}),
  145. isGeneratedAliasProvider: () => false,
  146. isHotmailProvider: () => false,
  147. isLuckmailProvider: () => false,
  148. isSignupEmailVerificationPageUrl: (url) => /\/email-verification(?:[/?#]|$)/i.test(url || ''),
  149. isSignupPasswordPageUrl: (url) => /\/create-account\/password(?:[/?#]|$)/i.test(url || ''),
  150. reuseOrCreateTab: async () => 21,
  151. sendToContentScriptResilient: async () => {
  152. passwordReadyChecks += 1;
  153. return {};
  154. },
  155. setEmailState: async () => {},
  156. SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
  157. SIGNUP_PAGE_INJECT_FILES: [],
  158. waitForTabUrlMatch: async () => ({
  159. id: 21,
  160. url: 'https://auth.openai.com/email-verification',
  161. }),
  162. });
  163. const result = await helpers.ensureSignupPostEmailPageReadyInTab(21, 2);
  164. assert.deepStrictEqual(result, {
  165. ready: true,
  166. state: 'verification_page',
  167. url: 'https://auth.openai.com/email-verification',
  168. });
  169. assert.equal(ensureCalls, 1);
  170. assert.equal(passwordReadyChecks, 0);
  171. });
  172. test('signup flow helper reuses existing managed alias email when it is still compatible', async () => {
  173. let buildCalls = 0;
  174. let setEmailCalls = 0;
  175. const helpers = signupFlowApi.createSignupFlowHelpers({
  176. buildGeneratedAliasEmail: () => {
  177. buildCalls += 1;
  178. return 'demo+fresh@gmail.com';
  179. },
  180. chrome: { tabs: { get: async () => ({ id: 21, url: 'https://auth.openai.com/create-account/password' }) } },
  181. ensureContentScriptReadyOnTab: async () => {},
  182. ensureHotmailAccountForFlow: async () => ({}),
  183. ensureLuckmailPurchaseForFlow: async () => ({}),
  184. isGeneratedAliasProvider: () => true,
  185. isReusableGeneratedAliasEmail: (_state, email) => email === 'demo+saved@gmail.com',
  186. isHotmailProvider: () => false,
  187. isLuckmailProvider: () => false,
  188. isSignupEmailVerificationPageUrl: () => false,
  189. isSignupPasswordPageUrl: () => true,
  190. reuseOrCreateTab: async () => 21,
  191. sendToContentScriptResilient: async () => ({}),
  192. setEmailState: async () => {
  193. setEmailCalls += 1;
  194. },
  195. SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
  196. SIGNUP_PAGE_INJECT_FILES: [],
  197. waitForTabUrlMatch: async () => null,
  198. });
  199. const email = await helpers.resolveSignupEmailForFlow({
  200. mailProvider: 'gmail',
  201. email: 'demo+saved@gmail.com',
  202. });
  203. assert.equal(email, 'demo+saved@gmail.com');
  204. assert.equal(buildCalls, 0);
  205. assert.equal(setEmailCalls, 0);
  206. });
  207. test('signup flow helper finalizes step 3 submit by reusing signup verification preparation', async () => {
  208. let ensureCalls = 0;
  209. const messages = [];
  210. const sendOptions = [];
  211. const helpers = signupFlowApi.createSignupFlowHelpers({
  212. buildGeneratedAliasEmail: () => '',
  213. chrome: { tabs: { get: async () => ({ id: 31, url: 'https://auth.openai.com/create-account/password' }) } },
  214. ensureContentScriptReadyOnTab: async (...args) => {
  215. ensureCalls += 1;
  216. messages.push({ type: 'ensure', args });
  217. },
  218. ensureHotmailAccountForFlow: async () => ({}),
  219. ensureLuckmailPurchaseForFlow: async () => ({}),
  220. isGeneratedAliasProvider: () => false,
  221. isReusableGeneratedAliasEmail: () => false,
  222. isHotmailProvider: () => false,
  223. isLuckmailProvider: () => false,
  224. isSignupEmailVerificationPageUrl: () => false,
  225. isSignupPasswordPageUrl: () => true,
  226. reuseOrCreateTab: async () => 31,
  227. sendToContentScriptResilient: async (_source, message, options) => {
  228. messages.push({ type: 'send', message });
  229. sendOptions.push(options);
  230. return { ready: true, retried: 1 };
  231. },
  232. setEmailState: async () => {},
  233. SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
  234. SIGNUP_PAGE_INJECT_FILES: ['content/utils.js', 'content/signup-page.js'],
  235. waitForTabUrlMatch: async () => null,
  236. });
  237. const result = await helpers.finalizeSignupPasswordSubmitInTab(31, 'Secret123!', 3);
  238. assert.deepStrictEqual(result, { ready: true, retried: 1 });
  239. assert.equal(ensureCalls, 1);
  240. assert.deepStrictEqual(messages.find((item) => item.type === 'send')?.message, {
  241. type: 'PREPARE_SIGNUP_VERIFICATION',
  242. step: 3,
  243. source: 'background',
  244. payload: {
  245. password: 'Secret123!',
  246. prepareSource: 'step3_finalize',
  247. prepareLogLabel: '步骤 3 收尾',
  248. },
  249. });
  250. assert.equal(sendOptions[0]?.timeoutMs, 65000);
  251. });