background-cpa-api.test.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. const test = require('node:test');
  2. const assert = require('node:assert/strict');
  3. const fs = require('node:fs');
  4. function loadCpaApi(fetchImpl) {
  5. const source = fs.readFileSync('background/cpa-api.js', 'utf8');
  6. const scope = {};
  7. new Function('self', 'fetch', `${source}; return self.MultiPageBackgroundCpaApi;`)(scope, fetchImpl);
  8. return scope.MultiPageBackgroundCpaApi;
  9. }
  10. function base64UrlJson(value) {
  11. return Buffer.from(JSON.stringify(value), 'utf8')
  12. .toString('base64')
  13. .replace(/\+/g, '-')
  14. .replace(/\//g, '_')
  15. .replace(/=+$/g, '');
  16. }
  17. function jwt(payload) {
  18. return `${base64UrlJson({ alg: 'RS256', typ: 'JWT' })}.${base64UrlJson(payload)}.signature`;
  19. }
  20. test('buildCpaSessionAuthJson converts ChatGPT session to codex auth JSON', () => {
  21. const api = loadCpaApi(async () => {
  22. throw new Error('fetch should not be called');
  23. }).createCpaApi();
  24. const accessToken = jwt({
  25. exp: 1893456000,
  26. 'https://api.openai.com/auth': {
  27. chatgpt_account_id: 'acct_123',
  28. chatgpt_user_id: 'user_123',
  29. chatgpt_plan_type: 'plus',
  30. },
  31. 'https://api.openai.com/profile': {
  32. email: 'profile@example.com',
  33. },
  34. });
  35. const result = api.buildCpaSessionAuthJson({
  36. accessToken,
  37. session: {
  38. user: { email: 'session@example.com' },
  39. refresh_token: 'refresh-token',
  40. session_token: 'session-token',
  41. },
  42. }, {
  43. now: new Date('2026-05-21T00:00:00.000Z'),
  44. });
  45. assert.equal(result.fileName, 'codex-session@example.com-plus.json');
  46. assert.equal(result.email, 'session@example.com');
  47. assert.equal(result.accountId, 'acct_123');
  48. assert.equal(result.hasRefreshToken, true);
  49. assert.deepEqual(result.authJson, {
  50. type: 'codex',
  51. account_id: 'acct_123',
  52. chatgpt_account_id: 'acct_123',
  53. email: 'session@example.com',
  54. name: 'session@example.com',
  55. plan_type: 'plus',
  56. chatgpt_plan_type: 'plus',
  57. id_token: result.authJson.id_token,
  58. id_token_synthetic: true,
  59. access_token: accessToken,
  60. refresh_token: 'refresh-token',
  61. session_token: 'session-token',
  62. last_refresh: '2026-05-21T00:00:00.000Z',
  63. expired: '2030-01-01T00:00:00.000Z',
  64. });
  65. assert.match(result.authJson.id_token, /\.synthetic$/);
  66. });
  67. test('importCurrentChatGptSession posts auth JSON to CPA management endpoint', async () => {
  68. const calls = [];
  69. const fetchImpl = async (url, options = {}) => {
  70. calls.push({ url, options });
  71. return {
  72. ok: true,
  73. status: 200,
  74. json: async () => ({ ok: true }),
  75. };
  76. };
  77. const logs = [];
  78. const api = loadCpaApi(fetchImpl).createCpaApi({
  79. addLog: async (message, level) => logs.push({ message, level }),
  80. fetchImpl,
  81. });
  82. const accessToken = jwt({
  83. exp: 1893456000,
  84. 'https://api.openai.com/auth': {
  85. chatgpt_account_id: 'acct_abc',
  86. chatgpt_plan_type: 'plus',
  87. },
  88. 'https://api.openai.com/profile': {
  89. email: 'sync@example.com',
  90. },
  91. });
  92. const result = await api.importCurrentChatGptSession({
  93. vpsUrl: 'http://127.0.0.1:8317/management.html#/oauth',
  94. vpsPassword: 'management-secret',
  95. session: {
  96. accessToken,
  97. user: { email: 'sync@example.com' },
  98. },
  99. }, {
  100. logLabel: '步骤 10',
  101. now: new Date('2026-05-21T00:00:00.000Z'),
  102. });
  103. assert.equal(result.cpaImportedFileName, 'codex-sync@example.com-plus.json');
  104. assert.equal(result.cpaImportedEmail, 'sync@example.com');
  105. assert.equal(calls.length, 1);
  106. assert.equal(
  107. calls[0].url,
  108. 'http://127.0.0.1:8317/v0/management/auth-files?name=codex-sync%40example.com-plus.json'
  109. );
  110. assert.equal(calls[0].options.method, 'POST');
  111. assert.equal(calls[0].options.headers.Authorization, 'Bearer management-secret');
  112. assert.equal(calls[0].options.headers['X-Management-Key'], 'management-secret');
  113. const body = JSON.parse(calls[0].options.body);
  114. assert.equal(body.type, 'codex');
  115. assert.equal(body.email, 'sync@example.com');
  116. assert.equal(body.access_token, accessToken);
  117. assert.equal(body.chatgpt_account_id, 'acct_abc');
  118. assert.equal(logs.some((entry) => /CPA 会话导入完成/.test(entry.message) && entry.level === 'ok'), true);
  119. });