Add configurable SUB2API account priority

This commit is contained in:
initiatione
2026-05-06 17:53:07 +08:00
parent 9e6899f37c
commit 5014cb4de3
10 changed files with 254 additions and 1 deletions
@@ -116,3 +116,49 @@ test('panel bridge can request cpa oauth url via management api', async () => {
globalThis.fetch = originalFetch;
}
});
test('panel bridge forwards SUB2API account priority when requesting oauth url', async () => {
const source = fs.readFileSync('background/panel-bridge.js', 'utf8');
const sentMessages = [];
const api = new Function('self', `${source}; return self.MultiPageBackgroundPanelBridge;`)({});
const bridge = api.createPanelBridge({
addLog: async () => {},
chrome: {
tabs: {
create: async () => ({ id: 72 }),
},
},
closeConflictingTabsForSource: async () => {},
ensureContentScriptReadyOnTab: async () => {},
getPanelMode: () => 'sub2api',
normalizeCodex2ApiUrl: (value) => value,
normalizeSub2ApiUrl: (value) => value,
rememberSourceLastUrl: async () => {},
sendToContentScript: async (sourceName, message, options) => {
sentMessages.push({ sourceName, message, options });
return {
oauthUrl: 'https://auth.openai.com/authorize?state=oauth-state',
sub2apiSessionId: 'session-123',
sub2apiOAuthState: 'oauth-state',
};
},
sendToContentScriptResilient: async () => ({}),
waitForTabUrlFamily: async () => ({ id: 72 }),
DEFAULT_SUB2API_GROUP_NAME: 'codex',
SUB2API_STEP1_RESPONSE_TIMEOUT_MS: 90000,
});
await bridge.requestOAuthUrlFromPanel({
panelMode: 'sub2api',
sub2apiUrl: 'https://sub.example/admin/accounts',
sub2apiEmail: 'admin@example.com',
sub2apiPassword: 'secret',
sub2apiGroupName: 'codex',
sub2apiAccountPriority: 3,
}, { logLabel: '步骤 7' });
assert.equal(sentMessages.length, 1);
assert.equal(sentMessages[0].sourceName, 'sub2api-panel');
assert.equal(sentMessages[0].message.payload.sub2apiAccountPriority, 3);
});
@@ -272,3 +272,34 @@ test('platform verify module sends Plus visible step 13 to SUB2API panel', async
assert.equal(sentMessages[0].message.step, 13);
});
test('platform verify module forwards SUB2API account priority to panel', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const sentMessages = [];
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
const { deps } = createDeps({
getPanelMode: () => 'sub2api',
getTabId: async () => 91,
isTabAlive: async () => true,
sendToContentScript: async (sourceName, message, options) => {
sentMessages.push({ sourceName, message, options });
return { ok: true };
},
});
const executor = api.createStep10Executor(deps);
await executor.executeStep10({
panelMode: 'sub2api',
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
sub2apiUrl: 'https://sub.example/admin/accounts',
sub2apiEmail: 'admin@example.com',
sub2apiPassword: 'secret',
sub2apiSessionId: 'session-1',
sub2apiOAuthState: 'oauth-state',
sub2apiAccountPriority: 2,
});
assert.equal(sentMessages.length, 1);
assert.equal(sentMessages[0].sourceName, 'sub2api-panel');
assert.equal(sentMessages[0].message.payload.sub2apiAccountPriority, 2);
});
@@ -0,0 +1,52 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
const source = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
test('sidepanel exposes SUB2API account priority below group setting', () => {
assert.match(html, /id="row-sub2api-account-priority"/);
assert.match(html, /id="input-sub2api-account-priority"/);
assert.match(html, /<span class="data-label">优先级<\/span>/);
const inputTag = html.match(/<input[^>]*id="input-sub2api-account-priority"[^>]*>/)?.[0] || '';
assert.match(inputTag, /type="number"/);
assert.match(inputTag, /min="1"/);
assert.match(inputTag, /step="1"/);
assert.ok(
html.indexOf('id="row-sub2api-account-priority"') > html.indexOf('id="row-sub2api-group"'),
'priority row should be placed below the SUB2API group row'
);
assert.ok(
html.indexOf('id="row-sub2api-account-priority"') < html.indexOf('id="row-sub2api-default-proxy"'),
'priority row should remain above the SUB2API default proxy row'
);
});
test('sidepanel persists and locks SUB2API account priority setting', () => {
assert.match(
source,
/const rowSub2ApiAccountPriority = document\.getElementById\('row-sub2api-account-priority'\);/
);
assert.match(
source,
/const inputSub2ApiAccountPriority = document\.getElementById\('input-sub2api-account-priority'\);/
);
assert.match(source, /function normalizeSub2ApiAccountPriorityValue\(/);
assert.match(source, /const sub2apiAccountPriorityNormalizer = typeof normalizeSub2ApiAccountPriorityValue === 'function'/);
assert.match(source, /sub2apiAccountPriority: sub2apiAccountPriorityNormalizer\(/);
assert.match(
source,
/inputSub2ApiAccountPriority\.value = String\(normalizeSub2ApiAccountPriorityValue\(state\?\.sub2apiAccountPriority\)\);/
);
assert.match(source, /rowSub2ApiAccountPriority\.style\.display = useSub2Api \? '' : 'none';/);
assert.match(source, /inputSub2ApiAccountPriority\.disabled = locked;/);
assert.match(
source,
/inputSub2ApiAccountPriority\.addEventListener\('input', \(\) => \{[\s\S]*scheduleSettingsAutoSave\(\);[\s\S]*\}\);/
);
assert.match(
source,
/inputSub2ApiAccountPriority\.addEventListener\('blur', \(\) => \{[\s\S]*saveSettings\(\{ silent: true \}\)/
);
});
+46
View File
@@ -277,3 +277,49 @@ test('SUB2API step 10 omits proxy_id when no proxy is configured', async () => {
assert.equal(Object.hasOwn(exchangeCall.body, 'proxy_id'), false);
assert.equal(Object.hasOwn(createCall.body, 'proxy_id'), false);
});
test('SUB2API step 10 creates account with configured account priority', async () => {
const fetchCalls = [];
const context = createSub2ApiPanelContext(fetchCalls);
await vm.runInContext(`
step9_submitOpenAiCallback({
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
sub2apiUrl: 'https://sub.example/admin/accounts',
sub2apiEmail: 'admin@example.com',
sub2apiPassword: 'secret',
sub2apiGroupName: 'codex',
sub2apiSessionId: 'session-1',
sub2apiOAuthState: 'oauth-state',
sub2apiGroupId: 5,
sub2apiAccountPriority: 3
})
`, context);
const createCall = fetchCalls.find((call) => call.path === '/api/v1/admin/accounts');
assert.equal(createCall.body.priority, 3);
});
test('SUB2API account priority must be an integer greater than or equal to 1', async () => {
const fetchCalls = [];
const context = createSub2ApiPanelContext(fetchCalls);
await assert.rejects(
() => vm.runInContext(`
step9_submitOpenAiCallback({
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
sub2apiUrl: 'https://sub.example/admin/accounts',
sub2apiEmail: 'admin@example.com',
sub2apiPassword: 'secret',
sub2apiGroupName: 'codex',
sub2apiSessionId: 'session-1',
sub2apiOAuthState: 'oauth-state',
sub2apiGroupId: 5,
sub2apiAccountPriority: 0
})
`, context),
/SUB2API 账号优先级必须是大于等于 1 的整数/
);
assert.equal(fetchCalls.some((call) => call.path === '/api/v1/admin/accounts'), false);
});