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
5 changes: 5 additions & 0 deletions brev/welcome-ui/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
__pycache__/
*.pyc
.vite/
*.log
1,369 changes: 1,369 additions & 0 deletions brev/welcome-ui/SERVER_ARCHITECTURE.md

Large diffs are not rendered by default.

76 changes: 76 additions & 0 deletions brev/welcome-ui/__tests__/brev-detection.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, it, expect, beforeEach } from 'vitest';
import serverModule from '../server.js';
const { extractBrevId, maybeDetectBrevId, buildOpenclawUrl, _resetForTesting, PORT } = serverModule;

// === TC-B01 through TC-B10: Brev ID detection and URL building ===

describe("extractBrevId", () => {
it("TC-B01: extracts ID from 80810-abcdef123.brevlab.com", () => {
expect(extractBrevId("80810-abcdef123.brevlab.com")).toBe("abcdef123");
});

it("TC-B02: extracts ID from 8080-xyz.brevlab.com", () => {
expect(extractBrevId("8080-xyz.brevlab.com")).toBe("xyz");
});

it("TC-B03: localhost:8081 returns empty string", () => {
expect(extractBrevId("localhost:8081")).toBe("");
});

it("TC-B04: non-matching host returns empty string", () => {
expect(extractBrevId("example.com")).toBe("");
expect(extractBrevId("")).toBe("");
expect(extractBrevId("some.other.domain")).toBe("");
});
});

describe("maybeDetectBrevId + buildOpenclawUrl", () => {
beforeEach(() => {
_resetForTesting();
});

it("TC-B05: detection is idempotent (once set, never overwritten)", () => {
maybeDetectBrevId("80810-first-id.brevlab.com");
maybeDetectBrevId("80810-second-id.brevlab.com");
const url = buildOpenclawUrl(null);
expect(url).toContain("first-id");
expect(url).not.toContain("second-id");
});

it("TC-B06: with Brev ID, URL is https://80810-{id}.brevlab.com/", () => {
maybeDetectBrevId("80810-myenv.brevlab.com");
expect(buildOpenclawUrl(null)).toBe("https://80810-myenv.brevlab.com/");
});

it("TC-B07: with Brev ID + token, URL has ?token=xxx", () => {
maybeDetectBrevId("80810-myenv.brevlab.com");
expect(buildOpenclawUrl("tok123")).toBe(
"https://80810-myenv.brevlab.com/?token=tok123"
);
});

it("TC-B08: without Brev ID, URL is http://127.0.0.1:{PORT}/", () => {
const url = buildOpenclawUrl(null);
expect(url).toBe(`http://127.0.0.1:${PORT}/`);
});

it("TC-B09: BREV_ENV_ID env var takes priority over Host detection", () => {
// BREV_ENV_ID is read at module load. If it was empty, detected takes over.
// We test that detected ID is used when BREV_ENV_ID is not set.
maybeDetectBrevId("80810-detected.brevlab.com");
const url = buildOpenclawUrl(null);
expect(url).toContain("detected");
});

it("TC-B10: connection details gateway URL uses port 8080 not 8081", () => {
maybeDetectBrevId("80810-env123.brevlab.com");
// buildOpenclawUrl uses port 80810 (welcome-ui port in Brev)
// The gateway URL is separate (tested in connection-details)
const url = buildOpenclawUrl(null);
expect(url).toContain("80810");
expect(url).not.toContain("8080-");
});
});
91 changes: 91 additions & 0 deletions brev/welcome-ui/__tests__/cli-parsing.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, it, expect } from 'vitest';
import setupModule from './setup.js';
const { FIXTURES } = setupModule;
import serverModule from '../server.js';
const { stripAnsi, parseProviderDetail, parseClusterInference } = serverModule;

// === TC-CL01 through TC-CL12: CLI output parsing ===

describe("stripAnsi", () => {
it("TC-CL01: strips green color code", () => {
expect(stripAnsi("\x1b[32mhello\x1b[0m")).toBe("hello");
});

it("TC-CL02: strips reset code", () => {
expect(stripAnsi("text\x1b[0m more")).toBe("text more");
});

it("TC-CL03: strips bold red code", () => {
expect(stripAnsi("\x1b[1;31merror\x1b[0m")).toBe("error");
});

it("TC-CL04: passes through text without ANSI codes unchanged", () => {
const plain = "No colors here at all.";
expect(stripAnsi(plain)).toBe(plain);
});
});

describe("parseProviderDetail", () => {
it("TC-CL05: parses complete provider output", () => {
const result = parseProviderDetail(FIXTURES.providerGetOutput);
expect(result).toEqual({
id: "abc-123",
name: "nvidia-inference",
type: "openai",
credentialKeys: ["OPENAI_API_KEY"],
configKeys: ["OPENAI_BASE_URL"],
});
});

it("TC-CL06: <none> for credential keys maps to empty array", () => {
const result = parseProviderDetail(FIXTURES.providerGetNone);
expect(result.credentialKeys).toEqual([]);
});

it("TC-CL07: comma-separated config keys parsed into array", () => {
const output = [
"Name: multi",
"Type: custom",
"Config keys: KEY1, KEY2, KEY3",
].join("\n");
const result = parseProviderDetail(output);
expect(result.configKeys).toEqual(["KEY1", "KEY2", "KEY3"]);
});

it("TC-CL08: output missing Name line returns null", () => {
const output = "Id: abc\nType: openai\n";
expect(parseProviderDetail(output)).toBeNull();
});

it("TC-CL09: ANSI codes in output are stripped before parsing", () => {
const result = parseProviderDetail(FIXTURES.providerGetAnsi);
expect(result).not.toBeNull();
expect(result.name).toBe("nvidia-inference");
expect(result.type).toBe("openai");
});
});

describe("parseClusterInference", () => {
it("TC-CL10: parses Provider, Model, Version lines", () => {
const result = parseClusterInference(FIXTURES.clusterInferenceOutput);
expect(result).toEqual({
providerName: "nvidia-inference",
modelId: "meta/llama-3.1-70b-instruct",
version: 2,
});
});

it("TC-CL11: non-integer version defaults to 0", () => {
const output = "Provider: test\nModel: m\nVersion: abc\n";
const result = parseClusterInference(output);
expect(result.version).toBe(0);
});

it("TC-CL12: missing Provider line returns null", () => {
const output = "Model: m\nVersion: 1\n";
expect(parseClusterInference(output)).toBeNull();
});
});
174 changes: 174 additions & 0 deletions brev/welcome-ui/__tests__/cluster-inference.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, it, expect, afterAll, beforeEach, vi } from 'vitest';
import supertest from 'supertest';

vi.mock('child_process', () => ({
execFile: vi.fn((cmd, args, opts, cb) => {
if (typeof opts === 'function') { cb = opts; opts = {}; }
cb(null, '', '');
}),
spawn: vi.fn(),
}));

import { execFile, spawn } from 'child_process';
import serverModule from '../server.js';
const { server, _resetForTesting, _setMocksForTesting } = serverModule;
import setupModule from './setup.js';
const { cleanTempFiles, FIXTURES } = setupModule;
const request = supertest;

// === TC-CI01 through TC-CI10: Cluster inference ===

describe("GET /api/cluster-inference", () => {
beforeEach(() => {
_resetForTesting();
_setMocksForTesting({ execFile, spawn });
cleanTempFiles();
execFile.mockClear();
});

afterAll(() => { server.close(); });

it("TC-CI01: returns parsed providerName, modelId, version on success", async () => {
execFile.mockImplementation((cmd, args, opts, cb) => {
if (typeof opts === "function") { cb = opts; opts = {}; }
cb(null, FIXTURES.clusterInferenceOutput, "");
});

const res = await request(server).get("/api/cluster-inference");
expect(res.status).toBe(200);
expect(res.body.ok).toBe(true);
expect(res.body.providerName).toBe("nvidia-inference");
expect(res.body.modelId).toBe("meta/llama-3.1-70b-instruct");
expect(res.body.version).toBe(2);
});

it("TC-CI02: returns nulls when 'not configured' in stderr", async () => {
execFile.mockImplementation((cmd, args, opts, cb) => {
if (typeof opts === "function") { cb = opts; opts = {}; }
const err = new Error("fail");
err.code = 1;
cb(err, "", "cluster inference not configured");
});

const res = await request(server).get("/api/cluster-inference");
expect(res.status).toBe(200);
expect(res.body.ok).toBe(true);
expect(res.body.providerName).toBeNull();
expect(res.body.modelId).toBe("");
expect(res.body.version).toBe(0);
});

it("TC-CI03: returns nulls when 'not found' in stderr", async () => {
execFile.mockImplementation((cmd, args, opts, cb) => {
if (typeof opts === "function") { cb = opts; opts = {}; }
const err = new Error("fail");
err.code = 1;
cb(err, "", "inference config not found");
});

const res = await request(server).get("/api/cluster-inference");
expect(res.status).toBe(200);
expect(res.body.providerName).toBeNull();
});

it("TC-CI04: returns 400 on other CLI errors", async () => {
execFile.mockImplementation((cmd, args, opts, cb) => {
if (typeof opts === "function") { cb = opts; opts = {}; }
const err = new Error("fail");
err.code = 1;
cb(err, "", "unexpected error occurred");
});

const res = await request(server).get("/api/cluster-inference");
expect(res.status).toBe(400);
expect(res.body.ok).toBe(false);
});

it("TC-CI05: ANSI codes in output are stripped before parsing", async () => {
execFile.mockImplementation((cmd, args, opts, cb) => {
if (typeof opts === "function") { cb = opts; opts = {}; }
cb(null, FIXTURES.clusterInferenceAnsi, "");
});

const res = await request(server).get("/api/cluster-inference");
expect(res.status).toBe(200);
expect(res.body.providerName).toBe("nvidia-inference");
expect(res.body.modelId).toBe("meta/llama-3.1-70b-instruct");
});
});

describe("POST /api/cluster-inference", () => {
beforeEach(() => {
_resetForTesting();
_setMocksForTesting({ execFile, spawn });
cleanTempFiles();
execFile.mockClear();
});

it("TC-CI06: returns 200 with parsed output on success", async () => {
execFile.mockImplementation((cmd, args, opts, cb) => {
if (typeof opts === "function") { cb = opts; opts = {}; }
cb(null, "Provider: my-prov\nModel: llama\nVersion: 1\n", "");
});

const res = await request(server)
.post("/api/cluster-inference")
.send({ providerName: "my-prov", modelId: "llama" });
expect(res.status).toBe(200);
expect(res.body.ok).toBe(true);
});

it("TC-CI07: returns 400 when providerName missing", async () => {
const res = await request(server)
.post("/api/cluster-inference")
.send({ modelId: "llama" });
expect(res.status).toBe(400);
expect(res.body.error).toContain("providerName");
});

it("TC-CI08: returns 400 when modelId missing", async () => {
const res = await request(server)
.post("/api/cluster-inference")
.send({ providerName: "prov" });
expect(res.status).toBe(400);
expect(res.body.error).toContain("modelId");
});

it("TC-CI09: returns 400 on CLI failure", async () => {
execFile.mockImplementation((cmd, args, opts, cb) => {
if (typeof opts === "function") { cb = opts; opts = {}; }
const err = new Error("fail");
err.code = 1;
cb(err, "", "set failed");
});

const res = await request(server)
.post("/api/cluster-inference")
.send({ providerName: "p", modelId: "m" });
expect(res.status).toBe(400);
});

it("TC-CI10: calls nemoclaw cluster inference set with --provider and --model", async () => {
execFile.mockImplementation((cmd, args, opts, cb) => {
if (typeof opts === "function") { cb = opts; opts = {}; }
cb(null, "", "");
});

await request(server)
.post("/api/cluster-inference")
.send({ providerName: "test-prov", modelId: "test-model" });

const setCall = execFile.mock.calls.find(
(c) => c[0] === "nemoclaw" && c[1]?.includes("inference") && c[1]?.includes("set")
);
expect(setCall).toBeDefined();
const args = setCall[1];
expect(args).toContain("--provider");
expect(args).toContain("test-prov");
expect(args).toContain("--model");
expect(args).toContain("test-model");
});
});
Loading