import { Command } from "commander"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { runRegisteredCli } from "../test-utils/command-runner.js"; import { registerModelsCli } from "./models-cli.js"; const mocks = vi.hoisted(() => ({ modelsStatusCommand: vi.fn().mockResolvedValue(undefined), noopAsync: vi.fn(async () => undefined), modelsAuthAddCommand: vi.fn().mockResolvedValue(undefined), modelsAuthLoginCommand: vi.fn().mockResolvedValue(undefined), modelsAuthPasteTokenCommand: vi.fn().mockResolvedValue(undefined), modelsAuthSetupTokenCommand: vi.fn().mockResolvedValue(undefined), })); const { modelsAuthAddCommand, modelsAuthLoginCommand, modelsAuthPasteTokenCommand, modelsAuthSetupTokenCommand, modelsStatusCommand, } = mocks; vi.mock("../commands/models.js", () => ({ modelsStatusCommand: mocks.modelsStatusCommand, modelsAliasesAddCommand: mocks.noopAsync, modelsAliasesListCommand: mocks.noopAsync, modelsAliasesRemoveCommand: mocks.noopAsync, modelsAuthAddCommand: mocks.noopAsync, modelsAuthLoginCommand: mocks.modelsAuthLoginCommand, modelsAuthOrderClearCommand: mocks.noopAsync, modelsAuthOrderGetCommand: mocks.noopAsync, modelsAuthOrderSetCommand: mocks.noopAsync, modelsAuthPasteTokenCommand: mocks.noopAsync, modelsAuthSetupTokenCommand: mocks.noopAsync, modelsFallbacksAddCommand: mocks.noopAsync, modelsFallbacksClearCommand: mocks.noopAsync, modelsFallbacksListCommand: mocks.noopAsync, modelsFallbacksRemoveCommand: mocks.noopAsync, modelsImageFallbacksAddCommand: mocks.noopAsync, modelsImageFallbacksClearCommand: mocks.noopAsync, modelsImageFallbacksListCommand: mocks.noopAsync, modelsImageFallbacksRemoveCommand: mocks.noopAsync, modelsListCommand: mocks.noopAsync, modelsScanCommand: mocks.noopAsync, modelsSetCommand: mocks.noopAsync, modelsSetImageCommand: mocks.noopAsync, })); vi.mock("../commands/models/list.js", () => ({ modelsListCommand: mocks.noopAsync, modelsStatusCommand: mocks.modelsStatusCommand, })); vi.mock("../commands/models/auth.js", () => ({ modelsAuthAddCommand: mocks.modelsAuthAddCommand, modelsAuthLoginCommand: mocks.modelsAuthLoginCommand, modelsAuthPasteTokenCommand: mocks.modelsAuthPasteTokenCommand, modelsAuthSetupTokenCommand: mocks.modelsAuthSetupTokenCommand, })); vi.mock("../commands/models/auth-order.js", () => ({ modelsAuthOrderClearCommand: mocks.noopAsync, modelsAuthOrderGetCommand: mocks.noopAsync, modelsAuthOrderSetCommand: mocks.noopAsync, })); vi.mock("../commands/models/aliases.js", () => ({ modelsAliasesAddCommand: mocks.noopAsync, modelsAliasesListCommand: mocks.noopAsync, modelsAliasesRemoveCommand: mocks.noopAsync, })); vi.mock("../commands/models/fallbacks.js", () => ({ modelsFallbacksAddCommand: mocks.noopAsync, modelsFallbacksClearCommand: mocks.noopAsync, modelsFallbacksListCommand: mocks.noopAsync, modelsFallbacksRemoveCommand: mocks.noopAsync, })); vi.mock("../commands/models/image-fallbacks.js", () => ({ modelsImageFallbacksAddCommand: mocks.noopAsync, modelsImageFallbacksClearCommand: mocks.noopAsync, modelsImageFallbacksListCommand: mocks.noopAsync, modelsImageFallbacksRemoveCommand: mocks.noopAsync, })); vi.mock("../commands/models/scan.js", () => ({ modelsScanCommand: mocks.noopAsync, })); vi.mock("../commands/models/set.js", () => ({ modelsSetCommand: mocks.noopAsync, })); vi.mock("../commands/models/set-image.js", () => ({ modelsSetImageCommand: mocks.noopAsync, })); describe("models cli", () => { beforeEach(() => { modelsAuthAddCommand.mockClear(); modelsAuthLoginCommand.mockClear(); modelsAuthPasteTokenCommand.mockClear(); modelsAuthSetupTokenCommand.mockClear(); modelsStatusCommand.mockClear(); }); function createProgram() { const program = new Command(); registerModelsCli(program); return program; } async function runModelsCommand(args: string[]) { await runRegisteredCli({ register: registerModelsCli as (program: Command) => void, argv: args, }); } it("registers github-copilot login command", async () => { const program = createProgram(); const models = program.commands.find((cmd) => cmd.name() === "models"); expect(models).toBeTruthy(); const auth = models?.commands.find((cmd) => cmd.name() === "auth"); expect(auth).toBeTruthy(); const login = auth?.commands.find((cmd) => cmd.name() === "login-github-copilot"); expect(login).toBeTruthy(); await program.parseAsync( ["models", "auth", "--agent", "poe", "login-github-copilot", "--yes"], { from: "user" }, ); expect(modelsAuthLoginCommand).toHaveBeenCalledTimes(1); expect(modelsAuthLoginCommand).toHaveBeenCalledWith( expect.objectContaining({ provider: "github-copilot", method: "device", yes: true, agent: "poe", }), expect.any(Object), ); }); it.each([ { label: "status flag", args: ["models", "status", "--agent", "poe"] }, { label: "parent flag", args: ["models", "--agent", "poe", "status"] }, ])("passes --agent to models status ($label)", async ({ args }) => { await runModelsCommand(args); expect(modelsStatusCommand).toHaveBeenCalledWith( expect.objectContaining({ agent: "poe" }), expect.any(Object), ); }); it.each([ { label: "add", args: ["models", "auth", "--agent", "poe", "add"], command: modelsAuthAddCommand, expected: { agent: "poe" }, }, { label: "login", args: ["models", "auth", "--agent", "poe", "login", "--provider", "openai-codex"], command: modelsAuthLoginCommand, expected: { agent: "poe", provider: "openai-codex" }, }, { label: "setup-token", args: ["models", "auth", "--agent", "poe", "setup-token", "--provider", "anthropic"], command: modelsAuthSetupTokenCommand, expected: { agent: "poe", provider: "anthropic" }, }, { label: "paste-token", args: ["models", "auth", "--agent", "poe", "paste-token", "--provider", "anthropic"], command: modelsAuthPasteTokenCommand, expected: { agent: "poe", provider: "anthropic" }, }, { label: "login-github-copilot", args: ["models", "auth", "--agent", "poe", "login-github-copilot", "--yes"], command: modelsAuthLoginCommand, expected: { agent: "poe", provider: "github-copilot", method: "device", yes: true }, }, ])("passes parent --agent to models auth $label", async ({ args, command, expected }) => { await runModelsCommand(args); expect(command).toHaveBeenCalledWith(expect.objectContaining(expected), expect.any(Object)); }); it("shows help for models auth without error exit", async () => { const program = new Command(); program.exitOverride(); program.configureOutput({ writeOut: () => {}, writeErr: () => {}, }); registerModelsCli(program); try { await program.parseAsync(["models", "auth"], { from: "user" }); expect.fail("expected help to exit"); } catch (err) { const error = err as { exitCode?: number }; expect(error.exitCode).toBe(0); } }); });