144 lines
4.0 KiB
JavaScript
144 lines
4.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
DEVICE_DISCOVERY_SIGNAL_SCHEMA,
|
|
} from "../../../packages/device-protocol-contract/src/index.mjs";
|
|
import { createControlCoreApp } from "../src/app.mjs";
|
|
|
|
const gatewayToken = "test-only-gateway-token-with-32-bytes";
|
|
const identifierPepper = "test-only-identifier-pepper-with-32-bytes";
|
|
const fakeImei = "000000000000001";
|
|
|
|
test("health reports database readiness and disabled command transport", async () => {
|
|
const runtime = await startTestServer({
|
|
repository: {
|
|
health: async () => "ready",
|
|
},
|
|
});
|
|
try {
|
|
const response = await fetch(`${runtime.baseUrl}/healthz`);
|
|
assert.equal(response.status, 200);
|
|
assert.deepEqual(await response.json(), {
|
|
ok: true,
|
|
service: "nodedc-device-control-core",
|
|
database: "ready",
|
|
discoveryIngest: "disabled",
|
|
commandTransport: "disabled",
|
|
});
|
|
} finally {
|
|
await runtime.close();
|
|
}
|
|
});
|
|
|
|
test("discovery ingest is closed by default", async () => {
|
|
const runtime = await startTestServer({
|
|
repository: {
|
|
health: async () => "ready",
|
|
},
|
|
});
|
|
try {
|
|
const response = await fetch(
|
|
`${runtime.baseUrl}/internal/v1/device-discoveries:observe`,
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: "{}",
|
|
},
|
|
);
|
|
assert.equal(response.status, 404);
|
|
assert.equal(
|
|
(await response.json()).error,
|
|
"device_discovery_ingest_disabled",
|
|
);
|
|
} finally {
|
|
await runtime.close();
|
|
}
|
|
});
|
|
|
|
test("authenticated ingest stores only digest and returns a masked view", async () => {
|
|
let stored;
|
|
const runtime = await startTestServer({
|
|
discoveryIngestEnabled: true,
|
|
gatewayToken,
|
|
identifierPepper,
|
|
repository: {
|
|
health: async () => "ready",
|
|
upsertQuarantineDiscovery: async (value) => {
|
|
stored = value;
|
|
return {
|
|
created: true,
|
|
value: {
|
|
...value.safeView,
|
|
discoveryRef: "discovery:test-001",
|
|
},
|
|
};
|
|
},
|
|
},
|
|
});
|
|
try {
|
|
const unauthorized = await fetch(
|
|
`${runtime.baseUrl}/internal/v1/device-discoveries:observe`,
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(fakeSignal()),
|
|
},
|
|
);
|
|
assert.equal(unauthorized.status, 401);
|
|
|
|
const response = await fetch(
|
|
`${runtime.baseUrl}/internal/v1/device-discoveries:observe`,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${gatewayToken}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(fakeSignal()),
|
|
},
|
|
);
|
|
assert.equal(response.status, 201);
|
|
const body = await response.json();
|
|
const serialized = JSON.stringify(body);
|
|
assert.equal(serialized.includes(fakeImei), false);
|
|
assert.equal(body.discovery.identifier.masked, "***********0001");
|
|
assert.match(stored.identifierDigest, /^hmac-sha256:[a-f0-9]{64}$/);
|
|
assert.equal(JSON.stringify(stored).includes(fakeImei), false);
|
|
} finally {
|
|
await runtime.close();
|
|
}
|
|
});
|
|
|
|
function fakeSignal() {
|
|
return {
|
|
schemaVersion: DEVICE_DISCOVERY_SIGNAL_SCHEMA,
|
|
sessionRef: "session:test-001",
|
|
modelProfileRef: "arusnavi.b2.internal.v1",
|
|
protocol: "INTERNAL",
|
|
observedAt: "2026-07-25T00:00:00.000Z",
|
|
identifier: { kind: "imei", value: fakeImei },
|
|
evidence: {
|
|
transport: "tcp",
|
|
bytesObserved: 128,
|
|
framingStatus: "verified",
|
|
specificationRef: "arusnavi.internal.framing.test-v1",
|
|
},
|
|
};
|
|
}
|
|
|
|
async function startTestServer(options) {
|
|
const server = createControlCoreApp(options);
|
|
await new Promise((resolve, reject) => {
|
|
server.once("error", reject);
|
|
server.listen(0, "127.0.0.1", resolve);
|
|
});
|
|
const address = server.address();
|
|
return {
|
|
baseUrl: `http://127.0.0.1:${address.port}`,
|
|
close: () => new Promise((resolve, reject) => {
|
|
server.close((error) => (error ? reject(error) : resolve()));
|
|
}),
|
|
};
|
|
}
|