Skip to content

Commit 0a6e90d

Browse files
test: isolate wizard TUI hatch mocks
1 parent db7384a commit 0a6e90d

2 files changed

Lines changed: 134 additions & 49 deletions

File tree

src/wizard/setup.finalize.test.ts

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,8 @@ describe("finalizeSetupWizard", () => {
319319
beforeEach(() => {
320320
launchTuiCli.mockClear();
321321
restoreTerminalState.mockClear();
322-
probeGatewayReachable.mockClear();
322+
probeGatewayReachable.mockReset();
323+
probeGatewayReachable.mockResolvedValue({ ok: false, detail: "offline" });
323324
waitForGatewayReachable.mockReset();
324325
waitForGatewayReachable.mockResolvedValue({ ok: true });
325326
setupWizardShellCompletion.mockClear();
@@ -427,12 +428,15 @@ describe("finalizeSetupWizard", () => {
427428
};
428429
expect(probeParams.url).toBe("ws://127.0.0.1:18789");
429430
expect(probeParams.password).toBe("resolved-gateway-password"); // pragma: allowlist secret
430-
expect(launchTuiCli).toHaveBeenCalledWith({
431-
local: true,
432-
deliver: false,
433-
message: undefined,
434-
timeoutMs: 300_000,
435-
});
431+
expect(launchTuiCli).toHaveBeenCalledWith(
432+
{
433+
local: true,
434+
deliver: false,
435+
message: undefined,
436+
timeoutMs: 300_000,
437+
},
438+
{},
439+
);
436440
});
437441

438442
it("bounds the bootstrap hatch TUI run timeout", async () => {
@@ -472,12 +476,15 @@ describe("finalizeSetupWizard", () => {
472476
runtime: createRuntime(),
473477
});
474478

475-
expect(launchTuiCli).toHaveBeenCalledWith({
476-
local: true,
477-
deliver: false,
478-
message: "Wake up, my friend!",
479-
timeoutMs: 300_000,
480-
});
479+
expect(launchTuiCli).toHaveBeenCalledWith(
480+
{
481+
local: true,
482+
deliver: false,
483+
message: "Wake up, my friend!",
484+
timeoutMs: 300_000,
485+
},
486+
{},
487+
);
481488
});
482489

483490
it("does not resend the bootstrap hatch message on setup reruns", async () => {
@@ -511,12 +518,15 @@ describe("finalizeSetupWizard", () => {
511518
runtime: createRuntime(),
512519
});
513520

514-
expect(launchTuiCli).toHaveBeenCalledWith({
515-
local: true,
516-
deliver: false,
517-
message: undefined,
518-
timeoutMs: 300_000,
519-
});
521+
expect(launchTuiCli).toHaveBeenCalledWith(
522+
{
523+
local: true,
524+
deliver: false,
525+
message: undefined,
526+
timeoutMs: 300_000,
527+
},
528+
{},
529+
);
520530
});
521531

522532
it("localizes the bootstrap hatch TUI seed message", async () => {
@@ -559,12 +569,15 @@ describe("finalizeSetupWizard", () => {
559569
runtime: createRuntime(),
560570
});
561571

562-
expect(launchTuiCli).toHaveBeenCalledWith({
563-
local: true,
564-
deliver: false,
565-
message: "醒醒,我的朋友!",
566-
timeoutMs: 300_000,
567-
});
572+
expect(launchTuiCli).toHaveBeenCalledWith(
573+
{
574+
local: true,
575+
deliver: false,
576+
message: "醒醒,我的朋友!",
577+
timeoutMs: 300_000,
578+
},
579+
{},
580+
);
568581
} finally {
569582
if (previousLocale === undefined) {
570583
delete process.env.OPENCLAW_LOCALE;

src/wizard/setup.test.ts

Lines changed: 96 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import fs from "node:fs/promises";
33
import os from "node:os";
44
import path from "node:path";
55
import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-model-shared";
6-
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
6+
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
77
import { createWizardPrompter as buildWizardPrompter } from "../../test/helpers/wizard-prompter.js";
88
import { DEFAULT_BOOTSTRAP_FILENAME } from "../agents/workspace.js";
99
import type { PluginCompatibilityNotice } from "../plugins/status.js";
@@ -111,7 +111,11 @@ const detectSetupMigrationSources = vi.hoisted(() => vi.fn(async () => []));
111111
const listSetupMigrationOptions = vi.hoisted(() => vi.fn(async () => []));
112112
const runSetupMigrationImport = vi.hoisted(() => vi.fn(async () => {}));
113113

114-
const setupChannels = vi.hoisted(() => vi.fn(async (cfg) => cfg));
114+
const setupChannels = vi.hoisted(() =>
115+
vi.fn(
116+
async (cfg: unknown, _runtime?: unknown, _prompter?: WizardPrompter, _options?: unknown) => cfg,
117+
),
118+
);
115119
const setupSkills = vi.hoisted(() => vi.fn(async (cfg) => cfg));
116120

117121
function providerPluginStub(
@@ -390,6 +394,61 @@ describe("runSetupWizard", () => {
390394
return dir;
391395
}
392396

397+
beforeEach(() => {
398+
vi.clearAllMocks();
399+
promptAuthChoiceGrouped.mockReset();
400+
promptAuthChoiceGrouped.mockResolvedValue("skip");
401+
applyAuthChoice.mockReset();
402+
applyAuthChoice.mockImplementation(async (args) => ({ config: args.config }));
403+
setupChannels.mockReset();
404+
setupChannels.mockImplementation(async (cfg) => cfg);
405+
setupSkills.mockReset();
406+
setupSkills.mockImplementation(async (cfg) => cfg);
407+
configureGatewayForSetup.mockReset();
408+
configureGatewayForSetup.mockImplementation(async (args) => ({
409+
nextConfig: args.nextConfig,
410+
settings: {
411+
port: args.localPort ?? 18789,
412+
bind: "loopback",
413+
authMode: "token",
414+
gatewayToken: "test-token",
415+
tailscaleMode: "off",
416+
tailscaleResetOnExit: false,
417+
},
418+
}));
419+
readConfigFileSnapshot.mockReset();
420+
readConfigFileSnapshot.mockResolvedValue({
421+
path: "/tmp/.openclaw/openclaw.json",
422+
exists: false,
423+
raw: null,
424+
parsed: {},
425+
resolved: {},
426+
valid: true,
427+
config: {},
428+
issues: [],
429+
warnings: [],
430+
legacyIssues: [],
431+
});
432+
probeGatewayReachable.mockReset();
433+
probeGatewayReachable.mockResolvedValue({ ok: false });
434+
resolvePreferredProviderForAuthChoice.mockReset();
435+
resolvePreferredProviderForAuthChoice.mockResolvedValue("demo-provider");
436+
resolvePluginProvidersRuntime.mockReset();
437+
resolvePluginProvidersRuntime.mockReturnValue([]);
438+
resolveManifestProviderAuthChoice.mockReset();
439+
resolveManifestProviderAuthChoice.mockReturnValue(undefined);
440+
resolvePluginSetupProvider.mockReset();
441+
resolvePluginSetupProvider.mockReturnValue(undefined);
442+
resolveProviderPluginChoice.mockReset();
443+
resolveProviderPluginChoice.mockReturnValue(null);
444+
promptDefaultModel.mockReset();
445+
promptDefaultModel.mockResolvedValue({});
446+
warnIfModelConfigLooksOff.mockReset();
447+
warnIfModelConfigLooksOff.mockResolvedValue(undefined);
448+
buildPluginCompatibilitySnapshotNotices.mockReset();
449+
buildPluginCompatibilitySnapshotNotices.mockReturnValue([]);
450+
});
451+
393452
it("exits successfully after the auto-launched TUI returns", async () => {
394453
const caseDir = await makeCaseDir("tui-success-exit-");
395454
await fs.writeFile(path.join(caseDir, DEFAULT_BOOTSTRAP_FILENAME), "");
@@ -970,22 +1029,24 @@ describe("runSetupWizard", () => {
9701029
const prompter = buildWizardPrompter({ select });
9711030
const runtime = createRuntime({ throwsOnExit: true });
9721031

973-
await runSetupWizard(
974-
{
975-
acceptRisk: true,
976-
flow: "quickstart",
977-
mode: "local",
978-
workspace: workspaceDir,
979-
authChoice: "skip",
980-
skipProviders: true,
981-
skipSkills: true,
982-
skipSearch: true,
983-
skipHealth: true,
984-
installDaemon: false,
985-
},
986-
runtime,
987-
prompter,
988-
);
1032+
await expect(
1033+
runSetupWizard(
1034+
{
1035+
acceptRisk: true,
1036+
flow: "quickstart",
1037+
mode: "local",
1038+
workspace: workspaceDir,
1039+
authChoice: "skip",
1040+
skipProviders: true,
1041+
skipSkills: true,
1042+
skipSearch: true,
1043+
skipHealth: true,
1044+
installDaemon: false,
1045+
},
1046+
runtime,
1047+
prompter,
1048+
),
1049+
).rejects.toThrow("exit:0");
9891050

9901051
expectRecordFields(
9911052
getMockCallArg(runTui, 0, 0, "tui launch"),
@@ -1085,9 +1146,18 @@ describe("runSetupWizard", () => {
10851146
});
10861147

10871148
it("disables back navigation before side-effecting channel setup", async () => {
1088-
setupChannels.mockClear();
1089-
const disableBackNavigation = vi.fn();
1090-
const prompter = buildWizardPrompter({ disableBackNavigation });
1149+
setupChannels.mockImplementationOnce(async (cfg, _runtime, channelPrompter) => {
1150+
if (!channelPrompter) {
1151+
throw new Error("expected channel setup prompter");
1152+
}
1153+
await channelPrompter.select({
1154+
message: "Channel side effect",
1155+
options: [{ value: "continue", label: "Continue" }],
1156+
});
1157+
return cfg;
1158+
});
1159+
const select = vi.fn(async () => "continue") as unknown as WizardPrompter["select"];
1160+
const prompter = buildWizardPrompter({ select });
10911161
const runtime = createRuntime();
10921162

10931163
await runSetupWizard(
@@ -1106,10 +1176,12 @@ describe("runSetupWizard", () => {
11061176
prompter,
11071177
);
11081178

1109-
expect(disableBackNavigation).toHaveBeenCalled();
11101179
expect(setupChannels).toHaveBeenCalledOnce();
1111-
expect(disableBackNavigation.mock.invocationCallOrder[0]).toBeLessThan(
1112-
setupChannels.mock.invocationCallOrder[0] ?? Number.POSITIVE_INFINITY,
1180+
expect(select).toHaveBeenCalledWith(
1181+
expect.objectContaining({
1182+
message: "Channel side effect",
1183+
navigation: { canGoBack: false, canGoForward: false },
1184+
}),
11131185
);
11141186
});
11151187

0 commit comments

Comments
 (0)