mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-23 07:51:33 +00:00
refactor(test): dedupe setup wizard test helpers
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js";
|
||||
import type { WizardPrompter } from "../../../src/wizard/prompts.js";
|
||||
import {
|
||||
createTestWizardPrompter,
|
||||
runSetupWizardConfigure,
|
||||
type WizardPrompter,
|
||||
} from "../../../test/helpers/extensions/setup-wizard.js";
|
||||
import { resolveBlueBubblesAccount } from "./accounts.js";
|
||||
import { DEFAULT_WEBHOOK_PATH } from "./webhook-shared.js";
|
||||
|
||||
@@ -34,10 +38,19 @@ async function createBlueBubblesConfigureAdapter() {
|
||||
});
|
||||
}
|
||||
|
||||
async function runBlueBubblesConfigure(params: { cfg: unknown; prompter: WizardPrompter }) {
|
||||
const adapter = await createBlueBubblesConfigureAdapter();
|
||||
type ConfigureContext = Parameters<NonNullable<typeof adapter.configure>>[0];
|
||||
return await runSetupWizardConfigure({
|
||||
configure: adapter.configure,
|
||||
cfg: params.cfg as ConfigureContext["cfg"],
|
||||
runtime: { ...console, exit: vi.fn() } as ConfigureContext["runtime"],
|
||||
prompter: params.prompter,
|
||||
});
|
||||
}
|
||||
|
||||
describe("bluebubbles setup surface", () => {
|
||||
it("preserves existing password SecretRef and keeps default webhook path", async () => {
|
||||
const adapter = await createBlueBubblesConfigureAdapter();
|
||||
type ConfigureContext = Parameters<NonNullable<typeof adapter.configure>>[0];
|
||||
const passwordRef = { source: "env", provider: "default", id: "BLUEBUBBLES_PASSWORD" };
|
||||
const confirm = vi
|
||||
.fn()
|
||||
@@ -45,10 +58,8 @@ describe("bluebubbles setup surface", () => {
|
||||
.mockResolvedValueOnce(true)
|
||||
.mockResolvedValueOnce(true);
|
||||
const text = vi.fn();
|
||||
const note = vi.fn();
|
||||
|
||||
const prompter = { confirm, text, note } as unknown as WizardPrompter;
|
||||
const context = {
|
||||
const result = await runBlueBubblesConfigure({
|
||||
cfg: {
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
@@ -58,14 +69,8 @@ describe("bluebubbles setup surface", () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
prompter,
|
||||
runtime: { ...console, exit: vi.fn() } as ConfigureContext["runtime"],
|
||||
forceAllowFrom: false,
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
} satisfies ConfigureContext;
|
||||
|
||||
const result = await adapter.configure(context);
|
||||
prompter: createTestWizardPrompter({ confirm, text }),
|
||||
});
|
||||
|
||||
expect(result.cfg.channels?.bluebubbles?.password).toEqual(passwordRef);
|
||||
expect(result.cfg.channels?.bluebubbles?.webhookPath).toBe(DEFAULT_WEBHOOK_PATH);
|
||||
@@ -73,18 +78,14 @@ describe("bluebubbles setup surface", () => {
|
||||
});
|
||||
|
||||
it("applies a custom webhook path when requested", async () => {
|
||||
const adapter = await createBlueBubblesConfigureAdapter();
|
||||
type ConfigureContext = Parameters<NonNullable<typeof adapter.configure>>[0];
|
||||
const confirm = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(true)
|
||||
.mockResolvedValueOnce(true)
|
||||
.mockResolvedValueOnce(true);
|
||||
const text = vi.fn().mockResolvedValueOnce("/custom-bluebubbles");
|
||||
const note = vi.fn();
|
||||
|
||||
const prompter = { confirm, text, note } as unknown as WizardPrompter;
|
||||
const context = {
|
||||
const result = await runBlueBubblesConfigure({
|
||||
cfg: {
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
@@ -94,14 +95,8 @@ describe("bluebubbles setup surface", () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
prompter,
|
||||
runtime: { ...console, exit: vi.fn() } as ConfigureContext["runtime"],
|
||||
forceAllowFrom: false,
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
} satisfies ConfigureContext;
|
||||
|
||||
const result = await adapter.configure(context);
|
||||
prompter: createTestWizardPrompter({ confirm, text }),
|
||||
});
|
||||
|
||||
expect(result.cfg.channels?.bluebubbles?.webhookPath).toBe("/custom-bluebubbles");
|
||||
expect(text).toHaveBeenCalledWith(
|
||||
@@ -113,23 +108,13 @@ describe("bluebubbles setup surface", () => {
|
||||
});
|
||||
|
||||
it("validates server URLs before accepting input", async () => {
|
||||
const adapter = await createBlueBubblesConfigureAdapter();
|
||||
type ConfigureContext = Parameters<NonNullable<typeof adapter.configure>>[0];
|
||||
const confirm = vi.fn().mockResolvedValueOnce(false);
|
||||
const text = vi.fn().mockResolvedValueOnce("127.0.0.1:1234").mockResolvedValueOnce("secret");
|
||||
const note = vi.fn();
|
||||
|
||||
const prompter = { confirm, text, note } as unknown as WizardPrompter;
|
||||
const context = {
|
||||
await runBlueBubblesConfigure({
|
||||
cfg: { channels: { bluebubbles: {} } },
|
||||
prompter,
|
||||
runtime: { ...console, exit: vi.fn() } as ConfigureContext["runtime"],
|
||||
forceAllowFrom: false,
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
} satisfies ConfigureContext;
|
||||
|
||||
await adapter.configure(context);
|
||||
prompter: createTestWizardPrompter({ confirm, text }),
|
||||
});
|
||||
|
||||
const serverUrlPrompt = text.mock.calls[0]?.[0] as {
|
||||
validate?: (value: string) => string | undefined;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/extensions/runtime-env.js";
|
||||
import {
|
||||
createPluginSetupWizardAdapter,
|
||||
createTestWizardPrompter,
|
||||
runSetupWizardConfigure,
|
||||
} from "../../../test/helpers/extensions/setup-wizard.js";
|
||||
|
||||
vi.mock("./probe.js", () => ({
|
||||
probeFeishu: vi.fn(async () => ({ ok: false, error: "mocked" })),
|
||||
@@ -7,13 +12,6 @@ vi.mock("./probe.js", () => ({
|
||||
|
||||
import { feishuPlugin } from "./channel.js";
|
||||
|
||||
const baseConfigureContext = {
|
||||
runtime: {} as never,
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: false,
|
||||
};
|
||||
|
||||
const baseStatusContext = {
|
||||
accountOverrides: {},
|
||||
};
|
||||
@@ -56,10 +54,7 @@ async function getStatusWithEnvRefs(params: { appIdKey: string; appSecretKey: st
|
||||
});
|
||||
}
|
||||
|
||||
const feishuConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({
|
||||
plugin: feishuPlugin,
|
||||
wizard: feishuPlugin.setupWizard!,
|
||||
});
|
||||
const feishuConfigureAdapter = createPluginSetupWizardAdapter(feishuPlugin);
|
||||
|
||||
describe("feishu setup wizard", () => {
|
||||
it("does not throw when config appId/appSecret are SecretRef objects", async () => {
|
||||
@@ -68,18 +63,17 @@ describe("feishu setup wizard", () => {
|
||||
.mockResolvedValueOnce("cli_from_prompt")
|
||||
.mockResolvedValueOnce("secret_from_prompt")
|
||||
.mockResolvedValueOnce("oc_group_1");
|
||||
|
||||
const prompter = {
|
||||
note: vi.fn(async () => undefined),
|
||||
const prompter = createTestWizardPrompter({
|
||||
text,
|
||||
confirm: vi.fn(async () => true),
|
||||
select: vi.fn(
|
||||
async ({ initialValue }: { initialValue?: string }) => initialValue ?? "allowlist",
|
||||
),
|
||||
} as never;
|
||||
) as never,
|
||||
});
|
||||
|
||||
await expect(
|
||||
feishuConfigureAdapter.configure({
|
||||
runSetupWizardConfigure({
|
||||
configure: feishuConfigureAdapter.configure,
|
||||
cfg: {
|
||||
channels: {
|
||||
feishu: {
|
||||
@@ -89,7 +83,7 @@ describe("feishu setup wizard", () => {
|
||||
},
|
||||
} as never,
|
||||
prompter,
|
||||
...baseConfigureContext,
|
||||
runtime: createRuntimeEnv({ throwOnExit: false }) as never,
|
||||
}),
|
||||
).resolves.toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/extensions/runtime-env.js";
|
||||
import {
|
||||
createPluginSetupWizardAdapter,
|
||||
createTestWizardPrompter,
|
||||
runSetupWizardConfigure,
|
||||
type WizardPrompter,
|
||||
} from "../../../test/helpers/extensions/setup-wizard.js";
|
||||
import type { OpenClawConfig } from "../runtime-api.js";
|
||||
import { googlechatPlugin } from "./channel.js";
|
||||
|
||||
const googlechatConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({
|
||||
plugin: googlechatPlugin,
|
||||
wizard: googlechatPlugin.setupWizard!,
|
||||
});
|
||||
const googlechatConfigureAdapter = createPluginSetupWizardAdapter(googlechatPlugin);
|
||||
|
||||
describe("googlechat setup wizard", () => {
|
||||
it("configures service-account auth and webhook audience", async () => {
|
||||
@@ -27,16 +24,11 @@ describe("googlechat setup wizard", () => {
|
||||
}) as WizardPrompter["text"],
|
||||
});
|
||||
|
||||
const runtime = createRuntimeEnv();
|
||||
|
||||
const result = await googlechatConfigureAdapter.configure({
|
||||
const result = await runSetupWizardConfigure({
|
||||
configure: googlechatConfigureAdapter.configure,
|
||||
cfg: {} as OpenClawConfig,
|
||||
runtime,
|
||||
prompter,
|
||||
options: {},
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: false,
|
||||
});
|
||||
|
||||
expect(result.accountId).toBe("default");
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/extensions/runtime-env.js";
|
||||
import {
|
||||
createPluginSetupWizardAdapter,
|
||||
createTestWizardPrompter,
|
||||
runSetupWizardConfigure,
|
||||
type WizardPrompter,
|
||||
} from "../../../test/helpers/extensions/setup-wizard.js";
|
||||
import { ircPlugin } from "./channel.js";
|
||||
import type { RuntimeEnv } from "./runtime-api.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
|
||||
const ircConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({
|
||||
plugin: ircPlugin,
|
||||
wizard: ircPlugin.setupWizard!,
|
||||
});
|
||||
const ircConfigureAdapter = createPluginSetupWizardAdapter(ircPlugin);
|
||||
|
||||
describe("irc setup wizard", () => {
|
||||
it("configures host and nick via setup prompts", async () => {
|
||||
@@ -52,16 +48,11 @@ describe("irc setup wizard", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
const runtime: RuntimeEnv = createRuntimeEnv();
|
||||
|
||||
const result = await ircConfigureAdapter.configure({
|
||||
const result = await runSetupWizardConfigure({
|
||||
configure: ircConfigureAdapter.configure,
|
||||
cfg: {} as CoreConfig,
|
||||
runtime,
|
||||
prompter,
|
||||
options: {},
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: false,
|
||||
});
|
||||
|
||||
expect(result.accountId).toBe("default");
|
||||
|
||||
@@ -5,9 +5,9 @@ import {
|
||||
resolveDefaultLineAccountId,
|
||||
resolveLineAccount,
|
||||
} from "../../../src/line/accounts.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/extensions/runtime-env.js";
|
||||
import {
|
||||
createTestWizardPrompter,
|
||||
runSetupWizardConfigure,
|
||||
type WizardPrompter,
|
||||
} from "../../../test/helpers/extensions/setup-wizard.js";
|
||||
import type { OpenClawConfig } from "../api.js";
|
||||
@@ -42,14 +42,11 @@ describe("line setup wizard", () => {
|
||||
}) as WizardPrompter["text"],
|
||||
});
|
||||
|
||||
const result = await lineConfigureAdapter.configure({
|
||||
const result = await runSetupWizardConfigure({
|
||||
configure: lineConfigureAdapter.configure,
|
||||
cfg: {} as OpenClawConfig,
|
||||
runtime: createRuntimeEnv(),
|
||||
prompter,
|
||||
options: {},
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: false,
|
||||
});
|
||||
|
||||
expect(result.accountId).toBe("default");
|
||||
|
||||
@@ -62,6 +62,12 @@ function createDeferred() {
|
||||
describe("FileBackedMatrixSyncStore", () => {
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
function createStoragePath(): string {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-matrix-sync-store-"));
|
||||
tempDirs.push(tempDir);
|
||||
return path.join(tempDir, "bot-storage.json");
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.useRealTimers();
|
||||
@@ -71,9 +77,7 @@ describe("FileBackedMatrixSyncStore", () => {
|
||||
});
|
||||
|
||||
it("persists sync data so restart resumes from the saved cursor", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-matrix-sync-store-"));
|
||||
tempDirs.push(tempDir);
|
||||
const storagePath = path.join(tempDir, "bot-storage.json");
|
||||
const storagePath = createStoragePath();
|
||||
|
||||
const firstStore = new FileBackedMatrixSyncStore(storagePath);
|
||||
expect(firstStore.hasSavedSync()).toBe(false);
|
||||
@@ -97,9 +101,7 @@ describe("FileBackedMatrixSyncStore", () => {
|
||||
});
|
||||
|
||||
it("only treats sync state as restart-safe after a clean shutdown persist", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-matrix-sync-store-"));
|
||||
tempDirs.push(tempDir);
|
||||
const storagePath = path.join(tempDir, "bot-storage.json");
|
||||
const storagePath = createStoragePath();
|
||||
|
||||
const firstStore = new FileBackedMatrixSyncStore(storagePath);
|
||||
await firstStore.setSyncData(createSyncResponse("s123"));
|
||||
@@ -118,9 +120,7 @@ describe("FileBackedMatrixSyncStore", () => {
|
||||
});
|
||||
|
||||
it("clears the clean-shutdown marker once fresh sync data arrives", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-matrix-sync-store-"));
|
||||
tempDirs.push(tempDir);
|
||||
const storagePath = path.join(tempDir, "bot-storage.json");
|
||||
const storagePath = createStoragePath();
|
||||
|
||||
const firstStore = new FileBackedMatrixSyncStore(storagePath);
|
||||
await firstStore.setSyncData(createSyncResponse("s123"));
|
||||
@@ -141,9 +141,7 @@ describe("FileBackedMatrixSyncStore", () => {
|
||||
|
||||
it("coalesces background persistence until the debounce window elapses", async () => {
|
||||
vi.useFakeTimers();
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-matrix-sync-store-"));
|
||||
tempDirs.push(tempDir);
|
||||
const storagePath = path.join(tempDir, "bot-storage.json");
|
||||
const storagePath = createStoragePath();
|
||||
const writeSpy = vi.spyOn(jsonFiles, "writeJsonAtomic").mockResolvedValue();
|
||||
|
||||
const store = new FileBackedMatrixSyncStore(storagePath);
|
||||
@@ -174,9 +172,7 @@ describe("FileBackedMatrixSyncStore", () => {
|
||||
|
||||
it("waits for an in-flight persist when shutdown flush runs", async () => {
|
||||
vi.useFakeTimers();
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-matrix-sync-store-"));
|
||||
tempDirs.push(tempDir);
|
||||
const storagePath = path.join(tempDir, "bot-storage.json");
|
||||
const storagePath = createStoragePath();
|
||||
const writeDeferred = createDeferred();
|
||||
const writeSpy = vi
|
||||
.spyOn(jsonFiles, "writeJsonAtomic")
|
||||
@@ -201,9 +197,7 @@ describe("FileBackedMatrixSyncStore", () => {
|
||||
});
|
||||
|
||||
it("persists client options alongside sync state", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-matrix-sync-store-"));
|
||||
tempDirs.push(tempDir);
|
||||
const storagePath = path.join(tempDir, "bot-storage.json");
|
||||
const storagePath = createStoragePath();
|
||||
|
||||
const firstStore = new FileBackedMatrixSyncStore(storagePath);
|
||||
await firstStore.storeClientOptions({ lazyLoadMembers: true });
|
||||
@@ -214,9 +208,7 @@ describe("FileBackedMatrixSyncStore", () => {
|
||||
});
|
||||
|
||||
it("loads legacy raw sync payloads from bot-storage.json", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-matrix-sync-store-"));
|
||||
tempDirs.push(tempDir);
|
||||
const storagePath = path.join(tempDir, "bot-storage.json");
|
||||
const storagePath = createStoragePath();
|
||||
|
||||
fs.writeFileSync(
|
||||
storagePath,
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/extensions/runtime-env.js";
|
||||
import {
|
||||
createPluginSetupWizardAdapter,
|
||||
createTestWizardPrompter,
|
||||
runSetupWizardConfigure,
|
||||
type WizardPrompter,
|
||||
} from "../../../test/helpers/extensions/setup-wizard.js";
|
||||
import type { OpenClawConfig } from "../runtime-api.js";
|
||||
import { nostrPlugin } from "./channel.js";
|
||||
|
||||
const nostrConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({
|
||||
plugin: nostrPlugin,
|
||||
wizard: nostrPlugin.setupWizard!,
|
||||
});
|
||||
const nostrConfigureAdapter = createPluginSetupWizardAdapter(nostrPlugin);
|
||||
|
||||
describe("nostr setup wizard", () => {
|
||||
it("configures a private key and relay URLs", async () => {
|
||||
@@ -27,14 +24,11 @@ describe("nostr setup wizard", () => {
|
||||
}) as WizardPrompter["text"],
|
||||
});
|
||||
|
||||
const result = await nostrConfigureAdapter.configure({
|
||||
const result = await runSetupWizardConfigure({
|
||||
configure: nostrConfigureAdapter.configure,
|
||||
cfg: {} as OpenClawConfig,
|
||||
runtime: createRuntimeEnv(),
|
||||
prompter,
|
||||
options: {},
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: false,
|
||||
});
|
||||
|
||||
expect(result.accountId).toBe("default");
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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 { createRuntimeEnv } from "../../../test/helpers/extensions/runtime-env.js";
|
||||
import {
|
||||
createTestWizardPrompter,
|
||||
runSetupWizardConfigure,
|
||||
type WizardPrompter,
|
||||
} from "../../../test/helpers/extensions/setup-wizard.js";
|
||||
import { synologyChatPlugin } from "./channel.js";
|
||||
@@ -31,14 +31,11 @@ describe("synology-chat setup wizard", () => {
|
||||
}) as WizardPrompter["text"],
|
||||
});
|
||||
|
||||
const result = await synologyChatConfigureAdapter.configure({
|
||||
const result = await runSetupWizardConfigure({
|
||||
configure: synologyChatConfigureAdapter.configure,
|
||||
cfg: {} as OpenClawConfig,
|
||||
runtime: createRuntimeEnv(),
|
||||
prompter,
|
||||
options: {},
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: false,
|
||||
});
|
||||
|
||||
expect(result.accountId).toBe("default");
|
||||
@@ -68,13 +65,11 @@ describe("synology-chat setup wizard", () => {
|
||||
}) as WizardPrompter["text"],
|
||||
});
|
||||
|
||||
const result = await synologyChatConfigureAdapter.configure({
|
||||
const result = await runSetupWizardConfigure({
|
||||
configure: synologyChatConfigureAdapter.configure,
|
||||
cfg: {} as OpenClawConfig,
|
||||
runtime: createRuntimeEnv(),
|
||||
prompter,
|
||||
options: {},
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/extensions/runtime-env.js";
|
||||
import {
|
||||
createPluginSetupWizardAdapter,
|
||||
createTestWizardPrompter,
|
||||
runSetupWizardConfigure,
|
||||
type WizardPrompter,
|
||||
} from "../../../test/helpers/extensions/setup-wizard.js";
|
||||
import type { OpenClawConfig, RuntimeEnv } from "../api.js";
|
||||
import type { OpenClawConfig } from "../api.js";
|
||||
import { tlonPlugin } from "./channel.js";
|
||||
|
||||
const tlonConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({
|
||||
plugin: tlonPlugin,
|
||||
wizard: tlonPlugin.setupWizard!,
|
||||
});
|
||||
const tlonConfigureAdapter = createPluginSetupWizardAdapter(tlonPlugin);
|
||||
|
||||
describe("tlon setup wizard", () => {
|
||||
it("configures ship, auth, and discovery settings", async () => {
|
||||
@@ -48,16 +45,11 @@ describe("tlon setup wizard", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
const runtime: RuntimeEnv = createRuntimeEnv();
|
||||
|
||||
const result = await tlonConfigureAdapter.configure({
|
||||
const result = await runSetupWizardConfigure({
|
||||
configure: tlonConfigureAdapter.configure,
|
||||
cfg: {} as OpenClawConfig,
|
||||
runtime,
|
||||
prompter,
|
||||
options: {},
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: false,
|
||||
});
|
||||
|
||||
expect(result.accountId).toBe("default");
|
||||
|
||||
@@ -2,7 +2,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../../../src/runtime.js";
|
||||
import type { WizardPrompter } from "../../../src/wizard/prompts.js";
|
||||
import {
|
||||
createQueuedWizardPrompter,
|
||||
runSetupWizardConfigure,
|
||||
} from "../../../test/helpers/extensions/setup-wizard.js";
|
||||
import { whatsappPlugin } from "./channel.js";
|
||||
|
||||
const loginWebMock = vi.hoisted(() => vi.fn(async () => {}));
|
||||
@@ -34,49 +37,6 @@ vi.mock("./accounts.js", () => ({
|
||||
resolveWhatsAppAuthDir: resolveWhatsAppAuthDirMock,
|
||||
}));
|
||||
|
||||
function createPrompterHarness(params?: {
|
||||
selectValues?: string[];
|
||||
textValues?: string[];
|
||||
confirmValues?: boolean[];
|
||||
}) {
|
||||
const selectValues = [...(params?.selectValues ?? [])];
|
||||
const textValues = [...(params?.textValues ?? [])];
|
||||
const confirmValues = [...(params?.confirmValues ?? [])];
|
||||
|
||||
const intro = vi.fn(async () => undefined);
|
||||
const outro = vi.fn(async () => undefined);
|
||||
const note = vi.fn(async () => undefined);
|
||||
const select = vi.fn(async () => selectValues.shift() ?? "");
|
||||
const multiselect = vi.fn(async () => [] as string[]);
|
||||
const text = vi.fn(async () => textValues.shift() ?? "");
|
||||
const confirm = vi.fn(async () => confirmValues.shift() ?? false);
|
||||
const progress = vi.fn(() => ({
|
||||
update: vi.fn(),
|
||||
stop: vi.fn(),
|
||||
}));
|
||||
|
||||
return {
|
||||
intro,
|
||||
outro,
|
||||
note,
|
||||
select,
|
||||
multiselect,
|
||||
text,
|
||||
confirm,
|
||||
progress,
|
||||
prompter: {
|
||||
intro,
|
||||
outro,
|
||||
note,
|
||||
select,
|
||||
multiselect,
|
||||
text,
|
||||
confirm,
|
||||
progress,
|
||||
} as WizardPrompter,
|
||||
};
|
||||
}
|
||||
|
||||
function createRuntime(): RuntimeEnv {
|
||||
return {
|
||||
error: vi.fn(),
|
||||
@@ -89,7 +49,7 @@ const whatsappConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({
|
||||
});
|
||||
|
||||
async function runConfigureWithHarness(params: {
|
||||
harness: ReturnType<typeof createPrompterHarness>;
|
||||
harness: ReturnType<typeof createQueuedWizardPrompter>;
|
||||
cfg?: Parameters<typeof whatsappConfigureAdapter.configure>[0]["cfg"];
|
||||
runtime?: RuntimeEnv;
|
||||
options?: Parameters<typeof whatsappConfigureAdapter.configure>[0]["options"];
|
||||
@@ -97,7 +57,8 @@ async function runConfigureWithHarness(params: {
|
||||
shouldPromptAccountIds?: boolean;
|
||||
forceAllowFrom?: boolean;
|
||||
}) {
|
||||
return await whatsappConfigureAdapter.configure({
|
||||
return await runSetupWizardConfigure({
|
||||
configure: whatsappConfigureAdapter.configure,
|
||||
cfg: params.cfg ?? {},
|
||||
runtime: params.runtime ?? createRuntime(),
|
||||
prompter: params.harness.prompter,
|
||||
@@ -109,7 +70,7 @@ async function runConfigureWithHarness(params: {
|
||||
}
|
||||
|
||||
function createSeparatePhoneHarness(params: { selectValues: string[]; textValues?: string[] }) {
|
||||
return createPrompterHarness({
|
||||
return createQueuedWizardPrompter({
|
||||
confirmValues: [false],
|
||||
selectValues: params.selectValues,
|
||||
textValues: params.textValues,
|
||||
@@ -138,7 +99,7 @@ describe("whatsapp setup wizard", () => {
|
||||
});
|
||||
|
||||
it("applies owner allowlist when forceAllowFrom is enabled", async () => {
|
||||
const harness = createPrompterHarness({
|
||||
const harness = createQueuedWizardPrompter({
|
||||
confirmValues: [false],
|
||||
textValues: ["+1 (555) 555-0123"],
|
||||
});
|
||||
@@ -184,7 +145,7 @@ describe("whatsapp setup wizard", () => {
|
||||
|
||||
it("enables allowlist self-chat mode for personal-phone setup", async () => {
|
||||
pathExistsMock.mockResolvedValue(true);
|
||||
const harness = createPrompterHarness({
|
||||
const harness = createQueuedWizardPrompter({
|
||||
confirmValues: [false],
|
||||
selectValues: ["personal"],
|
||||
textValues: ["+1 (555) 111-2222"],
|
||||
@@ -225,7 +186,7 @@ describe("whatsapp setup wizard", () => {
|
||||
|
||||
it("runs WhatsApp login when not linked and user confirms linking", async () => {
|
||||
pathExistsMock.mockResolvedValue(false);
|
||||
const harness = createPrompterHarness({
|
||||
const harness = createQueuedWizardPrompter({
|
||||
confirmValues: [true],
|
||||
selectValues: ["separate", "disabled"],
|
||||
});
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/extensions/runtime-env.js";
|
||||
import {
|
||||
createPluginSetupWizardAdapter,
|
||||
createTestWizardPrompter,
|
||||
runSetupWizardConfigure,
|
||||
type WizardPrompter,
|
||||
} from "../../../test/helpers/extensions/setup-wizard.js";
|
||||
import type { OpenClawConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import type { OpenClawConfig } from "../runtime-api.js";
|
||||
import { zaloPlugin } from "./channel.js";
|
||||
|
||||
const zaloConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({
|
||||
plugin: zaloPlugin,
|
||||
wizard: zaloPlugin.setupWizard!,
|
||||
});
|
||||
const zaloConfigureAdapter = createPluginSetupWizardAdapter(zaloPlugin);
|
||||
|
||||
describe("zalo setup wizard", () => {
|
||||
it("configures a polling token flow", async () => {
|
||||
@@ -31,16 +28,11 @@ describe("zalo setup wizard", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
const runtime: RuntimeEnv = createRuntimeEnv();
|
||||
|
||||
const result = await zaloConfigureAdapter.configure({
|
||||
const result = await runSetupWizardConfigure({
|
||||
configure: zaloConfigureAdapter.configure,
|
||||
cfg: {} as OpenClawConfig,
|
||||
runtime,
|
||||
prompter,
|
||||
options: { secretInputMode: "plaintext" },
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: false,
|
||||
options: { secretInputMode: "plaintext" as const },
|
||||
});
|
||||
|
||||
expect(result.accountId).toBe("default");
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/extensions/runtime-env.js";
|
||||
import { createTestWizardPrompter } from "../../../test/helpers/extensions/setup-wizard.js";
|
||||
import {
|
||||
createPluginSetupWizardAdapter,
|
||||
createTestWizardPrompter,
|
||||
runSetupWizardConfigure,
|
||||
} from "../../../test/helpers/extensions/setup-wizard.js";
|
||||
import type { OpenClawConfig } from "../runtime-api.js";
|
||||
import "./zalo-js.test-mocks.js";
|
||||
import { zalouserPlugin } from "./channel.js";
|
||||
|
||||
const zalouserConfigureAdapter = buildChannelSetupWizardAdapterFromSetupWizard({
|
||||
plugin: zalouserPlugin,
|
||||
wizard: zalouserPlugin.setupWizard!,
|
||||
});
|
||||
const zalouserConfigureAdapter = createPluginSetupWizardAdapter(zalouserPlugin);
|
||||
|
||||
async function runSetup(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
prompter: ReturnType<typeof createTestWizardPrompter>;
|
||||
options?: Record<string, unknown>;
|
||||
forceAllowFrom?: boolean;
|
||||
}) {
|
||||
return await runSetupWizardConfigure({
|
||||
configure: zalouserConfigureAdapter.configure,
|
||||
cfg: params.cfg as OpenClawConfig | undefined,
|
||||
prompter: params.prompter,
|
||||
options: params.options,
|
||||
forceAllowFrom: params.forceAllowFrom,
|
||||
});
|
||||
}
|
||||
|
||||
describe("zalouser setup wizard", () => {
|
||||
it("enables the account without forcing QR login", async () => {
|
||||
const runtime = createRuntimeEnv();
|
||||
const prompter = createTestWizardPrompter({
|
||||
confirm: vi.fn(async ({ message }: { message: string }) => {
|
||||
if (message === "Login via QR code now?") {
|
||||
@@ -26,15 +39,7 @@ describe("zalouser setup wizard", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await zalouserConfigureAdapter.configure({
|
||||
cfg: {} as OpenClawConfig,
|
||||
runtime,
|
||||
prompter,
|
||||
options: {},
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: false,
|
||||
});
|
||||
const result = await runSetup({ prompter });
|
||||
|
||||
expect(result.accountId).toBe("default");
|
||||
expect(result.cfg.channels?.zalouser?.enabled).toBe(true);
|
||||
@@ -42,7 +47,6 @@ describe("zalouser setup wizard", () => {
|
||||
});
|
||||
|
||||
it("prompts DM policy before group access in quickstart", async () => {
|
||||
const runtime = createRuntimeEnv();
|
||||
const seen: string[] = [];
|
||||
const prompter = createTestWizardPrompter({
|
||||
confirm: vi.fn(async ({ message }: { message: string }) => {
|
||||
@@ -70,14 +74,9 @@ describe("zalouser setup wizard", () => {
|
||||
) as ReturnType<typeof createTestWizardPrompter>["select"],
|
||||
});
|
||||
|
||||
const result = await zalouserConfigureAdapter.configure({
|
||||
cfg: {} as OpenClawConfig,
|
||||
runtime,
|
||||
const result = await runSetup({
|
||||
prompter,
|
||||
options: { quickstartDefaults: true },
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: false,
|
||||
});
|
||||
|
||||
expect(result.accountId).toBe("default");
|
||||
@@ -92,7 +91,6 @@ describe("zalouser setup wizard", () => {
|
||||
});
|
||||
|
||||
it("allows an empty quickstart DM allowlist with a warning", async () => {
|
||||
const runtime = createRuntimeEnv();
|
||||
const note = vi.fn(async (_message: string, _title?: string) => {});
|
||||
const prompter = createTestWizardPrompter({
|
||||
note,
|
||||
@@ -125,14 +123,9 @@ describe("zalouser setup wizard", () => {
|
||||
}) as ReturnType<typeof createTestWizardPrompter>["text"],
|
||||
});
|
||||
|
||||
const result = await zalouserConfigureAdapter.configure({
|
||||
cfg: {} as OpenClawConfig,
|
||||
runtime,
|
||||
const result = await runSetup({
|
||||
prompter,
|
||||
options: { quickstartDefaults: true },
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: false,
|
||||
});
|
||||
|
||||
expect(result.accountId).toBe("default");
|
||||
@@ -148,7 +141,6 @@ describe("zalouser setup wizard", () => {
|
||||
});
|
||||
|
||||
it("allows an empty group allowlist with a warning", async () => {
|
||||
const runtime = createRuntimeEnv();
|
||||
const note = vi.fn(async (_message: string, _title?: string) => {});
|
||||
const prompter = createTestWizardPrompter({
|
||||
note,
|
||||
@@ -181,15 +173,7 @@ describe("zalouser setup wizard", () => {
|
||||
}) as ReturnType<typeof createTestWizardPrompter>["text"],
|
||||
});
|
||||
|
||||
const result = await zalouserConfigureAdapter.configure({
|
||||
cfg: {} as OpenClawConfig,
|
||||
runtime,
|
||||
prompter,
|
||||
options: {},
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: false,
|
||||
});
|
||||
const result = await runSetup({ prompter });
|
||||
|
||||
expect(result.cfg.channels?.zalouser?.groupPolicy).toBe("allowlist");
|
||||
expect(result.cfg.channels?.zalouser?.groups).toEqual({});
|
||||
@@ -201,7 +185,6 @@ describe("zalouser setup wizard", () => {
|
||||
});
|
||||
|
||||
it("preserves non-quickstart forceAllowFrom behavior", async () => {
|
||||
const runtime = createRuntimeEnv();
|
||||
const note = vi.fn(async (_message: string, _title?: string) => {});
|
||||
const seen: string[] = [];
|
||||
const prompter = createTestWizardPrompter({
|
||||
@@ -225,15 +208,7 @@ describe("zalouser setup wizard", () => {
|
||||
}) as ReturnType<typeof createTestWizardPrompter>["text"],
|
||||
});
|
||||
|
||||
const result = await zalouserConfigureAdapter.configure({
|
||||
cfg: {} as OpenClawConfig,
|
||||
runtime,
|
||||
prompter,
|
||||
options: {},
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: true,
|
||||
});
|
||||
const result = await runSetup({ prompter, forceAllowFrom: true });
|
||||
|
||||
expect(result.cfg.channels?.zalouser?.dmPolicy).toBe("allowlist");
|
||||
expect(result.cfg.channels?.zalouser?.allowFrom).toEqual([]);
|
||||
@@ -247,7 +222,6 @@ describe("zalouser setup wizard", () => {
|
||||
});
|
||||
|
||||
it("allowlists the plugin when a plugin allowlist already exists", async () => {
|
||||
const runtime = createRuntimeEnv();
|
||||
const prompter = createTestWizardPrompter({
|
||||
confirm: vi.fn(async ({ message }: { message: string }) => {
|
||||
if (message === "Login via QR code now?") {
|
||||
@@ -260,18 +234,13 @@ describe("zalouser setup wizard", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await zalouserConfigureAdapter.configure({
|
||||
const result = await runSetup({
|
||||
cfg: {
|
||||
plugins: {
|
||||
allow: ["telegram"],
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
runtime,
|
||||
prompter,
|
||||
options: {},
|
||||
accountOverrides: {},
|
||||
shouldPromptAccountIds: false,
|
||||
forceAllowFrom: false,
|
||||
});
|
||||
|
||||
expect(result.cfg.plugins?.entries?.zalouser?.enabled).toBe(true);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { vi } from "vitest";
|
||||
import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/channels/plugins/setup-wizard.js";
|
||||
import type { WizardPrompter } from "../../../src/wizard/prompts.js";
|
||||
import { createRuntimeEnv } from "./runtime-env.js";
|
||||
|
||||
export type { WizardPrompter } from "../../../src/wizard/prompts.js";
|
||||
|
||||
@@ -26,3 +28,98 @@ export function createTestWizardPrompter(overrides: Partial<WizardPrompter> = {}
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function createQueuedWizardPrompter(params?: {
|
||||
selectValues?: string[];
|
||||
textValues?: string[];
|
||||
confirmValues?: boolean[];
|
||||
}) {
|
||||
const selectValues = [...(params?.selectValues ?? [])];
|
||||
const textValues = [...(params?.textValues ?? [])];
|
||||
const confirmValues = [...(params?.confirmValues ?? [])];
|
||||
|
||||
const intro = vi.fn(async () => undefined);
|
||||
const outro = vi.fn(async () => undefined);
|
||||
const note = vi.fn(async () => undefined);
|
||||
const select = vi.fn(async () => selectValues.shift() ?? "");
|
||||
const multiselect = vi.fn(async () => [] as string[]);
|
||||
const text = vi.fn(async () => textValues.shift() ?? "");
|
||||
const confirm = vi.fn(async () => confirmValues.shift() ?? false);
|
||||
const progress = vi.fn(() => ({
|
||||
update: vi.fn(),
|
||||
stop: vi.fn(),
|
||||
}));
|
||||
|
||||
return {
|
||||
intro,
|
||||
outro,
|
||||
note,
|
||||
select,
|
||||
multiselect,
|
||||
text,
|
||||
confirm,
|
||||
progress,
|
||||
prompter: createTestWizardPrompter({
|
||||
intro,
|
||||
outro,
|
||||
note,
|
||||
select: select as WizardPrompter["select"],
|
||||
multiselect: multiselect as WizardPrompter["multiselect"],
|
||||
text: text as WizardPrompter["text"],
|
||||
confirm,
|
||||
progress,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
type SetupWizardAdapterParams = Parameters<typeof buildChannelSetupWizardAdapterFromSetupWizard>[0];
|
||||
type SetupWizardPlugin = SetupWizardAdapterParams["plugin"];
|
||||
type SetupWizard = NonNullable<SetupWizardAdapterParams["wizard"]>;
|
||||
|
||||
export function createPluginSetupWizardAdapter<
|
||||
TPlugin extends SetupWizardPlugin & { setupWizard?: SetupWizard },
|
||||
>(plugin: TPlugin) {
|
||||
const wizard = plugin.setupWizard;
|
||||
if (!wizard) {
|
||||
throw new Error(`${plugin.id} is missing setupWizard`);
|
||||
}
|
||||
return buildChannelSetupWizardAdapterFromSetupWizard({
|
||||
plugin,
|
||||
wizard,
|
||||
});
|
||||
}
|
||||
|
||||
export async function runSetupWizardConfigure<
|
||||
TCfg,
|
||||
TOptions extends Record<string, unknown>,
|
||||
TAccountOverrides extends Record<string, string | undefined>,
|
||||
TRuntime,
|
||||
TResult,
|
||||
>(params: {
|
||||
configure: (args: {
|
||||
cfg: TCfg;
|
||||
runtime: TRuntime;
|
||||
prompter: WizardPrompter;
|
||||
options: TOptions;
|
||||
accountOverrides: TAccountOverrides;
|
||||
shouldPromptAccountIds: boolean;
|
||||
forceAllowFrom: boolean;
|
||||
}) => Promise<TResult>;
|
||||
cfg?: TCfg;
|
||||
runtime?: TRuntime;
|
||||
prompter: WizardPrompter;
|
||||
options?: TOptions;
|
||||
accountOverrides?: TAccountOverrides;
|
||||
shouldPromptAccountIds?: boolean;
|
||||
forceAllowFrom?: boolean;
|
||||
}): Promise<TResult> {
|
||||
return await params.configure({
|
||||
cfg: (params.cfg ?? {}) as TCfg,
|
||||
runtime: (params.runtime ?? createRuntimeEnv()) as TRuntime,
|
||||
prompter: params.prompter,
|
||||
options: (params.options ?? {}) as TOptions,
|
||||
accountOverrides: (params.accountOverrides ?? {}) as TAccountOverrides,
|
||||
shouldPromptAccountIds: params.shouldPromptAccountIds ?? false,
|
||||
forceAllowFrom: params.forceAllowFrom ?? false,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user