From d28cb8d821ea004091cd459f80f889ab3a8e335d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 17 Mar 2026 06:29:38 +0000 Subject: [PATCH] refactor(tests): share setup wizard prompter --- .../googlechat/src/setup-surface.test.ts | 27 ++--------------- extensions/irc/src/setup-surface.test.ts | 29 +++---------------- extensions/line/src/setup-surface.test.ts | 24 ++------------- extensions/nostr/src/setup-surface.test.ts | 24 ++------------- .../synology-chat/src/setup-surface.test.ts | 26 ++--------------- extensions/test-utils/setup-wizard.ts | 28 ++++++++++++++++++ extensions/tlon/src/setup-surface.test.ts | 27 ++--------------- extensions/zalo/src/setup-surface.test.ts | 20 +++---------- extensions/zalouser/src/setup-surface.test.ts | 27 ++--------------- 9 files changed, 52 insertions(+), 180 deletions(-) create mode 100644 extensions/test-utils/setup-wizard.ts diff --git a/extensions/googlechat/src/setup-surface.test.ts b/extensions/googlechat/src/setup-surface.test.ts index e8855648c99..8ecae3855cc 100644 --- a/extensions/googlechat/src/setup-surface.test.ts +++ b/extensions/googlechat/src/setup-surface.test.ts @@ -1,31 +1,10 @@ -import type { OpenClawConfig, WizardPrompter } from "openclaw/plugin-sdk/googlechat"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/googlechat"; import { describe, expect, it, vi } from "vitest"; import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js"; import { createRuntimeEnv } from "../../test-utils/runtime-env.js"; +import { createTestWizardPrompter, type WizardPrompter } from "../../test-utils/setup-wizard.js"; import { googlechatPlugin } from "./channel.js"; -const selectFirstOption = async (params: { options: Array<{ value: T }> }): Promise => { - const first = params.options[0]; - if (!first) { - throw new Error("no options"); - } - return first.value; -}; - -function createPrompter(overrides: Partial): WizardPrompter { - return { - intro: vi.fn(async () => {}), - outro: vi.fn(async () => {}), - note: vi.fn(async () => {}), - select: selectFirstOption as WizardPrompter["select"], - multiselect: vi.fn(async () => []), - text: vi.fn(async () => "") as WizardPrompter["text"], - confirm: vi.fn(async () => false), - progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })), - ...overrides, - }; -} - const googlechatConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ plugin: googlechatPlugin, wizard: googlechatPlugin.setupWizard!, @@ -33,7 +12,7 @@ const googlechatConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard describe("googlechat setup wizard", () => { it("configures service-account auth and webhook audience", async () => { - const prompter = createPrompter({ + const prompter = createTestWizardPrompter({ text: vi.fn(async ({ message }: { message: string }) => { if (message === "Service account JSON path") { return "/tmp/googlechat-service-account.json"; diff --git a/extensions/irc/src/setup-surface.test.ts b/extensions/irc/src/setup-surface.test.ts index 147432b6131..6ac3fb268cc 100644 --- a/extensions/irc/src/setup-surface.test.ts +++ b/extensions/irc/src/setup-surface.test.ts @@ -1,32 +1,11 @@ -import type { RuntimeEnv, WizardPrompter } from "openclaw/plugin-sdk/irc"; +import type { RuntimeEnv } from "openclaw/plugin-sdk/irc"; import { describe, expect, it, vi } from "vitest"; import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js"; import { createRuntimeEnv } from "../../test-utils/runtime-env.js"; +import { createTestWizardPrompter, type WizardPrompter } from "../../test-utils/setup-wizard.js"; import { ircPlugin } from "./channel.js"; import type { CoreConfig } from "./types.js"; -const selectFirstOption = async (params: { options: Array<{ value: T }> }): Promise => { - const first = params.options[0]; - if (!first) { - throw new Error("no options"); - } - return first.value; -}; - -function createPrompter(overrides: Partial): WizardPrompter { - return { - intro: vi.fn(async () => {}), - outro: vi.fn(async () => {}), - note: vi.fn(async () => {}), - select: selectFirstOption as WizardPrompter["select"], - multiselect: vi.fn(async () => []), - text: vi.fn(async () => "") as WizardPrompter["text"], - confirm: vi.fn(async () => false), - progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })), - ...overrides, - }; -} - const ircConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ plugin: ircPlugin, wizard: ircPlugin.setupWizard!, @@ -34,7 +13,7 @@ const ircConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ describe("irc setup wizard", () => { it("configures host and nick via setup prompts", async () => { - const prompter = createPrompter({ + const prompter = createTestWizardPrompter({ text: vi.fn(async ({ message }: { message: string }) => { if (message === "IRC server host") { return "irc.libera.chat"; @@ -93,7 +72,7 @@ describe("irc setup wizard", () => { }); it("writes DM allowFrom to top-level config for non-default account prompts", async () => { - const prompter = createPrompter({ + const prompter = createTestWizardPrompter({ text: vi.fn(async ({ message }: { message: string }) => { if (message === "IRC allowFrom (nick or nick!user@host)") { return "Alice, Bob!ident@example.org"; diff --git a/extensions/line/src/setup-surface.test.ts b/extensions/line/src/setup-surface.test.ts index 3fd98df4b2e..bf4e560e0df 100644 --- a/extensions/line/src/setup-surface.test.ts +++ b/extensions/line/src/setup-surface.test.ts @@ -6,30 +6,10 @@ import { resolveDefaultLineAccountId, resolveLineAccount, } from "../../../src/line/accounts.js"; -import type { WizardPrompter } from "../../../src/wizard/prompts.js"; import { createRuntimeEnv } from "../../test-utils/runtime-env.js"; +import { createTestWizardPrompter, type WizardPrompter } from "../../test-utils/setup-wizard.js"; import { lineSetupAdapter, lineSetupWizard } from "./setup-surface.js"; -function createPrompter(overrides: Partial = {}): WizardPrompter { - return { - intro: vi.fn(async () => {}), - outro: vi.fn(async () => {}), - note: vi.fn(async () => {}), - select: vi.fn(async ({ options }: { options: Array<{ value: string }> }) => { - const first = options[0]; - if (!first) { - throw new Error("no options"); - } - return first.value; - }) as WizardPrompter["select"], - multiselect: vi.fn(async () => []), - text: vi.fn(async () => "") as WizardPrompter["text"], - confirm: vi.fn(async () => false), - progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })), - ...overrides, - }; -} - const lineConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ plugin: { id: "line", @@ -47,7 +27,7 @@ const lineConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ describe("line setup wizard", () => { it("configures token and secret for the default account", async () => { - const prompter = createPrompter({ + const prompter = createTestWizardPrompter({ text: vi.fn(async ({ message }: { message: string }) => { if (message === "Enter LINE channel access token") { return "line-token"; diff --git a/extensions/nostr/src/setup-surface.test.ts b/extensions/nostr/src/setup-surface.test.ts index 0a46946f8f9..2985ff3e513 100644 --- a/extensions/nostr/src/setup-surface.test.ts +++ b/extensions/nostr/src/setup-surface.test.ts @@ -1,30 +1,10 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/nostr"; import { describe, expect, it, vi } from "vitest"; import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js"; -import type { WizardPrompter } from "../../../src/wizard/prompts.js"; import { createRuntimeEnv } from "../../test-utils/runtime-env.js"; +import { createTestWizardPrompter, type WizardPrompter } from "../../test-utils/setup-wizard.js"; import { nostrPlugin } from "./channel.js"; -function createPrompter(overrides: Partial): WizardPrompter { - return { - intro: vi.fn(async () => {}), - outro: vi.fn(async () => {}), - note: vi.fn(async () => {}), - select: vi.fn(async ({ options }: { options: Array<{ value: string }> }) => { - const first = options[0]; - if (!first) { - throw new Error("no options"); - } - return first.value; - }) as WizardPrompter["select"], - multiselect: vi.fn(async () => []), - text: vi.fn(async () => "") as WizardPrompter["text"], - confirm: vi.fn(async () => false), - progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })), - ...overrides, - }; -} - const nostrConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ plugin: nostrPlugin, wizard: nostrPlugin.setupWizard!, @@ -32,7 +12,7 @@ const nostrConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ describe("nostr setup wizard", () => { it("configures a private key and relay URLs", async () => { - const prompter = createPrompter({ + const prompter = createTestWizardPrompter({ text: vi.fn(async ({ message }: { message: string }) => { if (message === "Nostr private key (nsec... or hex)") { return "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; diff --git a/extensions/synology-chat/src/setup-surface.test.ts b/extensions/synology-chat/src/setup-surface.test.ts index 6c1289a8a84..96c17300e0f 100644 --- a/extensions/synology-chat/src/setup-surface.test.ts +++ b/extensions/synology-chat/src/setup-surface.test.ts @@ -1,31 +1,11 @@ import { describe, expect, it, vi } from "vitest"; import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js"; import type { OpenClawConfig } from "../../../src/config/config.js"; -import type { WizardPrompter } from "../../../src/wizard/prompts.js"; import { createRuntimeEnv } from "../../test-utils/runtime-env.js"; +import { createTestWizardPrompter, type WizardPrompter } from "../../test-utils/setup-wizard.js"; import { synologyChatPlugin } from "./channel.js"; import { synologyChatSetupWizard } from "./setup-surface.js"; -function createPrompter(overrides: Partial = {}): WizardPrompter { - return { - intro: vi.fn(async () => {}), - outro: vi.fn(async () => {}), - note: vi.fn(async () => {}), - select: vi.fn(async ({ options }: { options: Array<{ value: string }> }) => { - const first = options[0]; - if (!first) { - throw new Error("no options"); - } - return first.value; - }) as WizardPrompter["select"], - multiselect: vi.fn(async () => []), - text: vi.fn(async () => "") as WizardPrompter["text"], - confirm: vi.fn(async () => false), - progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })), - ...overrides, - }; -} - const synologyChatConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ plugin: synologyChatPlugin, wizard: synologyChatSetupWizard, @@ -33,7 +13,7 @@ const synologyChatConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWiza describe("synology-chat setup wizard", () => { it("configures token and incoming webhook for the default account", async () => { - const prompter = createPrompter({ + const prompter = createTestWizardPrompter({ text: vi.fn(async ({ message }: { message: string }) => { if (message === "Enter Synology Chat outgoing webhook token") { return "synology-token"; @@ -67,7 +47,7 @@ describe("synology-chat setup wizard", () => { }); it("records allowed user ids when setup forces allowFrom", async () => { - const prompter = createPrompter({ + const prompter = createTestWizardPrompter({ text: vi.fn(async ({ message }: { message: string }) => { if (message === "Enter Synology Chat outgoing webhook token") { return "synology-token"; diff --git a/extensions/test-utils/setup-wizard.ts b/extensions/test-utils/setup-wizard.ts new file mode 100644 index 00000000000..aab15a4aecc --- /dev/null +++ b/extensions/test-utils/setup-wizard.ts @@ -0,0 +1,28 @@ +import { vi } from "vitest"; +import type { WizardPrompter } from "../../src/wizard/prompts.js"; + +export type { WizardPrompter } from "../../src/wizard/prompts.js"; + +export async function selectFirstWizardOption(params: { + options: Array<{ value: T }>; +}): Promise { + const first = params.options[0]; + if (!first) { + throw new Error("no options"); + } + return first.value; +} + +export function createTestWizardPrompter(overrides: Partial = {}): WizardPrompter { + return { + intro: vi.fn(async () => {}), + outro: vi.fn(async () => {}), + note: vi.fn(async () => {}), + select: selectFirstWizardOption as WizardPrompter["select"], + multiselect: vi.fn(async () => []), + text: vi.fn(async () => "") as WizardPrompter["text"], + confirm: vi.fn(async () => false), + progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })), + ...overrides, + }; +} diff --git a/extensions/tlon/src/setup-surface.test.ts b/extensions/tlon/src/setup-surface.test.ts index d54db2c75a1..f2b53f0df72 100644 --- a/extensions/tlon/src/setup-surface.test.ts +++ b/extensions/tlon/src/setup-surface.test.ts @@ -1,31 +1,10 @@ -import type { OpenClawConfig, RuntimeEnv, WizardPrompter } from "openclaw/plugin-sdk/tlon"; +import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk/tlon"; import { describe, expect, it, vi } from "vitest"; import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js"; import { createRuntimeEnv } from "../../test-utils/runtime-env.js"; +import { createTestWizardPrompter, type WizardPrompter } from "../../test-utils/setup-wizard.js"; import { tlonPlugin } from "./channel.js"; -const selectFirstOption = async (params: { options: Array<{ value: T }> }): Promise => { - const first = params.options[0]; - if (!first) { - throw new Error("no options"); - } - return first.value; -}; - -function createPrompter(overrides: Partial): WizardPrompter { - return { - intro: vi.fn(async () => {}), - outro: vi.fn(async () => {}), - note: vi.fn(async () => {}), - select: selectFirstOption as WizardPrompter["select"], - multiselect: vi.fn(async () => []), - text: vi.fn(async () => "") as WizardPrompter["text"], - confirm: vi.fn(async () => false), - progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })), - ...overrides, - }; -} - const tlonConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ plugin: tlonPlugin, wizard: tlonPlugin.setupWizard!, @@ -33,7 +12,7 @@ const tlonConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ describe("tlon setup wizard", () => { it("configures ship, auth, and discovery settings", async () => { - const prompter = createPrompter({ + const prompter = createTestWizardPrompter({ text: vi.fn(async ({ message }: { message: string }) => { if (message === "Ship name") { return "sampel-palnet"; diff --git a/extensions/zalo/src/setup-surface.test.ts b/extensions/zalo/src/setup-surface.test.ts index f00060b50c6..a6e278b6f69 100644 --- a/extensions/zalo/src/setup-surface.test.ts +++ b/extensions/zalo/src/setup-surface.test.ts @@ -1,23 +1,10 @@ -import type { OpenClawConfig, RuntimeEnv, WizardPrompter } from "openclaw/plugin-sdk/zalo"; +import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk/zalo"; import { describe, expect, it, vi } from "vitest"; import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js"; import { createRuntimeEnv } from "../../test-utils/runtime-env.js"; +import { createTestWizardPrompter, type WizardPrompter } from "../../test-utils/setup-wizard.js"; import { zaloPlugin } from "./channel.js"; -function createPrompter(overrides: Partial): WizardPrompter { - return { - intro: vi.fn(async () => {}), - outro: vi.fn(async () => {}), - note: vi.fn(async () => {}), - select: vi.fn(async () => "plaintext") as WizardPrompter["select"], - multiselect: vi.fn(async () => []), - text: vi.fn(async () => "") as WizardPrompter["text"], - confirm: vi.fn(async () => false), - progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })), - ...overrides, - }; -} - const zaloConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ plugin: zaloPlugin, wizard: zaloPlugin.setupWizard!, @@ -25,7 +12,8 @@ const zaloConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ describe("zalo setup wizard", () => { it("configures a polling token flow", async () => { - const prompter = createPrompter({ + const prompter = createTestWizardPrompter({ + select: vi.fn(async () => "plaintext") as WizardPrompter["select"], text: vi.fn(async ({ message }: { message: string }) => { if (message === "Enter Zalo bot token") { return "12345689:abc-xyz"; diff --git a/extensions/zalouser/src/setup-surface.test.ts b/extensions/zalouser/src/setup-surface.test.ts index fc95b90ab8d..1aa8dd93bd0 100644 --- a/extensions/zalouser/src/setup-surface.test.ts +++ b/extensions/zalouser/src/setup-surface.test.ts @@ -1,7 +1,8 @@ -import type { OpenClawConfig, WizardPrompter } from "openclaw/plugin-sdk/zalouser"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/zalouser"; import { describe, expect, it, vi } from "vitest"; import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js"; import { createRuntimeEnv } from "../../test-utils/runtime-env.js"; +import { createTestWizardPrompter } from "../../test-utils/setup-wizard.js"; vi.mock("./zalo-js.js", async (importOriginal) => { const actual = await importOriginal(); @@ -28,28 +29,6 @@ vi.mock("./zalo-js.js", async (importOriginal) => { import { zalouserPlugin } from "./channel.js"; -const selectFirstOption = async (params: { options: Array<{ value: T }> }): Promise => { - const first = params.options[0]; - if (!first) { - throw new Error("no options"); - } - return first.value; -}; - -function createPrompter(overrides: Partial): WizardPrompter { - return { - intro: vi.fn(async () => {}), - outro: vi.fn(async () => {}), - note: vi.fn(async () => {}), - select: selectFirstOption as WizardPrompter["select"], - multiselect: vi.fn(async () => []), - text: vi.fn(async () => "") as WizardPrompter["text"], - confirm: vi.fn(async () => false), - progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })), - ...overrides, - }; -} - const zalouserConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ plugin: zalouserPlugin, wizard: zalouserPlugin.setupWizard!, @@ -58,7 +37,7 @@ const zalouserConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({ describe("zalouser setup wizard", () => { it("enables the account without forcing QR login", async () => { const runtime = createRuntimeEnv(); - const prompter = createPrompter({ + const prompter = createTestWizardPrompter({ confirm: vi.fn(async ({ message }: { message: string }) => { if (message === "Login via QR code now?") { return false;