fix(wizard): repair HTTP bot inbound test setup

This commit is contained in:
fdc310
2026-08-23 20:41:46 +08:00
parent 7a4fadc375
commit 6b60ad1678
3 changed files with 133 additions and 5 deletions
+36 -5
View File
@@ -48,6 +48,11 @@ import {
import { getAdapterDocUrl } from '@/app/infra/entities/adapter-docs'; import { getAdapterDocUrl } from '@/app/infra/entities/adapter-docs';
import i18n from 'i18next'; import i18n from 'i18next';
import {
ensureHttpBotSigningSecret,
getErrorMessage,
} from '@/app/wizard/utils';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { import {
@@ -177,15 +182,30 @@ export default function WizardPage() {
const botData = await httpClient.getBot(progress.created_bot_uuid); const botData = await httpClient.getBot(progress.created_bot_uuid);
if (cancelled) return; if (cancelled) return;
setSelectedAdapter(progress.selected_adapter); const restoredAdapter =
progress.selected_adapter ?? botData.bot.adapter;
const restoredConfig = (botData.bot.adapter_config ?? {}) as Record<
string,
unknown
>;
const configToRestore = ensureHttpBotSigningSecret(
restoredAdapter,
restoredConfig,
);
const configNeedsSave = configToRestore !== restoredConfig;
setSelectedAdapter(restoredAdapter);
setCreatedBotUuid(progress.created_bot_uuid); setCreatedBotUuid(progress.created_bot_uuid);
setCreatedPipelineUuid(progress.created_pipeline_uuid ?? null); setCreatedPipelineUuid(progress.created_pipeline_uuid ?? null);
setBotSaved(progress.bot_saved ?? false); setBotSaved(
configNeedsSave ? false : (progress.bot_saved ?? false),
);
setMessageReceived(progress.message_received ?? false); setMessageReceived(progress.message_received ?? false);
setSelectedRunner(progress.selected_runner); setSelectedRunner(progress.selected_runner);
// Restore bot name from fetched bot data // Restore bot name from fetched bot data
setBotName(botData.bot.name); setBotName(botData.bot.name);
setAdapterConfig(configToRestore);
// Restore webhook URLs // Restore webhook URLs
const runtimeValues = botData.bot.adapter_runtime_values as const runtimeValues = botData.bot.adapter_runtime_values as
@@ -360,12 +380,17 @@ export default function WizardPage() {
const defaultConfig = adapter const defaultConfig = adapter
? getDefaultValues(adapter.spec.config) ? getDefaultValues(adapter.spec.config)
: {}; : {};
const initialConfig = ensureHttpBotSigningSecret(
selectedAdapter,
defaultConfig,
);
setAdapterConfig(initialConfig);
const bot: Bot = { const bot: Bot = {
name: defaultName, name: defaultName,
description: '', description: '',
adapter: selectedAdapter, adapter: selectedAdapter,
adapter_config: defaultConfig, adapter_config: initialConfig,
enable: false, enable: false,
}; };
const resp = await httpClient.createBot(bot); const resp = await httpClient.createBot(bot);
@@ -454,11 +479,17 @@ export default function WizardPage() {
setCreatedPipelineUuid(pipelineUuid); setCreatedPipelineUuid(pipelineUuid);
} }
const configToSave = ensureHttpBotSigningSecret(
selectedAdapter,
adapterConfig,
);
setAdapterConfig(configToSave);
await httpClient.updateBot(createdBotUuid, { await httpClient.updateBot(createdBotUuid, {
name: botName, name: botName,
description: botDescription || '', description: botDescription || '',
adapter: selectedAdapter, adapter: selectedAdapter,
adapter_config: adapterConfig, adapter_config: configToSave,
enable: true, enable: true,
use_pipeline_uuid: pipelineUuid, use_pipeline_uuid: pipelineUuid,
}); });
@@ -1063,7 +1094,7 @@ function StepBotConfig({
} catch (error) { } catch (error) {
toast.error( toast.error(
t('wizard.botConfig.httpTestFailed', { t('wizard.botConfig.httpTestFailed', {
error: error instanceof Error ? error.message : String(error), error: getErrorMessage(error),
}), }),
); );
} finally { } finally {
+36
View File
@@ -0,0 +1,36 @@
export function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === 'object' && error !== null && 'msg' in error) {
const message = (error as { msg?: unknown }).msg;
if (typeof message === 'string') return message;
}
return String(error);
}
function createSigningSecret(): string {
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join(
'',
);
}
export function ensureHttpBotSigningSecret(
adapterName: string,
config: Record<string, unknown>,
): Record<string, unknown> {
if (
adapterName !== 'http_bot' ||
config.signature_required === false ||
(typeof config.inbound_secret === 'string' && config.inbound_secret)
) {
return config;
}
return {
...config,
inbound_secret: createSigningSecret(),
};
}
+61
View File
@@ -0,0 +1,61 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import test from 'node:test';
import ts from 'typescript';
import { fileURLToPath } from 'node:url';
const currentDirectory = path.dirname(fileURLToPath(import.meta.url));
const sourcePath = path.resolve(
currentDirectory,
'../../src/app/wizard/utils.ts',
);
function loadWizardUtils() {
const source = fs.readFileSync(sourcePath, 'utf8');
const compiled = ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.CommonJS },
}).outputText;
const loadedModule = { exports: {} };
new Function('require', 'module', 'exports', compiled)(
() => {
throw new Error('Wizard utils must not have runtime imports');
},
loadedModule,
loadedModule.exports,
);
return loadedModule.exports;
}
const { ensureHttpBotSigningSecret, getErrorMessage } = loadWizardUtils();
test('generates an HTTP Bot signing secret when signatures are enabled', () => {
const config = ensureHttpBotSigningSecret('http_bot', {
signature_required: true,
inbound_secret: '',
});
assert.match(config.inbound_secret, /^[a-f0-9]{64}$/);
});
test('preserves existing or intentionally disabled HTTP Bot signing config', () => {
const existing = { signature_required: true, inbound_secret: 'keep-me' };
const disabled = { signature_required: false, inbound_secret: '' };
assert.equal(ensureHttpBotSigningSecret('http_bot', existing), existing);
assert.equal(ensureHttpBotSigningSecret('http_bot', disabled), disabled);
});
test('does not add signing config to other adapters', () => {
const config = {};
assert.equal(ensureHttpBotSigningSecret('web_page_bot', config), config);
});
test('extracts the backend message from structured API errors', () => {
assert.equal(
getErrorMessage({ code: 400, msg: 'Signing secret is required' }),
'Signing secret is required',
);
assert.equal(getErrorMessage(new Error('Network failed')), 'Network failed');
});