210 lines
7.1 KiB
JavaScript
210 lines
7.1 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
assertPlatformCatalogAuthority,
|
|
DEVICE_INFRASTRUCTURE_COMMAND_KINDS,
|
|
normalizeInfrastructureManagementCommand,
|
|
} from "../src/infrastructure-management.mjs";
|
|
import {
|
|
ALL_DEVICE_MANAGEMENT_COMMAND_KINDS,
|
|
normalizeDeviceManagementCommand,
|
|
} from "../src/management-command.mjs";
|
|
import { normalizeManagementActor } from "../src/project-management.mjs";
|
|
|
|
const projectRef = "project:11111111-1111-4111-8111-111111111111";
|
|
const packageRef = "adapter-package:22222222-2222-4222-8222-222222222222";
|
|
const versionRef = "adapter-version:33333333-3333-4333-8333-333333333333";
|
|
const edgeRef = "edge:44444444-4444-4444-8444-444444444444";
|
|
const routeRef = "route:55555555-5555-4555-8555-555555555555";
|
|
const digest = `sha256:${"a".repeat(64)}`;
|
|
const identifierDigest = `hmac-sha256:${"b".repeat(64)}`;
|
|
|
|
test("aggregates project and infrastructure commands without a session mutation", () => {
|
|
for (const kind of DEVICE_INFRASTRUCTURE_COMMAND_KINDS) {
|
|
assert.equal(ALL_DEVICE_MANAGEMENT_COMMAND_KINDS.includes(kind), true);
|
|
}
|
|
assert.equal(ALL_DEVICE_MANAGEMENT_COMMAND_KINDS.includes("project.ensure"), true);
|
|
assert.equal(ALL_DEVICE_MANAGEMENT_COMMAND_KINDS.includes("session.ensure"), false);
|
|
assert.throws(
|
|
() => normalizeDeviceManagementCommand("session.ensure", {}),
|
|
/device_management_command_kind_invalid/,
|
|
);
|
|
});
|
|
|
|
test("normalizes immutable adapter version metadata and sorted capabilities", () => {
|
|
const command = normalizeInfrastructureManagementCommand(
|
|
"adapter_version.register",
|
|
{
|
|
adapterPackageRef: packageRef,
|
|
version: "1.2.3",
|
|
runtimePackageRef: "artifact:device-adapters/generic-1.2.3",
|
|
contentDigest: digest,
|
|
contractVersion: "nodedc.device-adapter.v1",
|
|
capabilities: ["telemetry.observe", "command.typed", "telemetry.observe"],
|
|
lifecycleState: "active",
|
|
},
|
|
);
|
|
|
|
assert.equal(command.adapterPackageId, packageRef.slice("adapter-package:".length));
|
|
assert.deepEqual(command.capabilities, ["command.typed", "telemetry.observe"]);
|
|
assert.equal(command.contentDigest, digest);
|
|
});
|
|
|
|
test("normalizes a generic model profile as artifact metadata, not executable payload", () => {
|
|
const command = normalizeInfrastructureManagementCommand(
|
|
"model_profile.register",
|
|
{
|
|
adapterVersionRef: versionRef,
|
|
profileRef: "vendor.model.protocol.v1",
|
|
schemaVersion: "nodedc.device-model-profile.v1",
|
|
vendor: "Example Vendor",
|
|
model: "Model One",
|
|
deviceType: "tracker",
|
|
protocol: "GENERIC_TCP",
|
|
schemaArtifactRef: "artifact:model-profiles/vendor-model-v1",
|
|
profileDigest: digest,
|
|
capabilities: ["telemetry.observe"],
|
|
},
|
|
);
|
|
|
|
assert.equal(command.adapterVersionId, versionRef.slice("adapter-version:".length));
|
|
assert.equal(command.protocol, "GENERIC_TCP");
|
|
assert.equal(command.lifecycleState, "draft");
|
|
assert.equal("profile" in command, false);
|
|
assert.equal("source" in command, false);
|
|
});
|
|
|
|
test("route and enrollment commands resolve only scoped references", () => {
|
|
const route = normalizeInfrastructureManagementCommand("route.ensure", {
|
|
projectRef,
|
|
routeKey: "primary-ingress",
|
|
displayName: "Primary ingress",
|
|
edgeRef,
|
|
modelProfileRef: "vendor.model.protocol.v1",
|
|
listenerRef: "listener:generic-tcp-primary",
|
|
protocol: "GENERIC_TCP",
|
|
direction: "bidirectional",
|
|
});
|
|
const enrollment = normalizeInfrastructureManagementCommand(
|
|
"enrollment_intent.ensure",
|
|
{
|
|
projectRef,
|
|
enrollmentKey: "pilot-device",
|
|
routeRef,
|
|
modelProfileRef: "vendor.model.protocol.v1",
|
|
displayName: "Pilot device",
|
|
identifierKind: "serial",
|
|
identifierDigest,
|
|
identifierMasked: "********0001",
|
|
expiresAt: "2026-09-01T00:00:00.000Z",
|
|
},
|
|
);
|
|
|
|
assert.equal(route.projectId, projectRef.slice("project:".length));
|
|
assert.equal(route.edgeId, edgeRef.slice("edge:".length));
|
|
assert.equal(enrollment.routeId, routeRef.slice("route:".length));
|
|
assert.equal(enrollment.identifierDigest, identifierDigest);
|
|
});
|
|
|
|
test("enrollment contract rejects raw identifiers and credential-shaped fields", () => {
|
|
const base = {
|
|
projectRef,
|
|
enrollmentKey: "pilot-device",
|
|
routeRef,
|
|
modelProfileRef: "vendor.model.protocol.v1",
|
|
displayName: "Pilot device",
|
|
identifierKind: "imei",
|
|
identifierDigest,
|
|
identifierMasked: "***********0001",
|
|
};
|
|
|
|
assert.throws(
|
|
() => normalizeInfrastructureManagementCommand(
|
|
"enrollment_intent.ensure",
|
|
{ ...base, identifierMasked: "000000000000001" },
|
|
),
|
|
/safe_projection_contains_unmasked_imei/,
|
|
);
|
|
assert.throws(
|
|
() => normalizeInfrastructureManagementCommand(
|
|
"enrollment_intent.ensure",
|
|
{ ...base, credential: "forbidden" },
|
|
),
|
|
/device_management_command_field_unexpected:credential/,
|
|
);
|
|
});
|
|
|
|
test("shared catalog and Edge authority requires the Hub owner ceiling", () => {
|
|
assert.doesNotThrow(() => assertPlatformCatalogAuthority(actor("owner")));
|
|
assert.throws(
|
|
() => assertPlatformCatalogAuthority(actor("admin")),
|
|
/device_platform_catalog_access_denied/,
|
|
);
|
|
});
|
|
|
|
test("normalizes only a pinned Core-initiated public Edge channel", () => {
|
|
const command = normalizeInfrastructureManagementCommand("edge.ensure", {
|
|
edgeKey: "moscow-edge",
|
|
displayName: "Moscow Edge",
|
|
deploymentRef: "deployment:device-edge/moscow-1",
|
|
lifecycleState: "active",
|
|
channel: {
|
|
endpoint: "https://155.212.211.15:8443/",
|
|
servername: "155.212.211.15",
|
|
generationRef: "channel-generation:1",
|
|
trustBundleRef: "edge-trust:moscow-edge",
|
|
certificateIdentities: [{
|
|
generationRef: "edge-identity:1",
|
|
fingerprint: "AA:".repeat(31) + "AA",
|
|
status: "active",
|
|
}],
|
|
lifecycleState: "active",
|
|
},
|
|
});
|
|
|
|
assert.equal(command.channel.endpoint, "https://155.212.211.15:8443/");
|
|
assert.equal(command.channel.lifecycleState, "active");
|
|
assert.equal(command.channel.certificateIdentities.length, 1);
|
|
for (const endpoint of [
|
|
"https://127.0.0.1:8443/",
|
|
"https://192.168.1.1:8443/",
|
|
"https://155.212.211.15:9921/",
|
|
"http://155.212.211.15:8443/",
|
|
]) {
|
|
assert.throws(
|
|
() => normalizeInfrastructureManagementCommand("edge.ensure", {
|
|
edgeKey: "bad-edge",
|
|
displayName: "Bad Edge",
|
|
channel: { ...command.channel, endpoint },
|
|
}),
|
|
/device_edge_channel_endpoint_invalid/,
|
|
);
|
|
}
|
|
assert.throws(
|
|
() => normalizeInfrastructureManagementCommand("edge.ensure", {
|
|
edgeKey: "bad-edge",
|
|
displayName: "Bad Edge",
|
|
channel: { ...command.channel, servername: "example.invalid" },
|
|
}),
|
|
/device_edge_channel_servername_mismatch/,
|
|
);
|
|
assert.throws(
|
|
() => normalizeInfrastructureManagementCommand("edge.ensure", {
|
|
edgeKey: "bad-edge",
|
|
displayName: "Bad Edge",
|
|
channel: { lifecycleState: "disabled", endpoint: command.channel.endpoint },
|
|
}),
|
|
/device_edge_channel_disabled_configuration_invalid/,
|
|
);
|
|
});
|
|
|
|
function actor(hubRole) {
|
|
return normalizeManagementActor({
|
|
userRef: "user:platform-admin",
|
|
hubRole,
|
|
groupRefs: [],
|
|
ownerScopes: [],
|
|
});
|
|
}
|