auto-run-step6-restart.test.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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.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. const bundle = [
  49. extractFunction('isAddPhoneAuthFailure'),
  50. extractFunction('isAddPhoneAuthUrl'),
  51. extractFunction('isAddPhoneAuthState'),
  52. extractFunction('getPostStep6AutoRestartDecision'),
  53. extractFunction('getNextActiveStep'),
  54. extractFunction('runAutoSequenceFromStep'),
  55. ].join('\n');
  56. function createHarness(options = {}) {
  57. const {
  58. startStep = 6,
  59. failureStep = 6,
  60. failureBudget = 1,
  61. failureMessage = '认证失败: Request failed with status code 502',
  62. authState = { state: 'password_page', url: 'https://auth.openai.com/log-in' },
  63. } = options;
  64. return new Function(`
  65. const STEP_IDS = [1, 2, 3, 4, 5, 6, 7, 8, 9];
  66. const AUTO_STEP_DELAYS = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0 };
  67. const LAST_STEP_ID = 9;
  68. const FINAL_OAUTH_CHAIN_START_STEP = null;
  69. const LOG_PREFIX = '[test]';
  70. const chrome = {
  71. tabs: {
  72. update: async () => {},
  73. },
  74. };
  75. let remainingFailures = ${JSON.stringify(failureBudget)};
  76. const events = {
  77. steps: [],
  78. logs: [],
  79. invalidations: [],
  80. };
  81. async function addLog(message, level = 'info') {
  82. events.logs.push({ message, level });
  83. }
  84. async function ensureAutoEmailReady() {}
  85. async function broadcastAutoRunStatus() {}
  86. async function getState() {
  87. return {
  88. stepStatuses: { 3: 'completed' },
  89. mailProvider: '163',
  90. };
  91. }
  92. function isStopError(error) {
  93. return (error?.message || String(error || '')) === '流程已被用户停止。';
  94. }
  95. function isStepDoneStatus(status) {
  96. return status === 'completed' || status === 'manual_completed' || status === 'skipped';
  97. }
  98. async function executeStepAndWait(step) {
  99. events.steps.push(step);
  100. if (step === ${JSON.stringify(failureStep)} && remainingFailures > 0) {
  101. remainingFailures -= 1;
  102. throw new Error(${JSON.stringify(failureMessage)});
  103. }
  104. }
  105. async function getTabId() {
  106. return 1;
  107. }
  108. async function invalidateDownstreamAfterStepRestart(step, options = {}) {
  109. events.invalidations.push({ step, options });
  110. }
  111. function getLoginAuthStateLabel(state) {
  112. return state || 'unknown';
  113. }
  114. function getErrorMessage(error) {
  115. return error?.message || String(error || '');
  116. }
  117. async function getLoginAuthStateFromContent() {
  118. return ${JSON.stringify(authState)};
  119. }
  120. ${bundle}
  121. return {
  122. async run() {
  123. await runAutoSequenceFromStep(${JSON.stringify(startStep)}, {
  124. targetRun: 1,
  125. totalRuns: 1,
  126. attemptRuns: 1,
  127. continued: false,
  128. });
  129. return events;
  130. },
  131. async runAndCaptureError() {
  132. try {
  133. await runAutoSequenceFromStep(${JSON.stringify(startStep)}, {
  134. targetRun: 1,
  135. totalRuns: 1,
  136. attemptRuns: 1,
  137. continued: false,
  138. });
  139. return null;
  140. } catch (error) {
  141. return { error, events };
  142. }
  143. },
  144. };
  145. `)();
  146. }
  147. test('auto-run no longer restarts from removed OAuth chain after payment-link failures', async () => {
  148. const harness = createHarness({
  149. failureStep: 6,
  150. failureBudget: 1,
  151. failureMessage: '认证失败: Request failed with status code 502',
  152. authState: { state: 'password_page', url: 'https://auth.openai.com/log-in' },
  153. });
  154. const result = await harness.runAndCaptureError();
  155. assert.ok(result?.error);
  156. assert.equal(result.events.invalidations.length, 0);
  157. assert.deepStrictEqual(result.events.steps, [6]);
  158. assert.ok(!result.events.logs.some(({ message }) => /回到步骤 7 重新开始授权流程/.test(message)));
  159. });
  160. test('auto-run does not revive removed step 7 when add-phone is detected after step 6', async () => {
  161. const harness = createHarness({
  162. failureStep: 6,
  163. failureBudget: 1,
  164. failureMessage: '当前页面已进入手机号页面。URL: https://auth.openai.com/add-phone',
  165. authState: { state: 'add_phone_page', url: 'https://auth.openai.com/add-phone' },
  166. });
  167. const result = await harness.runAndCaptureError();
  168. assert.ok(result?.error);
  169. assert.equal(result.events.invalidations.length, 0);
  170. assert.deepStrictEqual(result.events.steps, [6]);
  171. assert.ok(!result.events.logs.some(({ message }) => /回到步骤 7 重新开始授权流程/.test(message)));
  172. });
  173. test('auto-run does not restart removed OAuth chain on generic phone-page failures', async () => {
  174. const harness = createHarness({
  175. failureStep: 6,
  176. failureBudget: 1,
  177. failureMessage: '步骤 8:当前认证页进入手机号页面,当前流程无法继续自动授权。',
  178. authState: { state: 'password_page', url: 'https://auth.openai.com/log-in' },
  179. });
  180. const result = await harness.runAndCaptureError();
  181. assert.ok(result?.error);
  182. assert.equal(result.events.invalidations.length, 0);
  183. assert.deepStrictEqual(result.events.steps, [6]);
  184. assert.ok(!result.events.logs.some(({ message }) => /回到步骤 7 重新开始授权流程/.test(message)));
  185. });
  186. test('auto-run stop errors after removed OAuth chain are rethrown immediately', async () => {
  187. const harness = createHarness({
  188. failureStep: 6,
  189. failureBudget: 1,
  190. failureMessage: '流程已被用户停止。',
  191. authState: { state: 'password_page', url: 'https://auth.openai.com/log-in' },
  192. });
  193. const result = await harness.runAndCaptureError();
  194. assert.equal(result?.error?.message, '流程已被用户停止。');
  195. assert.equal(result.events.invalidations.length, 0);
  196. assert.deepStrictEqual(result.events.steps, [6]);
  197. assert.ok(!result.events.logs.some(({ message }) => /回到步骤 7 重新开始授权流程/.test(message)));
  198. });