background-step10-cpa-sync.test.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. const test = require('node:test');
  2. const assert = require('node:assert/strict');
  3. const fs = require('node:fs');
  4. function loadModules(fetchImpl) {
  5. const cpaSource = fs.readFileSync('background/cpa-api.js', 'utf8');
  6. const stepSource = fs.readFileSync('background/steps/sync-cpa-session.js', 'utf8');
  7. const scope = {};
  8. new Function('self', 'fetch', `${cpaSource}\n${stepSource}; return self;`)(scope, fetchImpl);
  9. return scope;
  10. }
  11. function base64UrlJson(value) {
  12. return Buffer.from(JSON.stringify(value), 'utf8')
  13. .toString('base64')
  14. .replace(/\+/g, '-')
  15. .replace(/\//g, '_')
  16. .replace(/=+$/g, '');
  17. }
  18. function jwt(payload) {
  19. return `${base64UrlJson({ alg: 'RS256', typ: 'JWT' })}.${base64UrlJson(payload)}.signature`;
  20. }
  21. test('step 10 reads ChatGPT session and imports CPA auth JSON', async () => {
  22. const accessToken = jwt({
  23. exp: 1893456000,
  24. 'https://api.openai.com/auth': {
  25. chatgpt_account_id: 'acct_step10',
  26. chatgpt_plan_type: 'plus',
  27. },
  28. 'https://api.openai.com/profile': {
  29. email: 'step10@example.com',
  30. },
  31. });
  32. const fetchCalls = [];
  33. const fetchImpl = async (url, options = {}) => {
  34. fetchCalls.push({ url, options });
  35. if (url === 'https://chatgpt.com/api/auth/session') {
  36. return {
  37. ok: true,
  38. status: 200,
  39. text: async () => JSON.stringify({
  40. accessToken,
  41. user: { email: 'step10@example.com' },
  42. }),
  43. };
  44. }
  45. if (url === 'https://cpa.example.com/v0/management/auth-files?name=codex-step10%40example.com-plus.json') {
  46. return {
  47. ok: true,
  48. status: 200,
  49. json: async () => ({ ok: true }),
  50. };
  51. }
  52. throw new Error(`unexpected fetch: ${url}`);
  53. };
  54. const scope = loadModules(fetchImpl);
  55. const events = {
  56. completed: null,
  57. logs: [],
  58. };
  59. const executor = scope.MultiPageBackgroundCpaSessionSync.createCpaSessionSyncExecutor({
  60. addLog: async (message, level = 'info') => events.logs.push({ message, level }),
  61. completeStepFromBackground: async (step, payload) => {
  62. events.completed = { step, payload };
  63. },
  64. createCpaApi: scope.MultiPageBackgroundCpaApi.createCpaApi,
  65. fetchImpl,
  66. getPanelMode: (state) => state.panelMode || 'cpa',
  67. });
  68. await executor.executeStep10({
  69. panelMode: 'cpa',
  70. vpsUrl: 'https://cpa.example.com/management.html#/oauth',
  71. vpsPassword: 'secret',
  72. });
  73. assert.equal(events.completed.step, 10);
  74. assert.deepEqual(events.completed.payload, {
  75. verifiedStatus: 'CPA 会话导入完成:step10@example.com',
  76. cpaImportedFileName: 'codex-step10@example.com-plus.json',
  77. cpaImportedEmail: 'step10@example.com',
  78. });
  79. assert.equal(fetchCalls.length, 2);
  80. assert.equal(fetchCalls[0].url, 'https://chatgpt.com/api/auth/session');
  81. assert.equal(fetchCalls[1].options.headers.Authorization, 'Bearer secret');
  82. assert.equal(JSON.parse(fetchCalls[1].options.body).access_token, accessToken);
  83. });
  84. test('step 10 skips CPA sync in sub2api mode', async () => {
  85. const scope = loadModules(async () => {
  86. throw new Error('fetch should not be called when sub2api is selected');
  87. });
  88. const events = {
  89. completed: null,
  90. logs: [],
  91. };
  92. const executor = scope.MultiPageBackgroundCpaSessionSync.createCpaSessionSyncExecutor({
  93. addLog: async (message, level = 'info') => events.logs.push({ message, level }),
  94. completeStepFromBackground: async (step, payload) => {
  95. events.completed = { step, payload };
  96. },
  97. createCpaApi: scope.MultiPageBackgroundCpaApi.createCpaApi,
  98. getPanelMode: () => 'sub2api',
  99. });
  100. await executor.executeStep10({ panelMode: 'sub2api' });
  101. assert.deepEqual(events.completed, {
  102. step: 10,
  103. payload: {
  104. cpaSyncSkipped: true,
  105. cpaSyncSkipReason: 'sub2api-mode',
  106. },
  107. });
  108. assert.equal(events.logs.some((entry) => /跳过 CPA session 同步/.test(entry.message)), true);
  109. });