250 lines
9.4 KiB
JavaScript
250 lines
9.4 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
createDeviceCoreClient,
|
|
createLocalPreviewDeviceCore,
|
|
} from "./device-core-client.mjs";
|
|
|
|
const token = "device-core-test-token-that-is-never-exposed";
|
|
const actor = Object.freeze({
|
|
userRef: "user:device-admin",
|
|
hubRole: "admin",
|
|
groupRefs: ["group:device-engineers"],
|
|
ownerScopes: [{ scopeKind: "personal", ownerRef: "user:device-admin" }],
|
|
});
|
|
const platformActor = Object.freeze({ ...actor, hubRole: "owner" });
|
|
|
|
test("Device Core client creates trusted actor headers and keeps its token server-side", async () => {
|
|
const calls = [];
|
|
const client = createDeviceCoreClient({
|
|
baseUrl: "http://device-control-core:3210",
|
|
token,
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init });
|
|
return jsonResponse(200, { ok: true, projects: [] });
|
|
},
|
|
});
|
|
|
|
assert.deepEqual(await client.listProjects(actor), []);
|
|
assert.equal(calls.length, 1);
|
|
assert.equal(calls[0].init.headers.Authorization, `Bearer ${token}`);
|
|
assert.equal(calls[0].init.headers["X-NODEDC-User-Ref"], actor.userRef);
|
|
assert.equal(calls[0].init.headers["X-NODEDC-Hub-Role"], "admin");
|
|
assert.equal(calls[0].init.headers["X-NODEDC-Group-Refs"], "group:device-engineers");
|
|
assert.equal(
|
|
calls[0].init.headers["X-NODEDC-Owner-Scopes"],
|
|
"personal=user:device-admin",
|
|
);
|
|
assert.equal(JSON.stringify(await client.listProjects(actor)).includes(token), false);
|
|
});
|
|
|
|
test("Device Core client accepts only canonical commands and entity refs", async () => {
|
|
const client = createDeviceCoreClient({
|
|
baseUrl: "http://127.0.0.1:3210",
|
|
token,
|
|
fetchImpl: async () => jsonResponse(200, { ok: true, replayed: false, result: {} }),
|
|
});
|
|
|
|
await assert.rejects(
|
|
client.execute("raw:proxy", actor, {}, "device-manager-12345678"),
|
|
/device_manager_command_invalid/,
|
|
);
|
|
await assert.rejects(
|
|
client.getWorkspace(actor, "project:not-a-uuid"),
|
|
/device_project_ref_invalid/,
|
|
);
|
|
await assert.rejects(
|
|
client.execute("projects:ensure", actor, {}, "short"),
|
|
/device_idempotency_key_invalid/,
|
|
);
|
|
});
|
|
|
|
test("local preview is empty and creates resources only through canonical commands", async () => {
|
|
const client = createLocalPreviewDeviceCore();
|
|
assert.deepEqual(await client.listProjects(actor), []);
|
|
|
|
const owner = await client.execute("owner-scopes:ensure", actor, {
|
|
scopeKind: "personal",
|
|
ownerRef: actor.userRef,
|
|
displayName: "Device Admin",
|
|
});
|
|
assert.equal(owner.result.created, true);
|
|
|
|
const created = await client.execute("projects:ensure", actor, {
|
|
scopeKind: "personal",
|
|
ownerRef: actor.userRef,
|
|
projectKey: "sandbox",
|
|
name: "Device sandbox",
|
|
description: null,
|
|
});
|
|
assert.equal(created.result.created, true);
|
|
const projectRef = created.result.project.projectRef;
|
|
|
|
await client.execute("collections:ensure", actor, {
|
|
projectRef,
|
|
collectionKey: "field-devices",
|
|
name: "Field devices",
|
|
description: null,
|
|
});
|
|
|
|
const adapterPackage = await client.execute("adapter-packages:ensure", platformActor, {
|
|
packageKey: "generic-tracker",
|
|
displayName: "Generic tracker",
|
|
publisherRef: "publisher:nodedc",
|
|
lifecycleState: "active",
|
|
});
|
|
const adapterVersion = await client.execute("adapter-versions:register", platformActor, {
|
|
adapterPackageRef: adapterPackage.result.adapterPackage.adapterPackageRef,
|
|
version: "1.0.0",
|
|
runtimePackageRef: "artifact:generic-tracker:1.0.0",
|
|
contentDigest: `sha256:${"a".repeat(64)}`,
|
|
contractVersion: "device-adapter.v1",
|
|
capabilities: ["telemetry"],
|
|
lifecycleState: "draft",
|
|
});
|
|
const modelProfile = await client.execute("model-profiles:register", platformActor, {
|
|
adapterVersionRef: adapterVersion.result.adapterVersion.adapterVersionRef,
|
|
profileRef: "generic.tracker.v1",
|
|
schemaVersion: "1.0.0",
|
|
vendor: "Generic",
|
|
model: "Tracker",
|
|
deviceType: "tracker",
|
|
protocol: "INTERNAL",
|
|
schemaArtifactRef: "schema:generic.tracker.v1",
|
|
profileDigest: `sha256:${"b".repeat(64)}`,
|
|
capabilities: ["telemetry"],
|
|
lifecycleState: "draft",
|
|
});
|
|
const edge = await client.execute("edges:ensure", platformActor, {
|
|
edgeKey: "preview-edge",
|
|
displayName: "Preview Edge",
|
|
deploymentRef: "deployment:preview-edge",
|
|
lifecycleState: "provisioning",
|
|
});
|
|
const route = await client.execute("routes:ensure", actor, {
|
|
projectRef,
|
|
routeKey: "preview-route",
|
|
displayName: "Preview route",
|
|
edgeRef: edge.result.edge.edgeRef,
|
|
modelProfileRef: modelProfile.result.modelProfile.modelProfileRef,
|
|
listenerRef: "listener:preview",
|
|
protocol: "INTERNAL",
|
|
direction: "telemetry",
|
|
lifecycleState: "draft",
|
|
});
|
|
await client.execute("adapter-versions:register", platformActor, {
|
|
adapterPackageRef: adapterPackage.result.adapterPackage.adapterPackageRef,
|
|
version: "1.0.0",
|
|
runtimePackageRef: "artifact:generic-tracker:1.0.0",
|
|
contentDigest: `sha256:${"a".repeat(64)}`,
|
|
contractVersion: "device-adapter.v1",
|
|
capabilities: ["telemetry"],
|
|
lifecycleState: "active",
|
|
});
|
|
await client.execute("model-profiles:register", platformActor, {
|
|
adapterVersionRef: adapterVersion.result.adapterVersion.adapterVersionRef,
|
|
profileRef: "generic.tracker.v1",
|
|
schemaVersion: "1.0.0",
|
|
vendor: "Generic",
|
|
model: "Tracker",
|
|
deviceType: "tracker",
|
|
protocol: "INTERNAL",
|
|
schemaArtifactRef: "schema:generic.tracker.v1",
|
|
profileDigest: `sha256:${"b".repeat(64)}`,
|
|
capabilities: ["telemetry"],
|
|
lifecycleState: "active",
|
|
});
|
|
await client.execute("edges:ensure", platformActor, {
|
|
edgeKey: "preview-edge",
|
|
displayName: "Preview Edge",
|
|
deploymentRef: "deployment:preview-edge",
|
|
lifecycleState: "active",
|
|
});
|
|
await client.execute("routes:ensure", actor, {
|
|
projectRef,
|
|
routeKey: "preview-route",
|
|
displayName: "Preview route",
|
|
edgeRef: edge.result.edge.edgeRef,
|
|
modelProfileRef: modelProfile.result.modelProfile.modelProfileRef,
|
|
listenerRef: "listener:preview",
|
|
protocol: "INTERNAL",
|
|
direction: "telemetry",
|
|
lifecycleState: "active",
|
|
});
|
|
const enrollment = await client.execute("enrollment-intents:ensure", actor, {
|
|
projectRef,
|
|
enrollmentKey: "preview-device",
|
|
routeRef: route.result.route.routeRef,
|
|
modelProfileRef: modelProfile.result.modelProfile.modelProfileRef,
|
|
displayName: "Preview device",
|
|
identifier: { kind: "imei", value: "123456789012345" },
|
|
expiresAt: null,
|
|
});
|
|
assert.equal(enrollment.result.enrollmentIntent.identifier.masked, "***********2345");
|
|
assert.equal(JSON.stringify(enrollment).includes("123456789012345"), false);
|
|
const collectionRef = (await client.getWorkspace(actor, projectRef))
|
|
.collections[0].collectionRef;
|
|
await client.execute("device-bindings:ensure", actor, {
|
|
projectRef,
|
|
bindingKey: "preview-binding",
|
|
displayName: "Preview binding",
|
|
source: { kind: "collection", ref: collectionRef },
|
|
targetKind: "foundry.application",
|
|
targetRef: "application:preview-map",
|
|
capabilities: ["observe"],
|
|
});
|
|
await client.execute("project-grants:upsert", actor, {
|
|
projectRef,
|
|
principalKind: "group",
|
|
principalRef: "group:preview-viewers",
|
|
projectRole: "viewer",
|
|
capabilityAllow: [],
|
|
capabilityDeny: [],
|
|
lifecycleState: "active",
|
|
});
|
|
|
|
const projects = await client.listProjects(actor);
|
|
assert.equal(projects.length, 1);
|
|
assert.equal(projects[0].counts.collections, 1);
|
|
const workspace = await client.getWorkspace(actor, projectRef);
|
|
assert.equal(workspace.collections[0].collectionKey, "field-devices");
|
|
assert.equal(workspace.adapterPackages[0].packageKey, "generic-tracker");
|
|
assert.equal(workspace.routes[0].routeKey, "preview-route");
|
|
assert.equal(workspace.routes[0].lifecycleState, "active");
|
|
assert.equal(workspace.enrollments[0].expectedIdentifier.masked, "***********2345");
|
|
assert.equal(workspace.bindings[0].lifecycleState, "pending_external_approval");
|
|
assert.equal(workspace.grants.length, 2);
|
|
assert.ok(workspace.auditEvents.some((event) => event.eventType === "device_binding.created"));
|
|
assert.equal(workspace.policies.commandTransport, "disabled");
|
|
assert.deepEqual(workspace.devices, []);
|
|
});
|
|
|
|
test("explicit B2 preview fixture is isolated from the empty canonical preview", async () => {
|
|
const client = createLocalPreviewDeviceCore({ fixture: "arusnavi-b2" });
|
|
const projects = await client.listProjects(actor);
|
|
assert.equal(projects.length, 1);
|
|
assert.equal(projects[0].counts.devices, 1);
|
|
const workspace = await client.getWorkspace(actor, projects[0].projectRef);
|
|
assert.equal(workspace.devices[0].modelProfileRef, "arusnavi.b2.internal.v1");
|
|
assert.equal(workspace.devices[0].identifier.masked, "***********1088");
|
|
assert.equal(workspace.devices[0].identifier.value, "863151070211088");
|
|
assert.equal(workspace.devices[0].integrationDeviceId, "8028");
|
|
assert.equal(workspace.sessions[0].lifecycleState, "online");
|
|
assert.equal(workspace.policies.commandTransport, "typed-service-ping-v1");
|
|
assert.equal(workspace.policies.identifierProjection, "authorized-full");
|
|
assert.equal(JSON.stringify(workspace).includes("123456789012345"), false);
|
|
|
|
assert.throws(
|
|
() => createLocalPreviewDeviceCore({ fixture: "unknown" }),
|
|
/device_manager_preview_fixture_invalid/,
|
|
);
|
|
});
|
|
|
|
function jsonResponse(status, body) {
|
|
return new Response(JSON.stringify(body), {
|
|
status,
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
}
|