From 088b41b04bc04fac0720bbad87a8794327578da5 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 14 Apr 2026 18:18:36 +0100 Subject: [PATCH] perf(test): split doctor preflight mock modes --- src/commands/doctor-config-flow.test-utils.ts | 108 ++++++++++++++++++ src/commands/doctor-config-flow.test.ts | 37 ++++++ 2 files changed, 145 insertions(+) diff --git a/src/commands/doctor-config-flow.test-utils.ts b/src/commands/doctor-config-flow.test-utils.ts index 2fd32da87da..1b76b918f95 100644 --- a/src/commands/doctor-config-flow.test-utils.ts +++ b/src/commands/doctor-config-flow.test-utils.ts @@ -4,6 +4,7 @@ type DoctorConfigTestInput = { config: Record; exists: boolean; path: string; + preflightMode: "fast" | "issues" | "compat"; }; function setDoctorConfigInputForTest(input: DoctorConfigTestInput | null): void { @@ -24,18 +25,125 @@ export function getDoctorConfigInputForTest(): DoctorConfigTestInput | null { return globalState[DOCTOR_CONFIG_TEST_INPUT] ?? null; } +function shouldUseCompatPreflight(path: ReadonlyArray, value: unknown): boolean { + if (path.length === 0) { + return false; + } + + const joined = path.join("."); + const last = path[path.length - 1]; + if ( + joined === "heartbeat" || + joined === "memorySearch" || + joined === "gateway.bind" || + joined === "hooks.internal.handlers" + ) { + return true; + } + if ( + joined === "channels.telegram.groupMentionsOnly" || + joined === "agents.defaults.sandbox.perSession" + ) { + return true; + } + if (path.length >= 4 && path[0] === "agents" && path[1] === "list" && last === "perSession") { + return true; + } + if (last === "ttlHours" && path[path.length - 2] === "threadBindings") { + return true; + } + if ( + last === "allow" && + typeof value === "boolean" && + ((path.length === 5 && + path[0] === "channels" && + path[1] === "slack" && + path[2] === "channels") || + (path.length === 5 && + path[0] === "channels" && + path[1] === "googlechat" && + path[2] === "groups") || + (path.length === 7 && + path[0] === "channels" && + path[1] === "discord" && + path[2] === "guilds" && + path[4] === "channels")) + ) { + return true; + } + if ( + last === "streamMode" || + last === "chunkMode" || + last === "blockStreaming" || + last === "draftChunk" || + last === "blockStreamingCoalesce" || + last === "nativeStreaming" + ) { + return true; + } + if (last === "streaming" && (typeof value === "boolean" || typeof value === "string")) { + return true; + } + if ( + joined === "talk.voiceId" || + joined === "talk.voiceAliases" || + joined === "talk.modelId" || + joined === "talk.outputFormat" || + joined === "talk.apiKey" + ) { + return true; + } + return false; +} + +function hasCompatPreflightSignals(config: Record): boolean { + const stack: Array<{ path: string[]; value: unknown }> = [{ path: [], value: config }]; + while (stack.length > 0) { + const current = stack.pop(); + if (!current || !current.value || typeof current.value !== "object") { + continue; + } + if (Array.isArray(current.value)) { + for (let index = current.value.length - 1; index >= 0; index -= 1) { + stack.push({ + path: [...current.path, String(index)], + value: current.value[index], + }); + } + continue; + } + for (const [key, entry] of Object.entries(current.value)) { + const nextPath = [...current.path, key]; + if (shouldUseCompatPreflight(nextPath, entry)) { + return true; + } + if (entry && typeof entry === "object") { + stack.push({ path: nextPath, value: entry }); + } + } + } + return false; +} + export async function runDoctorConfigWithInput(params: { config: Record; repair?: boolean; + preflightMode?: "fast" | "issues" | "compat"; run: (args: { options: { nonInteractive: boolean; repair?: boolean }; confirm: () => Promise; }) => Promise; }) { + const inferredPreflightMode = hasCompatPreflightSignals(params.config) + ? params.repair + ? "compat" + : "issues" + : "fast"; setDoctorConfigInputForTest({ config: structuredClone(params.config), exists: true, path: "/virtual/.openclaw/openclaw.json", + preflightMode: params.preflightMode ?? inferredPreflightMode, }); try { return await params.run({ diff --git a/src/commands/doctor-config-flow.test.ts b/src/commands/doctor-config-flow.test.ts index e7933975393..f143e30b9fe 100644 --- a/src/commands/doctor-config-flow.test.ts +++ b/src/commands/doctor-config-flow.test.ts @@ -551,6 +551,43 @@ vi.mock("./doctor-config-preflight.js", async () => { parsed = {}; } } + if (injected?.preflightMode === "fast") { + return { + snapshot: { + exists, + path: configPath, + parsed, + config: parsed, + sourceConfig: parsed, + valid: true, + warnings: [], + legacyIssues: [], + }, + baseConfig: parsed, + }; + } + if (injected?.preflightMode === "issues") { + const legacyIssues = findLegacyConfigIssues( + parsed, + parsed, + listPluginDoctorLegacyConfigRules({ + pluginIds: collectRelevantDoctorPluginIds(parsed), + }), + ); + return { + snapshot: { + exists, + path: configPath, + parsed, + config: parsed, + sourceConfig: parsed, + valid: legacyIssues.length === 0, + warnings: [], + legacyIssues, + }, + baseConfig: parsed, + }; + } const legacyIssues = findLegacyConfigIssues( parsed, parsed,