import assert from "node:assert/strict"; import { spawn } from "node:child_process"; import { createServer } from "node:http"; import test from "node:test"; const bunCommand = process.env.HWLAB_TEST_BUN_COMMAND || process.env.HWLAB_BUN_COMMAND || (process.versions.bun ? process.execPath : "bun"); const INTERNAL_TOKEN = "test-internal-token"; test("device pod executor exposes cloud-api authority boundary and method guards", async () => { const service = await startDevicePod("device-pod-test"); try { const health = await fetchJson(`http://127.0.0.1:${service.port}/health/live`); assert.equal(health.serviceId, "hwlab-device-pod"); assert.equal(health.status, "live"); assert.equal(health.devicePodId, "device-pod-test"); assert.equal(health.contractVersion, "device-pod-executor-v1"); assert.equal(health.role, "device-pod-internal-executor"); assert.equal(health.authority, "hwlab-cloud-api"); assert.equal(health.fake, false); const list = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods`); assert.equal(list.serviceId, "hwlab-device-pod"); assert.equal(list.selectedDevicePodId, "device-pod-test"); assert.equal(list.devicePods.length, 1); assert.equal(list.source.fake, false); const status = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/status`); assert.equal(status.devicePodId, "device-pod-test"); assert.equal(status.status, "blocked"); assert.equal(status.summary.blocker.code, "gateway_dispatch_unavailable"); assert.equal(status.targetId, null); assert.equal(status.profileHash, ""); assert.match(status.traceId, /^trc_devicepod_/u); assert.match(status.operationId, /^op_devicepod_/u); assert.equal(status.freshness.stale, true); assert.equal(status.truncation.truncated, false); assert.match(status.output.summary, /device-host-cli dispatch is owned/u); const events = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/events?limit=1`); assert.equal(events.events.length, 1); assert.equal(events.truncation.limit, 1); assert.equal(events.truncation.truncated, false); assert.equal(events.events[0].blocker.code, "gateway_dispatch_unavailable"); const externalJob = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ intent: "workspace.ls" }) }); assert.equal(externalJob.status, 403); assert.equal((await externalJob.json()).error.code, "cloud_api_authority_required"); const headerOnlyJob = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, { method: "POST", headers: { "content-type": "application/json", "x-hwlab-internal-service": "hwlab-cloud-api" }, body: JSON.stringify({ intent: "workspace.ls" }) }); assert.equal(headerOnlyJob.status, 403); assert.equal((await headerOnlyJob.json()).error.code, "cloud_api_authority_required"); const internalJob = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, { method: "POST", headers: internalHeaders({ "content-type": "application/json" }), body: JSON.stringify({ jobId: "job_devicepod_test", intent: "workspace.ls", traceId: "trc_device_pod_test", operationId: "op_device_pod_test" }) }); assert.equal(internalJob.status, 409); const internalJobPayload = await internalJob.json(); assert.equal(internalJobPayload.blocker.code, "gateway_dispatch_unavailable"); assert.equal(internalJobPayload.job.id, "job_devicepod_test"); assert.equal(internalJobPayload.outputUrl, "/v1/device-pods/device-pod-test/jobs/job_devicepod_test/output"); const storedJob = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs/job_devicepod_test`, { headers: internalHeaders() }); assert.equal(storedJob.job.id, "job_devicepod_test"); assert.equal(storedJob.status, "blocked"); const storedOutput = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs/job_devicepod_test/output`, { headers: internalHeaders() }); assert.equal(storedOutput.job.id, "job_devicepod_test"); assert.equal(storedOutput.output.summary, "HWLAB_CLOUD_API_INTERNAL_URL is not configured for gateway dispatch"); assert.equal(storedOutput.truncation.truncated, false); assert.equal(storedOutput.truncation.originalBytes, 0); const externalJobRead = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs/job_devicepod_test`); assert.equal(externalJobRead.status, 403); assert.equal((await externalJobRead.json()).error.code, "cloud_api_authority_required"); const cancelStored = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs/job_devicepod_test/cancel`, { method: "POST", headers: internalHeaders() }); assert.equal(cancelStored.status, 200); assert.equal((await cancelStored.json()).job.id, "job_devicepod_test"); const missing = await fetch(`http://127.0.0.1:${service.port}/missing`); assert.equal(missing.status, 404); assert.deepEqual(await missing.json(), { error: "not_found", path: "/missing" }); const methodGuard = await fetch(`http://127.0.0.1:${service.port}/health`, { method: "POST" }); assert.equal(methodGuard.status, 405); assert.deepEqual(await methodGuard.json(), { error: "method_not_allowed", method: "POST", path: "/health" }); } finally { await service.stop(); } }); test("device pod executor lists and isolates multiple logical device pods", async () => { const service = await startDevicePod("device-pod-main", { HWLAB_DEVICE_POD_IDS: "device-pod-main, device-pod-alt" }); try { const list = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods`); assert.equal(list.selectedDevicePodId, "device-pod-main"); assert.deepEqual(list.devicePods.map((pod) => pod.devicePodId), ["device-pod-main", "device-pod-alt"]); assert.equal(list.devicePods[1].routes.internalJob, "/v1/device-pods/device-pod-alt/jobs"); const mainJobResponse = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-main/jobs`, { method: "POST", headers: { "content-type": "application/json", ...internalHeaders() }, body: JSON.stringify({ jobId: "job_multi_main", intent: "workspace.ls", traceId: "trc_multi_main", operationId: "op_multi_main" }) }); assert.equal(mainJobResponse.status, 409); const altJobResponse = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-alt/jobs`, { method: "POST", headers: { "content-type": "application/json", ...internalHeaders() }, body: JSON.stringify({ jobId: "job_multi_alt", intent: "workspace.ls", traceId: "trc_multi_alt", operationId: "op_multi_alt" }) }); assert.equal(altJobResponse.status, 409); const mainJob = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-main/jobs/job_multi_main`, { headers: internalHeaders() }); assert.equal(mainJob.job.devicePodId, "device-pod-main"); const altJob = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-alt/jobs/job_multi_alt`, { headers: internalHeaders() }); assert.equal(altJob.job.devicePodId, "device-pod-alt"); const crossRead = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-alt/jobs/job_multi_main`, { headers: internalHeaders() }); assert.equal(crossRead.status, 404); } finally { await service.stop(); } }); test("device-pod executor bounds gateway output text", async () => { const longText = "z".repeat(13000); const cloudApi = createServer(async (request, response) => { const body = await requestJson(request); response.writeHead(200, { "content-type": "application/json; charset=utf-8" }); response.end(JSON.stringify({ jsonrpc: "2.0", id: body.id, result: { accepted: true, status: "completed", dispatch: { shellExecuted: true, dispatchStatus: "succeeded", stdout: longText, exitCode: 0 } } })); }); await new Promise((resolve) => cloudApi.listen(0, "127.0.0.1", resolve)); const cloudPort = cloudApi.address().port; const service = await startDevicePod("device-pod-test", { HWLAB_CLOUD_API_INTERNAL_URL: `http://127.0.0.1:${cloudPort}` }); try { const jobResponse = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, { method: "POST", headers: internalHeaders({ "content-type": "application/json" }), body: JSON.stringify({ jobId: "job_bounded_output_test", intent: "workspace.ls", args: { path: "src" }, traceId: "trc_bounded_output_test", operationId: "op_bounded_output_test", targetId: "target-device-pod-test", profileHash: "sha256:test", profile: { schemaVersion: 1, devicePodId: "device-pod-test", target: { id: "target-device-pod-test" }, route: { gatewaySessionId: "gws_devicepod_test", resourceId: "res_devicepod_test", capabilityId: "cap_device_host_cli" } } }) }); assert.equal(jobResponse.status, 202); const output = await waitForJobStatus(service.port, "device-pod-test", "job_bounded_output_test", "completed"); assert.equal(output.output.text.length, 12000); assert.equal(output.text.length, 12000); assert.equal(output.bytes, 12000); assert.equal(output.truncation.truncated, true); assert.equal(output.truncation.originalBytes, 13000); assert.equal(JSON.stringify(output).includes(longText), false); } finally { await service.stop(); await new Promise((resolve, reject) => cloudApi.close((error) => (error ? reject(error) : resolve()))); } }); test("device pod executor dispatches internal jobs through cloud-api gateway adapter", async () => { const dispatches = []; const cloudApi = createServer(async (request, response) => { const body = await requestJson(request); dispatches.push({ method: request.method, url: request.url, internalService: request.headers["x-hwlab-internal-service"], internalToken: request.headers["x-hwlab-internal-token"], body }); response.writeHead(200, { "content-type": "application/json; charset=utf-8" }); response.end(JSON.stringify({ jsonrpc: "2.0", id: body.id, result: { accepted: true, status: "completed", operationId: body.params.operationId, traceId: body.params.traceId, gatewaySessionId: body.params.gatewaySessionId, resourceId: body.params.resourceId, capabilityId: body.params.capabilityId, dispatch: { shellExecuted: true, dispatchStatus: "succeeded", stdout: "device-host-cli ok", exitCode: 0 }, auditId: "aud_devicepod_test", evidenceId: "evd_devicepod_test", gateway: { gatewayId: "gtw_devicepod_test" } } })); }); await new Promise((resolve) => cloudApi.listen(0, "127.0.0.1", resolve)); const cloudPort = cloudApi.address().port; const service = await startDevicePod("device-pod-test", { HWLAB_CLOUD_API_INTERNAL_URL: `http://127.0.0.1:${cloudPort}` }); try { const jobResponse = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, { method: "POST", headers: internalHeaders({ "content-type": "application/json" }), body: JSON.stringify({ jobId: "job_devicepod_gateway_test", intent: "workspace.ls", args: { path: "src" }, traceId: "trc_device_pod_gateway_test", operationId: "op_device_pod_gateway_test", targetId: "target-device-pod-test", profileHash: "sha256:test", ownerUserId: "usr_device_pod_test", profile: { schemaVersion: 1, devicePodId: "device-pod-test", target: { id: "target-device-pod-test" }, projectWorkspace: { workspaceRoot: "F:\\Work\\Project" }, route: { gatewaySessionId: "gws_devicepod_test", resourceId: "res_devicepod_test", capabilityId: "cap_device_host_cli", hostWorkspaceRoot: "F:\\Work\\Project", hostCli: "node tools\\device-host-cli.mjs" } } }) }); assert.equal(jobResponse.status, 202); const job = await jobResponse.json(); assert.equal(job.status, "running"); assert.equal(job.targetId, "target-device-pod-test"); assert.equal(job.profileHash, "sha256:test"); const output = await waitForJobStatus(service.port, "device-pod-test", "job_devicepod_gateway_test", "completed"); assert.equal(output.status, "completed"); assert.equal(output.output.text, "device-host-cli ok"); assert.equal(output.output.auditId, "aud_devicepod_test"); assert.equal(dispatches.length, 1); assert.equal(dispatches[0].internalService, "hwlab-device-pod"); assert.equal(dispatches[0].internalToken, INTERNAL_TOKEN); assert.equal(dispatches[0].body.params.gatewaySessionId, "gws_devicepod_test"); assert.equal(dispatches[0].body.params.input.cwd, "F:\\Work\\Project"); assert.match(dispatches[0].body.params.input.command, /^node tools\\device-host-cli\.mjs --profile-json-b64 /u); assert.equal(dispatches[0].body.params.input.command.includes("node -e"), false); } finally { await service.stop(); await new Promise((resolve, reject) => cloudApi.close((error) => (error ? reject(error) : resolve()))); } }); test("device-pod executor maps v0.1 CLI options to device-host-cli argv", async () => { let dispatchedBody = null; const cloudApi = createServer(async (request, response) => { dispatchedBody = await requestJson(request); response.writeHead(200, { "content-type": "application/json; charset=utf-8" }); response.end(JSON.stringify({ ok: true, result: { status: "completed", dispatch: { shellExecuted: true, dispatchStatus: "succeeded", stdout: "ok", exitCode: 0 }, auditId: "aud_options", evidenceId: "ev_options" } })); }); await new Promise((resolve) => cloudApi.listen(0, "127.0.0.1", resolve)); const cloudPort = cloudApi.address().port; const service = await startDevicePod("device-pod-test", { HWLAB_CLOUD_API_INTERNAL_URL: `http://127.0.0.1:${cloudPort}` }); try { const jobResponse = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, { method: "POST", headers: internalHeaders({ "content-type": "application/json" }), body: JSON.stringify({ jobId: "job_options_test", intent: "debug.download", args: { action: "start", captureUart: "uart/1", captureDurationMs: 8000, port: "COM4", baudRate: 921600 }, profileHash: "sha256:profile", profile: { devicePodId: "device-pod-test", target: { id: "target-test" }, route: { gatewaySessionId: "gws_test", resourceId: "res_test", capabilityId: "cap_test", hostCli: "node tools/device-host-cli.mjs" } } }) }); assert.equal(jobResponse.status, 202); await waitForJobStatus(service.port, "device-pod-test", "job_options_test", "completed"); const command = dispatchedBody.params.input.command; assert.equal(command.includes("node -e"), false); assert.match(command, /^node tools\/device-host-cli\.mjs --profile-json-b64 /u); assert.match(command, / --pod-id device-pod-test debug-probe download start /u); assert.match(command, / --capture-uart uart\/1 --capture-duration-ms 8000 --port COM4 --baud-rate 921600$/u); const encoded = command.match(/--profile-json-b64 ([A-Za-z0-9+/=]+)/u)[1]; const decoded = JSON.parse(Buffer.from(encoded, "base64").toString("utf8")); assert.equal(decoded.devicePodId, "device-pod-test"); assert.equal(decoded.route.gatewaySessionId, "gws_test"); } finally { await service.stop(); await new Promise((resolve, reject) => cloudApi.close((error) => (error ? reject(error) : resolve()))); } }); test("device-pod executor maps absorbed G14 device-pod operations to host CLI argv", async () => { const commands = []; const cloudApi = createServer(async (request, response) => { const body = await requestJson(request); commands.push(body.params.input.command); response.writeHead(200, { "content-type": "application/json; charset=utf-8" }); response.end(JSON.stringify({ jsonrpc: "2.0", id: body.id, result: { status: "completed", dispatch: { dispatchStatus: "succeeded", stdout: "ok", exitCode: 0 } } })); }); await new Promise((resolve) => cloudApi.listen(0, "127.0.0.1", resolve)); const cloudPort = cloudApi.address().port; const service = await startDevicePod("device-pod-test", { HWLAB_CLOUD_API_INTERNAL_URL: `http://127.0.0.1:${cloudPort}` }); const baseBody = { profile: { devicePodId: "device-pod-test", target: { id: "target-test" }, route: { gatewaySessionId: "gws_test", hostCli: "node tools/device-host-cli.mjs" } } }; try { await submitAndWait(service.port, "workspace.put", { path: "User/new.c", contentB64: Buffer.from("int x;\n").toString("base64"), createDirs: true }, "job_map_put", baseBody); await submitAndWait(service.port, "workspace.keil", { action: "add-source", base: "projects/app", path: "User/new.c", group: "User" }, "job_map_keil", baseBody); await submitAndWait(service.port, "io.uart.jsonrpc", { uartId: "uart/1", method: "gpio.read", params: "{\"pin\":\"PB5\"}", requireJsonrpcResult: true, expectResultField: ["pin", "value"] }, "job_map_jsonrpc", baseBody); assert.match(commands[0], / workspace put User\/new\.c --content-b64 /u); assert.match(commands[0], / --create-dirs$/u); assert.match(commands[1], / workspace keil add-source User\/new\.c --base projects\/app --group User$/u); assert.match(commands[2], / io-probe uart\/1 jsonrpc gpio\.read /u); assert.match(commands[2], / --params "\{\\"pin\\":\\"PB5\\"\}" /u); assert.match(commands[2], / --require-jsonrpc-result --expect-result-field pin --expect-result-field value$/u); } finally { await service.stop(); await new Promise((resolve, reject) => cloudApi.close((error) => (error ? reject(error) : resolve()))); } }); test("device-pod executor fails mismatched gateway JSON-RPC response ids", async () => { const cloudApi = createServer(async (_request, response) => { response.writeHead(200, { "content-type": "application/json; charset=utf-8" }); response.end(JSON.stringify({ jsonrpc: "2.0", id: "wrong-id", result: { status: "completed", dispatch: { dispatchStatus: "succeeded", stdout: "ok", exitCode: 0 } } })); }); await new Promise((resolve) => cloudApi.listen(0, "127.0.0.1", resolve)); const cloudPort = cloudApi.address().port; const service = await startDevicePod("device-pod-test", { HWLAB_CLOUD_API_INTERNAL_URL: `http://127.0.0.1:${cloudPort}` }); try { const output = await submitAndWait(service.port, "workspace.ls", { path: "." }, "job_bad_id", { profile: { devicePodId: "device-pod-test", target: { id: "target-test" }, route: { gatewaySessionId: "gws_test", hostCli: "node tools/device-host-cli.mjs" } } }, "failed"); assert.equal(output.status, "failed"); assert.match(output.blocker.summary, /JSON-RPC id mismatch/u); } finally { await service.stop(); await new Promise((resolve, reject) => cloudApi.close((error) => (error ? reject(error) : resolve()))); } }); async function submitAndWait(port, intent, args, jobId, extraBody = {}, expectedStatus = "completed") { const response = await fetch(`http://127.0.0.1:${port}/v1/device-pods/device-pod-test/jobs`, { method: "POST", headers: internalHeaders({ "content-type": "application/json" }), body: JSON.stringify({ jobId, intent, args, traceId: `trc_${jobId}`, operationId: `op_${jobId}`, ...extraBody }) }); assert.equal(response.status, 202); return await waitForJobStatus(port, "device-pod-test", jobId, expectedStatus); } async function startDevicePod(devicePodId, extraEnv = {}) { const port = await freePort(); const child = spawn(bunCommand, ["run", "cmd/hwlab-device-pod/main.ts"], { cwd: process.cwd(), env: { ...process.env, HWLAB_DEVICE_POD_PORT: String(port), HWLAB_DEVICE_POD_ID: devicePodId, HWLAB_ENVIRONMENT: "v02", HWLAB_DEVICE_POD_INTERNAL_TOKEN: INTERNAL_TOKEN, ...extraEnv }, stdio: ["ignore", "pipe", "pipe"] }); await waitForListening(child, "device pod"); return { port, stop: () => stopChild(child) }; } async function fetchJson(url, options = {}) { const response = await fetch(url, options); assert.equal(response.ok, true, `${url} returned ${response.status}`); return response.json(); } async function waitForJobStatus(port, devicePodId, jobId, status) { let last; for (let attempt = 0; attempt < 30; attempt += 1) { last = await fetchJson(`http://127.0.0.1:${port}/v1/device-pods/${devicePodId}/jobs/${jobId}/output`, { headers: internalHeaders() }); if (last.status === status) return last; await new Promise((resolve) => setTimeout(resolve, 50)); } assert.equal(last?.status, status); return last; } function internalHeaders(extra = {}) { return { ...extra, "x-hwlab-internal-service": "hwlab-cloud-api", "x-hwlab-internal-token": INTERNAL_TOKEN }; } async function requestJson(request) { const chunks = []; for await (const chunk of request) chunks.push(chunk); const text = Buffer.concat(chunks).toString("utf8").trim(); return text ? JSON.parse(text) : {}; } async function freePort() { const server = createServer(); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const port = server.address().port; await new Promise((resolve) => server.close(resolve)); return port; } async function waitForListening(child, label) { let stderr = ""; child.stderr.on("data", (chunk) => { stderr += chunk; }); await new Promise((resolve, reject) => { const timer = setTimeout(() => reject(new Error(`${label} did not start: ${stderr}`)), 5000); child.stdout.on("data", (chunk) => { if (chunk.toString().includes("listening")) { clearTimeout(timer); resolve(); } }); child.on("exit", (code) => { clearTimeout(timer); reject(new Error(`${label} exited early code=${code}: ${stderr}`)); }); }); } async function stopChild(child) { if (child.exitCode !== null) return; child.kill("SIGTERM"); await new Promise((resolve) => child.once("exit", resolve)); }