Add dockerized fake-agent for integration testing and live edge-broker smoke tests. Introduce agent CLI status command and report helpers. Add browser shell session handling for disconnected agents. Extend tests and improve token validation flow.

This commit is contained in:
Jeppe Bundgaard
2026-04-09 16:31:38 +02:00
parent 745e68aa4f
commit 2a0a3468c1
11 changed files with 955 additions and 44 deletions
+151
View File
@@ -1,18 +1,28 @@
import test from "node:test";
import assert from "node:assert/strict";
import { EventEmitter } from "node:events";
import { execFile as execFileCallback } from "node:child_process";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import {
buildStatusReport,
claimIfNeeded,
createShellBridge,
getAgentStatus,
getRelayStatus,
parseCliArgs,
runCli,
setRelayState,
startAgent,
} from "../dist/agent.mjs";
const execFile = promisify(execFileCallback);
const agentEntryPath = fileURLToPath(new URL("../dist/agent.mjs", import.meta.url));
test("claimIfNeeded persists claimed gateway credentials", async () => {
const tempDir = await mkdtemp(path.join(os.tmpdir(), "edge-agent-"));
const configPath = path.join(tempDir, "config.json");
@@ -199,6 +209,7 @@ test("startAgent reports broker state via metadata and executes polled commands"
const heartbeats = [];
const resultPosts = [];
let polledCommandDelivered = false;
let metricSample = 0;
const fakeFetch = async (url, options = {}) => {
const body = options.body ? JSON.parse(options.body) : {};
@@ -280,14 +291,37 @@ test("startAgent reports broker state via metadata and executes polled commands"
return socket;
};
const collectMetricsImpl = async ({ latencyMs }) => {
metricSample += 1;
return {
cpuSnapshot: { idle: metricSample, total: metricSample + 10 },
metrics: {
latency_ms: latencyMs,
cpu_usage_pct: 27.5,
memory_usage_pct: 48.2,
memory_used_bytes: 2048,
memory_total_bytes: 4096,
disk_usage_pct: 61.4,
disk_used_bytes: 8192,
disk_total_bytes: 16384,
disk_mount: "/",
},
};
};
const agent = await startAgent({
configPath,
fetchImpl: fakeFetch,
wsFactory,
collectMetricsImpl,
});
assert.equal(heartbeats[0].body.status, "DEGRADED");
assert.equal(heartbeats[0].body.metadata.broker_connected, false);
assert.equal(heartbeats[0].body.metadata.system_metrics.cpu_usage_pct, 27.5);
assert.equal(heartbeats[0].body.metadata.system_metrics.memory_usage_pct, 48.2);
assert.equal(heartbeats[0].body.metadata.system_metrics.disk_usage_pct, 61.4);
assert.equal(heartbeats[0].body.metadata.system_metrics.latency_ms, null);
assert.equal("discovery_status" in heartbeats[0].body, false);
assert.equal(sockets.length, 1);
@@ -303,6 +337,13 @@ test("startAgent reports broker state via metadata and executes polled commands"
await new Promise((resolve) => setTimeout(resolve, 5));
assert.ok(heartbeats.some((heartbeat) => heartbeat.body.metadata.broker_connected === true));
assert.ok(
heartbeats.some(
(heartbeat) =>
heartbeat.body.metadata.system_metrics &&
typeof heartbeat.body.metadata.system_metrics.latency_ms === "number"
)
);
assert.ok(heartbeats.every((heartbeat) => !("discovery_status" in heartbeat.body)));
sockets[0].emit("close");
@@ -314,3 +355,113 @@ test("startAgent reports broker state via metadata and executes polled commands"
agent.stop();
await rm(tempDir, { recursive: true, force: true });
});
test("status helpers report config without exposing the agent token", async () => {
const tempDir = await mkdtemp(path.join(os.tmpdir(), "edge-agent-status-"));
const configPath = path.join(tempDir, "config.json");
await writeFile(configPath, JSON.stringify({
apiUrl: "https://api.example.test",
brokerUrl: "https://broker.example.test",
installToken: "install-token",
gatewayId: 42,
agentToken: "agent-token",
installedVersion: "1.2.3",
targetVersion: "1.2.4",
releaseChannel: "stable",
heartbeatIntervalSeconds: 30,
}));
const report = await getAgentStatus(configPath);
const built = buildStatusReport(configPath, {
apiUrl: "https://api.example.test",
brokerUrl: "https://broker.example.test",
installToken: "install-token",
gatewayId: 42,
agentToken: "agent-token",
});
assert.equal(report.command, "status");
assert.equal(report.configPath, configPath);
assert.equal(report.claimed, true);
assert.equal(report.state, "CLAIMED");
assert.equal(report.gatewayId, 42);
assert.equal(report.brokerWsUrl, "wss://broker.example.test");
assert.equal(report.hasInstallToken, true);
assert.equal("agentToken" in report, false);
assert.equal(built.state, "CLAIMED");
assert.equal("agentToken" in built, false);
await rm(tempDir, { recursive: true, force: true });
});
test("parseCliArgs understands the status command and config flag", () => {
assert.deepEqual(
parseCliArgs(["status", "--config", "/tmp/config.json"]),
{ command: "status", configPath: "/tmp/config.json" }
);
assert.deepEqual(
parseCliArgs(["--config", "/tmp/config.json"]),
{ command: "start", configPath: "/tmp/config.json" }
);
});
test("runCli emits a status report for the status command", async () => {
const tempDir = await mkdtemp(path.join(os.tmpdir(), "edge-agent-cli-"));
const configPath = path.join(tempDir, "config.json");
await writeFile(configPath, JSON.stringify({
apiUrl: "https://api.example.test",
brokerUrl: "http://localhost:4300",
installToken: "install-token",
}));
let stdout = "";
const originalWrite = process.stdout.write.bind(process.stdout);
process.stdout.write = ((chunk, encoding, callback) => {
stdout += String(chunk);
if (typeof encoding === "function") {
encoding();
} else if (typeof callback === "function") {
callback();
}
return true;
});
try {
const report = await runCli(["status", "--config", configPath]);
assert.equal(report.state, "PENDING_CLAIM");
const parsed = JSON.parse(stdout);
assert.equal(parsed.command, "status");
assert.equal(parsed.claimed, false);
assert.equal(parsed.brokerWsUrl, "ws://localhost:4300");
} finally {
process.stdout.write = originalWrite;
}
await rm(tempDir, { recursive: true, force: true });
});
test("agent CLI status command prints the report", async () => {
const tempDir = await mkdtemp(path.join(os.tmpdir(), "edge-agent-process-"));
const configPath = path.join(tempDir, "config.json");
await writeFile(configPath, JSON.stringify({
apiUrl: "https://api.example.test",
brokerUrl: "https://broker.example.test",
installToken: "install-token",
gatewayId: 7,
agentToken: "secret-token",
}));
const { stdout } = await execFile(
process.execPath,
[agentEntryPath, "status", "--config", configPath],
{ windowsHide: true, encoding: "utf8" }
);
const parsed = JSON.parse(stdout);
assert.equal(parsed.command, "status");
assert.equal(parsed.gatewayId, 7);
assert.equal(parsed.claimed, true);
assert.equal("agentToken" in parsed, false);
await rm(tempDir, { recursive: true, force: true });
});