9928954ea6
Co-authored-by: Codex Agent <codex@hwlab.local>
930 lines
39 KiB
TypeScript
930 lines
39 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
|
|
import { createServer } from "node:http";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { test } from "bun:test";
|
|
|
|
import { runDevicePodCli } from "./src/device-pod-cli-lib.ts";
|
|
|
|
const ASSEMBLED_RUNTIME_ENV = {
|
|
HWLAB_RUNTIME_API_URL: "http://cloud.test",
|
|
HWLAB_RUNTIME_NAMESPACE: "hwlab-v02",
|
|
HWLAB_RUNTIME_LANE: "v02",
|
|
HWLAB_RUNTIME_ENDPOINT_LOCKED: "1",
|
|
HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1",
|
|
HWLAB_DEVICE_POD_API_KEY: "device-pod-api-key-a"
|
|
};
|
|
|
|
function runAssembledDevicePodCli(argv: string[], options: any = {}) {
|
|
return runDevicePodCli(argv, { ...options, env: { ...ASSEMBLED_RUNTIME_ENV, ...(options.env ?? {}) } });
|
|
}
|
|
|
|
async function runHostCli(root: string, profile: any, args: string[]) {
|
|
const profileJsonB64 = Buffer.from(JSON.stringify(profile), "utf8").toString("base64");
|
|
const proc = Bun.spawn([
|
|
process.env.HWLAB_TEST_NODE_COMMAND || "node",
|
|
path.join(process.cwd(), "skills/device-pod-cli/assets/device-host-cli.mjs"),
|
|
"--profile-json-b64",
|
|
profileJsonB64,
|
|
"--pod-id",
|
|
profile.devicePodId || "device-pod-test",
|
|
...args
|
|
], { cwd: root, stdout: "pipe", stderr: "pipe" });
|
|
const stdout = await new Response(proc.stdout).text();
|
|
const stderr = await new Response(proc.stderr).text();
|
|
const exitCode = await proc.exited;
|
|
assert.equal(stderr, "");
|
|
return { exitCode, payload: JSON.parse(stdout) };
|
|
}
|
|
|
|
test("device-pod-cli first-admin setup can seed a cloud-api device pod without local profile authority", async () => {
|
|
const seen: any[] = [];
|
|
const profile = { schemaVersion: 1, target: { id: "target-71-freq" }, route: { gatewaySessionId: "gws_admin_seed" } };
|
|
const result = await runAssembledDevicePodCli([
|
|
"--username", "admin",
|
|
"--password", "admin-pass",
|
|
"--pod-id", "device-pod-71-freq",
|
|
"--name", "71-FREQ",
|
|
"--profile-json", JSON.stringify(profile),
|
|
"setup",
|
|
"first-admin"
|
|
], {
|
|
fetchImpl: async (url, init) => {
|
|
seen.push({ url: String(url), init, body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return new Response(JSON.stringify({ created: true, actor: { id: "usr_admin", role: "admin" }, devicePodBootstrap: { requested: 1, initialized: 1 } }), { status: 201, headers: { "content-type": "application/json", "set-cookie": "hwlab_session=session-a" } });
|
|
},
|
|
now: () => "2026-05-29T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(result.payload.action, "setup.first-admin");
|
|
assert.equal(result.payload.devicePodSeedProvided, true);
|
|
assert.equal(result.payload.localProfileAuthority, false);
|
|
assert.equal(result.payload.setCookie, "hwlab_session=session-a");
|
|
assert.equal(seen[0].url, "http://cloud.test/v1/setup/first-admin");
|
|
assert.equal(seen[0].init.headers["x-hwlab-session-token"], undefined);
|
|
assert.deepEqual(seen[0].body.devicePod, { devicePodId: "device-pod-71-freq", name: "71-FREQ", profile });
|
|
});
|
|
|
|
test("device-pod-cli admin wrappers call cloud-api profile and grant routes", async () => {
|
|
const seen: any[] = [];
|
|
const profile = { schemaVersion: 1, target: { id: "target-71-freq" }, route: { gatewaySessionId: "gws_admin_seed" } };
|
|
const fetchImpl = async (url, init) => {
|
|
seen.push({ url: String(url), init, body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(201, { ok: true });
|
|
};
|
|
const adminEnv = {
|
|
HWLAB_RUNTIME_API_URL: "http://cloud.test",
|
|
HWLAB_RUNTIME_NAMESPACE: "hwlab-v02",
|
|
HWLAB_RUNTIME_LANE: "v02",
|
|
HWLAB_RUNTIME_ENDPOINT_LOCKED: "1",
|
|
HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1",
|
|
HWLAB_CLOUD_API_SESSION_TOKEN: "session-a"
|
|
};
|
|
const upsert = await runDevicePodCli([
|
|
"--pod-id", "device-pod-71-freq",
|
|
"--profile-json", JSON.stringify(profile),
|
|
"admin",
|
|
"device-pod",
|
|
"upsert"
|
|
], { env: adminEnv, fetchImpl, now: () => "2026-05-29T00:00:00.000Z" });
|
|
assert.equal(upsert.exitCode, 0);
|
|
assert.equal(seen[0].url, "http://cloud.test/v1/admin/device-pods");
|
|
assert.equal(seen[0].init.headers["x-hwlab-session-token"], "session-a");
|
|
assert.equal(seen[0].init.headers["x-hwlab-device-pod-api-key"], undefined);
|
|
assert.deepEqual(seen[0].body, { devicePodId: "device-pod-71-freq", profile });
|
|
|
|
const grant = await runDevicePodCli([
|
|
"--pod-id", "device-pod-71-freq",
|
|
"--user-id", "usr_alice",
|
|
"admin",
|
|
"grant"
|
|
], { env: adminEnv, fetchImpl, now: () => "2026-05-29T00:00:00.000Z" });
|
|
assert.equal(grant.exitCode, 0);
|
|
assert.equal(seen[1].url, "http://cloud.test/v1/admin/device-pod-grants");
|
|
assert.deepEqual(seen[1].body, { devicePodId: "device-pod-71-freq", userId: "usr_alice" });
|
|
});
|
|
|
|
test("device-pod-cli lists cloud-api authority and ignores local profile routes", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-device-pod-cli-"));
|
|
const profileDir = path.join(root, ".device-pod");
|
|
await mkdir(profileDir, { recursive: true });
|
|
await writeFile(path.join(profileDir, "device-pod-71-freq.json"), JSON.stringify({ gatewaySessionId: "gws_local_should_not_be_read", hostWorkspaceRoot: "F:\\legacy" }), "utf8");
|
|
const seen: any[] = [];
|
|
const result = await runAssembledDevicePodCli(["profile", "list"], {
|
|
env: { DEVICE_POD_PROFILE_DIR: profileDir },
|
|
fetchImpl: async (url, init) => {
|
|
seen.push({ url: String(url), init });
|
|
return jsonResponse(200, { ok: true, contractVersion: "device-pod-authority-v1", devicePods: [{ devicePodId: "device-pod-71-freq", profileHash: "sha256:abc", profile: { route: { gatewaySessionId: "redacted" } } }] });
|
|
},
|
|
now: () => "2026-05-29T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(result.payload.localProfileAuthority, false);
|
|
assert.equal(result.payload.localProfileRead, false);
|
|
assert.equal(seen[0].url, "http://cloud.test/v1/device-pods");
|
|
assert.equal(seen[0].init.headers["x-hwlab-session-token"], undefined);
|
|
assert.equal(seen[0].init.headers["x-hwlab-device-pod-api-key"], "device-pod-api-key-a");
|
|
assert.equal(JSON.stringify(result.payload).includes("gws_local_should_not_be_read"), false);
|
|
});
|
|
|
|
test("device-pod-cli auto-locates from assembled runtime and rejects manual URL in locked runners", async () => {
|
|
const ok = await runAssembledDevicePodCli(["profile", "list"], {
|
|
fetchImpl: async (url, init) => {
|
|
assert.equal(String(url), "http://cloud.test/v1/device-pods");
|
|
assert.equal(init?.headers?.["x-hwlab-session-token"], undefined);
|
|
assert.equal(init?.headers?.["x-hwlab-device-pod-api-key"], "device-pod-api-key-a");
|
|
return jsonResponse(200, { ok: true, devicePods: [] });
|
|
},
|
|
now: () => "2026-06-01T00:00:00.000Z"
|
|
});
|
|
assert.equal(ok.exitCode, 0);
|
|
assert.equal(ok.payload.apiBaseUrl, "http://cloud.test");
|
|
assert.equal(ok.payload.runtimeEndpoint.source, "runtime-env");
|
|
assert.equal(ok.payload.runtimeEndpoint.explicitOverride, false);
|
|
|
|
const rejected = await runAssembledDevicePodCli(["--api-base-url", "http://74.48.78.17:17667", "profile", "list"], {
|
|
fetchImpl: async () => jsonResponse(500, { ok: false }),
|
|
now: () => "2026-06-01T00:00:00.000Z"
|
|
});
|
|
assert.equal(rejected.exitCode, 1);
|
|
assert.equal(rejected.payload.error.code, "runtime_endpoint_manual_url_forbidden");
|
|
});
|
|
|
|
test("device-pod-cli default transport uses native HTTP for assembled cloud-api URLs", async () => {
|
|
const seen: any[] = [];
|
|
const server = createServer((request, response) => {
|
|
let body = "";
|
|
request.on("data", (chunk) => { body += chunk; });
|
|
request.on("end", () => {
|
|
seen.push({ method: request.method, url: request.url, headers: request.headers, body });
|
|
response.writeHead(200, { "content-type": "application/json" });
|
|
response.end(JSON.stringify({ ok: true, devicePods: [] }));
|
|
});
|
|
});
|
|
await listen(server);
|
|
try {
|
|
const result = await runDevicePodCli(["profile", "list"], {
|
|
env: {
|
|
...ASSEMBLED_RUNTIME_ENV,
|
|
HWLAB_RUNTIME_API_URL: serverUrl(server)
|
|
},
|
|
now: () => "2026-06-02T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(seen[0].method, "GET");
|
|
assert.equal(seen[0].url, "/v1/device-pods");
|
|
assert.equal(seen[0].headers["x-hwlab-device-pod-api-key"], "device-pod-api-key-a");
|
|
} finally {
|
|
await close(server);
|
|
}
|
|
});
|
|
|
|
test("device-pod-cli reuses matching hwlab-cli web session cookie when no explicit auth is assembled", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-device-pod-cli-session-"));
|
|
await mkdir(path.join(root, ".state/hwlab-cli"), { recursive: true });
|
|
await writeFile(path.join(root, ".state/hwlab-cli/session.json"), JSON.stringify({
|
|
baseUrl: "http://74.48.78.17:19666",
|
|
cookie: "hwlab_session=session-from-web",
|
|
user: { username: "admin" }
|
|
}), "utf8");
|
|
const originalCwd = process.cwd();
|
|
process.chdir(root);
|
|
try {
|
|
const result = await runDevicePodCli(["profile", "list"], {
|
|
env: {
|
|
HWLAB_RUNTIME_NAMESPACE: "hwlab-v02",
|
|
HWLAB_RUNTIME_LANE: "v02",
|
|
HWLAB_RUNTIME_ENDPOINT_LOCKED: "1",
|
|
HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1"
|
|
},
|
|
fetchImpl: async (url, init) => {
|
|
assert.equal(String(url), "http://74.48.78.17:19667/v1/device-pods");
|
|
assert.equal(init?.headers?.cookie, "hwlab_session=session-from-web");
|
|
assert.equal(init?.headers?.["x-hwlab-session-token"], undefined);
|
|
return jsonResponse(200, { ok: true, devicePods: [] });
|
|
},
|
|
now: () => "2026-06-02T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(result.payload.runtimeEndpoint.source, "runtime-namespace");
|
|
assert.equal(result.payload.runtimeEndpoint.explicitOverride, false);
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
}
|
|
});
|
|
|
|
test("device-pod-cli ignores stale hwlab-cli web session from another runtime scope", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-device-pod-cli-stale-session-"));
|
|
await mkdir(path.join(root, ".state/hwlab-cli"), { recursive: true });
|
|
await writeFile(path.join(root, ".state/hwlab-cli/session.json"), JSON.stringify({
|
|
baseUrl: "http://74.48.78.17:17666",
|
|
cookie: "hwlab_session=dev-session"
|
|
}), "utf8");
|
|
const originalCwd = process.cwd();
|
|
process.chdir(root);
|
|
try {
|
|
const result = await runDevicePodCli(["profile", "list"], {
|
|
env: {
|
|
HWLAB_RUNTIME_NAMESPACE: "hwlab-v02",
|
|
HWLAB_RUNTIME_LANE: "v02",
|
|
HWLAB_RUNTIME_ENDPOINT_LOCKED: "1",
|
|
HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1"
|
|
},
|
|
fetchImpl: async (_url, init) => {
|
|
assert.equal(init?.headers?.cookie, undefined);
|
|
return jsonResponse(401, { ok: false, error: { code: "auth_required" } });
|
|
},
|
|
now: () => "2026-06-02T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 1);
|
|
assert.equal(result.payload.httpStatus, 401);
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
}
|
|
});
|
|
|
|
test("device-pod-cli keeps manual API URL only as unlocked local debug override", async () => {
|
|
const seen: string[] = [];
|
|
const result = await runDevicePodCli(["--api-base-url", "http://debug.test", "--session-token", "debug-session", "profile", "list"], {
|
|
fetchImpl: async (url, init) => {
|
|
seen.push(String(url));
|
|
assert.equal(init?.headers?.["x-hwlab-session-token"], "debug-session");
|
|
return jsonResponse(200, { ok: true, devicePods: [] });
|
|
},
|
|
now: () => "2026-06-01T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(seen[0], "http://debug.test/v1/device-pods");
|
|
assert.equal(result.payload.runtimeEndpoint.source, "explicit-override");
|
|
});
|
|
|
|
test("device-pod-cli selector creates REST job instead of gateway RPC", async () => {
|
|
const seen: any[] = [];
|
|
const result = await runAssembledDevicePodCli(["device-pod-71-freq:workspace:/src", "ls"], {
|
|
fetchImpl: async (url, init) => {
|
|
seen.push({ url: String(url), init, body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(202, { ok: true, status: "running", job: { id: "job_1", status: "running" }, profileHash: "sha256:abc" });
|
|
},
|
|
now: () => "2026-05-29T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(seen[0].url, "http://cloud.test/v1/device-pods/device-pod-71-freq/jobs");
|
|
assert.deepEqual(seen[0].body, { intent: "workspace.ls", args: { path: "src" } });
|
|
assert.equal(JSON.stringify(seen[0].body).includes("gatewaySessionId"), false);
|
|
});
|
|
|
|
test("device-pod-cli bootsharp creates a formal workspace bootsharp REST job", async () => {
|
|
const seen: any[] = [];
|
|
const result = await runAssembledDevicePodCli(["bootsharp", "--pod-id", "D601-F103-V2", "--depth", "2"], {
|
|
fetchImpl: async (url, init) => {
|
|
seen.push({ url: String(url), init, body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(202, { ok: true, status: "running", job: { id: "job_bootsharp", status: "running" }, profileHash: "sha256:abc" });
|
|
},
|
|
now: () => "2026-05-31T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(seen[0].url, "http://cloud.test/v1/device-pods/D601-F103-V2/jobs");
|
|
assert.deepEqual(seen[0].body, { intent: "workspace.bootsharp", args: { path: ".", depth: 2 } });
|
|
assert.equal(result.payload.localProfileAuthority, false);
|
|
});
|
|
|
|
test("device-pod-cli job output is compact by default and full with --full", async () => {
|
|
const payload = {
|
|
serviceId: "hwlab-cloud-api",
|
|
contractVersion: "device-pod-job-v1",
|
|
status: "completed",
|
|
devicePodId: "D601-F103-V2",
|
|
traceId: "trc_job",
|
|
operationId: "op_job",
|
|
job: { id: "job_1", status: "completed" },
|
|
output: {
|
|
text: "x".repeat(5000),
|
|
dispatch: { command: "verbose command that should stay out of compact output" },
|
|
bytes: 5000,
|
|
summary: "gateway/device-host-cli dispatch completed"
|
|
}
|
|
};
|
|
const fetchImpl = async () => jsonResponse(200, payload);
|
|
const compact = await runAssembledDevicePodCli([
|
|
"job", "output",
|
|
"--pod-id", "D601-F103-V2",
|
|
"job_1"
|
|
], { fetchImpl, now: () => "2026-05-31T00:00:00.000Z" });
|
|
assert.equal(compact.exitCode, 0);
|
|
assert.equal(compact.payload.body.compacted, true);
|
|
assert.equal(compact.payload.body.text.length, 4000);
|
|
assert.equal(JSON.stringify(compact.payload).includes("verbose command"), false);
|
|
|
|
const full = await runAssembledDevicePodCli([
|
|
"--full",
|
|
"job", "output",
|
|
"--pod-id", "D601-F103-V2",
|
|
"job_1"
|
|
], { fetchImpl, now: () => "2026-05-31T00:00:00.000Z" });
|
|
assert.equal(full.exitCode, 0);
|
|
assert.equal(full.payload.full, true);
|
|
assert.equal(full.payload.body.output.dispatch.command, "verbose command that should stay out of compact output");
|
|
});
|
|
|
|
test("device-pod-cli sends reason without extra token for mutating jobs", async () => {
|
|
const seen: any[] = [];
|
|
const result = await runAssembledDevicePodCli(["--reason", "reset smoke", "device-pod-71-freq:debug-probe", "reset"], {
|
|
fetchImpl: async (url, init) => {
|
|
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(202, { ok: true, status: "running", job: { id: "job_reset", status: "running" } });
|
|
},
|
|
now: () => "2026-05-29T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.deepEqual(seen[0].body, { intent: "debug.reset", args: {}, reason: "reset smoke" });
|
|
});
|
|
|
|
test("device-pod-cli rejects standalone lease command with standard path guidance", async () => {
|
|
let called = false;
|
|
const result = await runAssembledDevicePodCli(["lease", "current", "--pod-id", "D601-F103-V2"], {
|
|
fetchImpl: async () => {
|
|
called = true;
|
|
return jsonResponse(500, { ok: false });
|
|
},
|
|
now: () => "2026-06-02T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 1);
|
|
assert.equal(called, false);
|
|
assert.equal(result.payload.error.code, "standalone_lease_command_removed");
|
|
assert.match(result.payload.error.message, /Device Pod jobs through cloud-api/);
|
|
assert.equal(JSON.stringify(result.payload.error.details.next).includes("hwpod lease acquire/current/release"), true);
|
|
});
|
|
|
|
test("device-pod-cli preserves common v0.1 hardware options in REST job args", async () => {
|
|
const seen: any[] = [];
|
|
const result = await runAssembledDevicePodCli([
|
|
"--reason", "boot log",
|
|
"--capture-uart", "uart/1",
|
|
"--capture-duration-ms", "8000",
|
|
"--port", "COM4",
|
|
"--baud-rate", "921600",
|
|
"device-pod-71-freq:debug-probe",
|
|
"download",
|
|
"start"
|
|
], {
|
|
fetchImpl: async (url, init) => {
|
|
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(202, { ok: true, status: "running", job: { id: "job_download", status: "running" } });
|
|
},
|
|
now: () => "2026-05-29T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.deepEqual(seen[0].body, {
|
|
intent: "debug.download",
|
|
args: { action: "start", captureUart: "uart/1", captureDurationMs: 8000, port: "COM4", baudRate: 921600 },
|
|
reason: "boot log"
|
|
});
|
|
});
|
|
|
|
test("device-pod-cli exposes workspace writes, keil source edits, and UART JSON-RPC as REST jobs", async () => {
|
|
const seen: any[] = [];
|
|
const fetchImpl = async (url, init) => {
|
|
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(202, { ok: true, status: "running", job: { id: `job_${seen.length}`, status: "running" } });
|
|
};
|
|
|
|
const put = await runAssembledDevicePodCli([
|
|
"--reason", "write file",
|
|
"device-pod-71-freq:workspace:/projects/app",
|
|
"put",
|
|
"User/new.c",
|
|
"--create-dirs"
|
|
], { fetchImpl, stdinText: "int main(void) { return 0; }\n", now: () => "2026-05-29T00:00:00.000Z" });
|
|
assert.equal(put.exitCode, 0);
|
|
assert.equal(seen[0].body.intent, "workspace.put");
|
|
assert.equal(seen[0].body.args.path, "projects/app/User/new.c");
|
|
assert.equal(seen[0].body.args.createDirs, true);
|
|
assert.equal(Buffer.from(seen[0].body.args.contentB64, "base64").toString("utf8"), "int main(void) { return 0; }\n");
|
|
|
|
const keil = await runAssembledDevicePodCli([
|
|
"--reason", "add source",
|
|
"device-pod-71-freq:workspace:/projects/app",
|
|
"keil",
|
|
"add-source",
|
|
"User/new.c",
|
|
"--group", "User"
|
|
], { fetchImpl, now: () => "2026-05-29T00:00:00.000Z" });
|
|
assert.equal(keil.exitCode, 0);
|
|
assert.deepEqual(seen[1].body.args, { action: "add-source", base: "projects/app", path: "User/new.c", group: "User" });
|
|
|
|
const jsonrpc = await runAssembledDevicePodCli([
|
|
"--reason", "gpio read",
|
|
"device-pod-71-freq:io-probe:/uart/1",
|
|
"jsonrpc",
|
|
"gpio.read",
|
|
"--params-json", "{\"pin\":\"PB5\"}",
|
|
"--require-jsonrpc-result",
|
|
"--expect-result-field", "pin",
|
|
"--expect-result-field", "value"
|
|
], { fetchImpl, now: () => "2026-05-29T00:00:00.000Z" });
|
|
assert.equal(jsonrpc.exitCode, 0);
|
|
assert.equal(seen[2].body.intent, "io.uart.jsonrpc");
|
|
assert.deepEqual(seen[2].body.args.expectResultField, ["pin", "value"]);
|
|
assert.equal(seen[2].body.args.params, "{\"pin\":\"PB5\"}");
|
|
});
|
|
|
|
test("device-pod-cli rejects legacy local profile create in formal mode", async () => {
|
|
const result = await runDevicePodCli(["profile", "create", "--pod-id", "device-pod-71-freq"], { now: () => "2026-05-29T00:00:00.000Z" });
|
|
assert.equal(result.exitCode, 1);
|
|
assert.equal(result.payload.error.code, "legacy_profile_create_removed");
|
|
});
|
|
|
|
test("device-pod-cli keeps debug-probe status as a probe operation, not job status", async () => {
|
|
const seen: any[] = [];
|
|
const result = await runAssembledDevicePodCli(["D601-F103-V2:debug-probe", "status"], {
|
|
fetchImpl: async (url, init) => {
|
|
seen.push({ url: String(url), init });
|
|
return jsonResponse(200, { ok: true, devicePodId: "D601-F103-V2", status: "ok" });
|
|
},
|
|
now: () => "2026-05-31T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(seen[0].url, "http://cloud.test/v1/device-pods/D601-F103-V2/status");
|
|
assert.equal(result.payload.action, "debug.status");
|
|
});
|
|
|
|
test("device-pod-cli forwards host build and download job ids as named device-pod jobs", async () => {
|
|
const seen: any[] = [];
|
|
const fetchImpl = async (url, init) => {
|
|
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(202, { ok: true, status: "running", job: { id: `job_${seen.length}`, status: "running" } });
|
|
};
|
|
const build = await runAssembledDevicePodCli([
|
|
"D601-F103-V2:workspace:/",
|
|
"build",
|
|
"status",
|
|
"20260531101208650-keil-build-030aab"
|
|
], { fetchImpl, now: () => "2026-05-31T00:00:00.000Z" });
|
|
assert.equal(build.exitCode, 0);
|
|
assert.deepEqual(seen[0].body, {
|
|
intent: "workspace.build",
|
|
args: { action: "status", jobId: "20260531101208650-keil-build-030aab" }
|
|
});
|
|
|
|
const download = await runAssembledDevicePodCli([
|
|
"D601-F103-V2:debug-probe",
|
|
"download",
|
|
"status",
|
|
"20260531103014862-keil-download-177dd9"
|
|
], { fetchImpl, now: () => "2026-05-31T00:00:00.000Z" });
|
|
assert.equal(download.exitCode, 0);
|
|
assert.deepEqual(seen[1].body, {
|
|
intent: "debug.download",
|
|
args: { action: "status", jobId: "20260531103014862-keil-download-177dd9" }
|
|
});
|
|
});
|
|
|
|
test("device-pod-cli maps build evidence / download evidence to first-class workspace.evidence / debug.evidence intents (HWLAB #801)", async () => {
|
|
const seen: any[] = [];
|
|
const fetchImpl = async (url, init) => {
|
|
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(202, { ok: true, status: "running", job: { id: `job_${seen.length}`, status: "running" } });
|
|
};
|
|
const build = await runAssembledDevicePodCli([
|
|
"D601-F103-V2:workspace:/projects/01_baseline",
|
|
"build",
|
|
"evidence",
|
|
"job_devicepod_7ce895d1-bb3e-4daa-b4a1-6d7d05522923",
|
|
"--tail",
|
|
"60"
|
|
], { fetchImpl, now: () => "2026-06-04T00:00:00.000Z" });
|
|
assert.equal(build.exitCode, 0);
|
|
assert.deepEqual(seen[0].body, {
|
|
intent: "workspace.evidence",
|
|
args: {
|
|
kind: "build",
|
|
jobId: "job_devicepod_7ce895d1-bb3e-4daa-b4a1-6d7d05522923",
|
|
tail: 60
|
|
}
|
|
});
|
|
|
|
const download = await runAssembledDevicePodCli([
|
|
"D601-F103-V2:debug-probe",
|
|
"download",
|
|
"evidence",
|
|
"job_devicepod_fb23ec52-339c-45cb-9b82-1af1fb7b6ee0",
|
|
"--full"
|
|
], { fetchImpl, now: () => "2026-06-04T00:00:00.000Z" });
|
|
assert.equal(download.exitCode, 0);
|
|
assert.deepEqual(seen[1].body, {
|
|
intent: "debug.evidence",
|
|
args: {
|
|
kind: "download",
|
|
jobId: "job_devicepod_fb23ec52-339c-45cb-9b82-1af1fb7b6ee0",
|
|
full: true
|
|
}
|
|
});
|
|
});
|
|
|
|
test("device-pod-cli maps build verify to read-only workspace.evidence verify intent (HWLAB #821)", async () => {
|
|
const seen: any[] = [];
|
|
const fetchImpl = async (url, init) => {
|
|
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(202, { ok: true, status: "running", job: { id: `job_${seen.length}`, status: "running" } });
|
|
};
|
|
const result = await runAssembledDevicePodCli([
|
|
"D601-71-FREQ:workspace:/",
|
|
"build",
|
|
"verify",
|
|
"--target",
|
|
"FREQ_Controller_FW",
|
|
"--expected",
|
|
"axf,hex",
|
|
"--head-bytes",
|
|
"16"
|
|
], { fetchImpl, now: () => "2026-06-04T00:00:00.000Z" });
|
|
assert.equal(result.exitCode, 0);
|
|
assert.deepEqual(seen[0].body, {
|
|
intent: "workspace.evidence",
|
|
args: {
|
|
kind: "verify",
|
|
target: "FREQ_Controller_FW",
|
|
expected: "axf,hex",
|
|
headBytes: 16
|
|
}
|
|
});
|
|
assert.equal(JSON.stringify(seen[0].body).includes("reason"), false);
|
|
});
|
|
|
|
test("device-host-cli build verify returns artifact stats, missing artifacts, and no-job blocker (HWLAB #821)", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-device-host-verify-"));
|
|
const projectRel = path.join("projects", "71-00075-11", "FirmWare", "MDK-ARM", "FREQ_Controller_FW.uvprojx");
|
|
const project = path.join(root, projectRel);
|
|
const outputDir = path.join(path.dirname(project), "FREQ_Controller_FW");
|
|
await mkdir(outputDir, { recursive: true });
|
|
await writeFile(project, [
|
|
"<Project>",
|
|
" <Targets>",
|
|
" <Target>",
|
|
" <TargetName>FREQ_Controller_FW</TargetName>",
|
|
" <TargetOption>",
|
|
" <TargetCommonOption>",
|
|
" <OutputDirectory>.\\FREQ_Controller_FW\\</OutputDirectory>",
|
|
" <OutputName>FREQ_Controller_FW</OutputName>",
|
|
" </TargetCommonOption>",
|
|
" </TargetOption>",
|
|
" </Target>",
|
|
" </Targets>",
|
|
"</Project>"
|
|
].join("\n"));
|
|
await writeFile(path.join(outputDir, "FREQ_Controller_FW.axf"), Buffer.from([0x7f, 0x45, 0x4c, 0x46, 1, 1, 1, 0]));
|
|
await writeFile(path.join(outputDir, "FREQ_Controller_FW.bin"), Buffer.from([1, 2, 3, 4]));
|
|
await writeFile(path.join(outputDir, "FREQ_Controller_FW.hex"), ":020000040800F2\n");
|
|
const jobsDir = path.join(root, ".device-pod", ".state", "device-host-cli", "jobs");
|
|
await mkdir(jobsDir, { recursive: true });
|
|
await writeFile(path.join(jobsDir, "20260604053140522-keil-build-09e72a.json"), JSON.stringify({
|
|
jobId: "20260604053140522-keil-build-09e72a",
|
|
kind: "keil-build",
|
|
status: "completed",
|
|
createdAt: "2026-06-04T05:31:40.522Z",
|
|
updatedAt: "2026-06-04T05:31:56.266Z",
|
|
result: {
|
|
success: true,
|
|
exitCode: 0,
|
|
elapsedMs: 15744,
|
|
project,
|
|
target: "FREQ_Controller_FW",
|
|
buildSummary: { hasSummary: true, errors: 0, warnings: 0, hasErrors: false }
|
|
}
|
|
}, null, 2));
|
|
const profile = {
|
|
devicePodId: "D601-71-FREQ",
|
|
workspaceRoot: root,
|
|
projectWorkspace: {
|
|
projectPath: projectRel,
|
|
targetName: "FREQ_Controller_FW",
|
|
hexPath: path.join("projects", "71-00075-11", "FirmWare", "MDK-ARM", "FREQ_Controller_FW", "FREQ_Controller_FW.hex")
|
|
}
|
|
};
|
|
|
|
const happy = await runHostCli(root, profile, ["workspace", "build", "verify", "--expected", "axf,bin,hex"]);
|
|
assert.equal(happy.exitCode, 0);
|
|
assert.equal(happy.payload.ok, true);
|
|
assert.equal(happy.payload.action, "workspace.build.verify");
|
|
assert.equal(happy.payload.data.buildJob.jobId, "20260604053140522-keil-build-09e72a");
|
|
assert.deepEqual(happy.payload.data.missing, []);
|
|
assert.deepEqual(happy.payload.data.artifacts.map((artifact) => artifact.kind), ["axf", "bin", "hex"]);
|
|
assert.equal(Buffer.from(happy.payload.data.artifacts[0].headMagicB64, "base64")[0], 0x7f);
|
|
|
|
await writeFile(path.join(outputDir, "FREQ_Controller_FW.bin"), Buffer.alloc(0));
|
|
const missing = await runHostCli(root, profile, ["workspace", "build", "verify", "--expected", "bin"]);
|
|
assert.equal(missing.exitCode, 1);
|
|
assert.equal(missing.payload.ok, false);
|
|
assert.deepEqual(missing.payload.data.missing, [{ kind: "bin", path: path.join(outputDir, "FREQ_Controller_FW.bin"), reason: "zero-bytes" }]);
|
|
|
|
const emptyRoot = await mkdtemp(path.join(os.tmpdir(), "hwlab-device-host-verify-no-job-"));
|
|
const emptyProject = path.join(emptyRoot, projectRel);
|
|
const emptyOutputDir = path.join(path.dirname(emptyProject), "FREQ_Controller_FW");
|
|
await mkdir(emptyOutputDir, { recursive: true });
|
|
await writeFile(emptyProject, await Bun.file(project).text());
|
|
await writeFile(path.join(emptyOutputDir, "FREQ_Controller_FW.axf"), Buffer.from([0x7f, 0x45, 0x4c, 0x46]));
|
|
const noJob = await runHostCli(emptyRoot, { ...profile, workspaceRoot: emptyRoot }, ["workspace", "build", "verify", "--expected", "axf"]);
|
|
assert.equal(noJob.exitCode, 1);
|
|
assert.equal(noJob.payload.ok, false);
|
|
assert.equal(noJob.payload.data.buildJob, null);
|
|
assert.equal(noJob.payload.data.blockers[0].code, "keil_build_job_not_found");
|
|
});
|
|
|
|
test("device-pod-cli keeps build start / download start on workspace.build / debug.download intents (HWLAB #801 regression)", async () => {
|
|
const seen: any[] = [];
|
|
const fetchImpl = async (url, init) => {
|
|
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(202, { ok: true, status: "running", job: { id: `job_${seen.length}`, status: "running" } });
|
|
};
|
|
const build = await runAssembledDevicePodCli([
|
|
"D601-F103-V2:workspace:/projects/01_baseline",
|
|
"build",
|
|
"start",
|
|
"--reason",
|
|
"smoke test"
|
|
], { fetchImpl, now: () => "2026-06-04T00:00:00.000Z" });
|
|
assert.equal(build.exitCode, 0);
|
|
assert.deepEqual(seen[0].body, {
|
|
intent: "workspace.build",
|
|
args: { action: "start" },
|
|
reason: "smoke test"
|
|
});
|
|
|
|
const download = await runAssembledDevicePodCli([
|
|
"D601-F103-V2:debug-probe",
|
|
"download",
|
|
"start",
|
|
"--capture-uart",
|
|
"uart/1",
|
|
"--capture-duration-ms",
|
|
"8000",
|
|
"--reason",
|
|
"smoke test"
|
|
], { fetchImpl, now: () => "2026-06-04T00:00:00.000Z" });
|
|
assert.equal(download.exitCode, 0);
|
|
assert.deepEqual(seen[1].body, {
|
|
intent: "debug.download",
|
|
args: { action: "start", captureUart: "uart/1", captureDurationMs: 8000 },
|
|
reason: "smoke test"
|
|
});
|
|
});
|
|
|
|
test("device-pod-cli maps selector build/download cloud job aliases to job routes", async () => {
|
|
const seen: any[] = [];
|
|
const fetchImpl = async (url, init) => {
|
|
seen.push({ url: String(url), method: init?.method });
|
|
return jsonResponse(200, { ok: true, status: "completed", output: { text: "done" } });
|
|
};
|
|
|
|
const build = await runAssembledDevicePodCli([
|
|
"D601-F103-V2:workspace:/",
|
|
"build",
|
|
"status",
|
|
"job_devicepod_build"
|
|
], { fetchImpl, now: () => "2026-05-31T00:00:00.000Z" });
|
|
assert.equal(build.exitCode, 0);
|
|
assert.equal(build.payload.action, "job.status");
|
|
assert.equal(seen[0].url, "http://cloud.test/v1/device-pods/D601-F103-V2/jobs/job_devicepod_build");
|
|
|
|
const download = await runAssembledDevicePodCli([
|
|
"D601-F103-V2:debug-probe",
|
|
"download",
|
|
"output",
|
|
"job_devicepod_download"
|
|
], { fetchImpl, now: () => "2026-05-31T00:00:00.000Z" });
|
|
assert.equal(download.exitCode, 0);
|
|
assert.equal(download.payload.action, "job.output");
|
|
assert.equal(seen[1].url, "http://cloud.test/v1/device-pods/D601-F103-V2/jobs/job_devicepod_download/output");
|
|
});
|
|
|
|
test("device-pod-cli accepts explicit workspace rg pattern and path options", async () => {
|
|
const seen: any[] = [];
|
|
const result = await runAssembledDevicePodCli([
|
|
"D601-F103-V2:workspace:/",
|
|
"rg",
|
|
"--pattern",
|
|
"Programming Done|Verify OK|Application running|Error",
|
|
"--path",
|
|
"projects/01_baseline/captures"
|
|
], {
|
|
fetchImpl: async (url, init) => {
|
|
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(202, { ok: true, status: "running", job: { id: "job_rg", status: "running" } });
|
|
},
|
|
now: () => "2026-05-31T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.deepEqual(seen[0].body, {
|
|
intent: "workspace.rg",
|
|
args: {
|
|
pattern: "Programming Done|Verify OK|Application running|Error",
|
|
path: "projects/01_baseline/captures"
|
|
}
|
|
});
|
|
});
|
|
|
|
test("device-pod-cli forwards workspace rg filename search and scan limit options", async () => {
|
|
const seen: any[] = [];
|
|
const result = await runAssembledDevicePodCli([
|
|
"D601-71-FREQ:workspace:/",
|
|
"rg",
|
|
"--pattern",
|
|
"FREQ_Controller_FW\\\\.(axf|bin|hex)$",
|
|
"--path",
|
|
"projects/71-00075-11/FirmWare/MDK-ARM",
|
|
"--name-only",
|
|
"--files-with-matches",
|
|
"--limit",
|
|
"25000"
|
|
], {
|
|
fetchImpl: async (url, init) => {
|
|
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(202, { ok: true, status: "running", job: { id: "job_rg_names", status: "running" } });
|
|
},
|
|
now: () => "2026-06-04T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.deepEqual(seen[0].body, {
|
|
intent: "workspace.rg",
|
|
args: {
|
|
pattern: "FREQ_Controller_FW\\\\.(axf|bin|hex)$",
|
|
path: "projects/71-00075-11/FirmWare/MDK-ARM",
|
|
nameOnly: true,
|
|
filesWithMatches: true,
|
|
limit: 25000
|
|
}
|
|
});
|
|
});
|
|
|
|
test("device-pod-cli forwards workspace rg base64 patterns with explicit path", async () => {
|
|
const seen: any[] = [];
|
|
const pattern = Buffer.from("Error\\(s\\)|Warning\\(s\\)", "utf8").toString("base64");
|
|
const result = await runAssembledDevicePodCli([
|
|
"D601-71-FREQ:workspace:/",
|
|
"rg",
|
|
"--pattern-b64",
|
|
pattern,
|
|
"--path",
|
|
"projects/71-00075-11/FirmWare/MDK-ARM"
|
|
], {
|
|
fetchImpl: async (url, init) => {
|
|
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(202, { ok: true, status: "running", job: { id: "job_rg_b64", status: "running" } });
|
|
},
|
|
now: () => "2026-06-04T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.deepEqual(seen[0].body, {
|
|
intent: "workspace.rg",
|
|
args: {
|
|
pattern: "Error\\(s\\)|Warning\\(s\\)",
|
|
path: "projects/71-00075-11/FirmWare/MDK-ARM"
|
|
}
|
|
});
|
|
});
|
|
|
|
function jsonResponse(status: number, body: unknown) {
|
|
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
}
|
|
|
|
function listen(server: ReturnType<typeof createServer>) {
|
|
return new Promise<void>((resolve, reject) => {
|
|
server.once("error", reject);
|
|
server.listen(0, "127.0.0.1", resolve);
|
|
});
|
|
}
|
|
|
|
function close(server: ReturnType<typeof createServer>) {
|
|
return new Promise<void>((resolve, reject) => {
|
|
server.close((error) => error ? reject(error) : resolve());
|
|
});
|
|
}
|
|
|
|
test("device-pod-cli fails fast on debug-probe flash with selector hint", async () => {
|
|
const result = await runAssembledDevicePodCli([
|
|
"device-pod-71-freq:debug-probe", "flash"
|
|
], { fetchImpl: async () => { throw new Error("should not reach cloud-api"); } });
|
|
assert.equal(result.exitCode, 1);
|
|
const payload = result.payload;
|
|
assert.equal(payload.ok, false);
|
|
assert.equal(payload.error.code, "unsupported_debug_operation");
|
|
assert.match(payload.error.message, /flash/);
|
|
assert.match(payload.error.message, /debug-probe download start/);
|
|
assert.match(payload.error.message, /Selector Cheat Sheet/);
|
|
assert.deepEqual(payload.error.details.allowed, ["status", "chip-id", "download", "reset"]);
|
|
assert.match(payload.error.details.hint, /debug-probe download start/);
|
|
});
|
|
|
|
test("device-pod-cli fails fast on workspace.keil download with selector hint", async () => {
|
|
const result = await runAssembledDevicePodCli([
|
|
"device-pod-71-freq:workspace:/", "keil", "download", "FREQ_Controller_FW.hex"
|
|
], { fetchImpl: async () => { throw new Error("should not reach cloud-api"); } });
|
|
assert.equal(result.exitCode, 1);
|
|
const payload = result.payload;
|
|
assert.equal(payload.ok, false);
|
|
assert.equal(payload.error.code, "unsupported_workspace_keil_action");
|
|
assert.match(payload.error.message, /download/);
|
|
assert.match(payload.error.message, /add-source/);
|
|
assert.match(payload.error.message, /remove-source/);
|
|
assert.match(payload.error.message, /debug-probe download start/);
|
|
assert.deepEqual(payload.error.details.allowed, ["add-source", "remove-source"]);
|
|
});
|
|
|
|
test("device-pod-cli fails fast on workspace.keil flash with selector hint", async () => {
|
|
const result = await runAssembledDevicePodCli([
|
|
"device-pod-71-freq:workspace:/", "keil", "flash"
|
|
], { fetchImpl: async () => { throw new Error("should not reach cloud-api"); } });
|
|
assert.equal(result.exitCode, 1);
|
|
const payload = result.payload;
|
|
assert.equal(payload.ok, false);
|
|
assert.equal(payload.error.code, "unsupported_workspace_keil_action");
|
|
assert.match(payload.error.message, /flash/);
|
|
});
|
|
|
|
test("device-pod-cli still accepts workspace.keil add-source and remove-source", async () => {
|
|
const seen: any[] = [];
|
|
const result = await runAssembledDevicePodCli([
|
|
"device-pod-71-freq:workspace:/", "keil", "add-source", "User/new.c", "--group", "User", "--reason", "DEV smoke"
|
|
], { fetchImpl: async (url, init) => {
|
|
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return jsonResponse(202, { ok: true, status: "running", job: { id: "job_keil_add", status: "running" } });
|
|
} });
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(seen[0].body.intent, "workspace.keil");
|
|
assert.equal(seen[0].body.args.action, "add-source");
|
|
assert.equal(seen[0].body.args.path, "User/new.c");
|
|
});
|
|
|
|
test("device-pod-cli rejects unknown debug-probe operation with allowed list", async () => {
|
|
const result = await runAssembledDevicePodCli([
|
|
"device-pod-71-freq:debug-probe", "wiggle"
|
|
], { fetchImpl: async () => { throw new Error("should not reach cloud-api"); } });
|
|
assert.equal(result.exitCode, 1);
|
|
const payload = result.payload;
|
|
assert.equal(payload.error.code, "unsupported_debug_operation");
|
|
assert.match(payload.error.message, /wiggle/);
|
|
assert.match(payload.error.message, /status, chip-id, download, reset/);
|
|
assert.deepEqual(payload.error.details.allowed, ["status", "chip-id", "download", "reset"]);
|
|
});
|
|
|
|
test("device-pod-cli help surfaces selectorCheatSheet with SOP table and common-mistake list", async () => {
|
|
const result = await runAssembledDevicePodCli(["--help"], { fetchImpl: async () => { throw new Error("help should not hit network"); } });
|
|
assert.equal(result.exitCode, 0);
|
|
const payload = result.payload;
|
|
assert.ok(Array.isArray(payload.selectorCheatSheet), "selectorCheatSheet array should be present");
|
|
const joined = payload.selectorCheatSheet.join("\n");
|
|
assert.match(joined, /workspace: ls \| cat/);
|
|
assert.match(joined, /debug-probe: status \| chip-id \| download/);
|
|
assert.match(joined, /io-probe: ports/);
|
|
assert.match(joined, /no flash op/);
|
|
assert.match(joined, /Selector Cheat Sheet/);
|
|
});
|
|
|
|
function serverUrl(server: ReturnType<typeof createServer>) {
|
|
const address = server.address();
|
|
if (!address || typeof address === "string") throw new Error("server address is unavailable");
|
|
return `http://127.0.0.1:${address.port}`;
|
|
}
|
|
|
|
test("device-pod-cli prefers HWLAB_API_KEY over HWLAB_DEVICE_POD_API_KEY when both are set", async () => {
|
|
const seen: any[] = [];
|
|
const server = createServer((request, response) => {
|
|
let chunks: Buffer[] = [];
|
|
request.on("data", (chunk) => chunks.push(chunk));
|
|
request.on("end", () => {
|
|
const body = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
seen.push({
|
|
url: request.url,
|
|
method: request.method,
|
|
devicePodKey: request.headers["x-hwlab-device-pod-api-key"] ?? null,
|
|
authorization: request.headers["authorization"] ?? null
|
|
});
|
|
response.writeHead(200, { "content-type": "application/json" });
|
|
response.end(JSON.stringify({ ok: true, status: "ok", devicePods: [], count: 0 }));
|
|
});
|
|
});
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
try {
|
|
const port = server.address().port;
|
|
const result = await runDevicePodCli(["profile", "list"], {
|
|
env: {
|
|
HWLAB_RUNTIME_API_URL: `http://127.0.0.1:${port}`,
|
|
HWLAB_RUNTIME_NAMESPACE: "hwlab-v02",
|
|
HWLAB_RUNTIME_LANE: "v02",
|
|
HWLAB_RUNTIME_ENDPOINT_LOCKED: "1",
|
|
HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1",
|
|
HWLAB_DEVICE_POD_API_KEY: "device-pod-api-key-a",
|
|
HWLAB_API_KEY: "hwl_live_user-default-key-1234567890"
|
|
},
|
|
now: () => "2026-06-03T00:00:00.000Z"
|
|
});
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(seen.length, 1);
|
|
assert.equal(seen[0].devicePodKey, null);
|
|
assert.equal(seen[0].authorization, "Bearer hwl_live_user-default-key-1234567890");
|
|
} finally {
|
|
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
|
|
}
|
|
});
|