mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
refactor(test): consolidate configure wizard fixtures (#130539)
* refactor(test): consolidate configure wizard fixtures * refactor(test): own wizard fixture import ordering
This commit is contained in:
committed by
GitHub
parent
6f4a39c80e
commit
d89de5c391
@@ -1,111 +0,0 @@
|
||||
import { vi, type Mock } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
|
||||
export const EMPTY_CONFIG_SNAPSHOT = {
|
||||
exists: false,
|
||||
valid: true,
|
||||
config: {},
|
||||
issues: [],
|
||||
};
|
||||
|
||||
export function createWizardTestRuntime() {
|
||||
return {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
type WizardStateMocks = {
|
||||
readConfigFileSnapshot: Mock;
|
||||
resolveGatewayPort: Mock;
|
||||
probeGatewayReachable: Mock;
|
||||
resolveControlUiLinks: Mock;
|
||||
resolveLocalControlUiProbeLinks: Mock;
|
||||
resolveAdvertisedControlUiLinks: Mock;
|
||||
inspectWindowsGatewayFirewall: Mock;
|
||||
summarizeExistingConfig: Mock;
|
||||
createClackPrompter: Mock;
|
||||
};
|
||||
|
||||
export function setupBaseWizardTestState(mocks: WizardStateMocks, config: OpenClawConfig = {}) {
|
||||
mocks.readConfigFileSnapshot.mockResolvedValue({ ...EMPTY_CONFIG_SNAPSHOT, config });
|
||||
mocks.resolveGatewayPort.mockReturnValue(18789);
|
||||
mocks.probeGatewayReachable.mockResolvedValue({ ok: false });
|
||||
mocks.resolveControlUiLinks.mockReturnValue({ wsUrl: "ws://127.0.0.1:18789" });
|
||||
mocks.resolveLocalControlUiProbeLinks.mockReturnValue({
|
||||
httpUrl: "http://127.0.0.1:18789/",
|
||||
wsUrl: "ws://127.0.0.1:18789",
|
||||
});
|
||||
mocks.resolveAdvertisedControlUiLinks.mockResolvedValue({
|
||||
httpUrl: "http://127.0.0.1:18789/",
|
||||
wsUrl: "ws://127.0.0.1:18789",
|
||||
});
|
||||
mocks.inspectWindowsGatewayFirewall.mockResolvedValue({
|
||||
applies: false,
|
||||
severity: "info",
|
||||
code: "windows_firewall_not_applicable",
|
||||
message: "Windows LAN firewall diagnostics do not apply.",
|
||||
details: [],
|
||||
});
|
||||
mocks.summarizeExistingConfig.mockReturnValue("");
|
||||
mocks.createClackPrompter.mockReturnValue({
|
||||
intro: vi.fn(async () => {}),
|
||||
outro: vi.fn(async () => {}),
|
||||
note: vi.fn(async () => {}),
|
||||
select: vi.fn(async () => "firecrawl"),
|
||||
multiselect: vi.fn(async () => []),
|
||||
text: vi.fn(async () => ""),
|
||||
confirm: vi.fn(async () => true),
|
||||
progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })),
|
||||
});
|
||||
}
|
||||
|
||||
export function queueWizardTestPrompts(
|
||||
mocks: {
|
||||
clackSelect: Mock;
|
||||
clackConfirm: Mock;
|
||||
clackText: Mock;
|
||||
clackIntro: Mock;
|
||||
clackOutro: Mock;
|
||||
},
|
||||
params: { select: string[]; confirm: boolean[]; text?: string },
|
||||
) {
|
||||
const selectQueue = [...params.select];
|
||||
const confirmQueue = [...params.confirm];
|
||||
mocks.clackSelect.mockImplementation(async () => selectQueue.shift());
|
||||
mocks.clackConfirm.mockImplementation(async () => confirmQueue.shift());
|
||||
mocks.clackText.mockResolvedValue(params.text ?? "");
|
||||
mocks.clackIntro.mockResolvedValue(undefined);
|
||||
mocks.clackOutro.mockResolvedValue(undefined);
|
||||
}
|
||||
|
||||
export function createSearchProviderOption(overrides: Record<string, unknown>) {
|
||||
return overrides;
|
||||
}
|
||||
|
||||
export function createEnabledWebSearchConfig(
|
||||
provider: string,
|
||||
pluginEntry: Record<string, unknown>,
|
||||
) {
|
||||
return (cfg: OpenClawConfig) => ({
|
||||
...cfg,
|
||||
tools: {
|
||||
...cfg.tools,
|
||||
web: {
|
||||
...cfg.tools?.web,
|
||||
search: {
|
||||
provider,
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
...cfg.plugins,
|
||||
entries: {
|
||||
...cfg.plugins?.entries,
|
||||
[provider]: pluginEntry,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -4,234 +4,17 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { ExitError, type RuntimeEnv } from "../runtime.js";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
import {
|
||||
createWizardTestRuntime,
|
||||
queueWizardTestPrompts,
|
||||
setupBaseWizardTestState,
|
||||
} from "./configure.wizard-test-helpers.js";
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
const writeConfigFile = vi.fn();
|
||||
return {
|
||||
clackIntro: vi.fn(),
|
||||
clackOutro: vi.fn(),
|
||||
clackSelect: vi.fn(),
|
||||
clackText: vi.fn(),
|
||||
clackConfirm: vi.fn(),
|
||||
clackPassword: vi.fn(),
|
||||
resolveSearchProviderOptions: vi.fn(),
|
||||
resolvePluginContributionOwners: vi.fn(),
|
||||
setupSearch: vi.fn(),
|
||||
assertConfigPathForWrite: vi.fn(),
|
||||
readConfigFileSnapshot: vi.fn(),
|
||||
writeConfigFile,
|
||||
replaceConfigFile: vi.fn(
|
||||
async (params: {
|
||||
nextConfig: unknown;
|
||||
writeOptions?: { assertConfigPathForWrite?: () => void };
|
||||
}) => {
|
||||
params.writeOptions?.assertConfigPathForWrite?.();
|
||||
await writeConfigFile(params.nextConfig);
|
||||
},
|
||||
),
|
||||
resolveGatewayPort: vi.fn(),
|
||||
createClackPrompter: vi.fn(),
|
||||
note: vi.fn(),
|
||||
printWizardHeader: vi.fn(),
|
||||
probeGatewayReachable: vi.fn(),
|
||||
waitForGatewayReachable: vi.fn(async () => ({ ok: true })),
|
||||
resolveAdvertisedControlUiLinks: vi.fn(),
|
||||
resolveControlUiLinks: vi.fn(),
|
||||
resolveLocalControlUiProbeLinks: vi.fn(),
|
||||
inspectWindowsGatewayFirewall: vi.fn(),
|
||||
summarizeExistingConfig: vi.fn(),
|
||||
healthCommand: vi.fn(),
|
||||
promptAuthConfig: vi.fn(),
|
||||
promptGatewayConfig: vi.fn(),
|
||||
promptRemoteGatewayConfig: vi.fn(
|
||||
async (cfg: OpenClawConfig): Promise<OpenClawConfig> => ({
|
||||
...cfg,
|
||||
gateway: { mode: "remote", remote: { url: "wss://gateway.example.test" } },
|
||||
}),
|
||||
),
|
||||
isCodexNativeWebSearchRelevant: vi.fn(({ config }: { config: OpenClawConfig }) =>
|
||||
Boolean(config.auth?.profiles?.["openai:default"]),
|
||||
),
|
||||
setupChannels: vi.fn(async (cfg: OpenClawConfig) => cfg),
|
||||
guardCancel: vi.fn((value: unknown, _runtime: RuntimeEnv, _exitCode?: number) => value),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@clack/prompts", () => ({
|
||||
intro: mocks.clackIntro,
|
||||
outro: mocks.clackOutro,
|
||||
select: mocks.clackSelect,
|
||||
text: mocks.clackText,
|
||||
confirm: mocks.clackConfirm,
|
||||
password: mocks.clackPassword,
|
||||
}));
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
CONFIG_PATH: "~/.openclaw/openclaw.json",
|
||||
createConfigIO: () => ({
|
||||
readConfigFileSnapshotForWrite: async () => ({
|
||||
snapshot: await mocks.readConfigFileSnapshot(),
|
||||
writeOptions: {
|
||||
assertConfigPathForWrite: mocks.assertConfigPathForWrite,
|
||||
expectedConfigPath: "/tmp/openclaw.json",
|
||||
ownedConfigPathForWrite: "/tmp/openclaw.json",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
readConfigFileSnapshot: mocks.readConfigFileSnapshot,
|
||||
readConfigFileSnapshotForWrite: async () => ({
|
||||
snapshot: await mocks.readConfigFileSnapshot(),
|
||||
writeOptions: {
|
||||
assertConfigPathForWrite: mocks.assertConfigPathForWrite,
|
||||
envSnapshotForRestore: { SECRET: "resolved-secret" },
|
||||
expectedConfigPath: "/tmp/openclaw.json",
|
||||
includeFileHashesForWrite: { "/tmp/plugins.json5": "stale-hash" },
|
||||
ownedConfigPathForWrite: "/tmp/openclaw.json",
|
||||
},
|
||||
}),
|
||||
resolveConfigWriteAfterWrite: (afterWrite?: { mode: string }) => afterWrite ?? { mode: "auto" },
|
||||
transformConfigFileWithRetry: async (
|
||||
params: Parameters<typeof import("../config/config.js").transformConfigFileWithRetry>[0],
|
||||
) => {
|
||||
const maxAttempts = params.maxAttempts ?? 5;
|
||||
for (let attempt = 0; ; attempt += 1) {
|
||||
const snapshot = await mocks.readConfigFileSnapshot();
|
||||
const previousHash = snapshot.hash ?? null;
|
||||
const config =
|
||||
params.base === "runtime"
|
||||
? (snapshot.runtimeConfig ?? snapshot.config)
|
||||
: (snapshot.sourceConfig ?? snapshot.config);
|
||||
try {
|
||||
const transformed = await params.transform(config, { snapshot, previousHash, attempt });
|
||||
const committed = await params.commit!({
|
||||
nextConfig: transformed.nextConfig,
|
||||
snapshot,
|
||||
...(previousHash ? { baseHash: previousHash } : {}),
|
||||
writeOptions: params.writeOptions,
|
||||
afterWrite: { mode: "auto" },
|
||||
});
|
||||
return { nextConfig: committed.config };
|
||||
} catch (error) {
|
||||
if (
|
||||
!(error instanceof Error) ||
|
||||
error.name !== "ConfigMutationConflictError" ||
|
||||
(error as { retryable?: boolean }).retryable === false ||
|
||||
attempt === maxAttempts - 1
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
writeConfigFile: mocks.writeConfigFile,
|
||||
replaceConfigFile: mocks.replaceConfigFile,
|
||||
resolveGatewayPort: mocks.resolveGatewayPort,
|
||||
}));
|
||||
|
||||
vi.mock("../infra/windows-gateway-firewall-diagnostics.js", () => ({
|
||||
inspectWindowsGatewayFirewall: mocks.inspectWindowsGatewayFirewall,
|
||||
formatWindowsGatewayFirewallGuidance: (params: { bind?: string }) =>
|
||||
params.bind === "lan"
|
||||
? [
|
||||
"Windows firewall: if another device cannot connect to the LAN URL, run `openclaw gateway status --deep` from this Windows host.",
|
||||
]
|
||||
: [],
|
||||
}));
|
||||
|
||||
vi.mock("../wizard/clack-prompter.js", () => ({
|
||||
createClackPrompter: mocks.createClackPrompter,
|
||||
}));
|
||||
|
||||
vi.mock("../../packages/terminal-core/src/note.js", () => ({
|
||||
note: mocks.note,
|
||||
}));
|
||||
|
||||
vi.mock("./onboard-helpers.js", () => ({
|
||||
DEFAULT_WORKSPACE: "~/.openclaw/workspace",
|
||||
applyWizardMetadata: (cfg: OpenClawConfig) => cfg,
|
||||
ensureWorkspaceAndSessions: vi.fn(),
|
||||
guardCancel: mocks.guardCancel,
|
||||
printWizardHeader: mocks.printWizardHeader,
|
||||
probeGatewayReachable: mocks.probeGatewayReachable,
|
||||
resolveAdvertisedControlUiLinks: mocks.resolveAdvertisedControlUiLinks,
|
||||
resolveControlUiLinks: mocks.resolveControlUiLinks,
|
||||
resolveLocalControlUiProbeLinks: mocks.resolveLocalControlUiProbeLinks,
|
||||
summarizeExistingConfig: mocks.summarizeExistingConfig,
|
||||
waitForGatewayReachable: mocks.waitForGatewayReachable,
|
||||
}));
|
||||
|
||||
vi.mock("./health.js", () => ({
|
||||
healthCommandNonExiting: mocks.healthCommand,
|
||||
}));
|
||||
|
||||
vi.mock("./health-format.js", () => ({
|
||||
formatHealthCheckFailure: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./configure.gateway.js", () => ({
|
||||
promptGatewayConfig: mocks.promptGatewayConfig,
|
||||
}));
|
||||
|
||||
vi.mock("./configure.gateway-auth.js", () => ({
|
||||
promptAuthConfig: mocks.promptAuthConfig,
|
||||
}));
|
||||
|
||||
vi.mock("./configure.channels.js", () => ({
|
||||
removeChannelConfigWizard: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./configure.daemon.js", () => ({
|
||||
maybeInstallDaemon: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./onboard-remote.js", () => ({
|
||||
promptRemoteGatewayConfig: mocks.promptRemoteGatewayConfig,
|
||||
}));
|
||||
|
||||
vi.mock("./onboard-skills.js", () => ({
|
||||
setupSkills: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./onboard-channels.js", () => ({
|
||||
setupChannels: mocks.setupChannels,
|
||||
}));
|
||||
|
||||
vi.mock("../flows/search-setup.js", () => ({
|
||||
resolveSearchProviderOptions: mocks.resolveSearchProviderOptions,
|
||||
runSearchSetupFlow: mocks.setupSearch,
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/plugin-registry.js", () => ({
|
||||
resolvePluginContributionOwners: mocks.resolvePluginContributionOwners,
|
||||
}));
|
||||
|
||||
vi.mock("../agents/codex-native-web-search.js", () => ({
|
||||
isCodexNativeWebSearchRelevant: mocks.isCodexNativeWebSearchRelevant,
|
||||
}));
|
||||
|
||||
vi.mock("../config/mutate.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../config/mutate.js")>("../config/mutate.js");
|
||||
return {
|
||||
...actual,
|
||||
ConfigMutationConflictError: actual.ConfigMutationConflictError,
|
||||
};
|
||||
});
|
||||
|
||||
import { WizardCancelledError } from "../wizard/prompts.js";
|
||||
import { maybeInstallDaemon } from "./configure.daemon.js";
|
||||
import { runConfigureWizard } from "./configure.wizard.js";
|
||||
import { formatHealthCheckFailure } from "./health-format.js";
|
||||
import {
|
||||
createWizardTestRuntime as createRuntime,
|
||||
queueWizardTestPrompts as queueWizardPrompts,
|
||||
runConfigureWizard,
|
||||
setupWizardTestDefaults,
|
||||
setupBaseWizardTestState as setupBaseWizardState,
|
||||
wizardTestMocks as mocks,
|
||||
} from "./configure.wizard.test-support.js";
|
||||
|
||||
const createRuntime = createWizardTestRuntime;
|
||||
|
||||
function setupBaseWizardState(config: OpenClawConfig = {}) {
|
||||
setupBaseWizardTestState(mocks, config);
|
||||
}
|
||||
const { maybeInstallDaemon, formatHealthCheckFailure } = mocks;
|
||||
|
||||
const requireRecord = createRequireRecord("object", "expected-label");
|
||||
|
||||
@@ -258,42 +41,10 @@ function getGateway(config: Record<string, unknown>) {
|
||||
return requireRecord(config.gateway, "gateway config");
|
||||
}
|
||||
|
||||
function queueWizardPrompts(params: { select: string[]; confirm: boolean[]; text?: string }) {
|
||||
queueWizardTestPrompts(mocks, params);
|
||||
}
|
||||
|
||||
describe("runConfigureWizard", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.healthCommand.mockReset();
|
||||
mocks.assertConfigPathForWrite.mockImplementation(() => {});
|
||||
mocks.resolvePluginContributionOwners.mockReturnValue(["firecrawl"]);
|
||||
mocks.resolveSearchProviderOptions.mockReturnValue([
|
||||
{
|
||||
id: "firecrawl",
|
||||
label: "Firecrawl Search",
|
||||
hint: "Structured results with optional result scraping",
|
||||
credentialLabel: "Firecrawl API key",
|
||||
envVars: ["FIRECRAWL_API_KEY"],
|
||||
placeholder: "fc-...",
|
||||
signupUrl: "https://www.firecrawl.dev/",
|
||||
credentialPath: "plugins.entries.firecrawl.config.webSearch.apiKey",
|
||||
},
|
||||
]);
|
||||
mocks.setupSearch.mockReset();
|
||||
mocks.setupSearch.mockImplementation(async (cfg: OpenClawConfig) => ({
|
||||
outcome: "completed",
|
||||
config: cfg,
|
||||
}));
|
||||
mocks.promptAuthConfig.mockReset();
|
||||
mocks.promptAuthConfig.mockImplementation(async (cfg: OpenClawConfig) => cfg);
|
||||
mocks.promptGatewayConfig.mockReset();
|
||||
mocks.promptGatewayConfig.mockImplementation(async (cfg: OpenClawConfig) => ({
|
||||
config: cfg,
|
||||
port: 18789,
|
||||
}));
|
||||
mocks.guardCancel.mockReset();
|
||||
mocks.guardCancel.mockImplementation((value: unknown) => value);
|
||||
vi.resetAllMocks();
|
||||
setupWizardTestDefaults();
|
||||
});
|
||||
|
||||
it("runs selected sections in canonical order and commits their combined config once", async () => {
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
import { vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
|
||||
const wizardTestMocks = vi.hoisted(() => {
|
||||
const writeConfigFile = vi.fn();
|
||||
return {
|
||||
clackIntro: vi.fn(),
|
||||
clackOutro: vi.fn(),
|
||||
clackSelect: vi.fn(),
|
||||
clackText: vi.fn(),
|
||||
clackConfirm: vi.fn(),
|
||||
clackPassword: vi.fn(),
|
||||
resolveSearchProviderOptions: vi.fn(),
|
||||
resolvePluginContributionOwners: vi.fn(),
|
||||
setupSearch: vi.fn(),
|
||||
assertConfigPathForWrite: vi.fn(),
|
||||
readConfigFileSnapshot: vi.fn(),
|
||||
writeConfigFile,
|
||||
replaceConfigFile: vi.fn(
|
||||
async (params: {
|
||||
nextConfig: unknown;
|
||||
writeOptions?: { assertConfigPathForWrite?: () => void };
|
||||
}) => {
|
||||
params.writeOptions?.assertConfigPathForWrite?.();
|
||||
await writeConfigFile(params.nextConfig);
|
||||
},
|
||||
),
|
||||
resolveGatewayPort: vi.fn(),
|
||||
createClackPrompter: vi.fn(),
|
||||
note: vi.fn(),
|
||||
printWizardHeader: vi.fn(),
|
||||
probeGatewayReachable: vi.fn(),
|
||||
waitForGatewayReachable: vi.fn(async () => ({ ok: true })),
|
||||
resolveAdvertisedControlUiLinks: vi.fn(),
|
||||
resolveControlUiLinks: vi.fn(),
|
||||
resolveLocalControlUiProbeLinks: vi.fn(),
|
||||
inspectWindowsGatewayFirewall: vi.fn(),
|
||||
summarizeExistingConfig: vi.fn(),
|
||||
healthCommand: vi.fn(),
|
||||
formatHealthCheckFailure: vi.fn<typeof import("./health-format.js").formatHealthCheckFailure>(),
|
||||
maybeInstallDaemon: vi.fn<typeof import("./configure.daemon.js").maybeInstallDaemon>(),
|
||||
promptAuthConfig: vi.fn(),
|
||||
promptGatewayConfig: vi.fn(),
|
||||
promptRemoteGatewayConfig: vi.fn(
|
||||
async (cfg: OpenClawConfig): Promise<OpenClawConfig> => ({
|
||||
...cfg,
|
||||
gateway: { mode: "remote", remote: { url: "wss://gateway.example.test" } },
|
||||
}),
|
||||
),
|
||||
isCodexNativeWebSearchRelevant: vi.fn(({ config }: { config: OpenClawConfig }) =>
|
||||
Boolean(config.auth?.profiles?.["openai:default"]),
|
||||
),
|
||||
setupChannels: vi.fn(async (cfg: OpenClawConfig) => cfg),
|
||||
guardCancel: vi.fn((value: unknown, _runtime: RuntimeEnv, _exitCode?: number) => value),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@clack/prompts", () => ({
|
||||
intro: wizardTestMocks.clackIntro,
|
||||
outro: wizardTestMocks.clackOutro,
|
||||
select: wizardTestMocks.clackSelect,
|
||||
text: wizardTestMocks.clackText,
|
||||
confirm: wizardTestMocks.clackConfirm,
|
||||
password: wizardTestMocks.clackPassword,
|
||||
}));
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
CONFIG_PATH: "~/.openclaw/openclaw.json",
|
||||
createConfigIO: () => ({
|
||||
readConfigFileSnapshotForWrite: async () => ({
|
||||
snapshot: await wizardTestMocks.readConfigFileSnapshot(),
|
||||
writeOptions: {
|
||||
assertConfigPathForWrite: wizardTestMocks.assertConfigPathForWrite,
|
||||
expectedConfigPath: "/tmp/openclaw.json",
|
||||
ownedConfigPathForWrite: "/tmp/openclaw.json",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
readConfigFileSnapshot: wizardTestMocks.readConfigFileSnapshot,
|
||||
readConfigFileSnapshotForWrite: async () => ({
|
||||
snapshot: await wizardTestMocks.readConfigFileSnapshot(),
|
||||
writeOptions: {
|
||||
assertConfigPathForWrite: wizardTestMocks.assertConfigPathForWrite,
|
||||
envSnapshotForRestore: { SECRET: "resolved-secret" },
|
||||
expectedConfigPath: "/tmp/openclaw.json",
|
||||
includeFileHashesForWrite: { "/tmp/plugins.json5": "stale-hash" },
|
||||
ownedConfigPathForWrite: "/tmp/openclaw.json",
|
||||
},
|
||||
}),
|
||||
resolveConfigWriteAfterWrite: (afterWrite?: { mode: string }) => afterWrite ?? { mode: "auto" },
|
||||
transformConfigFileWithRetry: async (
|
||||
params: Parameters<typeof import("../config/config.js").transformConfigFileWithRetry>[0],
|
||||
) => {
|
||||
const maxAttempts = params.maxAttempts ?? 5;
|
||||
for (let attempt = 0; ; attempt += 1) {
|
||||
const snapshot = await wizardTestMocks.readConfigFileSnapshot();
|
||||
const previousHash = snapshot.hash ?? null;
|
||||
const config =
|
||||
params.base === "runtime"
|
||||
? (snapshot.runtimeConfig ?? snapshot.config)
|
||||
: (snapshot.sourceConfig ?? snapshot.config);
|
||||
try {
|
||||
const transformed = await params.transform(config, { snapshot, previousHash, attempt });
|
||||
const committed = await params.commit!({
|
||||
nextConfig: transformed.nextConfig,
|
||||
snapshot,
|
||||
...(previousHash ? { baseHash: previousHash } : {}),
|
||||
writeOptions: params.writeOptions,
|
||||
afterWrite: { mode: "auto" },
|
||||
});
|
||||
return { nextConfig: committed.config };
|
||||
} catch (error) {
|
||||
if (
|
||||
!(error instanceof Error) ||
|
||||
error.name !== "ConfigMutationConflictError" ||
|
||||
(error as { retryable?: boolean }).retryable === false ||
|
||||
attempt === maxAttempts - 1
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
writeConfigFile: wizardTestMocks.writeConfigFile,
|
||||
replaceConfigFile: wizardTestMocks.replaceConfigFile,
|
||||
resolveGatewayPort: wizardTestMocks.resolveGatewayPort,
|
||||
}));
|
||||
|
||||
vi.mock("../infra/windows-gateway-firewall-diagnostics.js", () => ({
|
||||
inspectWindowsGatewayFirewall: wizardTestMocks.inspectWindowsGatewayFirewall,
|
||||
formatWindowsGatewayFirewallGuidance: (params: { bind?: string }) =>
|
||||
params.bind === "lan"
|
||||
? [
|
||||
"Windows firewall: if another device cannot connect to the LAN URL, run `openclaw gateway status --deep` from this Windows host.",
|
||||
]
|
||||
: [],
|
||||
}));
|
||||
|
||||
vi.mock("../wizard/clack-prompter.js", () => ({
|
||||
createClackPrompter: wizardTestMocks.createClackPrompter,
|
||||
}));
|
||||
|
||||
vi.mock("../../packages/terminal-core/src/note.js", () => ({
|
||||
note: wizardTestMocks.note,
|
||||
}));
|
||||
|
||||
vi.mock("./onboard-helpers.js", () => ({
|
||||
DEFAULT_WORKSPACE: "~/.openclaw/workspace",
|
||||
applyWizardMetadata: (cfg: OpenClawConfig) => cfg,
|
||||
ensureWorkspaceAndSessions: vi.fn(),
|
||||
guardCancel: wizardTestMocks.guardCancel,
|
||||
printWizardHeader: wizardTestMocks.printWizardHeader,
|
||||
probeGatewayReachable: wizardTestMocks.probeGatewayReachable,
|
||||
resolveAdvertisedControlUiLinks: wizardTestMocks.resolveAdvertisedControlUiLinks,
|
||||
resolveControlUiLinks: wizardTestMocks.resolveControlUiLinks,
|
||||
resolveLocalControlUiProbeLinks: wizardTestMocks.resolveLocalControlUiProbeLinks,
|
||||
summarizeExistingConfig: wizardTestMocks.summarizeExistingConfig,
|
||||
waitForGatewayReachable: wizardTestMocks.waitForGatewayReachable,
|
||||
}));
|
||||
|
||||
vi.mock("./health.js", () => ({
|
||||
healthCommandNonExiting: wizardTestMocks.healthCommand,
|
||||
}));
|
||||
|
||||
vi.mock("./health-format.js", () => ({
|
||||
formatHealthCheckFailure: wizardTestMocks.formatHealthCheckFailure,
|
||||
}));
|
||||
|
||||
vi.mock("./configure.gateway.js", () => ({
|
||||
promptGatewayConfig: wizardTestMocks.promptGatewayConfig,
|
||||
}));
|
||||
|
||||
vi.mock("./configure.gateway-auth.js", () => ({
|
||||
promptAuthConfig: wizardTestMocks.promptAuthConfig,
|
||||
}));
|
||||
|
||||
vi.mock("./configure.channels.js", () => ({
|
||||
removeChannelConfigWizard: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./configure.daemon.js", () => ({
|
||||
maybeInstallDaemon: wizardTestMocks.maybeInstallDaemon,
|
||||
}));
|
||||
|
||||
vi.mock("./onboard-remote.js", () => ({
|
||||
promptRemoteGatewayConfig: wizardTestMocks.promptRemoteGatewayConfig,
|
||||
}));
|
||||
|
||||
vi.mock("./onboard-skills.js", () => ({
|
||||
setupSkills: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./onboard-channels.js", () => ({
|
||||
setupChannels: wizardTestMocks.setupChannels,
|
||||
}));
|
||||
|
||||
vi.mock("../flows/search-setup.js", () => ({
|
||||
resolveSearchProviderOptions: wizardTestMocks.resolveSearchProviderOptions,
|
||||
runSearchSetupFlow: wizardTestMocks.setupSearch,
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/plugin-registry.js", () => ({
|
||||
resolvePluginContributionOwners: wizardTestMocks.resolvePluginContributionOwners,
|
||||
}));
|
||||
|
||||
vi.mock("../agents/codex-native-web-search.js", () => ({
|
||||
isCodexNativeWebSearchRelevant: wizardTestMocks.isCodexNativeWebSearchRelevant,
|
||||
}));
|
||||
|
||||
// Load the wizard through this fixture so mocks register before its dependencies.
|
||||
const { runConfigureWizard } = await import("./configure.wizard.js");
|
||||
export { runConfigureWizard, wizardTestMocks };
|
||||
|
||||
export function setupWizardTestDefaults() {
|
||||
wizardTestMocks.assertConfigPathForWrite.mockImplementation(() => {});
|
||||
wizardTestMocks.resolvePluginContributionOwners.mockReturnValue(["firecrawl"]);
|
||||
wizardTestMocks.resolveSearchProviderOptions.mockReturnValue([
|
||||
{
|
||||
id: "firecrawl",
|
||||
label: "Firecrawl Search",
|
||||
hint: "Structured results with optional result scraping",
|
||||
credentialLabel: "Firecrawl API key",
|
||||
envVars: ["FIRECRAWL_API_KEY"],
|
||||
placeholder: "fc-...",
|
||||
signupUrl: "https://www.firecrawl.dev/",
|
||||
credentialPath: "plugins.entries.firecrawl.config.webSearch.apiKey",
|
||||
},
|
||||
]);
|
||||
wizardTestMocks.setupSearch.mockImplementation(async (cfg: OpenClawConfig) => ({
|
||||
outcome: "completed",
|
||||
config: cfg,
|
||||
}));
|
||||
wizardTestMocks.promptAuthConfig.mockImplementation(async (cfg: OpenClawConfig) => cfg);
|
||||
wizardTestMocks.promptGatewayConfig.mockImplementation(async (cfg: OpenClawConfig) => ({
|
||||
config: cfg,
|
||||
port: 18789,
|
||||
}));
|
||||
wizardTestMocks.guardCancel.mockImplementation((value: unknown) => value);
|
||||
}
|
||||
|
||||
export const EMPTY_CONFIG_SNAPSHOT = {
|
||||
exists: false,
|
||||
valid: true,
|
||||
config: {},
|
||||
issues: [],
|
||||
};
|
||||
|
||||
export function createWizardTestRuntime() {
|
||||
return {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
export function setupBaseWizardTestState(config: OpenClawConfig = {}) {
|
||||
wizardTestMocks.readConfigFileSnapshot.mockResolvedValue({ ...EMPTY_CONFIG_SNAPSHOT, config });
|
||||
wizardTestMocks.resolveGatewayPort.mockReturnValue(18789);
|
||||
wizardTestMocks.probeGatewayReachable.mockResolvedValue({ ok: false });
|
||||
wizardTestMocks.resolveControlUiLinks.mockReturnValue({ wsUrl: "ws://127.0.0.1:18789" });
|
||||
wizardTestMocks.resolveLocalControlUiProbeLinks.mockReturnValue({
|
||||
httpUrl: "http://127.0.0.1:18789/",
|
||||
wsUrl: "ws://127.0.0.1:18789",
|
||||
});
|
||||
wizardTestMocks.resolveAdvertisedControlUiLinks.mockResolvedValue({
|
||||
httpUrl: "http://127.0.0.1:18789/",
|
||||
wsUrl: "ws://127.0.0.1:18789",
|
||||
});
|
||||
wizardTestMocks.inspectWindowsGatewayFirewall.mockResolvedValue({
|
||||
applies: false,
|
||||
severity: "info",
|
||||
code: "windows_firewall_not_applicable",
|
||||
message: "Windows LAN firewall diagnostics do not apply.",
|
||||
details: [],
|
||||
});
|
||||
wizardTestMocks.summarizeExistingConfig.mockReturnValue("");
|
||||
wizardTestMocks.createClackPrompter.mockReturnValue({
|
||||
intro: vi.fn(async () => {}),
|
||||
outro: vi.fn(async () => {}),
|
||||
note: vi.fn(async () => {}),
|
||||
select: vi.fn(async () => "firecrawl"),
|
||||
multiselect: vi.fn(async () => []),
|
||||
text: vi.fn(async () => ""),
|
||||
confirm: vi.fn(async () => true),
|
||||
progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })),
|
||||
});
|
||||
}
|
||||
|
||||
export function queueWizardTestPrompts(params: {
|
||||
select: string[];
|
||||
confirm: boolean[];
|
||||
text?: string;
|
||||
}) {
|
||||
const selectQueue = [...params.select];
|
||||
const confirmQueue = [...params.confirm];
|
||||
wizardTestMocks.clackSelect.mockImplementation(async () => selectQueue.shift());
|
||||
wizardTestMocks.clackConfirm.mockImplementation(async () => confirmQueue.shift());
|
||||
wizardTestMocks.clackText.mockResolvedValue(params.text ?? "");
|
||||
wizardTestMocks.clackIntro.mockResolvedValue(undefined);
|
||||
wizardTestMocks.clackOutro.mockResolvedValue(undefined);
|
||||
}
|
||||
|
||||
export function createEnabledWebSearchConfig(
|
||||
provider: string,
|
||||
pluginEntry: Record<string, unknown>,
|
||||
) {
|
||||
return (cfg: OpenClawConfig) => ({
|
||||
...cfg,
|
||||
tools: {
|
||||
...cfg.tools,
|
||||
web: {
|
||||
...cfg.tools?.web,
|
||||
search: {
|
||||
provider,
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
...cfg.plugins,
|
||||
entries: {
|
||||
...cfg.plugins?.entries,
|
||||
[provider]: pluginEntry,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -2,236 +2,17 @@
|
||||
import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { ConfigMutationConflictError } from "../config/mutate.js";
|
||||
import {
|
||||
createEnabledWebSearchConfig,
|
||||
createSearchProviderOption,
|
||||
createWizardTestRuntime,
|
||||
createWizardTestRuntime as createRuntime,
|
||||
EMPTY_CONFIG_SNAPSHOT,
|
||||
queueWizardTestPrompts,
|
||||
setupBaseWizardTestState,
|
||||
} from "./configure.wizard-test-helpers.js";
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
const writeConfigFile = vi.fn();
|
||||
return {
|
||||
clackIntro: vi.fn(),
|
||||
clackOutro: vi.fn(),
|
||||
clackSelect: vi.fn(),
|
||||
clackText: vi.fn(),
|
||||
clackConfirm: vi.fn(),
|
||||
clackPassword: vi.fn(),
|
||||
resolveSearchProviderOptions: vi.fn(),
|
||||
resolvePluginContributionOwners: vi.fn(),
|
||||
setupSearch: vi.fn(),
|
||||
assertConfigPathForWrite: vi.fn(),
|
||||
readConfigFileSnapshot: vi.fn(),
|
||||
writeConfigFile,
|
||||
replaceConfigFile: vi.fn(
|
||||
async (params: {
|
||||
nextConfig: unknown;
|
||||
writeOptions?: { assertConfigPathForWrite?: () => void };
|
||||
}) => {
|
||||
params.writeOptions?.assertConfigPathForWrite?.();
|
||||
await writeConfigFile(params.nextConfig);
|
||||
},
|
||||
),
|
||||
resolveGatewayPort: vi.fn(),
|
||||
createClackPrompter: vi.fn(),
|
||||
note: vi.fn(),
|
||||
printWizardHeader: vi.fn(),
|
||||
probeGatewayReachable: vi.fn(),
|
||||
waitForGatewayReachable: vi.fn(async () => ({ ok: true })),
|
||||
resolveAdvertisedControlUiLinks: vi.fn(),
|
||||
resolveControlUiLinks: vi.fn(),
|
||||
resolveLocalControlUiProbeLinks: vi.fn(),
|
||||
inspectWindowsGatewayFirewall: vi.fn(),
|
||||
summarizeExistingConfig: vi.fn(),
|
||||
healthCommand: vi.fn(),
|
||||
promptAuthConfig: vi.fn(),
|
||||
promptGatewayConfig: vi.fn(),
|
||||
promptRemoteGatewayConfig: vi.fn(
|
||||
async (cfg: OpenClawConfig): Promise<OpenClawConfig> => ({
|
||||
...cfg,
|
||||
gateway: { mode: "remote", remote: { url: "wss://gateway.example.test" } },
|
||||
}),
|
||||
),
|
||||
isCodexNativeWebSearchRelevant: vi.fn(({ config }: { config: OpenClawConfig }) =>
|
||||
Boolean(config.auth?.profiles?.["openai:default"]),
|
||||
),
|
||||
setupChannels: vi.fn(async (cfg: OpenClawConfig) => cfg),
|
||||
guardCancel: vi.fn((value: unknown, _runtime: RuntimeEnv, _exitCode?: number) => value),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@clack/prompts", () => ({
|
||||
intro: mocks.clackIntro,
|
||||
outro: mocks.clackOutro,
|
||||
select: mocks.clackSelect,
|
||||
text: mocks.clackText,
|
||||
confirm: mocks.clackConfirm,
|
||||
password: mocks.clackPassword,
|
||||
}));
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
CONFIG_PATH: "~/.openclaw/openclaw.json",
|
||||
createConfigIO: () => ({
|
||||
readConfigFileSnapshotForWrite: async () => ({
|
||||
snapshot: await mocks.readConfigFileSnapshot(),
|
||||
writeOptions: {
|
||||
assertConfigPathForWrite: mocks.assertConfigPathForWrite,
|
||||
expectedConfigPath: "/tmp/openclaw.json",
|
||||
ownedConfigPathForWrite: "/tmp/openclaw.json",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
readConfigFileSnapshot: mocks.readConfigFileSnapshot,
|
||||
readConfigFileSnapshotForWrite: async () => ({
|
||||
snapshot: await mocks.readConfigFileSnapshot(),
|
||||
writeOptions: {
|
||||
assertConfigPathForWrite: mocks.assertConfigPathForWrite,
|
||||
envSnapshotForRestore: { SECRET: "resolved-secret" },
|
||||
expectedConfigPath: "/tmp/openclaw.json",
|
||||
includeFileHashesForWrite: { "/tmp/plugins.json5": "stale-hash" },
|
||||
ownedConfigPathForWrite: "/tmp/openclaw.json",
|
||||
},
|
||||
}),
|
||||
resolveConfigWriteAfterWrite: (afterWrite?: { mode: string }) => afterWrite ?? { mode: "auto" },
|
||||
transformConfigFileWithRetry: async (
|
||||
params: Parameters<typeof import("../config/config.js").transformConfigFileWithRetry>[0],
|
||||
) => {
|
||||
const maxAttempts = params.maxAttempts ?? 5;
|
||||
for (let attempt = 0; ; attempt += 1) {
|
||||
const snapshot = await mocks.readConfigFileSnapshot();
|
||||
const previousHash = snapshot.hash ?? null;
|
||||
const config =
|
||||
params.base === "runtime"
|
||||
? (snapshot.runtimeConfig ?? snapshot.config)
|
||||
: (snapshot.sourceConfig ?? snapshot.config);
|
||||
try {
|
||||
const transformed = await params.transform(config, { snapshot, previousHash, attempt });
|
||||
const committed = await params.commit!({
|
||||
nextConfig: transformed.nextConfig,
|
||||
snapshot,
|
||||
...(previousHash ? { baseHash: previousHash } : {}),
|
||||
writeOptions: params.writeOptions,
|
||||
afterWrite: { mode: "auto" },
|
||||
});
|
||||
return { nextConfig: committed.config };
|
||||
} catch (error) {
|
||||
if (
|
||||
!(error instanceof Error) ||
|
||||
error.name !== "ConfigMutationConflictError" ||
|
||||
(error as { retryable?: boolean }).retryable === false ||
|
||||
attempt === maxAttempts - 1
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
writeConfigFile: mocks.writeConfigFile,
|
||||
replaceConfigFile: mocks.replaceConfigFile,
|
||||
resolveGatewayPort: mocks.resolveGatewayPort,
|
||||
}));
|
||||
|
||||
vi.mock("../infra/windows-gateway-firewall-diagnostics.js", () => ({
|
||||
inspectWindowsGatewayFirewall: mocks.inspectWindowsGatewayFirewall,
|
||||
formatWindowsGatewayFirewallGuidance: (params: { bind?: string }) =>
|
||||
params.bind === "lan"
|
||||
? [
|
||||
"Windows firewall: if another device cannot connect to the LAN URL, run `openclaw gateway status --deep` from this Windows host.",
|
||||
]
|
||||
: [],
|
||||
}));
|
||||
|
||||
vi.mock("../wizard/clack-prompter.js", () => ({
|
||||
createClackPrompter: mocks.createClackPrompter,
|
||||
}));
|
||||
|
||||
vi.mock("../../packages/terminal-core/src/note.js", () => ({
|
||||
note: mocks.note,
|
||||
}));
|
||||
|
||||
vi.mock("./onboard-helpers.js", () => ({
|
||||
DEFAULT_WORKSPACE: "~/.openclaw/workspace",
|
||||
applyWizardMetadata: (cfg: OpenClawConfig) => cfg,
|
||||
ensureWorkspaceAndSessions: vi.fn(),
|
||||
guardCancel: mocks.guardCancel,
|
||||
printWizardHeader: mocks.printWizardHeader,
|
||||
probeGatewayReachable: mocks.probeGatewayReachable,
|
||||
resolveAdvertisedControlUiLinks: mocks.resolveAdvertisedControlUiLinks,
|
||||
resolveControlUiLinks: mocks.resolveControlUiLinks,
|
||||
resolveLocalControlUiProbeLinks: mocks.resolveLocalControlUiProbeLinks,
|
||||
summarizeExistingConfig: mocks.summarizeExistingConfig,
|
||||
waitForGatewayReachable: mocks.waitForGatewayReachable,
|
||||
}));
|
||||
|
||||
vi.mock("./health.js", () => ({
|
||||
healthCommandNonExiting: mocks.healthCommand,
|
||||
}));
|
||||
|
||||
vi.mock("./health-format.js", () => ({
|
||||
formatHealthCheckFailure: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./configure.gateway.js", () => ({
|
||||
promptGatewayConfig: mocks.promptGatewayConfig,
|
||||
}));
|
||||
|
||||
vi.mock("./configure.gateway-auth.js", () => ({
|
||||
promptAuthConfig: mocks.promptAuthConfig,
|
||||
}));
|
||||
|
||||
vi.mock("./configure.channels.js", () => ({
|
||||
removeChannelConfigWizard: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./configure.daemon.js", () => ({
|
||||
maybeInstallDaemon: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./onboard-remote.js", () => ({
|
||||
promptRemoteGatewayConfig: mocks.promptRemoteGatewayConfig,
|
||||
}));
|
||||
|
||||
vi.mock("./onboard-skills.js", () => ({
|
||||
setupSkills: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./onboard-channels.js", () => ({
|
||||
setupChannels: mocks.setupChannels,
|
||||
}));
|
||||
|
||||
vi.mock("../flows/search-setup.js", () => ({
|
||||
resolveSearchProviderOptions: mocks.resolveSearchProviderOptions,
|
||||
runSearchSetupFlow: mocks.setupSearch,
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/plugin-registry.js", () => ({
|
||||
resolvePluginContributionOwners: mocks.resolvePluginContributionOwners,
|
||||
}));
|
||||
|
||||
vi.mock("../agents/codex-native-web-search.js", () => ({
|
||||
isCodexNativeWebSearchRelevant: mocks.isCodexNativeWebSearchRelevant,
|
||||
}));
|
||||
|
||||
vi.mock("../config/mutate.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../config/mutate.js")>("../config/mutate.js");
|
||||
return {
|
||||
...actual,
|
||||
ConfigMutationConflictError: actual.ConfigMutationConflictError,
|
||||
};
|
||||
});
|
||||
|
||||
import { ConfigMutationConflictError } from "../config/mutate.js";
|
||||
import { runConfigureWizard } from "./configure.wizard.js";
|
||||
|
||||
const createRuntime = createWizardTestRuntime;
|
||||
|
||||
function setupBaseWizardState(config: OpenClawConfig = {}) {
|
||||
setupBaseWizardTestState(mocks, config);
|
||||
}
|
||||
queueWizardTestPrompts as queueWizardPrompts,
|
||||
runConfigureWizard,
|
||||
setupWizardTestDefaults,
|
||||
setupBaseWizardTestState as setupBaseWizardState,
|
||||
wizardTestMocks as mocks,
|
||||
} from "./configure.wizard.test-support.js";
|
||||
|
||||
const requireRecord = createRequireRecord("object", "expected-label");
|
||||
|
||||
@@ -266,46 +47,14 @@ function getPluginEntry(config: Record<string, unknown>, pluginId: string) {
|
||||
return requireRecord(entries[pluginId], `${pluginId} entry`);
|
||||
}
|
||||
|
||||
function queueWizardPrompts(params: { select: string[]; confirm: boolean[]; text?: string }) {
|
||||
queueWizardTestPrompts(mocks, params);
|
||||
}
|
||||
|
||||
async function runWebConfigureWizard() {
|
||||
await runConfigureWizard({ command: "configure", sections: ["web"] }, createRuntime());
|
||||
}
|
||||
|
||||
describe("runConfigureWizard", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.healthCommand.mockReset();
|
||||
mocks.assertConfigPathForWrite.mockImplementation(() => {});
|
||||
mocks.resolvePluginContributionOwners.mockReturnValue(["firecrawl"]);
|
||||
mocks.resolveSearchProviderOptions.mockReturnValue([
|
||||
{
|
||||
id: "firecrawl",
|
||||
label: "Firecrawl Search",
|
||||
hint: "Structured results with optional result scraping",
|
||||
credentialLabel: "Firecrawl API key",
|
||||
envVars: ["FIRECRAWL_API_KEY"],
|
||||
placeholder: "fc-...",
|
||||
signupUrl: "https://www.firecrawl.dev/",
|
||||
credentialPath: "plugins.entries.firecrawl.config.webSearch.apiKey",
|
||||
},
|
||||
]);
|
||||
mocks.setupSearch.mockReset();
|
||||
mocks.setupSearch.mockImplementation(async (cfg: OpenClawConfig) => ({
|
||||
outcome: "completed",
|
||||
config: cfg,
|
||||
}));
|
||||
mocks.promptAuthConfig.mockReset();
|
||||
mocks.promptAuthConfig.mockImplementation(async (cfg: OpenClawConfig) => cfg);
|
||||
mocks.promptGatewayConfig.mockReset();
|
||||
mocks.promptGatewayConfig.mockImplementation(async (cfg: OpenClawConfig) => ({
|
||||
config: cfg,
|
||||
port: 18789,
|
||||
}));
|
||||
mocks.guardCancel.mockReset();
|
||||
mocks.guardCancel.mockImplementation((value: unknown) => value);
|
||||
vi.resetAllMocks();
|
||||
setupWizardTestDefaults();
|
||||
});
|
||||
|
||||
it("persists provider-owned web search config changes returned by setupSearch", async () => {
|
||||
@@ -462,7 +211,7 @@ describe("runConfigureWizard", () => {
|
||||
it("still supports keyless web search providers through the shared setup flow", async () => {
|
||||
setupBaseWizardState();
|
||||
mocks.resolveSearchProviderOptions.mockReturnValue([
|
||||
createSearchProviderOption({
|
||||
{
|
||||
id: "duckduckgo",
|
||||
label: "DuckDuckGo Search (experimental)",
|
||||
hint: "Free fallback",
|
||||
@@ -472,7 +221,7 @@ describe("runConfigureWizard", () => {
|
||||
signupUrl: "https://duckduckgo.com/",
|
||||
docsUrl: "https://docs.openclaw.ai/tools/web",
|
||||
credentialPath: "",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
mocks.setupSearch.mockImplementation(async (cfg: OpenClawConfig) => ({
|
||||
outcome: "completed",
|
||||
|
||||
Reference in New Issue
Block a user