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
73 changes: 73 additions & 0 deletions test/e2e/fixtures/environment-profiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import os from "node:os";
import path from "node:path";

import { buildAvailabilityProbeEnv } from "./availability-env.ts";

function withDefaults(
base: NodeJS.ProcessEnv,
extra: NodeJS.ProcessEnv,
gateway: string | undefined,
): NodeJS.ProcessEnv {
return {
...base,
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1",
NEMOCLAW_NON_INTERACTIVE: "1",
OPENSHELL_GATEWAY: gateway ?? "nemoclaw",
...extra,
};
}

function withInstalledCliPath(base: NodeJS.ProcessEnv, home: string): NodeJS.ProcessEnv {
const entries = [
path.join(home, ".local", "bin"),
path.join(home, ".npm-global", "bin"),
...(base.PATH?.split(path.delimiter) ?? []),
];
return {
...base,
HOME: home,
PATH: [...new Set(entries.filter(Boolean))].join(path.delimiter),
};
}

export function commandEnvironment(
extra: NodeJS.ProcessEnv = {},
source: NodeJS.ProcessEnv = process.env,
): NodeJS.ProcessEnv {
return withDefaults(buildAvailabilityProbeEnv(source), extra, source.OPENSHELL_GATEWAY);
}

export function installedCommandEnvironment(
extra: NodeJS.ProcessEnv = {},
home = os.homedir(),
source: NodeJS.ProcessEnv = process.env,
): NodeJS.ProcessEnv {
const base = buildAvailabilityProbeEnv({ ...source, HOME: home });
return withDefaults(withInstalledCliPath(base, home), extra, source.OPENSHELL_GATEWAY);
}

export function testHomeEnvironment(
home: string,
extra: NodeJS.ProcessEnv = {},
source: NodeJS.ProcessEnv = process.env,
): NodeJS.ProcessEnv {
return installedCommandEnvironment(extra, home, source);
}

export function sandboxCommandEnvironment(
sandboxName: string,
extra: NodeJS.ProcessEnv = {},
source: NodeJS.ProcessEnv = process.env,
): NodeJS.ProcessEnv {
return commandEnvironment(
{
NEMOCLAW_RECREATE_SANDBOX: "1",
NEMOCLAW_SANDBOX_NAME: sandboxName,
...extra,
},
source,
);
}
12 changes: 2 additions & 10 deletions test/e2e/live/bedrock-runtime-compatible-anthropic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
validateSandboxName,
} from "../fixtures/clients/sandbox.ts";
import { expect, test } from "../fixtures/e2e-test.ts";
import { testHomeEnvironment } from "../fixtures/environment-profiles.ts";
import { shouldRunLiveE2E } from "../fixtures/live-project-gate.ts";
import { redactString } from "../fixtures/redaction.ts";
import {
Expand Down Expand Up @@ -134,16 +135,7 @@ function assertAgent(value: string): asserts value is AgentName {
}

function testEnv(home: string, extra: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv {
const base = buildAvailabilityProbeEnv({ ...process.env, HOME: home });
return {
...base,
HOME: home,
PATH: [path.join(home, ".local", "bin"), base.PATH].filter(Boolean).join(":"),
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1",
NEMOCLAW_NON_INTERACTIVE: "1",
OPENSHELL_GATEWAY: process.env.OPENSHELL_GATEWAY ?? "nemoclaw",
...extra,
};
return testHomeEnvironment(home, extra);
}

function onboardEnv(home: string, agent: AgentName): NodeJS.ProcessEnv {
Expand Down
14 changes: 2 additions & 12 deletions test/e2e/live/cloud-inference.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { buildAvailabilityProbeEnv } from "../fixtures/availability-env.ts";
import type { HostCliClient } from "../fixtures/clients/host.ts";
import { type SandboxClient, validateSandboxName } from "../fixtures/clients/sandbox.ts";
import { expect, test } from "../fixtures/e2e-test.ts";
import { testHomeEnvironment } from "../fixtures/environment-profiles.ts";
import { requireHostedInferenceConfig } from "../fixtures/hosted-inference.ts";
import { shouldRunLiveE2E } from "../fixtures/live-project-gate.ts";
import type { ShellProbeResult } from "../fixtures/shell-probe.ts";
Expand Down Expand Up @@ -86,18 +87,7 @@ async function writePreContractExternalProviderSkip(
}

function testEnv(home: string, extra: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv {
const base = buildAvailabilityProbeEnv();
return {
...base,
HOME: home,
PATH: [path.join(home, ".local", "bin"), path.join(home, ".npm-global", "bin"), base.PATH]
.filter(Boolean)
.join(":"),
NEMOCLAW_NON_INTERACTIVE: "1",
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1",
OPENSHELL_GATEWAY: "nemoclaw",
...extra,
};
return testHomeEnvironment(home, extra, { ...process.env, OPENSHELL_GATEWAY: "nemoclaw" });
}

async function bestEffort(run: () => Promise<unknown>): Promise<void> {
Expand Down
11 changes: 2 additions & 9 deletions test/e2e/live/credential-migration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { buildAvailabilityProbeEnv } from "../fixtures/availability-env.ts";
import type { HostCliClient } from "../fixtures/clients/host.ts";
import { validateSandboxName } from "../fixtures/clients/sandbox.ts";
import { expect, test } from "../fixtures/e2e-test.ts";
import { testHomeEnvironment } from "../fixtures/environment-profiles.ts";
import { requireHostedInferenceConfig } from "../fixtures/hosted-inference.ts";
import { shouldRunLiveE2E } from "../fixtures/live-project-gate.ts";

Expand Down Expand Up @@ -40,15 +41,7 @@ function resultText(result: { stdout: string; stderr: string }): string {
}

function testEnv(home: string, extra: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv {
const base = buildAvailabilityProbeEnv();
return {
...base,
HOME: home,
PATH: [path.join(home, ".local", "bin"), base.PATH].filter(Boolean).join(":"),
NEMOCLAW_NON_INTERACTIVE: "1",
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1",
...extra,
};
return testHomeEnvironment(home, extra);
}

async function bestEffort(run: () => Promise<unknown>): Promise<void> {
Expand Down
12 changes: 2 additions & 10 deletions test/e2e/live/credential-sanitization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { buildAvailabilityProbeEnv } from "../fixtures/availability-env.ts";
import type { HostCliClient } from "../fixtures/clients/host.ts";
import { type SandboxClient, validateSandboxName } from "../fixtures/clients/sandbox.ts";
import { expect, test } from "../fixtures/e2e-test.ts";
import { testHomeEnvironment } from "../fixtures/environment-profiles.ts";

const REPO_ROOT = path.resolve(import.meta.dirname, "../../..");
const CLI_ENTRYPOINT = path.join(REPO_ROOT, "bin", "nemoclaw.js");
Expand All @@ -49,16 +50,7 @@ function resultText(result: CommandText): string {
}

function testEnv(home: string, extra: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv {
const base = buildAvailabilityProbeEnv();
return {
...base,
HOME: home,
PATH: [path.join(home, ".local", "bin"), base.PATH].filter(Boolean).join(":"),
NEMOCLAW_NON_INTERACTIVE: "1",
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1",
OPENSHELL_GATEWAY: process.env.OPENSHELL_GATEWAY ?? "nemoclaw",
...extra,
};
return testHomeEnvironment(home, extra);
}

async function bestEffort(run: () => Promise<unknown>): Promise<void> {
Expand Down
12 changes: 3 additions & 9 deletions test/e2e/live/diagnostics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import path from "node:path";
import { buildAvailabilityProbeEnv } from "../fixtures/availability-env.ts";
import { validateSandboxName } from "../fixtures/clients/sandbox.ts";
import { expect, test } from "../fixtures/e2e-test.ts";
import { testHomeEnvironment } from "../fixtures/environment-profiles.ts";
import { requireHostedInferenceConfig } from "../fixtures/hosted-inference.ts";
import { shouldRunLiveE2E } from "../fixtures/live-project-gate.ts";
import type { ShellProbeResult } from "../fixtures/shell-probe.ts";
Expand Down Expand Up @@ -73,19 +74,12 @@ function runRawNodeCliForLeakAssertion(
}

function testEnv(home: string, extra: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv {
const base = buildAvailabilityProbeEnv();
return {
...base,
HOME: home,
PATH: [path.join(home, ".local", "bin"), base.PATH].filter(Boolean).join(":"),
NEMOCLAW_NON_INTERACTIVE: "1",
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1",
return testHomeEnvironment(home, {
NEMOCLAW_RECREATE_SANDBOX: "1",
NEMOCLAW_SANDBOX_NAME: SANDBOX_NAME,
NEMOCLAW_DISABLE_GATEWAY_DRIFT_PREFLIGHT: "1",
OPENSHELL_GATEWAY: process.env.OPENSHELL_GATEWAY ?? "nemoclaw",
...extra,
};
});
}

async function bestEffort(run: () => Promise<unknown>): Promise<void> {
Expand Down
14 changes: 2 additions & 12 deletions test/e2e/live/openclaw-skill-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
validateSandboxName,
} from "../fixtures/clients/sandbox.ts";
import { expect, test } from "../fixtures/e2e-test.ts";
import { testHomeEnvironment } from "../fixtures/environment-profiles.ts";
import { requireHostedInferenceConfig } from "../fixtures/hosted-inference.ts";
import { shouldRunLiveE2E } from "../fixtures/live-project-gate.ts";
import type { ShellProbeResult } from "../fixtures/shell-probe.ts";
Expand Down Expand Up @@ -52,18 +53,7 @@ function singleLineSandboxScript(script: string) {
}

function testEnv(home: string, extra: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv {
const base = buildAvailabilityProbeEnv();
return {
...base,
HOME: home,
PATH: [path.join(home, ".local", "bin"), path.join(home, ".npm-global", "bin"), base.PATH]
.filter(Boolean)
.join(":"),
NEMOCLAW_NON_INTERACTIVE: "1",
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1",
OPENSHELL_GATEWAY: process.env.OPENSHELL_GATEWAY ?? "nemoclaw",
...extra,
};
return testHomeEnvironment(home, extra);
}

async function bestEffort(run: () => Promise<unknown>): Promise<void> {
Expand Down
72 changes: 72 additions & 0 deletions test/e2e/support/e2e-environment-profiles.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import path from "node:path";

import { describe, expect, it } from "vitest";

import {
commandEnvironment,
sandboxCommandEnvironment,
testHomeEnvironment,
} from "../fixtures/environment-profiles.ts";

describe("E2E environment profiles", () => {
it("filters the source and lets caller overrides win without mutation", () => {
const source = {
PATH: "/usr/bin",
OPENSHELL_GATEWAY: "source-gateway",
UNRELATED_SECRET: "must-not-pass",
};
const extra = {
NEMOCLAW_NON_INTERACTIVE: "override",
NVIDIA_INFERENCE_API_KEY: "test-secret-overlay",
};

const result = commandEnvironment(extra, source);

expect(result).toMatchObject({
PATH: "/usr/bin",
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1",
NEMOCLAW_NON_INTERACTIVE: "override",
NVIDIA_INFERENCE_API_KEY: "test-secret-overlay",
OPENSHELL_GATEWAY: "source-gateway",
});
expect(result.UNRELATED_SECRET).toBeUndefined();
expect(source).toEqual({
PATH: "/usr/bin",
OPENSHELL_GATEWAY: "source-gateway",
UNRELATED_SECRET: "must-not-pass",
});
});

it("centralizes test HOME CLI paths with caller precedence", () => {
const home = path.join(path.sep, "tmp", "nemoclaw-test-home");
const result = testHomeEnvironment(home, { HOME: "/override-home" }, { PATH: "/usr/bin" });

expect(result.HOME).toBe("/override-home");
expect(result.PATH?.split(path.delimiter)).toEqual([
path.join(home, ".local", "bin"),
path.join(home, ".npm-global", "bin"),
"/usr/bin",
]);
});

it("composes sandbox identity and secret-bearing overlays", () => {
const result = sandboxCommandEnvironment(
"e2e-profile",
{
COMPATIBLE_API_KEY: "compatible-secret",
NEMOCLAW_RECREATE_SANDBOX: "0",
},
{ PATH: "/usr/bin" },
);

expect(result).toMatchObject({
COMPATIBLE_API_KEY: "compatible-secret",
NEMOCLAW_RECREATE_SANDBOX: "0",
NEMOCLAW_SANDBOX_NAME: "e2e-profile",
OPENSHELL_GATEWAY: "nemoclaw",
});
});
});