重构 Grok SSO 上传到 webchat2api
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user