200 lines
7.3 KiB
JavaScript
200 lines
7.3 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import { AUDIT_FIELD_NAMES } from "../audit/index.mjs";
|
|
import { ERROR_CODES, validateResponse } from "../protocol/index.mjs";
|
|
import { createCloudRuntimeStore } from "../db/runtime-store.mjs";
|
|
import { handleJsonRpcRequest } from "./json-rpc.mjs";
|
|
|
|
test("unknown JSON-RPC method returns an error envelope with audit fields", async () => {
|
|
const response = await handleJsonRpcRequest({
|
|
jsonrpc: "2.0",
|
|
id: "req_01J00000000000000000000000",
|
|
method: "hardware.operation.cancel",
|
|
params: {
|
|
operationId: "op_01J00000000000000000000000"
|
|
},
|
|
meta: {
|
|
traceId: "trc_01J00000000000000000000000",
|
|
actorId: "usr_01J00000000000000000000000",
|
|
serviceId: "hwlab-cloud-web",
|
|
environment: "dev"
|
|
}
|
|
});
|
|
|
|
validateResponse(response);
|
|
assert.equal(response.error.code, ERROR_CODES.methodNotFound);
|
|
assert.equal(Object.hasOwn(response, "result"), false);
|
|
|
|
const audit = response.error.data.audit;
|
|
for (const field of AUDIT_FIELD_NAMES) {
|
|
assert.ok(Object.hasOwn(audit, field), `expected audit.${field}`);
|
|
}
|
|
assert.equal(audit.requestId, "req_01J00000000000000000000000");
|
|
assert.equal(audit.actor.id, "usr_01J00000000000000000000000");
|
|
assert.equal(audit.operation, "hardware.operation.cancel");
|
|
assert.equal(audit.target.type, "rpc_method");
|
|
assert.equal(audit.result, "rejected");
|
|
});
|
|
|
|
test("system.health includes the redacted DEV DB env contract", async () => {
|
|
const originalUrl = process.env.HWLAB_CLOUD_DB_URL;
|
|
const originalSslMode = process.env.HWLAB_CLOUD_DB_SSL_MODE;
|
|
delete process.env.HWLAB_CLOUD_DB_URL;
|
|
delete process.env.HWLAB_CLOUD_DB_SSL_MODE;
|
|
|
|
try {
|
|
const response = await handleJsonRpcRequest({
|
|
jsonrpc: "2.0",
|
|
id: "req_01J00000000000000000000002",
|
|
method: "system.health",
|
|
params: {},
|
|
meta: {
|
|
traceId: "trc_01J00000000000000000000002",
|
|
serviceId: "hwlab-cloud-web",
|
|
environment: "dev"
|
|
}
|
|
});
|
|
|
|
validateResponse(response);
|
|
assert.equal(response.result.status, "degraded");
|
|
assert.equal(response.result.db.status, "blocked");
|
|
assert.equal(response.result.db.connected, false);
|
|
assert.equal(response.result.runtime.durable, false);
|
|
assert.ok(response.result.db.missingEnv.includes("HWLAB_CLOUD_DB_URL"));
|
|
assert.equal(response.result.db.secretRefs[0].secretName, "hwlab-cloud-api-dev-db");
|
|
assert.equal(response.result.db.secretRefs[0].secretKey, "database-url");
|
|
assert.equal(response.result.db.secretRefs[0].redacted, true);
|
|
} finally {
|
|
if (originalUrl === undefined) {
|
|
delete process.env.HWLAB_CLOUD_DB_URL;
|
|
} else {
|
|
process.env.HWLAB_CLOUD_DB_URL = originalUrl;
|
|
}
|
|
if (originalSslMode === undefined) {
|
|
delete process.env.HWLAB_CLOUD_DB_SSL_MODE;
|
|
} else {
|
|
process.env.HWLAB_CLOUD_DB_SSL_MODE = originalSslMode;
|
|
}
|
|
}
|
|
});
|
|
|
|
test("cloud-api runtime accepts register/report/invoke and exposes audit/evidence records", async () => {
|
|
const runtimeStore = createCloudRuntimeStore({
|
|
now: () => "2026-05-21T00:00:00.000Z"
|
|
});
|
|
const context = { runtimeStore };
|
|
const meta = {
|
|
traceId: "trc_01J00000000000000000000010",
|
|
actorId: "usr_01J00000000000000000000010",
|
|
serviceId: "hwlab-cloud-web",
|
|
environment: "dev"
|
|
};
|
|
|
|
async function rpc(id, method, params = {}) {
|
|
const response = await handleJsonRpcRequest(
|
|
{
|
|
jsonrpc: "2.0",
|
|
id,
|
|
method,
|
|
params,
|
|
meta
|
|
},
|
|
context
|
|
);
|
|
validateResponse(response);
|
|
assert.equal(Object.hasOwn(response, "error"), false, response.error?.message);
|
|
return response.result;
|
|
}
|
|
|
|
const gateway = await rpc("req_01J00000000000000000000010", "gateway.session.register", {
|
|
projectId: "prj_01J00000000000000000000010",
|
|
gatewaySessionId: "gws_01J00000000000000000000010",
|
|
gatewayId: "gtw_01J00000000000000000000010",
|
|
serviceId: "hwlab-gateway-simu",
|
|
endpoint: "http://127.0.0.1:7101"
|
|
});
|
|
assert.equal(gateway.registered, true);
|
|
assert.equal(gateway.gatewaySession.status, "connected");
|
|
assert.equal(gateway.auditEvent.action, "gateway.session.register");
|
|
|
|
const resource = await rpc("req_01J00000000000000000000011", "box.resource.register", {
|
|
projectId: "prj_01J00000000000000000000010",
|
|
gatewaySessionId: "gws_01J00000000000000000000010",
|
|
resourceId: "res_01J00000000000000000000010",
|
|
boxId: "box_01J00000000000000000000010",
|
|
resourceType: "board",
|
|
state: "available"
|
|
});
|
|
assert.equal(resource.registered, true);
|
|
assert.equal(resource.resource.environment, "dev");
|
|
|
|
const capability = await rpc("req_01J00000000000000000000012", "box.capability.report", {
|
|
capabilityId: "cap_01J00000000000000000000010",
|
|
resourceId: "res_01J00000000000000000000010",
|
|
projectId: "prj_01J00000000000000000000010",
|
|
name: "shell.exec",
|
|
direction: "bidirectional",
|
|
valueType: "object",
|
|
mutatesState: true
|
|
});
|
|
assert.equal(capability.reported, true);
|
|
assert.equal(capability.capabilities[0].name, "shell.exec");
|
|
|
|
const invoke = await rpc("req_01J00000000000000000000013", "hardware.invoke.shell", {
|
|
projectId: "prj_01J00000000000000000000010",
|
|
gatewaySessionId: "gws_01J00000000000000000000010",
|
|
resourceId: "res_01J00000000000000000000010",
|
|
capabilityId: "cap_01J00000000000000000000010",
|
|
input: {
|
|
command: "echo hwlab"
|
|
}
|
|
});
|
|
assert.equal(invoke.accepted, true);
|
|
assert.equal(invoke.operation.status, "accepted");
|
|
assert.equal(invoke.dispatch.shellExecuted, false);
|
|
assert.equal(invoke.dispatch.dispatchStatus, "not_connected");
|
|
assert.equal(invoke.auditEvent.action, "hardware.invoke.shell");
|
|
assert.equal(invoke.evidenceRecord.kind, "trace");
|
|
assert.match(invoke.evidenceRecord.sha256, /^[a-f0-9]{64}$/);
|
|
|
|
const audit = await rpc("req_01J00000000000000000000014", "audit.event.query", {
|
|
projectId: "prj_01J00000000000000000000010"
|
|
});
|
|
assert.equal(audit.count, 5);
|
|
assert.ok(audit.events.some((event) => event.action === "hardware.operation.request"));
|
|
assert.ok(audit.events.some((event) => event.action === "hardware.invoke.shell"));
|
|
|
|
const evidence = await rpc("req_01J00000000000000000000015", "evidence.record.query", {
|
|
projectId: "prj_01J00000000000000000000010"
|
|
});
|
|
assert.equal(evidence.count, 1);
|
|
assert.equal(evidence.records[0].operationId, invoke.operationId);
|
|
assert.equal(evidence.records[0].serviceId, "hwlab-cloud-api");
|
|
});
|
|
|
|
test("hardware.operation.request rejects missing registration instead of faking dispatch", async () => {
|
|
const response = await handleJsonRpcRequest({
|
|
jsonrpc: "2.0",
|
|
id: "req_01J00000000000000000000020",
|
|
method: "hardware.operation.request",
|
|
params: {
|
|
projectId: "prj_01J00000000000000000000020",
|
|
gatewaySessionId: "gws_01J00000000000000000000020",
|
|
resourceId: "res_01J00000000000000000000020",
|
|
capabilityId: "cap_01J00000000000000000000020"
|
|
},
|
|
meta: {
|
|
traceId: "trc_01J00000000000000000000020",
|
|
actorId: "usr_01J00000000000000000000020",
|
|
serviceId: "hwlab-cloud-web",
|
|
environment: "dev"
|
|
}
|
|
});
|
|
|
|
validateResponse(response);
|
|
assert.equal(response.error.code, ERROR_CODES.sessionNotFound);
|
|
assert.equal(response.error.data.audit.operation, "hardware.operation.request");
|
|
assert.equal(response.error.data.audit.result, "rejected");
|
|
});
|