Skip to content

Commit 7c47cd5

Browse files
authored
refactor: migrated server from python to node js backend (#16)
1 parent 8a985e1 commit 7c47cd5

24 files changed

+7944
-1208
lines changed

brev/welcome-ui/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules/
2+
__pycache__/
3+
*.pyc
4+
.vite/
5+
*.log

brev/welcome-ui/SERVER_ARCHITECTURE.md

Lines changed: 1369 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { describe, it, expect, beforeEach } from 'vitest';
5+
import serverModule from '../server.js';
6+
const { extractBrevId, maybeDetectBrevId, buildOpenclawUrl, _resetForTesting, PORT } = serverModule;
7+
8+
// === TC-B01 through TC-B10: Brev ID detection and URL building ===
9+
10+
describe("extractBrevId", () => {
11+
it("TC-B01: extracts ID from 80810-abcdef123.brevlab.com", () => {
12+
expect(extractBrevId("80810-abcdef123.brevlab.com")).toBe("abcdef123");
13+
});
14+
15+
it("TC-B02: extracts ID from 8080-xyz.brevlab.com", () => {
16+
expect(extractBrevId("8080-xyz.brevlab.com")).toBe("xyz");
17+
});
18+
19+
it("TC-B03: localhost:8081 returns empty string", () => {
20+
expect(extractBrevId("localhost:8081")).toBe("");
21+
});
22+
23+
it("TC-B04: non-matching host returns empty string", () => {
24+
expect(extractBrevId("example.com")).toBe("");
25+
expect(extractBrevId("")).toBe("");
26+
expect(extractBrevId("some.other.domain")).toBe("");
27+
});
28+
});
29+
30+
describe("maybeDetectBrevId + buildOpenclawUrl", () => {
31+
beforeEach(() => {
32+
_resetForTesting();
33+
});
34+
35+
it("TC-B05: detection is idempotent (once set, never overwritten)", () => {
36+
maybeDetectBrevId("80810-first-id.brevlab.com");
37+
maybeDetectBrevId("80810-second-id.brevlab.com");
38+
const url = buildOpenclawUrl(null);
39+
expect(url).toContain("first-id");
40+
expect(url).not.toContain("second-id");
41+
});
42+
43+
it("TC-B06: with Brev ID, URL is https://80810-{id}.brevlab.com/", () => {
44+
maybeDetectBrevId("80810-myenv.brevlab.com");
45+
expect(buildOpenclawUrl(null)).toBe("https://80810-myenv.brevlab.com/");
46+
});
47+
48+
it("TC-B07: with Brev ID + token, URL has ?token=xxx", () => {
49+
maybeDetectBrevId("80810-myenv.brevlab.com");
50+
expect(buildOpenclawUrl("tok123")).toBe(
51+
"https://80810-myenv.brevlab.com/?token=tok123"
52+
);
53+
});
54+
55+
it("TC-B08: without Brev ID, URL is http://127.0.0.1:{PORT}/", () => {
56+
const url = buildOpenclawUrl(null);
57+
expect(url).toBe(`http://127.0.0.1:${PORT}/`);
58+
});
59+
60+
it("TC-B09: BREV_ENV_ID env var takes priority over Host detection", () => {
61+
// BREV_ENV_ID is read at module load. If it was empty, detected takes over.
62+
// We test that detected ID is used when BREV_ENV_ID is not set.
63+
maybeDetectBrevId("80810-detected.brevlab.com");
64+
const url = buildOpenclawUrl(null);
65+
expect(url).toContain("detected");
66+
});
67+
68+
it("TC-B10: connection details gateway URL uses port 8080 not 8081", () => {
69+
maybeDetectBrevId("80810-env123.brevlab.com");
70+
// buildOpenclawUrl uses port 80810 (welcome-ui port in Brev)
71+
// The gateway URL is separate (tested in connection-details)
72+
const url = buildOpenclawUrl(null);
73+
expect(url).toContain("80810");
74+
expect(url).not.toContain("8080-");
75+
});
76+
});
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { describe, it, expect } from 'vitest';
5+
import setupModule from './setup.js';
6+
const { FIXTURES } = setupModule;
7+
import serverModule from '../server.js';
8+
const { stripAnsi, parseProviderDetail, parseClusterInference } = serverModule;
9+
10+
// === TC-CL01 through TC-CL12: CLI output parsing ===
11+
12+
describe("stripAnsi", () => {
13+
it("TC-CL01: strips green color code", () => {
14+
expect(stripAnsi("\x1b[32mhello\x1b[0m")).toBe("hello");
15+
});
16+
17+
it("TC-CL02: strips reset code", () => {
18+
expect(stripAnsi("text\x1b[0m more")).toBe("text more");
19+
});
20+
21+
it("TC-CL03: strips bold red code", () => {
22+
expect(stripAnsi("\x1b[1;31merror\x1b[0m")).toBe("error");
23+
});
24+
25+
it("TC-CL04: passes through text without ANSI codes unchanged", () => {
26+
const plain = "No colors here at all.";
27+
expect(stripAnsi(plain)).toBe(plain);
28+
});
29+
});
30+
31+
describe("parseProviderDetail", () => {
32+
it("TC-CL05: parses complete provider output", () => {
33+
const result = parseProviderDetail(FIXTURES.providerGetOutput);
34+
expect(result).toEqual({
35+
id: "abc-123",
36+
name: "nvidia-inference",
37+
type: "openai",
38+
credentialKeys: ["OPENAI_API_KEY"],
39+
configKeys: ["OPENAI_BASE_URL"],
40+
});
41+
});
42+
43+
it("TC-CL06: <none> for credential keys maps to empty array", () => {
44+
const result = parseProviderDetail(FIXTURES.providerGetNone);
45+
expect(result.credentialKeys).toEqual([]);
46+
});
47+
48+
it("TC-CL07: comma-separated config keys parsed into array", () => {
49+
const output = [
50+
"Name: multi",
51+
"Type: custom",
52+
"Config keys: KEY1, KEY2, KEY3",
53+
].join("\n");
54+
const result = parseProviderDetail(output);
55+
expect(result.configKeys).toEqual(["KEY1", "KEY2", "KEY3"]);
56+
});
57+
58+
it("TC-CL08: output missing Name line returns null", () => {
59+
const output = "Id: abc\nType: openai\n";
60+
expect(parseProviderDetail(output)).toBeNull();
61+
});
62+
63+
it("TC-CL09: ANSI codes in output are stripped before parsing", () => {
64+
const result = parseProviderDetail(FIXTURES.providerGetAnsi);
65+
expect(result).not.toBeNull();
66+
expect(result.name).toBe("nvidia-inference");
67+
expect(result.type).toBe("openai");
68+
});
69+
});
70+
71+
describe("parseClusterInference", () => {
72+
it("TC-CL10: parses Provider, Model, Version lines", () => {
73+
const result = parseClusterInference(FIXTURES.clusterInferenceOutput);
74+
expect(result).toEqual({
75+
providerName: "nvidia-inference",
76+
modelId: "meta/llama-3.1-70b-instruct",
77+
version: 2,
78+
});
79+
});
80+
81+
it("TC-CL11: non-integer version defaults to 0", () => {
82+
const output = "Provider: test\nModel: m\nVersion: abc\n";
83+
const result = parseClusterInference(output);
84+
expect(result.version).toBe(0);
85+
});
86+
87+
it("TC-CL12: missing Provider line returns null", () => {
88+
const output = "Model: m\nVersion: 1\n";
89+
expect(parseClusterInference(output)).toBeNull();
90+
});
91+
});
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { describe, it, expect, afterAll, beforeEach, vi } from 'vitest';
5+
import supertest from 'supertest';
6+
7+
vi.mock('child_process', () => ({
8+
execFile: vi.fn((cmd, args, opts, cb) => {
9+
if (typeof opts === 'function') { cb = opts; opts = {}; }
10+
cb(null, '', '');
11+
}),
12+
spawn: vi.fn(),
13+
}));
14+
15+
import { execFile, spawn } from 'child_process';
16+
import serverModule from '../server.js';
17+
const { server, _resetForTesting, _setMocksForTesting } = serverModule;
18+
import setupModule from './setup.js';
19+
const { cleanTempFiles, FIXTURES } = setupModule;
20+
const request = supertest;
21+
22+
// === TC-CI01 through TC-CI10: Cluster inference ===
23+
24+
describe("GET /api/cluster-inference", () => {
25+
beforeEach(() => {
26+
_resetForTesting();
27+
_setMocksForTesting({ execFile, spawn });
28+
cleanTempFiles();
29+
execFile.mockClear();
30+
});
31+
32+
afterAll(() => { server.close(); });
33+
34+
it("TC-CI01: returns parsed providerName, modelId, version on success", async () => {
35+
execFile.mockImplementation((cmd, args, opts, cb) => {
36+
if (typeof opts === "function") { cb = opts; opts = {}; }
37+
cb(null, FIXTURES.clusterInferenceOutput, "");
38+
});
39+
40+
const res = await request(server).get("/api/cluster-inference");
41+
expect(res.status).toBe(200);
42+
expect(res.body.ok).toBe(true);
43+
expect(res.body.providerName).toBe("nvidia-inference");
44+
expect(res.body.modelId).toBe("meta/llama-3.1-70b-instruct");
45+
expect(res.body.version).toBe(2);
46+
});
47+
48+
it("TC-CI02: returns nulls when 'not configured' in stderr", async () => {
49+
execFile.mockImplementation((cmd, args, opts, cb) => {
50+
if (typeof opts === "function") { cb = opts; opts = {}; }
51+
const err = new Error("fail");
52+
err.code = 1;
53+
cb(err, "", "cluster inference not configured");
54+
});
55+
56+
const res = await request(server).get("/api/cluster-inference");
57+
expect(res.status).toBe(200);
58+
expect(res.body.ok).toBe(true);
59+
expect(res.body.providerName).toBeNull();
60+
expect(res.body.modelId).toBe("");
61+
expect(res.body.version).toBe(0);
62+
});
63+
64+
it("TC-CI03: returns nulls when 'not found' in stderr", async () => {
65+
execFile.mockImplementation((cmd, args, opts, cb) => {
66+
if (typeof opts === "function") { cb = opts; opts = {}; }
67+
const err = new Error("fail");
68+
err.code = 1;
69+
cb(err, "", "inference config not found");
70+
});
71+
72+
const res = await request(server).get("/api/cluster-inference");
73+
expect(res.status).toBe(200);
74+
expect(res.body.providerName).toBeNull();
75+
});
76+
77+
it("TC-CI04: returns 400 on other CLI errors", async () => {
78+
execFile.mockImplementation((cmd, args, opts, cb) => {
79+
if (typeof opts === "function") { cb = opts; opts = {}; }
80+
const err = new Error("fail");
81+
err.code = 1;
82+
cb(err, "", "unexpected error occurred");
83+
});
84+
85+
const res = await request(server).get("/api/cluster-inference");
86+
expect(res.status).toBe(400);
87+
expect(res.body.ok).toBe(false);
88+
});
89+
90+
it("TC-CI05: ANSI codes in output are stripped before parsing", async () => {
91+
execFile.mockImplementation((cmd, args, opts, cb) => {
92+
if (typeof opts === "function") { cb = opts; opts = {}; }
93+
cb(null, FIXTURES.clusterInferenceAnsi, "");
94+
});
95+
96+
const res = await request(server).get("/api/cluster-inference");
97+
expect(res.status).toBe(200);
98+
expect(res.body.providerName).toBe("nvidia-inference");
99+
expect(res.body.modelId).toBe("meta/llama-3.1-70b-instruct");
100+
});
101+
});
102+
103+
describe("POST /api/cluster-inference", () => {
104+
beforeEach(() => {
105+
_resetForTesting();
106+
_setMocksForTesting({ execFile, spawn });
107+
cleanTempFiles();
108+
execFile.mockClear();
109+
});
110+
111+
it("TC-CI06: returns 200 with parsed output on success", async () => {
112+
execFile.mockImplementation((cmd, args, opts, cb) => {
113+
if (typeof opts === "function") { cb = opts; opts = {}; }
114+
cb(null, "Provider: my-prov\nModel: llama\nVersion: 1\n", "");
115+
});
116+
117+
const res = await request(server)
118+
.post("/api/cluster-inference")
119+
.send({ providerName: "my-prov", modelId: "llama" });
120+
expect(res.status).toBe(200);
121+
expect(res.body.ok).toBe(true);
122+
});
123+
124+
it("TC-CI07: returns 400 when providerName missing", async () => {
125+
const res = await request(server)
126+
.post("/api/cluster-inference")
127+
.send({ modelId: "llama" });
128+
expect(res.status).toBe(400);
129+
expect(res.body.error).toContain("providerName");
130+
});
131+
132+
it("TC-CI08: returns 400 when modelId missing", async () => {
133+
const res = await request(server)
134+
.post("/api/cluster-inference")
135+
.send({ providerName: "prov" });
136+
expect(res.status).toBe(400);
137+
expect(res.body.error).toContain("modelId");
138+
});
139+
140+
it("TC-CI09: returns 400 on CLI failure", async () => {
141+
execFile.mockImplementation((cmd, args, opts, cb) => {
142+
if (typeof opts === "function") { cb = opts; opts = {}; }
143+
const err = new Error("fail");
144+
err.code = 1;
145+
cb(err, "", "set failed");
146+
});
147+
148+
const res = await request(server)
149+
.post("/api/cluster-inference")
150+
.send({ providerName: "p", modelId: "m" });
151+
expect(res.status).toBe(400);
152+
});
153+
154+
it("TC-CI10: calls nemoclaw cluster inference set with --provider and --model", async () => {
155+
execFile.mockImplementation((cmd, args, opts, cb) => {
156+
if (typeof opts === "function") { cb = opts; opts = {}; }
157+
cb(null, "", "");
158+
});
159+
160+
await request(server)
161+
.post("/api/cluster-inference")
162+
.send({ providerName: "test-prov", modelId: "test-model" });
163+
164+
const setCall = execFile.mock.calls.find(
165+
(c) => c[0] === "nemoclaw" && c[1]?.includes("inference") && c[1]?.includes("set")
166+
);
167+
expect(setCall).toBeDefined();
168+
const args = setCall[1];
169+
expect(args).toContain("--provider");
170+
expect(args).toContain("test-prov");
171+
expect(args).toContain("--model");
172+
expect(args).toContain("test-model");
173+
});
174+
});

0 commit comments

Comments
 (0)