177 lines
6.0 KiB
JavaScript
177 lines
6.0 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const { _private } = require('../src/services/openai_oauth_service');
|
|
|
|
function jwt(payload) {
|
|
const header = Buffer.from(JSON.stringify({ alg: 'none' })).toString('base64url');
|
|
const body = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
return `${header}.${body}.signature`;
|
|
}
|
|
|
|
function createMockPage(responses) {
|
|
let currentUrl = 'https://auth.openai.com/oauth/token';
|
|
const requests = [];
|
|
return {
|
|
requests,
|
|
url: () => currentUrl,
|
|
goto: async url => {
|
|
currentUrl = url;
|
|
return null;
|
|
},
|
|
evaluate: async (_fn, payload) => {
|
|
requests.push(payload);
|
|
const response = responses.find(item => (
|
|
typeof item.match === 'string'
|
|
? payload.targetUrl.startsWith(item.match)
|
|
: item.match(payload)
|
|
));
|
|
if (!response) throw new Error(`未配置模拟响应:${payload.targetUrl}`);
|
|
return typeof response.reply === 'function' ? response.reply(payload) : response.reply;
|
|
},
|
|
};
|
|
}
|
|
|
|
(async () => {
|
|
const selected = _private.selectChatGPTAccountInfo({
|
|
accounts: {
|
|
expired: {
|
|
account: { account_id: 'expired', plan_type: 'plus', is_default: true },
|
|
entitlement: { expires_at: '2020-01-01T00:00:00.000Z' },
|
|
},
|
|
free: {
|
|
account: { account_id: 'free', plan_type: 'free' },
|
|
entitlement: { expires_at: '2099-01-01T00:00:00.000Z' },
|
|
},
|
|
paid: {
|
|
account: { account_id: 'paid', plan_type: 'team' },
|
|
entitlement: { expires_at: '2099-06-01T00:00:00.000Z' },
|
|
},
|
|
},
|
|
}, 'paid', Date.parse('2026-01-01T00:00:00.000Z'));
|
|
assert.strictEqual(selected.account_id, 'paid');
|
|
assert.strictEqual(selected.plan_type, 'team');
|
|
|
|
const oldRefreshToken = 'refresh-old';
|
|
const refreshedAccessToken = jwt({
|
|
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
email: 'user@gmail.com',
|
|
'https://api.openai.com/auth': {
|
|
user_id: 'user-1',
|
|
chatgpt_account_id: 'account-1',
|
|
},
|
|
});
|
|
const page = createMockPage([
|
|
{
|
|
match: 'https://auth.openai.com/oauth/token',
|
|
reply: {
|
|
ok: true,
|
|
status: 200,
|
|
data: {
|
|
access_token: refreshedAccessToken,
|
|
id_token: jwt({ sub: 'user-1', email: 'user@gmail.com' }),
|
|
token_type: 'Bearer',
|
|
expires_in: 3600,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
match: _private.CHATGPT_ACCOUNTS_CHECK_URL,
|
|
reply: {
|
|
ok: true,
|
|
status: 200,
|
|
data: {
|
|
accounts: {
|
|
'account-1': {
|
|
account: {
|
|
account_id: 'account-1',
|
|
email: 'user@gmail.com',
|
|
plan_type: 'plus',
|
|
is_default: true,
|
|
},
|
|
entitlement: { expires_at: '2027-01-01T00:00:00.000Z' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
match: _private.CHATGPT_USAGE_URL,
|
|
reply: {
|
|
ok: true,
|
|
status: 200,
|
|
data: {
|
|
account_id: 'account-1',
|
|
email: 'user@gmail.com',
|
|
plan_type: 'plus',
|
|
rate_limit: {
|
|
allowed: true,
|
|
limit_reached: false,
|
|
primary_window: { used_percent: 25, reset_at: 1800000000 },
|
|
},
|
|
unrelated_large_field: 'must not be stored',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
match: _private.CHATGPT_SUBSCRIPTIONS_URL,
|
|
reply: {
|
|
ok: true,
|
|
status: 200,
|
|
data: {
|
|
id: 'sub-1',
|
|
plan_type: 'plus',
|
|
active_until: '2027-02-01T00:00:00.000Z',
|
|
will_renew: true,
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
const authJson = {
|
|
access_token: jwt({
|
|
exp: Math.floor(Date.now() / 1000) - 3600,
|
|
email: 'user@gmail.com',
|
|
'https://api.openai.com/auth': { chatgpt_account_id: 'account-1' },
|
|
}),
|
|
refresh_token: oldRefreshToken,
|
|
id_token: jwt({ sub: 'user-1', email: 'user@gmail.com' }),
|
|
expires_at: '2020-01-01T00:00:00.000Z',
|
|
tokens: { refresh_token: oldRefreshToken, account_id: 'account-1' },
|
|
};
|
|
const logs = [];
|
|
const enriched = await _private.enrichOpenAIAuthJson(page, authJson, message => logs.push(message));
|
|
|
|
assert.strictEqual(enriched.access_token, refreshedAccessToken);
|
|
assert.strictEqual(enriched.refresh_token, oldRefreshToken, '刷新接口不返回新 RT 时必须保留旧 RT');
|
|
assert.strictEqual(enriched.tokens.refresh_token, oldRefreshToken);
|
|
assert.strictEqual(enriched.chatgpt_account_id, 'account-1');
|
|
assert.strictEqual(enriched.plan_type, 'plus');
|
|
assert.strictEqual(enriched.subscription_expires_at, '2027-02-01T00:00:00.000Z');
|
|
assert.strictEqual(enriched.usage.rate_limit.primary_window.used_percent, 25);
|
|
assert.strictEqual(enriched.usage.unrelated_large_field, undefined);
|
|
assert.strictEqual(enriched.subscription.will_renew, true);
|
|
assert.ok(enriched.usage_fetched_at);
|
|
assert.ok(enriched.account_info_fetched_at);
|
|
assert.ok(logs.some(message => message.includes('正在使用 refresh_token 刷新')));
|
|
assert.ok(logs.some(message => message.includes('主窗口已用 25%')));
|
|
|
|
const refreshRequest = page.requests.find(item => item.targetUrl === 'https://auth.openai.com/oauth/token');
|
|
assert.strictEqual(refreshRequest.form.grant_type, 'refresh_token');
|
|
assert.strictEqual(refreshRequest.form.refresh_token, oldRefreshToken);
|
|
const usageRequest = page.requests.find(item => item.targetUrl === _private.CHATGPT_USAGE_URL);
|
|
assert.strictEqual(usageRequest.headers['ChatGPT-Account-Id'], 'account-1');
|
|
assert.strictEqual(usageRequest.headers['OpenAI-Beta'], 'codex-1');
|
|
|
|
const failingPage = createMockPage([]);
|
|
failingPage.goto = async url => { throw new Error(`无法访问 ${url}`); };
|
|
const original = { access_token: 'still-save-me', refresh_token: 'refresh-1' };
|
|
const fallback = await _private.enrichOpenAIAuthJson(failingPage, original, () => {});
|
|
assert.strictEqual(fallback.access_token, original.access_token, '附加查询失败不能影响 Token 保存');
|
|
|
|
console.log('openai_auth_account_details.test.js passed');
|
|
})().catch(error => {
|
|
console.error(error);
|
|
process.exitCode = 1;
|
|
});
|