Files
pikasTech-HWLAB/tools/device-pod-cli.test.ts
T
2026-05-29 14:17:44 +08:00

161 lines
7.7 KiB
TypeScript

import assert from "node:assert/strict";
import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { test } from "bun:test";
import { runDevicePodCli } from "./src/device-pod-cli-lib.ts";
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 runDevicePodCli([
"--api-base-url", "http://cloud.test",
"--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 upsert = await runDevicePodCli([
"--api-base-url", "http://cloud.test",
"--session-token", "session-a",
"--pod-id", "device-pod-71-freq",
"--profile-json", JSON.stringify(profile),
"admin",
"device-pod",
"upsert"
], { 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.deepEqual(seen[0].body, { devicePodId: "device-pod-71-freq", profile });
const grant = await runDevicePodCli([
"--api-base-url", "http://cloud.test",
"--session-token", "session-a",
"--pod-id", "device-pod-71-freq",
"--user-id", "usr_alice",
"admin",
"grant"
], { 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 runDevicePodCli(["--api-base-url", "http://cloud.test", "--session-token", "session-a", "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"], "session-a");
assert.equal(JSON.stringify(result.payload).includes("gws_local_should_not_be_read"), false);
});
test("device-pod-cli selector creates REST job instead of gateway RPC", async () => {
const seen: any[] = [];
const result = await runDevicePodCli(["--api-base-url", "http://cloud.test", "--session-token", "session-a", "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 sends reason and lease token for mutating jobs", async () => {
const seen: any[] = [];
const result = await runDevicePodCli(["--api-base-url", "http://cloud.test", "--session-token", "session-a", "--lease-token", "lease-a", "--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", leaseToken: "lease-a" });
});
test("device-pod-cli preserves common v0.1 hardware options in REST job args", async () => {
const seen: any[] = [];
const result = await runDevicePodCli([
"--api-base-url", "http://cloud.test",
"--session-token", "session-a",
"--reason", "boot log",
"--lease-token", "lease-a",
"--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",
leaseToken: "lease-a"
});
});
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");
});
function jsonResponse(status: number, body: unknown) {
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
}