重构 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);
|
||||
});
|
||||
@@ -37,6 +37,12 @@ test('grok state view projects canonical runtime state into legacy flat read fie
|
||||
cookies: ['cookie-a', 'cookie-b', 'cookie-a'],
|
||||
extractedAt: 3000,
|
||||
},
|
||||
upload: {
|
||||
status: 'uploaded',
|
||||
uploadedAt: 4000,
|
||||
message: 'ok',
|
||||
targetUrl: 'https://remote.example.com/api/remote-account/inject',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -55,9 +61,14 @@ test('grok state view projects canonical runtime state into legacy flat read fie
|
||||
assert.equal(view.grokSsoCookie, 'cookie-a');
|
||||
assert.deepEqual(view.grokSsoCookies, ['cookie-a', 'cookie-b']);
|
||||
assert.equal(view.grokSsoExtractedAt, 3000);
|
||||
assert.equal(view.grokWebchat2ApiUploadStatus, 'uploaded');
|
||||
assert.equal(view.grokWebchat2ApiUploadedAt, 4000);
|
||||
assert.equal(view.grokWebchat2ApiUploadMessage, 'ok');
|
||||
assert.equal(view.grokWebchat2ApiTargetUrl, 'https://remote.example.com/api/remote-account/inject');
|
||||
assert.equal(view.runtimeState.flowState.openai.preserved, true);
|
||||
assert.equal(view.runtimeState.flowState.grok.register.email, 'user@example.com');
|
||||
assert.equal(view.flowState.grok.sso.currentCookie, 'cookie-a');
|
||||
assert.equal(view.flowState.grok.upload.status, 'uploaded');
|
||||
assert.equal(view.flows.grok.sso.cookies.length, 2);
|
||||
});
|
||||
|
||||
@@ -69,6 +80,10 @@ test('grok completion payloads update canonical runtime state and flat compatibi
|
||||
grokSsoCookie: 'cookie-z',
|
||||
grokSsoCookies: ['cookie-z', 'cookie-z', 'cookie-y'],
|
||||
grokCompletedAt: 456,
|
||||
grokWebchat2ApiUploadStatus: 'uploaded',
|
||||
grokWebchat2ApiUploadedAt: 789,
|
||||
grokWebchat2ApiUploadMessage: '上传成功',
|
||||
grokWebchat2ApiTargetUrl: 'http://remote.example.com/api/remote-account/inject',
|
||||
});
|
||||
|
||||
assert.equal(patch.grokEmail, 'grok@example.com');
|
||||
@@ -77,11 +92,39 @@ test('grok completion payloads update canonical runtime state and flat compatibi
|
||||
assert.deepEqual(patch.grokSsoCookies, ['cookie-z', 'cookie-y']);
|
||||
assert.equal(patch.grokCompletedAt, 456);
|
||||
assert.equal(patch.grokSsoExtractedAt, 456);
|
||||
assert.equal(patch.grokWebchat2ApiUploadStatus, 'uploaded');
|
||||
assert.equal(patch.grokWebchat2ApiUploadedAt, 789);
|
||||
assert.equal(patch.grokWebchat2ApiUploadMessage, '上传成功');
|
||||
assert.equal(patch.grokWebchat2ApiTargetUrl, 'http://remote.example.com/api/remote-account/inject');
|
||||
assert.equal(patch.runtimeState.flowState.grok.register.email, 'grok@example.com');
|
||||
assert.equal(patch.runtimeState.flowState.grok.sso.currentCookie, 'cookie-z');
|
||||
assert.equal(patch.runtimeState.flowState.grok.upload.status, 'uploaded');
|
||||
});
|
||||
|
||||
test('grok fresh keep-state reset preserves SSO cookies but clears registration runtime', () => {
|
||||
test('grok state keeps canonical runtime values when flat compatibility fields are empty', () => {
|
||||
const api = loadGrokStateApi();
|
||||
const runtimeState = api.ensureRuntimeState({
|
||||
grokSsoCookie: '',
|
||||
grokSsoCookies: [],
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
grok: {
|
||||
sso: {
|
||||
currentCookie: 'canonical-cookie',
|
||||
cookies: ['canonical-cookie'],
|
||||
extractedAt: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(runtimeState.sso.currentCookie, 'canonical-cookie');
|
||||
assert.deepEqual(runtimeState.sso.cookies, ['canonical-cookie']);
|
||||
assert.equal(runtimeState.sso.extractedAt, 1234);
|
||||
});
|
||||
|
||||
test('grok fresh keep-state reset clears registration, SSO, and upload runtime', () => {
|
||||
const api = loadGrokStateApi();
|
||||
const patch = api.buildFreshKeepState({
|
||||
runtimeState: {
|
||||
@@ -101,6 +144,12 @@ test('grok fresh keep-state reset preserves SSO cookies but clears registration
|
||||
cookies: ['cookie-a', 'cookie-b'],
|
||||
extractedAt: 2000,
|
||||
},
|
||||
upload: {
|
||||
status: 'uploaded',
|
||||
uploadedAt: 3000,
|
||||
message: 'ok',
|
||||
targetUrl: 'https://remote.example.com/api/remote-account/inject',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -111,11 +160,16 @@ test('grok fresh keep-state reset preserves SSO cookies but clears registration
|
||||
assert.equal(patch.grokEmail, '');
|
||||
assert.equal(patch.grokRegisterStatus, '');
|
||||
assert.equal(patch.grokCompletedAt, 0);
|
||||
assert.equal(patch.grokSsoCookie, 'cookie-a');
|
||||
assert.deepEqual(patch.grokSsoCookies, ['cookie-a', 'cookie-b']);
|
||||
assert.equal(patch.grokSsoExtractedAt, 2000);
|
||||
assert.equal(patch.grokSsoCookie, '');
|
||||
assert.deepEqual(patch.grokSsoCookies, []);
|
||||
assert.equal(patch.grokSsoExtractedAt, 0);
|
||||
assert.equal(patch.grokWebchat2ApiUploadStatus, '');
|
||||
assert.equal(patch.grokWebchat2ApiUploadedAt, 0);
|
||||
assert.equal(patch.grokWebchat2ApiUploadMessage, '');
|
||||
assert.equal(patch.grokWebchat2ApiTargetUrl, '');
|
||||
assert.equal(patch.runtimeState.flowState.grok.register.email, '');
|
||||
assert.equal(patch.runtimeState.flowState.grok.sso.currentCookie, 'cookie-a');
|
||||
assert.equal(patch.runtimeState.flowState.grok.sso.currentCookie, '');
|
||||
assert.equal(patch.runtimeState.flowState.grok.upload.status, '');
|
||||
});
|
||||
|
||||
test('grok downstream reset clears only the state owned by the restarted tail', () => {
|
||||
|
||||
@@ -19,12 +19,14 @@ test('background imports node registry and wires the rebuilt Kiro executors', ()
|
||||
assert.match(source, /flows\/kiro\/background\/publisher-kiro-rs\.js/);
|
||||
assert.match(source, /flows\/grok\/background\/state\.js/);
|
||||
assert.match(source, /flows\/grok\/background\/register-runner\.js/);
|
||||
assert.match(source, /flows\/grok\/background\/publisher-webchat2api\.js/);
|
||||
assert.doesNotMatch(source, /background\/steps\/kiro-device-auth\.js/);
|
||||
|
||||
assert.match(source, /const kiroRegisterRunner = self\.MultiPageBackgroundKiroRegisterRunner\?\.createKiroRegisterRunner\(/);
|
||||
assert.match(source, /const kiroDesktopAuthorizeRunner = self\.MultiPageBackgroundKiroDesktopAuthorizeRunner\?\.createKiroDesktopAuthorizeRunner\(/);
|
||||
assert.match(source, /const kiroPublisher = self\.MultiPageBackgroundKiroPublisherKiroRs\?\.createKiroRsPublisher\(/);
|
||||
assert.match(source, /const grokRegisterRunner = self\.MultiPageBackgroundGrokRegisterRunner\?\.createGrokRegisterRunner\(/);
|
||||
assert.match(source, /const grokWebchat2ApiPublisher = self\.MultiPageBackgroundGrokPublisherWebchat2Api\?\.createGrokWebchat2ApiPublisher\(/);
|
||||
|
||||
assert.match(source, /'kiro-open-register-page': \(state\) => kiroRegisterRunner\.executeKiroOpenRegisterPage\(state\)/);
|
||||
assert.match(source, /'kiro-submit-email': \(state\) => kiroRegisterRunner\.executeKiroSubmitEmail\(state\)/);
|
||||
@@ -40,6 +42,7 @@ test('background imports node registry and wires the rebuilt Kiro executors', ()
|
||||
assert.match(source, /'grok-submit-verification-code': \(state\) => grokRegisterRunner\.executeGrokSubmitVerificationCode\(state\)/);
|
||||
assert.match(source, /'grok-submit-profile': \(state\) => grokRegisterRunner\.executeGrokSubmitProfile\(state\)/);
|
||||
assert.match(source, /'grok-extract-sso-cookie': \(state\) => grokRegisterRunner\.executeGrokExtractSsoCookie\(state\)/);
|
||||
assert.match(source, /'grok-upload-sso-to-webchat2api': \(state\) => grokWebchat2ApiPublisher\.executeGrokUploadSsoToWebchat2Api\(state\)/);
|
||||
|
||||
assert.match(
|
||||
source,
|
||||
@@ -47,7 +50,7 @@ test('background imports node registry and wires the rebuilt Kiro executors', ()
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/'grok-open-signup-page',[\s\S]*'grok-submit-email',[\s\S]*'grok-submit-verification-code',[\s\S]*'grok-submit-profile',[\s\S]*'grok-extract-sso-cookie'/
|
||||
/'grok-open-signup-page',[\s\S]*'grok-submit-email',[\s\S]*'grok-submit-verification-code',[\s\S]*'grok-submit-profile',[\s\S]*'grok-extract-sso-cookie',[\s\S]*'grok-upload-sso-to-webchat2api'/
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ test('settings schema can project canonical state into a read view without legac
|
||||
assert.deepEqual(view.stepExecutionRangeByFlow.grok, {
|
||||
enabled: false,
|
||||
fromStep: 1,
|
||||
toStep: 5,
|
||||
toStep: 6,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ test('grok verification runner polls by flow node and submits normalized code',
|
||||
assert.equal(getGrokRuntime(completedPayload).register.status, 'verified');
|
||||
});
|
||||
|
||||
test('grok SSO extraction accumulates unique cookies without logging the secret value', async () => {
|
||||
test('grok SSO extraction stores only the current cookie without logging the secret value', async () => {
|
||||
const api = loadGrokRunnerApi();
|
||||
const logs = [];
|
||||
let completedPayload = null;
|
||||
@@ -118,7 +118,7 @@ test('grok SSO extraction accumulates unique cookies without logging the secret
|
||||
let currentState = {
|
||||
activeFlowId: 'grok',
|
||||
grokRegisterTabId: 202,
|
||||
grokSsoCookies: ['existing-cookie', 'new-cookie'],
|
||||
grokSsoCookies: ['old-cookie'],
|
||||
runtimeState: {
|
||||
flowState: {
|
||||
grok: {
|
||||
@@ -126,7 +126,13 @@ test('grok SSO extraction accumulates unique cookies without logging the secret
|
||||
registerTabId: 202,
|
||||
},
|
||||
sso: {
|
||||
cookies: ['existing-cookie', 'new-cookie'],
|
||||
cookies: ['old-cookie'],
|
||||
},
|
||||
upload: {
|
||||
status: 'uploaded',
|
||||
uploadedAt: 1000,
|
||||
message: 'old upload',
|
||||
targetUrl: 'https://old.example.com/api/remote-account/inject',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -169,9 +175,12 @@ test('grok SSO extraction accumulates unique cookies without logging the secret
|
||||
await runner.executeGrokExtractSsoCookie({ nodeId: 'grok-extract-sso-cookie', ...currentState });
|
||||
|
||||
assert.equal(completedPayload.grokSsoCookie, 'new-cookie');
|
||||
assert.deepEqual(completedPayload.grokSsoCookies, ['existing-cookie', 'new-cookie']);
|
||||
assert.deepEqual(completedPayload.grokSsoCookies, ['new-cookie']);
|
||||
assert.equal(completedPayload.grokWebchat2ApiUploadStatus, '');
|
||||
assert.equal(getGrokRuntime(completedPayload).sso.currentCookie, 'new-cookie');
|
||||
assert.deepEqual(getGrokRuntime(completedPayload).sso.cookies, ['existing-cookie', 'new-cookie']);
|
||||
assert.deepEqual(getGrokRuntime(completedPayload).sso.cookies, ['new-cookie']);
|
||||
assert.equal(getGrokRuntime(completedPayload).upload.status, '');
|
||||
assert.equal(getGrokRuntime(completedPayload).upload.targetUrl, '');
|
||||
assert.equal(markUsedPayload.grokSsoCookie, 'new-cookie');
|
||||
assert.equal(logs.some(({ message }) => message.includes('new-cookie')), false);
|
||||
});
|
||||
|
||||
@@ -49,15 +49,17 @@ test('sidepanel html exposes flow selector and kiro source fields', () => {
|
||||
'id="row-kiro-upload-status"',
|
||||
'id="row-grok-register-status"',
|
||||
'id="row-grok-sso-status"',
|
||||
'id="row-grok-webchat2api-upload-status"',
|
||||
'id="display-grok-webchat2api-upload-status"',
|
||||
'id="row-grok-sso-settings"',
|
||||
'id="btn-copy-grok-sso"',
|
||||
'id="btn-export-grok-sso"',
|
||||
'id="btn-clear-grok-sso"',
|
||||
'<script src="../flows/grok/index.js"></script>',
|
||||
'<script src="../flows/grok/workflow.js"></script>',
|
||||
].forEach((snippet) => {
|
||||
assert.match(sidepanelHtml, new RegExp(snippet.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
});
|
||||
assert.doesNotMatch(sidepanelHtml, /id="btn-export-grok-sso"/);
|
||||
assert.ok(
|
||||
sidepanelHtml.indexOf('<script src="../flows/kiro/workflow.js"></script>')
|
||||
< sidepanelHtml.indexOf('<script src="../flows/grok/index.js"></script>')
|
||||
@@ -86,6 +88,7 @@ test('sidepanel renders Grok SSO status from canonical runtime state', () => {
|
||||
extractFunction(sidepanelSource, 'getGrokRuntimeState'),
|
||||
extractFunction(sidepanelSource, 'normalizeGrokSsoCookies'),
|
||||
extractFunction(sidepanelSource, 'getGrokRegisterStatusLabel'),
|
||||
extractFunction(sidepanelSource, 'getGrokWebchat2ApiUploadStatusLabel'),
|
||||
extractFunction(sidepanelSource, 'renderGrokRuntimeState'),
|
||||
].join('\n');
|
||||
|
||||
@@ -94,17 +97,17 @@ let latestState = {};
|
||||
const displayGrokRegisterStatus = { textContent: '' };
|
||||
const displayGrokSsoStatus = { textContent: '' };
|
||||
const displayGrokSsoCookie = { textContent: '', title: '' };
|
||||
const displayGrokWebchat2ApiUploadStatus = { textContent: '', title: '' };
|
||||
const buttons = [];
|
||||
const btnCopyGrokSso = { disabled: false };
|
||||
const btnExportGrokSso = { disabled: false };
|
||||
const btnClearGrokSso = { disabled: false };
|
||||
${bundle}
|
||||
return {
|
||||
displayGrokRegisterStatus,
|
||||
displayGrokSsoStatus,
|
||||
displayGrokSsoCookie,
|
||||
displayGrokWebchat2ApiUploadStatus,
|
||||
btnCopyGrokSso,
|
||||
btnExportGrokSso,
|
||||
btnClearGrokSso,
|
||||
renderGrokRuntimeState,
|
||||
};
|
||||
@@ -120,6 +123,12 @@ return {
|
||||
cookies: ['1234567890abcdef', 'second-cookie'],
|
||||
extractedAt: 0,
|
||||
},
|
||||
upload: {
|
||||
status: 'uploaded',
|
||||
uploadedAt: 0,
|
||||
message: '上传成功',
|
||||
targetUrl: 'https://remote.example.com/api/remote-account/inject',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -128,8 +137,9 @@ return {
|
||||
assert.equal(api.displayGrokRegisterStatus.textContent, '已完成');
|
||||
assert.match(api.displayGrokSsoStatus.textContent, /^已提取 2 条/);
|
||||
assert.equal(api.displayGrokSsoCookie.textContent, '12345678...abcdef');
|
||||
assert.equal(api.displayGrokWebchat2ApiUploadStatus.textContent, '已上传:上传成功');
|
||||
assert.equal(api.displayGrokWebchat2ApiUploadStatus.title, 'https://remote.example.com/api/remote-account/inject');
|
||||
assert.equal(api.btnCopyGrokSso.disabled, false);
|
||||
assert.equal(api.btnExportGrokSso.disabled, false);
|
||||
assert.equal(api.btnClearGrokSso.disabled, false);
|
||||
});
|
||||
|
||||
|
||||
@@ -197,4 +197,5 @@ test('shared source registry exposes canonical Kiro sources and drivers', () =>
|
||||
assert.equal(registry.driverAcceptsCommand('flows/kiro/background/publisher-kiro-rs', 'kiro-upload-credential'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('flows/grok/content/register-page', 'grok-submit-profile'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('flows/grok/background/register-runner', 'grok-extract-sso-cookie'), true);
|
||||
assert.equal(registry.driverAcceptsCommand('flows/grok/background/publisher-webchat2api', 'grok-upload-sso-to-webchat2api'), true);
|
||||
});
|
||||
|
||||
@@ -220,6 +220,7 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
'grok-submit-verification-code',
|
||||
'grok-submit-profile',
|
||||
'grok-extract-sso-cookie',
|
||||
'grok-upload-sso-to-webchat2api',
|
||||
]
|
||||
);
|
||||
assert.equal(grokSteps.every((step) => step.flowId === 'grok'), true);
|
||||
@@ -228,10 +229,10 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
assert.equal(grokSteps[2].mailRuleId, 'grok-submit-verification-code');
|
||||
assert.deepStrictEqual(
|
||||
grokSteps.map((step) => step.title),
|
||||
['打开 Grok 注册页', '获取邮箱并继续', '获取验证码并继续', '填写资料并继续', '提取 SSO Cookie']
|
||||
['打开 Grok 注册页', '获取邮箱并继续', '获取验证码并继续', '填写资料并继续', '提取 SSO Cookie', '上传 SSO 到 webchat2api']
|
||||
);
|
||||
assert.deepStrictEqual(api.getStepIds({ activeFlowId: 'grok' }), [1, 2, 3, 4, 5]);
|
||||
assert.equal(api.getLastStepId({ activeFlowId: 'grok' }), 5);
|
||||
assert.deepStrictEqual(api.getStepIds({ activeFlowId: 'grok' }), [1, 2, 3, 4, 5, 6]);
|
||||
assert.equal(api.getLastStepId({ activeFlowId: 'grok' }), 6);
|
||||
assert.deepStrictEqual(
|
||||
api.getNodes({ activeFlowId: 'grok' }).map((node) => node.next),
|
||||
[
|
||||
@@ -239,6 +240,7 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
||||
['grok-submit-verification-code'],
|
||||
['grok-submit-profile'],
|
||||
['grok-extract-sso-cookie'],
|
||||
['grok-upload-sso-to-webchat2api'],
|
||||
[],
|
||||
]
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user