test: stabilize gateway wizard e2e flow

This commit is contained in:
Peter Steinberger
2026-04-05 13:05:50 +09:00
parent 4cfb990382
commit 5e0e50b12e
3 changed files with 44 additions and 5 deletions

View File

@@ -103,6 +103,7 @@ describe("gateway e2e", () => {
"OPENCLAW_SKIP_CRON",
"OPENCLAW_SKIP_CANVAS_HOST",
"OPENCLAW_SKIP_BROWSER_CONTROL_SERVER",
"OPENCLAW_TEST_MINIMAL_GATEWAY",
]);
const { baseUrl: openaiBaseUrl, restore } = installOpenAiResponsesMock();
@@ -116,6 +117,7 @@ describe("gateway e2e", () => {
process.env.OPENCLAW_SKIP_CRON = "1";
process.env.OPENCLAW_SKIP_CANVAS_HOST = "1";
process.env.OPENCLAW_SKIP_BROWSER_CONTROL_SERVER = "1";
process.env.OPENCLAW_TEST_MINIMAL_GATEWAY = "1";
const token = nextGatewayId("test-token");
process.env.OPENCLAW_GATEWAY_TOKEN = token;
@@ -309,6 +311,7 @@ module.exports = {
"OPENCLAW_SKIP_CRON",
"OPENCLAW_SKIP_CANVAS_HOST",
"OPENCLAW_SKIP_BROWSER_CONTROL_SERVER",
"OPENCLAW_TEST_MINIMAL_GATEWAY",
]);
process.env.OPENCLAW_SKIP_CHANNELS = "1";
@@ -316,6 +319,7 @@ module.exports = {
process.env.OPENCLAW_SKIP_CRON = "1";
process.env.OPENCLAW_SKIP_CANVAS_HOST = "1";
process.env.OPENCLAW_SKIP_BROWSER_CONTROL_SERVER = "1";
process.env.OPENCLAW_TEST_MINIMAL_GATEWAY = "1";
delete process.env.OPENCLAW_GATEWAY_TOKEN;
const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-wizard-home-"));
@@ -362,22 +366,30 @@ module.exports = {
let next = start;
let didSendToken = false;
const seenSteps: string[] = [];
while (!next.done) {
const step = next.step;
if (!step) {
throw new Error("wizard missing step");
}
seenSteps.push(`${step.type}:${step.id}`);
const value = step.type === "text" ? wizardToken : null;
if (step.type === "text") {
didSendToken = true;
}
next = await client.request("wizard.next", {
sessionId,
answer: { stepId: step.id, value },
});
next = await client.request(
"wizard.next",
{
sessionId,
answer: { stepId: step.id, value },
},
{ timeoutMs: 60_000 },
);
}
expect(didSendToken).toBe(true);
expect(didSendToken, `seenSteps=${seenSteps.join(",")} final=${JSON.stringify(next)}`).toBe(
true,
);
expect(next.status).toBe("done");
const parsed = JSON.parse(await fs.readFile(resolveConfigPath(), "utf8"));

View File

@@ -71,4 +71,23 @@ describe("WizardSession", () => {
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");
});
});

View File

@@ -163,6 +163,7 @@ class WizardSessionPrompter implements WizardPrompter {
export class WizardSession {
private currentStep: WizardStep | null = null;
private stepDeferred: Deferred<WizardStep | null> | null = null;
private pendingTerminalResolution = false;
private answerDeferred = new Map<string, Deferred<unknown>>();
private status: WizardSessionStatus = "running";
private error: string | undefined;
@@ -176,6 +177,10 @@ export class WizardSession {
if (this.currentStep) {
return { done: false, step: this.currentStep, status: this.status };
}
if (this.pendingTerminalResolution) {
this.pendingTerminalResolution = false;
return { done: true, status: this.status, error: this.error };
}
if (this.status !== "running") {
return { done: true, status: this.status, error: this.error };
}
@@ -247,6 +252,9 @@ export class WizardSession {
private resolveStep(step: WizardStep | null) {
if (!this.stepDeferred) {
if (step === null) {
this.pendingTerminalResolution = true;
}
return;
}
const deferred = this.stepDeferred;