101 lines
3.6 KiB
TypeScript
101 lines
3.6 KiB
TypeScript
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");
|
|
|
|
test("device pod fake service exposes health, list, status, events, 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.role, "fake-device-pod-server");
|
|
|
|
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);
|
|
|
|
const status = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/status`);
|
|
assert.equal(status.devicePod.devicePodId, "device-pod-test");
|
|
assert.equal(status.summary.targetId, "stm32f103-minsys-01");
|
|
|
|
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, true);
|
|
|
|
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" });
|
|
} finally {
|
|
await service.stop();
|
|
}
|
|
});
|
|
|
|
async function startDevicePod(devicePodId) {
|
|
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"
|
|
},
|
|
stdio: ["ignore", "pipe", "pipe"]
|
|
});
|
|
await waitForListening(child, "device pod");
|
|
return {
|
|
port,
|
|
stop: () => stopChild(child)
|
|
};
|
|
}
|
|
|
|
async function fetchJson(url) {
|
|
const response = await fetch(url);
|
|
assert.equal(response.ok, true, `${url} returned ${response.status}`);
|
|
return response.json();
|
|
}
|
|
|
|
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));
|
|
}
|