import { Command } from "commander"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { createDeferred } from "../../../test/helpers/promise.js"; import { configureCommandFromSectionsArg } from "../../commands/configure.commands.js"; import { defaultRuntime, ExitError } from "../../runtime.js"; import { registerConfigCli } from "../config-cli.js"; import { registerConfigureCommand } from "./register.configure.js"; const mocks = vi.hoisted(() => ({ interactive: vi.fn(), runWizard: vi.fn(), runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() }, })); vi.mock("../../commands/configure.wizard.js", () => ({ runConfigureWizard: mocks.runWizard })); vi.mock("../terminal-interactivity.js", () => ({ isTerminalInteractive: mocks.interactive })); vi.mock("../../runtime.js", async (importOriginal) => ({ ...(await importOriginal()), defaultRuntime: mocks.runtime, })); const routes = ["configure", "config"] as const; type Route = (typeof routes)[number]; async function parse(route: Route, options: string[] = []) { const program = new Command(); if (route === "configure") { registerConfigureCommand(program); } else { registerConfigCli(program); } return await program.parseAsync([route, ...options], { from: "user" }); } describe("registered configure dispatch", { concurrent: false }, () => { beforeEach(() => { vi.resetAllMocks(); mocks.interactive.mockReturnValue(true); mocks.runWizard.mockResolvedValue(undefined); mocks.runtime.exit.mockImplementation((code: number) => { throw new ExitError(code); }); }); it.each(routes)("%s omits sections for the full chooser", async (route) => { await parse(route); expect(mocks.runWizard).toHaveBeenCalledExactlyOnceWith( { command: "configure" }, defaultRuntime, ); expect(mocks.runWizard.mock.calls[0]?.[1]).toBe(defaultRuntime); expect(mocks.runtime.exit).not.toHaveBeenCalled(); }); it.each(routes)("%s preserves repeated, trimmed section values", async (route) => { await parse(route, ["--section", " channels ", "--section", "health", "--section", "channels"]); expect(mocks.runWizard).toHaveBeenCalledExactlyOnceWith( { command: "configure", sections: ["channels", "health", "channels"] }, defaultRuntime, ); expect(mocks.runWizard.mock.calls[0]?.[1]).toBe(defaultRuntime); }); it.each(routes)("%s rejects a blank section before testing the terminal", async (route) => { mocks.interactive.mockReturnValue(false); await expect(parse(route, ["--section", " "])).rejects.toMatchObject({ code: 1 }); expect(mocks.runtime.error).toHaveBeenCalledOnce(); expect(mocks.runtime.error.mock.calls[0]?.[0]).toContain('Invalid --section: "".'); expect(mocks.interactive).not.toHaveBeenCalled(); expect(mocks.runWizard).not.toHaveBeenCalled(); }); it.each(routes)("%s rejects an unknown section without widening scope", async (route) => { await expect(parse(route, ["--section", "not-a-section"])).rejects.toMatchObject({ code: 1 }); expect(mocks.runtime.error.mock.calls[0]?.[0]).toContain("Invalid --section: not-a-section."); expect(mocks.runWizard).not.toHaveBeenCalled(); }); it.each(routes)("%s refuses a non-interactive terminal", async (route) => { mocks.interactive.mockReturnValue(false); await expect(parse(route, ["--section", "channels"])).rejects.toMatchObject({ code: 1 }); expect(mocks.runtime.error).toHaveBeenCalledOnce(); expect(mocks.runtime.error.mock.calls[0]?.[0]).toContain( "requires an interactive terminal (TTY)", ); expect(mocks.runWizard).not.toHaveBeenCalled(); }); it.each(routes)("%s waits for the admitted wizard to settle", async (route) => { const entered = createDeferred(); const released = createDeferred(); mocks.runWizard.mockImplementationOnce(async () => { entered.resolve(undefined); await released.promise; }); let settled = false; const parsing = parse(route, ["--section", "channels"]); void parsing.catch(entered.reject); const observed = parsing.then( () => { settled = true; }, () => { settled = true; }, ); try { await entered.promise; expect(settled).toBe(false); } finally { released.resolve(undefined); await observed; } await parsing; expect(settled).toBe(true); expect(mocks.runWizard).toHaveBeenCalledOnce(); }); it.each(routes)("%s preserves its registered failure contract", async (route) => { const failure = new Error("fixture wizard failure"); mocks.runWizard.mockRejectedValueOnce(failure); if (route === "configure") { await expect(parse(route)).rejects.toMatchObject({ code: 1 }); expect(mocks.runtime.error).toHaveBeenCalledWith("fixture wizard failure"); } else { await expect(parse(route)).rejects.toBe(failure); expect(mocks.runtime.error).not.toHaveBeenCalled(); expect(mocks.runtime.exit).not.toHaveBeenCalled(); } expect(mocks.runWizard).toHaveBeenCalledOnce(); }); it("passes a caller-provided runtime by identity", async () => { const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() }; await configureCommandFromSectionsArg(["channels"], runtime, { interactive: true }); expect(mocks.runWizard).toHaveBeenCalledExactlyOnceWith( { command: "configure", sections: ["channels"] }, runtime, ); expect(mocks.runWizard.mock.calls[0]?.[1]).toBe(runtime); }); });