Files
FlowPilot/tests/background-platform-verify-cpa-api.test.js
2026-05-21 07:21:34 +08:00

391 lines
14 KiB
JavaScript

const assert = require('node:assert/strict');
const fs = require('node:fs');
const test = require('node:test');
function createDeps(overrides = {}) {
const logs = [];
const completed = [];
const uiCalls = [];
const deps = {
addLog: async (message, level = 'info', options = {}) => {
logs.push({ message, level, step: options.step, stepKey: options.stepKey });
},
chrome: {
tabs: {
update: async () => {},
},
},
closeConflictingTabsForSource: async () => {},
completeNodeFromBackground: async (step, payload) => {
completed.push({ step, payload });
},
ensureContentScriptReadyOnTab: async () => {},
getPanelMode: () => 'cpa',
getTabId: async () => 0,
isLocalhostOAuthCallbackUrl: (value) => String(value || '').includes('/auth/callback?code='),
isTabAlive: async () => false,
normalizeCodex2ApiUrl: (value) => value,
normalizeSub2ApiUrl: (value) => value,
rememberSourceLastUrl: async () => {},
reuseOrCreateTab: async () => 91,
sendToContentScript: async () => ({}),
sendToContentScriptResilient: async (source, message, options) => {
uiCalls.push({ source, message, options });
return {};
},
shouldBypassStep9ForLocalCpa: () => false,
SUB2API_STEP9_RESPONSE_TIMEOUT_MS: 120000,
...overrides,
};
return { deps, logs, completed, uiCalls };
}
function loadStep10WithSub2Api() {
const sub2apiSource = fs.readFileSync('background/sub2api-api.js', 'utf8');
const step10Source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
return new Function('self', `${sub2apiSource}\n${step10Source}; return self.MultiPageBackgroundStep10;`)({});
}
function createSub2ApiResponse(payload, status = 200) {
return {
ok: status >= 200 && status < 300,
status,
text: async () => JSON.stringify(payload),
};
}
test('platform verify module submits CPA callback via management API first', async () => {
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
let uiCalled = false;
globalThis.fetch = async (url, options = {}) => {
assert.equal(url, 'http://localhost:8317/v0/management/oauth-callback');
assert.equal(options.method, 'POST');
assert.equal(options.headers.Authorization, 'Bearer cpa-key');
assert.equal(options.headers['X-Management-Key'], 'cpa-key');
assert.deepStrictEqual(JSON.parse(options.body), {
provider: 'codex',
redirect_url: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
});
return {
ok: true,
json: async () => ({
message: 'CPA API 回调提交成功',
}),
};
};
try {
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
const { deps, logs, completed } = createDeps({
sendToContentScriptResilient: async () => {
uiCalled = true;
return {};
},
});
const executor = api.createStep10Executor(deps);
await executor.executeStep10({
panelMode: 'cpa',
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
vpsUrl: 'http://localhost:8317/admin/oauth',
vpsPassword: 'cpa-key',
});
assert.equal(uiCalled, false);
assert.deepStrictEqual(completed, [
{
step: 'platform-verify',
payload: {
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
verifiedStatus: 'CPA API 回调提交成功',
},
},
]);
assert.deepStrictEqual(logs, [
{ message: '正在通过 CPA 管理接口提交回调地址...', level: 'info', step: 10, stepKey: 'platform-verify' },
{ message: 'CPA API 回调提交成功', level: 'ok', step: 10, stepKey: 'platform-verify' },
]);
} finally {
globalThis.fetch = originalFetch;
}
});
test('platform verify module prefers cpaManagementOrigin when provided', async () => {
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url, options = {}) => {
assert.equal(url, 'http://localhost:9999/v0/management/oauth-callback');
assert.equal(options.method, 'POST');
assert.equal(options.headers.Authorization, 'Bearer cpa-key');
assert.equal(options.headers['X-Management-Key'], 'cpa-key');
return {
ok: true,
json: async () => ({
message: 'CPA API 回调提交成功',
}),
};
};
try {
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
const { deps, completed } = createDeps();
const executor = api.createStep10Executor(deps);
await executor.executeStep10({
panelMode: 'cpa',
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
cpaManagementOrigin: 'http://localhost:9999',
vpsUrl: 'http://localhost:8317/admin/oauth',
vpsPassword: 'cpa-key',
});
assert.equal(completed.length, 1);
} finally {
globalThis.fetch = originalFetch;
}
});
test('platform verify module fails fast when CPA API submit fails', async () => {
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => ({
ok: false,
status: 500,
json: async () => ({ message: 'failed to persist oauth callback' }),
});
try {
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
const { deps, logs, completed, uiCalls } = createDeps();
const executor = api.createStep10Executor(deps);
await assert.rejects(
() => executor.executeStep10({
panelMode: 'cpa',
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
vpsUrl: 'http://localhost:8317/admin/oauth',
vpsPassword: 'cpa-key',
}),
/failed to persist oauth callback/
);
assert.equal(uiCalls.length, 0);
assert.equal(completed.length, 0);
assert.equal(logs[0].message, '正在通过 CPA 管理接口提交回调地址...');
assert.equal(logs[0].step, 10);
assert.match(logs[1].message, /CPA 接口提交失败:failed to persist oauth callback/);
assert.equal(logs[1].step, 10);
assert.equal(logs[1].level, 'error');
} finally {
globalThis.fetch = originalFetch;
}
});
test('platform verify module requires management key for CPA API-only flow', async () => {
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
let fetchCalled = false;
globalThis.fetch = async () => {
fetchCalled = true;
return {
ok: true,
json: async () => ({}),
};
};
try {
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
const { deps, logs, completed, uiCalls } = createDeps();
const executor = api.createStep10Executor(deps);
await assert.rejects(
() => executor.executeStep10({
panelMode: 'cpa',
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
vpsUrl: 'http://localhost:8317/admin/oauth',
vpsPassword: ' ',
}),
/尚未配置 CPA 管理密钥/
);
assert.equal(fetchCalled, false);
assert.equal(uiCalls.length, 0);
assert.equal(completed.length, 0);
assert.equal(logs.length, 0);
} finally {
globalThis.fetch = originalFetch;
}
});
test('platform verify module rejects callback when cpa oauth state mismatches', async () => {
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
let fetchCalled = false;
globalThis.fetch = async () => {
fetchCalled = true;
return {
ok: true,
json: async () => ({ message: 'should not happen' }),
};
};
try {
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
const { deps } = createDeps();
const executor = api.createStep10Executor(deps);
await assert.rejects(
() => executor.executeStep10({
panelMode: 'cpa',
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=callback-state',
cpaOAuthState: 'expected-state',
vpsUrl: 'http://localhost:8317/admin/oauth',
vpsPassword: 'cpa-key',
}),
/CPA 回调 state 与当前授权会话不匹配/
);
assert.equal(fetchCalled, false);
} finally {
globalThis.fetch = originalFetch;
}
});
test('platform verify module submits Plus visible step 13 to SUB2API via direct API', async () => {
const originalFetch = globalThis.fetch;
const fetchCalls = [];
const sentMessages = [];
globalThis.fetch = async (url, options = {}) => {
const parsed = new URL(url);
const body = options.body ? JSON.parse(options.body) : null;
fetchCalls.push({ path: parsed.pathname, method: options.method || 'GET', body });
if (parsed.pathname === '/api/v1/auth/login') {
return createSub2ApiResponse({ code: 0, data: { access_token: 'admin-token' } });
}
if (parsed.pathname === '/api/v1/admin/groups/all') {
return createSub2ApiResponse({ code: 0, data: [{ id: 5, name: 'codex', platform: 'openai' }] });
}
if (parsed.pathname === '/api/v1/admin/openai/exchange-code') {
return createSub2ApiResponse({
code: 0,
data: {
access_token: 'openai-access',
refresh_token: 'openai-refresh',
email: 'flow@example.com',
},
});
}
if (parsed.pathname === '/api/v1/admin/accounts') {
return createSub2ApiResponse({ code: 0, data: { id: 11 } });
}
return createSub2ApiResponse({ code: 1, message: `unexpected path ${parsed.pathname}` }, 404);
};
const api = loadStep10WithSub2Api();
const { deps, completed } = 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);
try {
await executor.executeStep10({
panelMode: 'sub2api',
visibleStep: 13,
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',
});
const exchangeCall = fetchCalls.find((call) => call.path === '/api/v1/admin/openai/exchange-code');
const createCall = fetchCalls.find((call) => call.path === '/api/v1/admin/accounts');
assert.equal(sentMessages.length, 0);
assert.equal(exchangeCall.body.code, 'callback-code');
assert.equal(exchangeCall.body.state, 'oauth-state');
assert.deepStrictEqual(createCall.body.group_ids, [5]);
assert.deepStrictEqual(completed, [{
step: 'platform-verify',
payload: {
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
verifiedStatus: 'SUB2API 已创建账号 #11',
},
}]);
} finally {
globalThis.fetch = originalFetch;
}
});
test('platform verify module forwards SUB2API account priority to direct create API', async () => {
const originalFetch = globalThis.fetch;
const fetchCalls = [];
const sentMessages = [];
globalThis.fetch = async (url, options = {}) => {
const parsed = new URL(url);
const body = options.body ? JSON.parse(options.body) : null;
fetchCalls.push({ path: parsed.pathname, method: options.method || 'GET', body });
if (parsed.pathname === '/api/v1/auth/login') {
return createSub2ApiResponse({ code: 0, data: { access_token: 'admin-token' } });
}
if (parsed.pathname === '/api/v1/admin/openai/exchange-code') {
return createSub2ApiResponse({
code: 0,
data: {
access_token: 'openai-access',
refresh_token: 'openai-refresh',
email: 'flow@example.com',
},
});
}
if (parsed.pathname === '/api/v1/admin/accounts') {
return createSub2ApiResponse({ code: 0, data: { id: 11 } });
}
return createSub2ApiResponse({ code: 1, message: `unexpected path ${parsed.pathname}` }, 404);
};
const api = loadStep10WithSub2Api();
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);
try {
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',
sub2apiGroupId: 5,
sub2apiAccountPriority: 2,
});
const createCall = fetchCalls.find((call) => call.path === '/api/v1/admin/accounts');
assert.equal(sentMessages.length, 0);
assert.equal(createCall.body.priority, 2);
} finally {
globalThis.fetch = originalFetch;
}
});