Files
FlowPilot/tests/background-grok-publisher-webchat2api.test.js
2026-05-23 20:41:22 +08:00

275 lines
8.9 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadPublisherApi() {
const stateSource = fs.readFileSync('flows/grok/background/state.js', 'utf8');
const publisherSource = fs.readFileSync('flows/grok/background/publisher-webchat2api.js', 'utf8');
const globalScope = {};
new Function('self', `${stateSource}; ${publisherSource}; return self;`)(globalScope);
return globalScope.MultiPageBackgroundGrokPublisherWebchat2Api;
}
function createJsonResponse(payload, status = 200) {
return {
ok: status >= 200 && status < 300,
status,
statusText: status >= 200 && status < 300 ? 'OK' : 'Error',
text: async () => JSON.stringify(payload),
};
}
function getGrokRuntime(state = {}) {
return state?.runtimeState?.flowState?.grok || {};
}
test('grok webchat2api publisher exposes helpers and normalizes to origin inject endpoint', () => {
const api = loadPublisherApi();
assert.equal(typeof api?.createGrokWebchat2ApiPublisher, 'function');
assert.equal(
api.buildWebchat2ApiInjectUrl('https://remote.example.com/admin/deep/path'),
'https://remote.example.com/api/remote-account/inject'
);
assert.equal(
api.buildWebchat2ApiInjectUrl('remote.example.com/admin'),
'http://remote.example.com/api/remote-account/inject'
);
});
test('grok webchat2api publisher requires URL, admin key, and SSO cookie', async () => {
const api = loadPublisherApi();
assert.throws(
() => api.buildWebchat2ApiInjectUrl(''),
/缺少 webchat2api 地址/
);
assert.throws(
() => api.buildGrokSsoInjectPayload(''),
/缺少 Grok SSO Cookie/
);
await assert.rejects(
() => api.uploadGrokSsoToWebchat2Api('http://remote.example.com', '', 'sso-cookie', async () => createJsonResponse({})),
/缺少 webchat2api 管理密钥/
);
});
test('grok webchat2api publisher posts Grok SSO payload with bearer admin key', async () => {
const api = loadPublisherApi();
const requests = [];
const result = await api.uploadGrokSsoToWebchat2Api(
'https://remote.example.com/admin/deep/path',
' admin-secret ',
'sso-cookie-001',
async (url, options = {}) => {
requests.push({
url,
method: options.method,
authorization: options.headers?.Authorization,
contentType: options.headers?.['Content-Type'],
body: JSON.parse(options.body),
});
return createJsonResponse({ code: 0, data: { total: 1 }, message: 'ok' });
}
);
assert.equal(requests.length, 1);
assert.equal(requests[0].url, 'https://remote.example.com/api/remote-account/inject');
assert.equal(requests[0].method, 'POST');
assert.equal(requests[0].authorization, 'Bearer admin-secret');
assert.equal(requests[0].contentType, 'application/json');
assert.deepEqual(requests[0].body, {
accounts: [{
token: 'sso-cookie-001',
provider: 'grok',
type: 'sso',
}],
strategy: 'merge',
source_id: 'flowpilot-grok-sso',
source_name: 'FlowPilot Grok SSO',
provider: 'grok',
});
assert.equal(result.endpointUrl, 'https://remote.example.com/api/remote-account/inject');
assert.equal(result.message, 'ok');
});
test('grok webchat2api publisher accepts code-zero envelopes and rejects HTTP/API errors', async () => {
const api = loadPublisherApi();
const success = await api.uploadGrokSsoToWebchat2Api(
'http://remote.example.com',
'admin-secret',
'sso-cookie',
async () => createJsonResponse({ code: 0, data: { added: 1 } })
);
assert.equal(success.message, '上传成功');
await assert.rejects(
() => api.uploadGrokSsoToWebchat2Api(
'http://remote.example.com',
'admin-secret',
'sso-cookie',
async () => createJsonResponse({ error: 'invalid admin key' }, 403)
),
/webchat2api SSO 上传失败:invalid admin key/
);
await assert.rejects(
() => api.uploadGrokSsoToWebchat2Api(
'http://remote.example.com',
'admin-secret',
'sso-cookie',
async () => createJsonResponse({ code: 4001, message: 'bad token' })
),
/webchat2api SSO 上传失败:bad token/
);
});
test('grok webchat2api executor reads latest state and writes upload runtime without leaking secrets to logs', async () => {
const api = loadPublisherApi();
const requests = [];
const logs = [];
const completed = [];
let liveState = {
grokWebchat2ApiUrl: '',
grokWebchat2ApiAdminKey: '',
grokSsoCookie: '',
settingsState: {
flows: {
grok: {
targets: {
webchat2api: {
baseUrl: 'https://remote.example.com/admin',
apiKey: 'live-admin-key',
},
},
},
},
},
runtimeState: {
flowState: {
grok: {
sso: {
currentCookie: 'live-sso-cookie',
},
},
},
},
};
const publisher = api.createGrokWebchat2ApiPublisher({
addLog: async (message, level) => {
logs.push({ message, level });
},
completeNodeFromBackground: async (nodeId, payload) => {
completed.push({ nodeId, payload });
},
fetchImpl: async (url, options = {}) => {
requests.push({
url,
authorization: options.headers?.Authorization,
body: JSON.parse(options.body),
});
return createJsonResponse({ code: 0, message: 'uploaded' });
},
getState: async () => ({ ...liveState }),
setState: async (updates = {}) => {
liveState = {
...liveState,
...updates,
runtimeState: {
...(liveState.runtimeState || {}),
...(updates.runtimeState || {}),
flowState: {
...(liveState.runtimeState?.flowState || {}),
...(updates.runtimeState?.flowState || {}),
},
},
};
},
});
await publisher.executeGrokUploadSsoToWebchat2Api({
nodeId: 'grok-upload-sso-to-webchat2api',
grokWebchat2ApiAdminKey: 'stale-key',
grokSsoCookie: 'stale-sso-cookie',
});
assert.equal(requests.length, 1);
assert.equal(requests[0].url, 'https://remote.example.com/api/remote-account/inject');
assert.equal(requests[0].authorization, 'Bearer live-admin-key');
assert.equal(requests[0].body.accounts[0].token, 'live-sso-cookie');
assert.equal(completed.length, 1);
assert.equal(completed[0].nodeId, 'grok-upload-sso-to-webchat2api');
assert.equal(getGrokRuntime(completed[0].payload).upload.status, 'uploaded');
assert.equal(getGrokRuntime(completed[0].payload).upload.message, 'uploaded');
assert.equal(getGrokRuntime(completed[0].payload).upload.targetUrl, 'https://remote.example.com/api/remote-account/inject');
assert.equal(typeof getGrokRuntime(completed[0].payload).upload.uploadedAt, 'number');
assert.equal(logs.some(({ message }) => message.includes('live-sso-cookie') || message.includes('live-admin-key')), false);
});
test('grok webchat2api executor persists failure state without completing or leaking secrets', async () => {
const api = loadPublisherApi();
const logs = [];
const completed = [];
let liveState = {
settingsState: {
flows: {
grok: {
targets: {
webchat2api: {
baseUrl: 'https://remote.example.com/admin',
apiKey: 'secret-admin-key',
},
},
},
},
},
runtimeState: {
flowState: {
grok: {
sso: {
currentCookie: 'secret-sso-cookie',
},
},
},
},
};
const publisher = api.createGrokWebchat2ApiPublisher({
addLog: async (message, level) => {
logs.push({ message, level });
},
completeNodeFromBackground: async (nodeId, payload) => {
completed.push({ nodeId, payload });
},
fetchImpl: async () => createJsonResponse({ error: 'invalid admin key' }, 403),
getState: async () => ({ ...liveState }),
setState: async (updates = {}) => {
liveState = {
...liveState,
...updates,
runtimeState: {
...(liveState.runtimeState || {}),
...(updates.runtimeState || {}),
flowState: {
...(liveState.runtimeState?.flowState || {}),
...(updates.runtimeState?.flowState || {}),
},
},
};
},
});
await assert.rejects(
() => publisher.executeGrokUploadSsoToWebchat2Api({ nodeId: 'grok-upload-sso-to-webchat2api' }),
/webchat2api SSO 上传失败:invalid admin key/
);
assert.equal(completed.length, 0);
assert.equal(getGrokRuntime(liveState).upload.status, 'error');
assert.equal(getGrokRuntime(liveState).upload.uploadedAt, 0);
assert.equal(getGrokRuntime(liveState).upload.message, 'webchat2api SSO 上传失败:invalid admin key');
assert.equal(getGrokRuntime(liveState).upload.targetUrl, 'https://remote.example.com/api/remote-account/inject');
assert.equal(logs.some(({ message }) => message.includes('secret-sso-cookie') || message.includes('secret-admin-key')), false);
});