Skip to content

Commit 6fe3daf

Browse files
betegonclaude
andcommitted
fix: update tests for WizardError throws + add 10s timeout guard
Tests now expect runWizard to throw WizardError instead of setting process.exitCode directly. Added a 10s timeout on the describe block to prevent CI from hanging indefinitely if a test accidentally waits for interactive input. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a354a34 commit 6fe3daf

File tree

1 file changed

+20
-27
lines changed

1 file changed

+20
-27
lines changed

test/lib/init/wizard-runner.test.ts

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import * as banner from "../../../src/lib/banner.js";
2727
import * as auth from "../../../src/lib/db/auth.js";
2828
// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference
2929
import * as userDb from "../../../src/lib/db/user.js";
30+
import { WizardError } from "../../../src/lib/errors.js";
3031
// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference
3132
import * as fmt from "../../../src/lib/init/formatters.js";
3233
// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference
@@ -259,7 +260,11 @@ afterEach(() => {
259260

260261
// ── Tests ───────────────────────────────────────────────────────────────────
261262

262-
describe("runWizard", () => {
263+
// Guard against tests that accidentally wait for interactive input.
264+
// If a test hangs for 10s it's almost certainly blocked on stdin, not slow I/O.
265+
const TEST_TIMEOUT_MS = 10_000;
266+
267+
describe("runWizard", { timeout: TEST_TIMEOUT_MS }, () => {
263268
describe("success path", () => {
264269
test("calls formatResult when workflow completes successfully", async () => {
265270
mockStartResult = { status: "success", result: { platform: "React" } };
@@ -273,23 +278,21 @@ describe("runWizard", () => {
273278
});
274279

275280
describe("TTY check", () => {
276-
test("writes error to stderr when not TTY and not --yes", async () => {
281+
test("throws WizardError when not TTY and not --yes", async () => {
277282
const origIsTTY = process.stdin.isTTY;
278283
Object.defineProperty(process.stdin, "isTTY", {
279284
value: false,
280285
configurable: true,
281286
});
282287

283-
await runWizard(makeOptions({ yes: false }));
288+
await expect(runWizard(makeOptions({ yes: false }))).rejects.toThrow(
289+
WizardError
290+
);
284291

285292
Object.defineProperty(process.stdin, "isTTY", {
286293
value: origIsTTY,
287294
configurable: true,
288295
});
289-
290-
const written = stderrSpy.mock.calls.map((c) => String(c[0])).join("");
291-
expect(written).toContain("Interactive mode requires a terminal");
292-
expect(process.exitCode).toBe(1);
293296
});
294297
});
295298

@@ -404,12 +407,11 @@ describe("runWizard", () => {
404407
// Advance past the timeout
405408
jest.advanceTimersByTime(API_TIMEOUT_MS);
406409

407-
await promise;
410+
await expect(promise).rejects.toThrow(WizardError);
408411

409412
expect(logErrorSpy).toHaveBeenCalled();
410413
const errorMsg: string = logErrorSpy.mock.calls[0][0];
411414
expect(errorMsg).toContain("timed out");
412-
expect(process.exitCode).toBe(1);
413415

414416
jest.useRealTimers();
415417
});
@@ -424,23 +426,21 @@ describe("runWizard", () => {
424426
};
425427
getWorkflowSpy.mockReturnValue(mockWorkflow as any);
426428

427-
await runWizard(makeOptions());
429+
await expect(runWizard(makeOptions())).rejects.toThrow(WizardError);
428430

429431
expect(logErrorSpy).toHaveBeenCalledWith("Connection refused");
430432
expect(cancelSpy).toHaveBeenCalledWith("Setup failed");
431-
expect(process.exitCode).toBe(1);
432433
});
433434
});
434435

435436
describe("workflow failure", () => {
436437
test("calls formatError when status is failed", async () => {
437438
mockStartResult = { status: "failed", error: "workflow exploded" };
438439

439-
await runWizard(makeOptions());
440+
await expect(runWizard(makeOptions())).rejects.toThrow(WizardError);
440441

441442
expect(formatErrorSpy).toHaveBeenCalled();
442443
expect(formatResultSpy).not.toHaveBeenCalled();
443-
expect(process.exitCode).toBe(1);
444444
});
445445
});
446446

@@ -451,10 +451,9 @@ describe("runWizard", () => {
451451
result: { exitCode: 10 },
452452
};
453453

454-
await runWizard(makeOptions());
454+
await expect(runWizard(makeOptions())).rejects.toThrow(WizardError);
455455

456456
expect(formatErrorSpy).toHaveBeenCalled();
457-
expect(process.exitCode).toBe(1);
458457
});
459458
});
460459

@@ -734,12 +733,11 @@ describe("runWizard", () => {
734733
},
735734
};
736735

737-
await runWizard(makeOptions());
736+
await expect(runWizard(makeOptions())).rejects.toThrow(WizardError);
738737

739738
expect(logErrorSpy).toHaveBeenCalled();
740739
const errorMsg: string = logErrorSpy.mock.calls[0][0];
741740
expect(errorMsg).toContain("alien");
742-
expect(process.exitCode).toBe(1);
743741
});
744742

745743
test("handles missing suspend payload", async () => {
@@ -749,12 +747,11 @@ describe("runWizard", () => {
749747
steps: {},
750748
};
751749

752-
await runWizard(makeOptions());
750+
await expect(runWizard(makeOptions())).rejects.toThrow(WizardError);
753751

754752
expect(logErrorSpy).toHaveBeenCalled();
755753
const errorMsg: string = logErrorSpy.mock.calls[0][0];
756754
expect(errorMsg).toContain("No suspend payload");
757-
expect(process.exitCode).toBe(1);
758755
});
759756

760757
test("non-WizardCancelledError in catch triggers log.error + cancel", async () => {
@@ -775,11 +772,10 @@ describe("runWizard", () => {
775772
},
776773
};
777774

778-
await runWizard(makeOptions());
775+
await expect(runWizard(makeOptions())).rejects.toThrow(WizardError);
779776

780777
expect(logErrorSpy).toHaveBeenCalledWith("string error");
781778
expect(cancelSpy).toHaveBeenCalledWith("Setup failed");
782-
expect(process.exitCode).toBe(1);
783779
});
784780

785781
test("falls back to result.suspendPayload when step payload missing", async () => {
@@ -880,12 +876,11 @@ describe("runWizard", () => {
880876
};
881877
getWorkflowSpy.mockReturnValue(badWorkflow as any);
882878

883-
await runWizard(makeOptions());
879+
await expect(runWizard(makeOptions())).rejects.toThrow(WizardError);
884880

885881
expect(logErrorSpy).toHaveBeenCalledWith(
886882
"Invalid workflow response: expected object"
887883
);
888-
expect(process.exitCode).toBe(1);
889884
});
890885

891886
test("rejects response with invalid status", async () => {
@@ -900,12 +895,11 @@ describe("runWizard", () => {
900895
};
901896
getWorkflowSpy.mockReturnValue(badWorkflow as any);
902897

903-
await runWizard(makeOptions());
898+
await expect(runWizard(makeOptions())).rejects.toThrow(WizardError);
904899

905900
expect(logErrorSpy).toHaveBeenCalledWith(
906901
"Unexpected workflow status: banana"
907902
);
908-
expect(process.exitCode).toBe(1);
909903
});
910904

911905
test("rejects null response from startAsync", async () => {
@@ -918,12 +912,11 @@ describe("runWizard", () => {
918912
};
919913
getWorkflowSpy.mockReturnValue(badWorkflow as any);
920914

921-
await runWizard(makeOptions());
915+
await expect(runWizard(makeOptions())).rejects.toThrow(WizardError);
922916

923917
expect(logErrorSpy).toHaveBeenCalledWith(
924918
"Invalid workflow response: expected object"
925919
);
926-
expect(process.exitCode).toBe(1);
927920
});
928921
});
929922
});

0 commit comments

Comments
 (0)