273 lines
9.0 KiB
JavaScript
273 lines
9.0 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') => {
|
||
logs.push({ message, level });
|
||
},
|
||
chrome: {
|
||
tabs: {
|
||
update: async () => {},
|
||
},
|
||
},
|
||
closeConflictingTabsForSource: async () => {},
|
||
completeStepFromBackground: 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 };
|
||
}
|
||
|
||
test('platform verify module submits CPA callback via management API first', async () => {
|
||
const source = fs.readFileSync('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: 10,
|
||
payload: {
|
||
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
|
||
verifiedStatus: 'CPA API 回调提交成功',
|
||
},
|
||
},
|
||
]);
|
||
assert.deepStrictEqual(logs, [
|
||
{ message: '步骤 10:正在通过 CPA 管理接口提交回调地址...', level: 'info' },
|
||
{ message: '步骤 10:CPA API 回调提交成功', level: 'ok' },
|
||
]);
|
||
} finally {
|
||
globalThis.fetch = originalFetch;
|
||
}
|
||
});
|
||
|
||
test('platform verify module prefers cpaManagementOrigin when provided', async () => {
|
||
const source = fs.readFileSync('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('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, '步骤 10:正在通过 CPA 管理接口提交回调地址...');
|
||
assert.match(logs[1].message, /步骤 10:CPA 接口提交失败:failed to persist oauth callback/);
|
||
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('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('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 sends Plus visible step 13 to SUB2API 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',
|
||
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',
|
||
sub2apiSessionId: 'session-1',
|
||
sub2apiOAuthState: 'oauth-state',
|
||
});
|
||
|
||
assert.equal(sentMessages.length, 1);
|
||
assert.equal(sentMessages[0].sourceName, 'sub2api-panel');
|
||
assert.equal(sentMessages[0].message.step, 13);
|
||
});
|
||
|