Files
moltbot/src/wizard/session.test.ts
ANURAG BHEEMAPPA GNANAMURTHY 727398f41a fix(onboarding): mask token/credential inputs in CLI wizard prompts (#76693)
Summary:
- The PR adds `sensitive` support to wizard text prompts, routes sensitive Clack prompts through `password()`, ...  preserves existing gateway secrets through masked-preview confirms, and adds tests plus a changelog entry.
- Reproducibility: yes. Source inspection shows current main routes onboarding credential entry through visibl ... y provides a concrete Windows PowerShell `openclaw onboard --install-daemon` reproduction with screenshots.

Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.

Validation:
- ClawSweeper review passed for head a3db64c265.
- Required merge gates passed before the squash merge.

Prepared head SHA: a3db64c265
Review: https://github.com/openclaw/openclaw/pull/76693#issuecomment-4366253531

Co-authored-by: anurag-bg-neu <bheemappagnanamurt.a@northeastern.edu>
2026-05-03 14:33:58 +00:00

136 lines
4.0 KiB
TypeScript

import { describe, expect, test } from "vitest";
import { WizardSession } from "./session.js";
function noteRunner() {
return new WizardSession(async (prompter) => {
await prompter.note("Welcome");
const name = await prompter.text({ message: "Name" });
await prompter.note(`Hello ${name}`);
});
}
describe("WizardSession", () => {
test("steps progress in order", async () => {
const session = noteRunner();
const first = await session.next();
expect(first.done).toBe(false);
expect(first.step?.type).toBe("note");
const secondPeek = await session.next();
expect(secondPeek.step?.id).toBe(first.step?.id);
if (!first.step) {
throw new Error("expected first step");
}
await session.answer(first.step.id, null);
const second = await session.next();
expect(second.done).toBe(false);
expect(second.step?.type).toBe("text");
if (!second.step) {
throw new Error("expected second step");
}
await session.answer(second.step.id, "Peter");
const third = await session.next();
expect(third.step?.type).toBe("note");
if (!third.step) {
throw new Error("expected third step");
}
await session.answer(third.step.id, null);
const done = await session.next();
expect(done.done).toBe(true);
expect(done.status).toBe("done");
});
test("plain output is a client note with plain format", async () => {
const session = new WizardSession(async (prompter) => {
await prompter.plain?.('{"ok":true}');
});
const first = await session.next();
expect(first.step).toMatchObject({
type: "note",
message: '{"ok":true}',
format: "plain",
});
if (!first.step) {
throw new Error("expected plain note");
}
await session.answer(first.step.id, null);
const done = await session.next();
expect(done.done).toBe(true);
});
test("invalid answers throw", async () => {
const session = noteRunner();
const first = await session.next();
await expect(session.answer("bad-id", null)).rejects.toThrow(/wizard: no pending step/i);
if (!first.step) {
throw new Error("expected first step");
}
await session.answer(first.step.id, null);
});
test("cancel marks session and unblocks", async () => {
const session = new WizardSession(async (prompter) => {
await prompter.text({ message: "Name" });
});
const step = await session.next();
expect(step.step?.type).toBe("text");
session.cancel();
const done = await session.next();
expect(done.done).toBe(true);
expect(done.status).toBe("cancelled");
});
test("does not lose terminal completion when the last answer finishes the runner immediately", async () => {
const session = new WizardSession(async (prompter) => {
await prompter.text({ message: "Token" });
});
const first = await session.next();
expect(first.step?.type).toBe("text");
if (!first.step) {
throw new Error("expected first step");
}
await session.answer(first.step.id, "ok");
await Promise.resolve();
const done = await session.next();
expect(done.done).toBe(true);
expect(done.status).toBe("done");
});
test("forwards sensitive flag to the emitted text step", async () => {
const session = new WizardSession(async (prompter) => {
await prompter.text({ message: "API key", sensitive: true });
await prompter.text({ message: "Username" });
});
const sensitiveStep = (await session.next()).step;
expect(sensitiveStep?.type).toBe("text");
expect(sensitiveStep?.sensitive).toBe(true);
if (!sensitiveStep) {
throw new Error("expected sensitive step");
}
await session.answer(sensitiveStep.id, "fake-key-aa11");
const plainStep = (await session.next()).step;
expect(plainStep?.type).toBe("text");
expect(plainStep?.sensitive).toBeUndefined();
if (!plainStep) {
throw new Error("expected plain step");
}
await session.answer(plainStep.id, "alice");
});
});