Files
NODEDC_PLATFORM/device-plane/services/device-gateway/test/discovery-integration.test.mjs
T

139 lines
4.1 KiB
JavaScript

import assert from "node:assert/strict";
import { connect } from "node:net";
import test from "node:test";
import {
DEVICE_ADAPTER_CATALOG,
} from "../../../packages/device-adapter-catalog/src/index.mjs";
import {
createControlCoreApp,
} from "../../device-control-core/src/app.mjs";
import { createCoreGatewayClient } from "../src/core-client.mjs";
import { createDeviceGatewayRuntime } from "../src/runtime.mjs";
const gatewayToken = "test-only-gateway-token-with-32-bytes";
const identifierPepper = "test-only-identifier-pepper-with-32-bytes";
const specificationHeader = Buffer.from(
"FF23E9EF782DE7120300",
"hex",
);
const specificationPackage = Buffer.from(
"5B01010000FBDEC251EC5D",
"hex",
);
test("B2 HEADER2 becomes a persisted masked quarantine discovery before ACK", async () => {
let stored;
let storedMessage;
const core = createControlCoreApp({
discoveryIngestEnabled: true,
gatewayToken,
identifierPepper,
repository: {
health: async () => "ready",
upsertQuarantineDiscovery: async (value) => {
stored = value;
return {
created: true,
value: {
...value.safeView,
discoveryRef: "discovery:integration-001",
},
};
},
acceptAdapterMessage: async (value) => {
storedMessage = value;
return {
schemaVersion: "nodedc.device-adapter-acceptance.v1",
acceptanceRef: "acceptance:integration-001",
idempotencyKey: value.safeView.idempotencyKey,
status: "accepted",
replayed: false,
acceptedAt: "2026-08-11T12:00:00.000Z",
};
},
},
});
await listen(core);
const coreAddress = core.address();
const observe = createCoreGatewayClient({
coreUrl: `http://127.0.0.1:${coreAddress.port}`,
gatewayToken,
});
const gateway = createDeviceGatewayRuntime({
healthPort: 0,
tcpHost: "0.0.0.0",
tcpPort: 0,
listenEnabled: true,
publicIngressEnabled: true,
coreChannelAuthenticated: true,
adapterRegistry: DEVICE_ADAPTER_CATALOG.registry,
protocolProfileRef: DEVICE_ADAPTER_CATALOG.defaultProfileRef,
edgeRef: "edge:integration-001",
now: () => new Date(0x52db95de * 1000),
onDiscovery: observe.observeDiscovery,
onMessage: observe.acceptMessage,
});
const addresses = await gateway.start();
try {
const response = await exchange(
addresses.tcpAddress.port,
Buffer.concat([specificationHeader, specificationPackage]),
13,
);
assert.equal(
response.toString("hex").toUpperCase(),
"7B0400A0DE95DB527D7B00017D",
);
assert.match(stored.identifierDigest, /^hmac-sha256:[a-f0-9]{64}$/);
assert.equal(stored.safeView.lifecycleState, "quarantine");
assert.equal(stored.safeView.identifier.masked, "***********7769");
assert.equal(stored.safeView.commandTransport, "disabled");
assert.equal(
JSON.stringify(stored).includes("865209039777769"),
false,
);
assert.equal(storedMessage.safeView.messageType, "telemetry.package");
assert.equal(storedMessage.safeView.identifier.masked, "***********7769");
assert.equal(
JSON.stringify(storedMessage).includes("865209039777769"),
false,
);
} finally {
await gateway.stop();
await close(core);
}
});
function listen(server) {
return new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
}
function close(server) {
return new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
function exchange(port, payload, expectedBytes) {
return new Promise((resolve, reject) => {
const chunks = [];
let byteLength = 0;
const socket = connect({ host: "127.0.0.1", port }, () => {
socket.write(payload);
});
socket.on("data", (chunk) => {
chunks.push(chunk);
byteLength += chunk.length;
if (byteLength >= expectedBytes) {
socket.destroy();
resolve(Buffer.concat(chunks, byteLength));
}
});
socket.on("error", reject);
});
}