f8b919e883
This keeps the existing CPA, SUB2API, and contribution flows intact while introducing codex2api as a separate source that plugs into Step 7 and Step 10 through backend APIs instead of page DOM. The sidepanel now exposes codex2api settings, preserves the built-in local default through placeholder-only UI, and accepts user-provided public admin URLs. Step 7 now treats missing or invalid management secrets as terminal config errors so the flow stops instead of retrying needlessly. Constraint: New source must be additive and must not break existing CPA/SUB2API/contribution paths Rejected: Drive codex2api through admin page button clicks | too brittle against frontend DOM changes Rejected: Reuse contribution mode as a source surface | violates existing panelMode/runtime-mode boundary Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep codex2api on the protocol path unless its API becomes insufficient; do not add a panel content script without proving the protocol path cannot work Tested: npm test; targeted codex2api/auth retry loops repeated 40 times; full suite repeated 5 times Not-tested: Real browser run against a live codex2api instance with a real Admin Secret and OpenAI authorization
74 lines
2.8 KiB
JavaScript
74 lines
2.8 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
|
|
test('background imports panel bridge module', () => {
|
|
const source = fs.readFileSync('background.js', 'utf8');
|
|
assert.match(source, /importScripts\([\s\S]*'background\/panel-bridge\.js'/);
|
|
});
|
|
|
|
test('panel bridge module exposes a factory', () => {
|
|
const source = fs.readFileSync('background/panel-bridge.js', 'utf8');
|
|
const globalScope = {};
|
|
|
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundPanelBridge;`)(globalScope);
|
|
|
|
assert.equal(typeof api?.createPanelBridge, 'function');
|
|
});
|
|
|
|
test('panel bridge requests oauth url with step 7 log label payload', () => {
|
|
const source = fs.readFileSync('background/panel-bridge.js', 'utf8');
|
|
assert.match(source, /logStep:\s*7/);
|
|
assert.doesNotMatch(source, /logStep:\s*6/);
|
|
});
|
|
|
|
test('panel bridge can request codex2api oauth url via protocol', async () => {
|
|
const source = fs.readFileSync('background/panel-bridge.js', 'utf8');
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = async (url, options = {}) => {
|
|
assert.equal(url, 'http://localhost:8080/api/admin/oauth/generate-auth-url');
|
|
assert.equal(options.method, 'POST');
|
|
assert.equal(options.headers['X-Admin-Key'], 'admin-secret');
|
|
return {
|
|
ok: true,
|
|
json: async () => ({
|
|
auth_url: 'https://auth.openai.com/authorize?state=oauth-state',
|
|
session_id: 'session-123',
|
|
}),
|
|
};
|
|
};
|
|
|
|
try {
|
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundPanelBridge;`)({});
|
|
const bridge = api.createPanelBridge({
|
|
addLog: async () => {},
|
|
chrome: {},
|
|
closeConflictingTabsForSource: async () => {},
|
|
ensureContentScriptReadyOnTab: async () => {},
|
|
getPanelMode: () => 'codex2api',
|
|
normalizeCodex2ApiUrl: (value) => value ? `http://${value.replace(/^https?:\/\//, '')}`.replace(/\/admin$/, '/admin/accounts') : 'http://localhost:8080/admin/accounts',
|
|
normalizeSub2ApiUrl: (value) => value,
|
|
rememberSourceLastUrl: async () => {},
|
|
sendToContentScript: async () => ({}),
|
|
sendToContentScriptResilient: async () => ({}),
|
|
waitForTabUrlFamily: async () => null,
|
|
DEFAULT_SUB2API_GROUP_NAME: 'codex',
|
|
SUB2API_STEP1_RESPONSE_TIMEOUT_MS: 90000,
|
|
});
|
|
|
|
const result = await bridge.requestOAuthUrlFromPanel({
|
|
panelMode: 'codex2api',
|
|
codex2apiUrl: 'localhost:8080/admin',
|
|
codex2apiAdminKey: 'admin-secret',
|
|
}, { logLabel: '步骤 7' });
|
|
|
|
assert.deepStrictEqual(result, {
|
|
oauthUrl: 'https://auth.openai.com/authorize?state=oauth-state',
|
|
codex2apiSessionId: 'session-123',
|
|
codex2apiOAuthState: 'oauth-state',
|
|
});
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|