From fafe46cdd45e9a928168f85cbc0e62558e498166 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sun, 7 Jun 2026 18:58:09 -0700 Subject: [PATCH] test(cli): cover policy mutation public route Signed-off-by: Carlos Villela --- test/cli/sandbox-mutations.test.ts | 79 ++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/cli/sandbox-mutations.test.ts b/test/cli/sandbox-mutations.test.ts index 7855c6a2ab..ab9b1adc0f 100644 --- a/test/cli/sandbox-mutations.test.ts +++ b/test/cli/sandbox-mutations.test.ts @@ -8,6 +8,47 @@ import { describe, expect, it } from "vitest"; import { runWithEnv, testTimeoutOptions, writeSandboxRegistry } from "./helpers"; +function readSandboxPolicies(home: string, sandboxName = "alpha"): string[] { + const registryPath = path.join(home, ".nemoclaw", "sandboxes.json"); + const registry = JSON.parse(fs.readFileSync(registryPath, "utf8")) as { + sandboxes?: Record; + }; + const policies = registry.sandboxes?.[sandboxName]?.policies; + return Array.isArray(policies) + ? policies.filter((policy): policy is string => typeof policy === "string") + : []; +} + +function writePolicyMutationOpenshellStub(home: string): string { + const localBin = path.join(home, "bin"); + fs.mkdirSync(localBin, { recursive: true }); + const openshell = path.join(localBin, "openshell"); + fs.writeFileSync( + openshell, + [ + "#!/usr/bin/env bash", + "set -euo pipefail", + 'if [ "$1" = "policy" ] && [ "$2" = "get" ]; then', + " cat <<'YAML'", + "version: 1", + "network_policies:", + " github:", + " name: github", + " host: github.com", + "YAML", + " exit 0", + "fi", + 'if [ "$1" = "policy" ] && [ "$2" = "set" ]; then', + " exit 0", + "fi", + 'printf "unexpected openshell args: %s\\n" "$*" >&2', + "exit 1", + ].join("\n"), + { mode: 0o755 }, + ); + return openshell; +} + describe("CLI dispatch", () => { it("connect help uses native oclif usage through the public sandbox route", () => { const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-cli-inspection-help-")); @@ -68,6 +109,44 @@ describe("CLI dispatch", () => { expect(snapshots.out).toContain("No snapshots found for 'alpha'."); }); + it("keeps public policy-add/remove built-in mutation routes", () => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-cli-policy-mutation-")); + writeSandboxRegistry(home); + const openshell = writePolicyMutationOpenshellStub(home); + + const add = runWithEnv("alpha policy-add github --yes", { + HOME: home, + NEMOCLAW_OPENSHELL_BIN: openshell, + }); + expect(add.code).toBe(0); + expect(add.out).toContain("Applied preset: github"); + expect(readSandboxPolicies(home)).toContain("github"); + + const remove = runWithEnv("alpha policy-remove github -y", { + HOME: home, + NEMOCLAW_OPENSHELL_BIN: openshell, + }); + expect(remove.code).toBe(0); + expect(remove.out).toContain("Removed preset: github"); + expect(readSandboxPolicies(home)).not.toContain("github"); + }); + + it("keeps public policy-add non-interactive missing-preset failure before mutation", () => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-cli-policy-noninteractive-")); + writeSandboxRegistry(home); + const openshell = writePolicyMutationOpenshellStub(home); + + const result = runWithEnv("alpha policy-add", { + HOME: home, + NEMOCLAW_NON_INTERACTIVE: "1", + NEMOCLAW_OPENSHELL_BIN: openshell, + }); + + expect(result.code).toBe(1); + expect(result.out).toContain("Non-interactive mode requires a preset name."); + expect(readSandboxPolicies(home)).toEqual([]); + }); + it("sandbox channels start rejects a sandbox missing from the registry (#4584)", () => { const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-cli-channels-missing-")); writeSandboxRegistry(home);