Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/commands/sandbox/oclif-command-adapters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,28 @@ describe("sandbox oclif command adapters", () => {
}
});

it("rejects the removed connect permission bypass before dispatch", async () => {
const previousExitCode = process.exitCode;
const lines: string[] = [];
const errorSpy = vi.spyOn(console, "error").mockImplementation((line = "") => {
lines.push(String(line));
});
process.exitCode = undefined;

try {
await ConnectCliCommand.run(["alpha", "--dangerously-skip-permissions"], rootDir);

expect(lines.join("\n")).toContain(
"--dangerously-skip-permissions was removed; use shields commands instead.",
);
expect(process.exitCode).toBe(1);
expect(mocks.connectSandbox).not.toHaveBeenCalled();
} finally {
errorSpy.mockRestore();
process.exitCode = previousExitCode;
}
});

it("threads --cleanup-gateway / --no-cleanup-gateway through destroy (#2166)", async () => {
const originalCleanupGatewayEnv = process.env.NEMOCLAW_CLEANUP_GATEWAY;
delete process.env.NEMOCLAW_CLEANUP_GATEWAY;
Expand Down
1 change: 1 addition & 0 deletions src/lib/actions/sandbox/connect-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe("connectSandbox flow", () => {

afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllEnvs();
if (originalStdoutIsTty === undefined) {
Object.defineProperty(process.stdout, "isTTY", { configurable: true, value: undefined });
} else {
Expand Down
190 changes: 190 additions & 0 deletions src/lib/actions/sandbox/connect-route-lifecycle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { afterEach, beforeEach, describe, expect, it, type MockInstance, vi } from "vitest";

import {
connectModulePath,
createConnectHarness,
requireDist,
} from "../../../../test/support/connect-flow-test-harness";

describe("connectSandbox route lifecycle", () => {
let exitSpy: MockInstance;
const originalStdoutIsTty = process.stdout.isTTY;

beforeEach(() => {
process.env.NEMOCLAW_TEST_NO_SLEEP = "1";
Object.defineProperty(process.stdout, "isTTY", { configurable: true, value: true });
exitSpy = vi.spyOn(process, "exit").mockImplementation(((code?: number | string | null) => {
throw new Error(`process.exit(${code ?? 0})`);
}) as never);
});

afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllEnvs();
Object.defineProperty(process.stdout, "isTTY", {
configurable: true,
value: originalStdoutIsTty,
});
delete process.env.NEMOCLAW_TEST_NO_SLEEP;
delete require.cache[requireDist.resolve(connectModulePath)];
});

it("skips the vLLM model preflight only for probe-only connects (#4585)", async () => {
const harness = createConnectHarness();

await expect(harness.connectSandbox("alpha", { probeOnly: true })).resolves.toBeUndefined();
await expect(harness.connectSandbox("alpha", { probeOnly: true })).resolves.toBeUndefined();
expect(harness.preflightVllmSpy).not.toHaveBeenCalled();

await expect(harness.connectSandbox("alpha")).rejects.toThrow("process.exit(0)");
expect(harness.preflightVllmSpy).toHaveBeenCalledOnce();
});

it("warns and aligns a diverged route during a quiet probe-only connect (#3726)", async () => {
const harness = createConnectHarness({
inferenceGetOutput:
"Gateway inference:\n Provider: nvidia-prod\n Model: nvidia/nemotron-3-super-120b-a12b\n",
registryEntry: {
model: "claude-sonnet-4-20250514",
provider: "anthropic-prod",
},
});

await expect(harness.connectSandbox("alpha", { probeOnly: true })).resolves.toBeUndefined();

const errorOutput = harness.errorSpy.mock.calls.map((call) => String(call[0] ?? "")).join("\n");
expect(errorOutput).toContain("differs from the recorded route");
expect(errorOutput).toContain(
"Aligning the gateway to anthropic-prod/claude-sonnet-4-20250514",
);
expect(harness.runOpenshellSpy).toHaveBeenCalledWith(
[
"inference",
"set",
"--provider",
"anthropic-prod",
"--model",
"claude-sonnet-4-20250514",
"--no-verify",
],
expect.objectContaining({ ignoreError: true }),
);
expect(harness.spawnSyncSpy).not.toHaveBeenCalledWith(
"openshell",
["sandbox", "connect", "alpha"],
expect.any(Object),
);
});

it("wires the forced VM DNS monkeypatch into connect route repair", async () => {
vi.stubEnv("NEMOCLAW_FORCE_VM_DNS_MONKEYPATCH", "1");
try {
const harness = createConnectHarness({
inferenceGetOutput:
"Gateway inference:\n Provider: nvidia-prod\n Model: nvidia/nemotron-3-super-120b-a12b\n",
inferenceProbeResponses: ['BROKEN 503 {"error":"inference service unavailable"}', "OK 200"],
registryEntry: {
model: "nvidia/nemotron-3-super-120b-a12b",
openshellDriver: "vm",
provider: "nvidia-prod",
},
});

await expect(harness.connectSandbox("alpha", { probeOnly: true })).resolves.toBeUndefined();

expect(harness.applyVmDnsMonkeypatchSpy).toHaveBeenCalledWith(
"alpha",
expect.objectContaining({ openshellDriver: "vm" }),
);
expect(harness.runSetupDnsProxySpy).not.toHaveBeenCalled();
expect(harness.runOpenshellSpy).not.toHaveBeenCalled();
const routeProbeCalls = harness.captureOpenshellSpy.mock.calls.filter((call) =>
JSON.stringify(call[0]).includes("inference.local/v1/models"),
);
expect(routeProbeCalls).toHaveLength(2);
} finally {
vi.unstubAllEnvs();
}
});

it.each([
["null", null, null],
["provider-only", "nvidia-prod", null],
["model-only", null, "nvidia/test"],
["blank-provider", " ", "nvidia/test"],
["blank-model", "nvidia-prod", " "],
] as const)("skips inference reconciliation for %s registry entries (#5937)", async (_description, provider, model) => {
const harness = createConnectHarness({ registryEntry: { model, provider } });

await expect(harness.connectSandbox("alpha", { probeOnly: true })).resolves.toBeUndefined();

expect(harness.captureOpenshellSpy).not.toHaveBeenCalledWith(
["inference", "get"],
expect.any(Object),
);
expect(harness.runOpenshellSpy).not.toHaveBeenCalled();
});

it("does not reset an inference route that already matches the sandbox", async () => {
const harness = createConnectHarness({
inferenceGetOutput:
"Gateway inference:\n Provider: nvidia-prod\n Model: nvidia/nemotron-3-super-120b-a12b\n",
registryEntry: {
model: "nvidia/nemotron-3-super-120b-a12b",
provider: "nvidia-prod",
},
});

await expect(harness.connectSandbox("alpha", { probeOnly: true })).resolves.toBeUndefined();

expect(harness.captureOpenshellSpy).toHaveBeenCalledWith(
["inference", "get"],
expect.objectContaining({ ignoreError: true }),
);
expect(harness.runOpenshellSpy).not.toHaveBeenCalled();
});

it("stops before opening SSH when route repair and reset both fail", async () => {
const harness = createConnectHarness({
inferenceGetOutput:
"Gateway inference:\n Provider: nvidia-prod\n Model: nvidia/nemotron-3-super-120b-a12b\n",
inferenceProbeResponses: Array(7).fill('BROKEN 503 {"error":"upstream unavailable"}'),
registryEntry: {
model: "nvidia/nemotron-3-super-120b-a12b",
openshellDriver: "kubernetes",
provider: "nvidia-prod",
},
});

await expect(harness.connectSandbox("alpha")).rejects.toThrow("process.exit(1)");

expect(harness.runSetupDnsProxySpy).toHaveBeenCalledOnce();
expect(harness.runOpenshellSpy).toHaveBeenCalledOnce();
expect(harness.runOpenshellSpy).toHaveBeenCalledWith(
[
"inference",
"set",
"--provider",
"nvidia-prod",
"--model",
"nvidia/nemotron-3-super-120b-a12b",
"--no-verify",
],
expect.objectContaining({ ignoreError: true }),
);
expect(harness.spawnSyncSpy).not.toHaveBeenCalledWith(
"openshell",
["sandbox", "connect", "alpha"],
expect.any(Object),
);
const errorOutput = harness.errorSpy.mock.calls.map((call) => String(call[0] ?? "")).join("\n");
expect(errorOutput).toContain("inference.local is still unavailable");
expect(errorOutput).toContain(
"Connect is stopping because the sandbox inference route is known to be broken",
);
expect(exitSpy).toHaveBeenCalledWith(1);
});
});
11 changes: 11 additions & 0 deletions src/lib/actions/sandbox/connect-route-repair.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ describe("sandbox connect route repair unit flow", () => {
});
expect(calls.legacyRepairs).toEqual([{ sandboxName: "legacy-box", quiet: false }]);
expect(calls.reapplications).toEqual([]);
expect(calls.probeOptions).toEqual([undefined, { attempts: 3, delayMs: 2000 }]);
expect(calls.logs).toContain(
" inference.local is unavailable inside 'legacy-box'. Repairing sandbox DNS proxy...",
);
expect(calls.logs).toContain(" inference.local route repaired.");
});

Expand Down Expand Up @@ -225,6 +229,13 @@ describe("sandbox connect route repair unit flow", () => {
expect(calls.monkeypatches).toEqual(["vm-box"]);
expect(calls.reapplications).toEqual([]);
expect(calls.legacyRepairs).toEqual([]);
expect(calls.probeOptions).toEqual([undefined, { attempts: 3, delayMs: 2000 }]);
expect(calls.logs).toContain(
" inference.local is unavailable inside 'vm-box'. Applying OpenShell VM DNS monkeypatch...",
);
expect(calls.logs).not.toContain(
" inference.local is unavailable inside 'vm-box'. Reapplying OpenShell inference route...",
);
});

it("falls back to inference reapply when the VM monkeypatch leaves the route broken", () => {
Expand Down
Loading