diff --git a/src/lib/onboard-cli-commands.test.ts b/src/lib/onboard-cli-commands.test.ts new file mode 100644 index 00000000000..94c02a15542 --- /dev/null +++ b/src/lib/onboard-cli-commands.test.ts @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, it, vi } from "vitest"; + +import { OnboardCliCommand } from "./onboard-cli-commands"; +import { runOnboardAction } from "./global-cli-actions"; + +vi.mock("./global-cli-actions", () => ({ + runOnboardAction: vi.fn().mockResolvedValue(undefined), + runSetupAction: vi.fn().mockResolvedValue(undefined), + runSetupSparkAction: vi.fn().mockResolvedValue(undefined), +})); + +const rootDir = process.cwd(); + +describe("onboard oclif command", () => { + it("rejects mutually exclusive resume and fresh flags before dispatch", async () => { + await expect(OnboardCliCommand.run(["--resume", "--fresh"], rootDir)).rejects.toThrow( + /resume|fresh/, + ); + + expect(runOnboardAction).not.toHaveBeenCalled(); + }); +}); diff --git a/src/lib/onboard-cli-commands.ts b/src/lib/onboard-cli-commands.ts index 5d2bb5ad792..a3cb55f7124 100644 --- a/src/lib/onboard-cli-commands.ts +++ b/src/lib/onboard-cli-commands.ts @@ -39,8 +39,14 @@ function buildOnboardFlags(): Record { return { help: Flags.help({ char: "h" }), "non-interactive": Flags.boolean({ description: "Run without interactive prompts" }), - resume: Flags.boolean({ description: "Resume an interrupted onboarding session" }), - fresh: Flags.boolean({ description: "Ignore any saved onboarding session" }), + resume: Flags.boolean({ + description: "Resume an interrupted onboarding session", + exclusive: ["fresh"], + }), + fresh: Flags.boolean({ + description: "Ignore any saved onboarding session", + exclusive: ["resume"], + }), "recreate-sandbox": Flags.boolean({ description: "Delete and recreate an existing sandbox" }), from: Flags.string({ description: "Path to a Dockerfile to use as the sandbox image source" }), name: Flags.string({ description: "Sandbox name" }), diff --git a/src/lib/policy-mutate-cli-commands.test.ts b/src/lib/policy-mutate-cli-commands.test.ts index 6ac03345b95..c49d6d77a49 100644 --- a/src/lib/policy-mutate-cli-commands.test.ts +++ b/src/lib/policy-mutate-cli-commands.test.ts @@ -62,4 +62,18 @@ describe("policy mutation oclif commands", () => { expect(runtime.sandboxPolicyAdd).not.toHaveBeenCalled(); }); + + it("rejects mutually exclusive custom policy sources before dispatch", async () => { + const runtime = { + sandboxPolicyAdd: vi.fn().mockResolvedValue(undefined), + sandboxPolicyRemove: vi.fn().mockResolvedValue(undefined), + }; + setPolicyRuntimeBridgeFactoryForTest(() => runtime); + + await expect( + PolicyAddCommand.run(["alpha", "--from-file", "preset.yaml", "--from-dir", "presets"], rootDir), + ).rejects.toThrow(/from-file|from-dir/); + + expect(runtime.sandboxPolicyAdd).not.toHaveBeenCalled(); + }); }); diff --git a/src/lib/policy-mutate-cli-commands.ts b/src/lib/policy-mutate-cli-commands.ts index 129c5b817a1..8ca2fa92f35 100644 --- a/src/lib/policy-mutate-cli-commands.ts +++ b/src/lib/policy-mutate-cli-commands.ts @@ -66,8 +66,14 @@ export class PolicyAddCommand extends Command { yes: Flags.boolean({ char: "y", description: "Skip the confirmation prompt" }), force: Flags.boolean({ description: "Skip the confirmation prompt" }), "dry-run": Flags.boolean({ description: "Preview without applying" }), - "from-file": Flags.string({ description: "Load one custom preset YAML file" }), - "from-dir": Flags.string({ description: "Load all custom preset YAML files in a directory" }), + "from-file": Flags.string({ + description: "Load one custom preset YAML file", + exclusive: ["from-dir"], + }), + "from-dir": Flags.string({ + description: "Load all custom preset YAML files in a directory", + exclusive: ["from-file"], + }), }; public async run(): Promise { diff --git a/test/policies.test.ts b/test/policies.test.ts index a80f1f563a6..cc51241e845 100644 --- a/test/policies.test.ts +++ b/test/policies.test.ts @@ -1611,7 +1611,7 @@ Promise.resolve(require(${CLI_PATH}).mainPromise).finally(() => { it("errors when --from-file and --from-dir are combined", () => { const result = runPolicyAddExternal(["--from-file", "a.yaml", "--from-dir", "b"]); expect(result.status).not.toBe(0); - expect(result.stderr).toMatch(/mutually exclusive/); + expect(result.stderr).toMatch(/cannot also be provided/); }); it("errors when --from-file is missing its path argument", () => {