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
147 changes: 73 additions & 74 deletions src/lib/inventory/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// 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 { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import {
getSandboxInventory,
Expand Down Expand Up @@ -543,14 +543,30 @@ describe("inventory commands", () => {
expect(showServiceStatus).toHaveBeenCalledWith({ sandboxName: "alpha" });
});

it("reuses the existing sandbox list when resolving status service sandbox", () => {
describe("#1077 — env-resolved default sandbox", () => {
const savedSandboxName = process.env.SANDBOX_NAME;
const savedNemoclawSandboxName = process.env.NEMOCLAW_SANDBOX_NAME;
const savedNemoclawSandbox = process.env.NEMOCLAW_SANDBOX;
delete process.env.SANDBOX_NAME;
delete process.env.NEMOCLAW_SANDBOX_NAME;
delete process.env.NEMOCLAW_SANDBOX;
try {

beforeEach(() => {
delete process.env.SANDBOX_NAME;
delete process.env.NEMOCLAW_SANDBOX_NAME;
delete process.env.NEMOCLAW_SANDBOX;
});

afterEach(() => {
if (savedSandboxName !== undefined) process.env.SANDBOX_NAME = savedSandboxName;
else delete process.env.SANDBOX_NAME;
if (savedNemoclawSandboxName !== undefined) {
process.env.NEMOCLAW_SANDBOX_NAME = savedNemoclawSandboxName;
} else {
delete process.env.NEMOCLAW_SANDBOX_NAME;
}
if (savedNemoclawSandbox !== undefined) process.env.NEMOCLAW_SANDBOX = savedNemoclawSandbox;
else delete process.env.NEMOCLAW_SANDBOX;
});

it("reuses the existing sandbox list when resolving status service sandbox", () => {
const listSandboxes = vi.fn(() => ({
sandboxes: [{ name: "alpha", model: "nvidia/nemotron-3-super-120b-a12b" }],
defaultSandbox: "alpha",
Expand All @@ -564,27 +580,9 @@ describe("inventory commands", () => {
});
expect(listSandboxes).toHaveBeenCalledOnce();
expect(showServiceStatus).toHaveBeenCalledWith({ sandboxName: "alpha" });
} finally {
if (savedSandboxName !== undefined) process.env.SANDBOX_NAME = savedSandboxName;
else delete process.env.SANDBOX_NAME;
if (savedNemoclawSandboxName !== undefined) {
process.env.NEMOCLAW_SANDBOX_NAME = savedNemoclawSandboxName;
} else {
delete process.env.NEMOCLAW_SANDBOX_NAME;
}
if (savedNemoclawSandbox !== undefined) process.env.NEMOCLAW_SANDBOX = savedNemoclawSandbox;
else delete process.env.NEMOCLAW_SANDBOX;
}
});
});

it("reuses the existing sandbox list when resolving JSON status service sandbox", () => {
const savedSandboxName = process.env.SANDBOX_NAME;
const savedNemoclawSandboxName = process.env.NEMOCLAW_SANDBOX_NAME;
const savedNemoclawSandbox = process.env.NEMOCLAW_SANDBOX;
delete process.env.SANDBOX_NAME;
delete process.env.NEMOCLAW_SANDBOX_NAME;
delete process.env.NEMOCLAW_SANDBOX;
try {
it("reuses the existing sandbox list when resolving JSON status service sandbox", () => {
const listSandboxes = vi.fn(() => ({
sandboxes: [{ name: "alpha", model: "nvidia/nemotron-3-super-120b-a12b" }],
defaultSandbox: "alpha",
Expand All @@ -599,27 +597,10 @@ describe("inventory commands", () => {
expect(listSandboxes).toHaveBeenCalledOnce();
expect(getServiceStatuses).toHaveBeenCalledWith({ sandboxName: "alpha" });
expect(report.defaultSandbox).toBe("alpha");
} finally {
if (savedSandboxName !== undefined) process.env.SANDBOX_NAME = savedSandboxName;
else delete process.env.SANDBOX_NAME;
if (savedNemoclawSandboxName !== undefined) {
process.env.NEMOCLAW_SANDBOX_NAME = savedNemoclawSandboxName;
} else {
delete process.env.NEMOCLAW_SANDBOX_NAME;
}
if (savedNemoclawSandbox !== undefined) process.env.NEMOCLAW_SANDBOX = savedNemoclawSandbox;
else delete process.env.NEMOCLAW_SANDBOX;
}
});
});

it("resolves service status sandbox from SANDBOX_NAME env (#1077)", () => {
const savedSandboxName = process.env.SANDBOX_NAME;
const savedNemoclawSandboxName = process.env.NEMOCLAW_SANDBOX_NAME;
const savedNemoclawSandbox = process.env.NEMOCLAW_SANDBOX;
delete process.env.NEMOCLAW_SANDBOX_NAME;
delete process.env.NEMOCLAW_SANDBOX;
process.env.SANDBOX_NAME = "env-sandbox";
try {
it("resolves service status sandbox from SANDBOX_NAME env", () => {
process.env.SANDBOX_NAME = "env-sandbox";
const showServiceStatus = vi.fn();
showStatusCommand({
listSandboxes: () => ({
Expand All @@ -631,27 +612,10 @@ describe("inventory commands", () => {
log: vi.fn(),
});
expect(showServiceStatus).toHaveBeenCalledWith({ sandboxName: "env-sandbox" });
} finally {
if (savedSandboxName !== undefined) process.env.SANDBOX_NAME = savedSandboxName;
else delete process.env.SANDBOX_NAME;
if (savedNemoclawSandboxName !== undefined) {
process.env.NEMOCLAW_SANDBOX_NAME = savedNemoclawSandboxName;
} else {
delete process.env.NEMOCLAW_SANDBOX_NAME;
}
if (savedNemoclawSandbox !== undefined) process.env.NEMOCLAW_SANDBOX = savedNemoclawSandbox;
else delete process.env.NEMOCLAW_SANDBOX;
}
});
});

it("resolves JSON service status sandbox from NEMOCLAW_SANDBOX_NAME env (#1077)", () => {
const savedName = process.env.NEMOCLAW_SANDBOX_NAME;
const savedSandboxName = process.env.SANDBOX_NAME;
const savedNemoclawSandbox = process.env.NEMOCLAW_SANDBOX;
delete process.env.SANDBOX_NAME;
delete process.env.NEMOCLAW_SANDBOX;
process.env.NEMOCLAW_SANDBOX_NAME = "json-sandbox";
try {
it("resolves JSON service status sandbox from NEMOCLAW_SANDBOX_NAME env", () => {
process.env.NEMOCLAW_SANDBOX_NAME = "json-sandbox";
const getServiceStatuses = vi.fn().mockReturnValue([]);
const report = getStatusReport({
listSandboxes: () => ({
Expand All @@ -665,14 +629,49 @@ describe("inventory commands", () => {
expect(getServiceStatuses).toHaveBeenCalledWith({ sandboxName: "json-sandbox" });
expect(report.defaultSandbox).toBe("json-sandbox");
expect(report.sandboxes[0]?.isDefault).toBe(true);
} finally {
if (savedName !== undefined) process.env.NEMOCLAW_SANDBOX_NAME = savedName;
else delete process.env.NEMOCLAW_SANDBOX_NAME;
if (savedSandboxName !== undefined) process.env.SANDBOX_NAME = savedSandboxName;
else delete process.env.SANDBOX_NAME;
if (savedNemoclawSandbox !== undefined) process.env.NEMOCLAW_SANDBOX = savedNemoclawSandbox;
else delete process.env.NEMOCLAW_SANDBOX;
}
});

it("resolves list default sandbox from SANDBOX_NAME env", async () => {
process.env.SANDBOX_NAME = "env-sandbox";
const inventory = await getSandboxInventory({
recoverRegistryEntries: async () => ({
sandboxes: [
{ name: "env-sandbox", model: "m1", provider: "p1" },
{ name: "registry-default", model: "m2", provider: "p2" },
],
defaultSandbox: "registry-default",
}),
getLiveInference: () => null,
loadLastSession: () => null,
getActiveSessionCount: () => 0,
});

expect(inventory.defaultSandbox).toBe("env-sandbox");
expect(inventory.sandboxes.find((row) => row.name === "env-sandbox")?.isDefault).toBe(true);
expect(inventory.sandboxes.find((row) => row.name === "registry-default")?.isDefault).toBe(
false,
);
});

it("marks the env-resolved sandbox with * in list output", async () => {
process.env.SANDBOX_NAME = "env-sandbox";
const lines: string[] = [];
await listSandboxesCommand({
recoverRegistryEntries: async () => ({
sandboxes: [
{ name: "registry-default", model: "m1", provider: "p1" },
{ name: "env-sandbox", model: "m2", provider: "p2" },
],
defaultSandbox: "registry-default",
}),
getLiveInference: () => null,
loadLastSession: () => null,
log: (message = "") => lines.push(message),
});

expect(lines).toContain(" env-sandbox *");
expect(lines.some((line) => line.startsWith(" registry-default *"))).toBe(false);
});
});

it("does not annotate status when the live gateway matches the onboarded model", () => {
Expand Down
7 changes: 4 additions & 3 deletions src/lib/inventory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ export async function getSandboxInventory(
deps: ListSandboxesCommandDeps,
): Promise<SandboxInventoryResult> {
const recovery = await deps.recoverRegistryEntries();
const defaultSandbox = recovery.defaultSandbox || null;
const resolvedDefault =
resolveDefaultSandboxName(() => ({ defaultSandbox: recovery.defaultSandbox ?? null })) ?? null;
const lastSession = deps.loadLastSession();
// #2753: only surface the last-onboarded name when its sandbox step
// actually completed. Otherwise an interrupted onboard would leave the
Expand All @@ -211,14 +212,14 @@ export async function getSandboxInventory(

return {
schemaVersion: 1,
defaultSandbox,
defaultSandbox: resolvedDefault,
recovery: {
recoveredFromSession: recovery.recoveredFromSession === true,
recoveredFromGateway: recovery.recoveredFromGateway || 0,
},
lastOnboardedSandbox,
sandboxes: recovery.sandboxes.map((sandbox) =>
buildSandboxInventoryRow(sandbox, defaultSandbox, deps.getActiveSessionCount),
buildSandboxInventoryRow(sandbox, resolvedDefault, deps.getActiveSessionCount),
),
};
}
Expand Down
12 changes: 6 additions & 6 deletions src/lib/tunnel/services.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ describe("#1077 — status host service PID dir matches start/stop env", () => {
const savedNemoclawSandbox = process.env.NEMOCLAW_SANDBOX;
const savedNemoclawSandboxName = process.env.NEMOCLAW_SANDBOX_NAME;

beforeEach(() => {
delete process.env.SANDBOX_NAME;
delete process.env.NEMOCLAW_SANDBOX;
delete process.env.NEMOCLAW_SANDBOX_NAME;
});

afterEach(() => {
if (savedSandboxName !== undefined) process.env.SANDBOX_NAME = savedSandboxName;
else delete process.env.SANDBOX_NAME;
Expand All @@ -165,8 +171,6 @@ describe("#1077 — status host service PID dir matches start/stop env", () => {

it("reports running cloudflared when status passes env-resolved sandboxName", () => {
resetIntegrationPidDirs();
delete process.env.NEMOCLAW_SANDBOX;
delete process.env.NEMOCLAW_SANDBOX_NAME;
process.env.SANDBOX_NAME = INTEGRATION_ENV_SANDBOX;
seedAliveCloudflaredPid(INTEGRATION_ENV_PID_DIR);

Expand All @@ -183,8 +187,6 @@ describe("#1077 — status host service PID dir matches start/stop env", () => {

it("reports stopped cloudflared when status passes registry sandbox but env PID dir has the process", () => {
resetIntegrationPidDirs();
delete process.env.NEMOCLAW_SANDBOX;
delete process.env.NEMOCLAW_SANDBOX_NAME;
process.env.SANDBOX_NAME = INTEGRATION_ENV_SANDBOX;
seedAliveCloudflaredPid(INTEGRATION_ENV_PID_DIR);

Expand All @@ -196,8 +198,6 @@ describe("#1077 — status host service PID dir matches start/stop env", () => {

it("showStatus prints running cloudflared from env-resolved production PID dir", () => {
resetIntegrationPidDirs();
delete process.env.NEMOCLAW_SANDBOX;
delete process.env.NEMOCLAW_SANDBOX_NAME;
process.env.SANDBOX_NAME = INTEGRATION_ENV_SANDBOX;
seedAliveCloudflaredPid(INTEGRATION_ENV_PID_DIR);

Expand Down
Loading