feat: add CPA plus session import strategy
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const test = require('node:test');
|
||||
|
||||
function loadCpaSessionImportModule() {
|
||||
const source = fs.readFileSync('background/steps/cpa-session-import.js', 'utf8');
|
||||
return new Function('self', `${source}; return self.MultiPageBackgroundCpaSessionImport;`)({});
|
||||
}
|
||||
|
||||
test('CPA session import step reads current ChatGPT session and completes node', async () => {
|
||||
const moduleApi = loadCpaSessionImportModule();
|
||||
const completed = [];
|
||||
const logs = [];
|
||||
const ensureCalls = [];
|
||||
const sentMessages = [];
|
||||
const importedPayloads = [];
|
||||
|
||||
const executor = moduleApi.createCpaSessionImportExecutor({
|
||||
addLog: async (message, level = 'info', options = {}) => {
|
||||
logs.push({ message, level, step: options.step, stepKey: options.stepKey });
|
||||
},
|
||||
chrome: {
|
||||
tabs: {
|
||||
get: async (tabId) => ({
|
||||
id: tabId,
|
||||
url: 'https://chatgpt.com/?model=gpt-4o',
|
||||
}),
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async (nodeId, payload) => {
|
||||
completed.push({ nodeId, payload });
|
||||
},
|
||||
createCpaApi: () => ({
|
||||
importCurrentChatGptSession: async (state, options) => {
|
||||
importedPayloads.push({ state, options });
|
||||
return {
|
||||
verifiedStatus: 'CPA 会话导入完成:flow@example.com',
|
||||
cpaImportedFileName: 'codex-flow@example.com-plus.json',
|
||||
cpaImportedEmail: 'flow@example.com',
|
||||
};
|
||||
},
|
||||
}),
|
||||
ensureContentScriptReadyOnTabUntilStopped: async (source, tabId, options = {}) => {
|
||||
ensureCalls.push({ source, tabId, options });
|
||||
},
|
||||
getTabId: async () => null,
|
||||
isTabAlive: async () => false,
|
||||
registerTab: async () => {},
|
||||
sendTabMessageUntilStopped: async (tabId, source, message) => {
|
||||
sentMessages.push({ tabId, source, message });
|
||||
return {
|
||||
session: {
|
||||
accessToken: 'session-access-token',
|
||||
expires: '2026-05-20T12:34:56.000Z',
|
||||
user: {
|
||||
email: 'flow@example.com',
|
||||
},
|
||||
},
|
||||
accessToken: 'session-access-token',
|
||||
};
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
waitForTabCompleteUntilStopped: async () => {},
|
||||
});
|
||||
|
||||
await executor.executeCpaSessionImport({
|
||||
nodeId: 'cpa-session-import',
|
||||
visibleStep: 10,
|
||||
plusCheckoutTabId: 91,
|
||||
vpsUrl: 'https://cpa.example.com/management.html#/oauth',
|
||||
vpsPassword: 'management-key',
|
||||
});
|
||||
|
||||
assert.equal(ensureCalls.length, 1);
|
||||
assert.equal(ensureCalls[0].source, 'plus-checkout');
|
||||
assert.deepStrictEqual(ensureCalls[0].options.inject, [
|
||||
'content/utils.js',
|
||||
'content/operation-delay.js',
|
||||
'content/plus-checkout.js',
|
||||
]);
|
||||
assert.deepStrictEqual(sentMessages, [{
|
||||
tabId: 91,
|
||||
source: 'plus-checkout',
|
||||
message: {
|
||||
type: 'PLUS_CHECKOUT_GET_STATE',
|
||||
source: 'background',
|
||||
payload: {
|
||||
includeSession: true,
|
||||
includeAccessToken: true,
|
||||
},
|
||||
},
|
||||
}]);
|
||||
assert.equal(importedPayloads.length, 1);
|
||||
assert.equal(importedPayloads[0].state.accessToken, 'session-access-token');
|
||||
assert.equal(importedPayloads[0].state.session.user.email, 'flow@example.com');
|
||||
assert.equal(completed.length, 1);
|
||||
assert.deepStrictEqual(completed[0], {
|
||||
nodeId: 'cpa-session-import',
|
||||
payload: {
|
||||
verifiedStatus: 'CPA 会话导入完成:flow@example.com',
|
||||
cpaImportedFileName: 'codex-flow@example.com-plus.json',
|
||||
cpaImportedEmail: 'flow@example.com',
|
||||
},
|
||||
});
|
||||
assert.equal(
|
||||
logs.some((entry) => entry.stepKey === 'cpa-session-import' && /ChatGPT/.test(entry.message)),
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test('CPA session import step falls back to an active ChatGPT tab when no checkout tab is tracked', async () => {
|
||||
const moduleApi = loadCpaSessionImportModule();
|
||||
const completed = [];
|
||||
const importedPayloads = [];
|
||||
const queryCalls = [];
|
||||
const registerCalls = [];
|
||||
|
||||
const sessionTab = {
|
||||
id: 77,
|
||||
url: 'https://chatgpt.com/?model=gpt-4o',
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
lastAccessed: 1234,
|
||||
};
|
||||
|
||||
const executor = moduleApi.createCpaSessionImportExecutor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
get: async (tabId) => ({
|
||||
...sessionTab,
|
||||
id: tabId,
|
||||
}),
|
||||
query: async (queryInfo = {}) => {
|
||||
queryCalls.push(queryInfo);
|
||||
if (queryInfo.active && queryInfo.currentWindow) {
|
||||
return [sessionTab];
|
||||
}
|
||||
return [sessionTab];
|
||||
},
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async (nodeId, payload) => {
|
||||
completed.push({ nodeId, payload });
|
||||
},
|
||||
createCpaApi: () => ({
|
||||
importCurrentChatGptSession: async (state) => {
|
||||
importedPayloads.push(state);
|
||||
return {
|
||||
verifiedStatus: 'CPA 会话导入完成:fallback@example.com',
|
||||
};
|
||||
},
|
||||
}),
|
||||
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||
getTabId: async () => null,
|
||||
isTabAlive: async () => false,
|
||||
registerTab: async (source, tabId) => {
|
||||
registerCalls.push({ source, tabId });
|
||||
},
|
||||
sendTabMessageUntilStopped: async () => ({
|
||||
session: {
|
||||
accessToken: 'session-access-token',
|
||||
user: {
|
||||
email: 'fallback@example.com',
|
||||
},
|
||||
},
|
||||
accessToken: 'session-access-token',
|
||||
}),
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
waitForTabCompleteUntilStopped: async () => {},
|
||||
});
|
||||
|
||||
await executor.executeCpaSessionImport({
|
||||
nodeId: 'cpa-session-import',
|
||||
visibleStep: 10,
|
||||
vpsUrl: 'https://cpa.example.com/management.html#/oauth',
|
||||
vpsPassword: 'management-key',
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(queryCalls, [
|
||||
{ active: true, currentWindow: true },
|
||||
{},
|
||||
]);
|
||||
assert.deepStrictEqual(registerCalls, [{
|
||||
source: 'plus-checkout',
|
||||
tabId: 77,
|
||||
}]);
|
||||
assert.equal(importedPayloads.length, 1);
|
||||
assert.equal(importedPayloads[0].session.user.email, 'fallback@example.com');
|
||||
assert.equal(completed.length, 1);
|
||||
});
|
||||
|
||||
test('CPA session import step reports missing readable session tab when tracked tabs are unusable', async () => {
|
||||
const moduleApi = loadCpaSessionImportModule();
|
||||
let sendCalled = false;
|
||||
|
||||
const executor = moduleApi.createCpaSessionImportExecutor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
get: async (tabId) => ({
|
||||
id: tabId,
|
||||
url: 'https://example.com/not-chatgpt',
|
||||
}),
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
completeNodeFromBackground: async () => {},
|
||||
createCpaApi: () => ({
|
||||
importCurrentChatGptSession: async () => ({}),
|
||||
}),
|
||||
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||
getTabId: async () => 91,
|
||||
isTabAlive: async () => true,
|
||||
sendTabMessageUntilStopped: async () => {
|
||||
sendCalled = true;
|
||||
return {};
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
waitForTabCompleteUntilStopped: async () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executeCpaSessionImport({
|
||||
nodeId: 'cpa-session-import',
|
||||
visibleStep: 10,
|
||||
vpsUrl: 'https://cpa.example.com/management.html#/oauth',
|
||||
vpsPassword: 'management-key',
|
||||
}),
|
||||
/未找到可读取 ChatGPT 会话的标签页/
|
||||
);
|
||||
|
||||
assert.equal(sendCalled, false);
|
||||
});
|
||||
|
||||
test('background wires CPA session import executor into the workflow runtime', () => {
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
assert.match(source, /background\/cpa-api\.js/);
|
||||
assert.match(source, /background\/steps\/cpa-session-import\.js/);
|
||||
assert.match(source, /'cpa-session-import': \(state\) => cpaSessionImportExecutor\.executeCpaSessionImport\(state\)/);
|
||||
assert.match(source, /'cpa-session-import',[\s\S]*'oauth-login'/);
|
||||
});
|
||||
Reference in New Issue
Block a user