NODEDC_MISSION_CORE/apps/control-station/test/observationSources.test.mjs

372 lines
13 KiB
JavaScript

import assert from "node:assert/strict";
import { after, before, test } from "node:test";
import { createServer } from "vite";
let server;
let xgridsK1Manifest;
let xgridsK1ObservationSources;
let openObservationSource;
let shouldRestartObservationSource;
let consumeCameraLeaseRetry;
let resetCameraLeaseRetryBudget;
before(async () => {
server = await createServer({
appType: "custom",
logLevel: "silent",
server: { middlewareMode: true },
});
({ xgridsK1Manifest } = await server.ssrLoadModule(
"/src/device-plugins/xgrids-k1/manifest.ts",
));
({ xgridsK1ObservationSources } = await server.ssrLoadModule(
"/src/device-plugins/xgrids-k1/observationSources.ts",
));
({ openObservationSource, shouldRestartObservationSource } = await server.ssrLoadModule(
"/src/core/observation/layoutPolicy.ts",
));
({ consumeCameraLeaseRetry, resetCameraLeaseRetryBudget } = await server.ssrLoadModule(
"/src/components/MseFmp4WebSocketPlayer.tsx",
));
});
after(async () => {
await server?.close();
});
function model() {
const activeModel = xgridsK1Manifest.spec.models[0];
assert.ok(activeModel, "XGRIDS plugin must declare at least one model");
return activeModel;
}
function cameraRow(sourceId, label) {
return {
stream_id: `camera.preview.${sourceId.split(".").at(-1)}`,
source_id: sourceId,
semantic_channel_id: "camera.preview.live",
label,
sensor_kind: "camera",
modality: "encoded-video",
availability: "available",
endpoint_label: "MSE · fMP4",
activation: {
group_id: "camera.preview.decoder",
max_active: 1,
selected: false,
controllable: true,
},
delivery: null,
};
}
function declaredState(cameraRows = [
cameraRow("sensor.camera.left", "K1 · камера слева"),
cameraRow("sensor.camera.right", "K1 · камера справа"),
]) {
const activeModel = model();
return {
phase: "connected",
source_mode: "idle",
k1_ip: "192.168.7.10",
foxglove_ws_url: "ws://192.168.7.10:8765",
foxglove_viewer_url: "http://192.168.7.10:8765/vendor-viewer",
compatibility: {
profile_id: "xgrids.lixelkity-k1.fw-3.0.2.direct-lan.v1",
camera_preview: "rtsp://192.168.7.10:8554/vendor-preview",
},
device_ref: {
device_id: "device-k1-001",
model_id: activeModel.id,
identity_stability: "stable",
identity_basis: "hardware-identifier",
},
device_session: {
device_session_id: "device-session-001",
device_id: "device-k1-001",
compatibility_profile_id: "xgrids.lixelkity-k1.fw-3.0.2.direct-lan.v1",
connectivity: "connected",
},
sensor_catalog: {
schema_version: "missioncore.sensor-catalog/v1alpha2",
revision: "test-profile",
streams: [
{ stream_id: "spatial.point-cloud.live", modality: "point-cloud", availability: "observed" },
...cameraRows,
],
},
camera_preview: {
phase: "idle",
revision: 1,
generation: 0,
active_source_id: null,
delivery: null,
},
};
}
function pointCloudStreamingState() {
return {
...declaredState(),
phase: "streaming",
source_mode: "live",
rerun_grpc_url: "rerun+http://127.0.0.1:9877/proxy",
acquisition: {
acquisition_id: "acquisition-001",
device_id: "device-k1-001",
device_session_id: "device-session-001",
compatibility_profile_id: "xgrids.lixelkity-k1.fw-3.0.2.direct-lan.v1",
control_mode: "operator-manual",
requested_streams: ["spatial.point-cloud.live"],
target_host: "127.0.0.1",
duration_seconds: 0,
evidence_policy: "required",
state: "acquiring",
state_revision: 3,
},
};
}
function cameraStreamingState(sourceId) {
const state = pointCloudStreamingState();
const delivery = {
id: `preview-generation-7:${sourceId}`,
kind: "mse-fmp4-websocket",
url: "/api/v1/device-plugins/xgrids-k1/camera-preview/ws?generation=7",
media_type: 'video/mp4; codecs="avc1.640028"',
};
state.sensor_catalog = {
...state.sensor_catalog,
streams: state.sensor_catalog.streams.map((stream) =>
stream.source_id === sourceId
? {
...stream,
availability: "streaming",
activation: { ...stream.activation, selected: true },
delivery,
}
: stream),
};
state.camera_preview = {
phase: "streaming",
revision: 4,
generation: 7,
active_source_id: sourceId,
delivery,
};
return state;
}
function collectUrlLikeStrings(value, found = []) {
if (typeof value === "string") {
if (value.includes("://")) found.push(value);
return found;
}
if (Array.isArray(value)) {
for (const item of value) collectUrlLikeStrings(item, found);
return found;
}
if (value && typeof value === "object") {
for (const item of Object.values(value)) collectUrlLikeStrings(item, found);
}
return found;
}
test("K1 maps zero, one or N catalog cameras without model-specific source ids", () => {
const zero = xgridsK1ObservationSources(declaredState([]), model());
const one = xgridsK1ObservationSources(
declaredState([cameraRow("rig.front", "Передняя камера")]),
model(),
);
const many = xgridsK1ObservationSources(declaredState(), model());
assert.deepEqual(zero.map(({ modality }) => modality), ["point-cloud"]);
assert.deepEqual(one.map(({ sourceId }) => sourceId), ["sensor.lidar.primary", "rig.front"]);
assert.deepEqual(many.map(({ sourceId }) => sourceId), [
"sensor.lidar.primary",
"sensor.camera.left",
"sensor.camera.right",
]);
assert.equal(new Set(many.map(({ id }) => id)).size, many.length);
assert.ok(many.every(({ provider }) => provider.pluginId && provider.modelId));
assert.ok(many.every(({ binding }) => binding.deviceId === "device-k1-001"));
});
test("descriptor ids remain stable while point cloud and selected camera start streaming", () => {
const declared = xgridsK1ObservationSources(declaredState(), model());
const streaming = xgridsK1ObservationSources(
cameraStreamingState("sensor.camera.left"),
model(),
);
assert.deepEqual(streaming.map(({ id }) => id), declared.map(({ id }) => id));
assert.equal(declared[0].availability, "available");
assert.equal(streaming[0].availability, "streaming");
assert.equal(streaming[0].previewUrl, "rerun+http://127.0.0.1:9877/proxy");
});
test("only the authoritative selected camera receives browser delivery", () => {
const sources = xgridsK1ObservationSources(
cameraStreamingState("sensor.camera.left"),
model(),
);
const cameras = sources.filter(({ modality }) => modality === "video");
const [left, right] = cameras;
assert.equal(cameras.length, 2);
assert.equal(left.activation.selected, true);
assert.equal(left.activation.maxActive, 1);
assert.equal(left.availability, "streaming");
assert.equal(left.delivery.kind, "mse-fmp4-websocket");
assert.equal(left.transport, "websocket");
assert.equal(right.activation.selected, false);
assert.equal(right.availability, "available");
assert.equal(right.delivery, null);
assert.equal(left.activation.groupId, right.activation.groupId);
assert.match(left.activation.groupId, /device-session-001/);
});
test("switching left to right keeps ids stable and never exposes both deliveries", () => {
const left = xgridsK1ObservationSources(
cameraStreamingState("sensor.camera.left"),
model(),
).filter(({ modality }) => modality === "video");
const right = xgridsK1ObservationSources(
cameraStreamingState("sensor.camera.right"),
model(),
).filter(({ modality }) => modality === "video");
assert.deepEqual(right.map(({ id }) => id), left.map(({ id }) => id));
assert.deepEqual(left.filter(({ delivery }) => delivery).map(({ sourceId }) => sourceId), [
"sensor.camera.left",
]);
assert.deepEqual(right.filter(({ delivery }) => delivery).map(({ sourceId }) => sourceId), [
"sensor.camera.right",
]);
});
test("camera descriptors never leak vendor RTSP endpoints or device IP addresses", () => {
const sources = xgridsK1ObservationSources(
cameraStreamingState("sensor.camera.left"),
model(),
);
const cameras = sources.filter(({ modality }) => modality === "video");
assert.ok(cameras.every(({ previewUrl }) => previewUrl === null));
assert.deepEqual(collectUrlLikeStrings(sources), [
"rerun+http://127.0.0.1:9877/proxy",
]);
const serialized = JSON.stringify(cameras);
assert.doesNotMatch(serialized, /rtsp:\/\//i);
assert.doesNotMatch(serialized, /192\.168\.7\.10/);
assert.doesNotMatch(serialized, /vendor-(?:preview|viewer)/);
});
test("unattested, duplicate and unsafe camera entries fail closed", () => {
const duplicate = cameraRow("sensor.camera.left", "Duplicate");
const state = cameraStreamingState("sensor.camera.left");
state.compatibility.profile_id = null;
state.device_session.compatibility_profile_id = null;
state.sensor_catalog.streams.push(duplicate);
state.camera_preview.delivery = {
...state.camera_preview.delivery,
url: "ws://192.168.7.10:9000/leak",
};
const cameras = xgridsK1ObservationSources(state, model()).filter(
({ modality }) => modality === "video",
);
assert.deepEqual(cameras.map(({ sourceId }) => sourceId), ["sensor.camera.right"]);
assert.equal(cameras[0].availability, "unverified");
assert.equal(cameras[0].activation.controllable, false);
assert.equal(cameras[0].delivery, null);
});
test("camera delivery rejects literal and encoded endpoint or credential leaks", () => {
const unsafeUrls = [
"/api/preview?upstream=rtsp://camera.local/live",
"/api/preview?upstream=rtsp%3A%2F%2Fcamera.local%2Flive",
"/api/preview?upstream=rtsp%253A%252F%252Fcamera.local%252Flive",
"/api/preview?endpoint=192.168.68.52:8554",
"/api/preview?endpoint=192%2E168%2E68%2E52",
"/api/preview?password=not-for-the-browser",
"/api/preview?%70%61%73%73%77%6f%72%64=not-for-the-browser",
"/api/preview/camera:secret@device",
"/%2f%2fevil.example/preview",
];
for (const url of unsafeUrls) {
const state = cameraStreamingState("sensor.camera.left");
state.camera_preview.delivery = { ...state.camera_preview.delivery, url };
state.sensor_catalog.streams = state.sensor_catalog.streams.map((stream) =>
stream.source_id === "sensor.camera.left"
? { ...stream, delivery: { ...stream.delivery, url } }
: stream,
);
const left = xgridsK1ObservationSources(state, model()).find(
({ sourceId }) => sourceId === "sensor.camera.left",
);
assert.ok(left, `left camera descriptor missing for ${url}`);
assert.equal(left.delivery, null, `unsafe delivery escaped for ${url}`);
}
});
test("manual camera reconnect restores an exhausted lease retry budget", () => {
let budget = resetCameraLeaseRetryBudget("delivery-7");
for (const expectedDelay of [400, 1_000, 2_000]) {
const retry = consumeCameraLeaseRetry(budget, "delivery-7");
assert.equal(retry.delay, expectedDelay);
budget = retry.budget;
}
assert.equal(consumeCameraLeaseRetry(budget, "delivery-7").delay, null);
budget = resetCameraLeaseRetryBudget("delivery-7");
const retryAfterManualReset = consumeCameraLeaseRetry(budget, "delivery-7");
assert.equal(retryAfterManualReset.delay, 400);
assert.equal(retryAfterManualReset.budget.count, 1);
});
test("layout policy evicts only exclusive camera peers", () => {
const sources = xgridsK1ObservationSources(
cameraStreamingState("sensor.camera.left"),
model(),
);
const left = sources.find(({ sourceId }) => sourceId === "sensor.camera.left");
const right = sources.find(({ sourceId }) => sourceId === "sensor.camera.right");
const pointCloud = sources.find(({ modality }) => modality === "point-cloud");
assert.ok(left && right && pointCloud);
const change = openObservationSource(
[pointCloud.id, left.id],
right.id,
sources,
);
assert.deepEqual(change.visibleIds, [pointCloud.id, right.id]);
assert.deepEqual(change.removedIds, [left.id]);
});
test("selected camera without delivery is explicitly restartable", () => {
const state = cameraStreamingState("sensor.camera.left");
state.camera_preview = {
...state.camera_preview,
phase: "error",
delivery: null,
};
state.sensor_catalog.streams = state.sensor_catalog.streams.map((stream) =>
stream.source_id === "sensor.camera.left"
? { ...stream, availability: "error", delivery: null }
: stream,
);
const sources = xgridsK1ObservationSources(state, model());
const left = sources.find(({ sourceId }) => sourceId === "sensor.camera.left");
const right = sources.find(({ sourceId }) => sourceId === "sensor.camera.right");
assert.ok(left && right);
assert.equal(left.activation.selected, true);
assert.equal(left.delivery, null);
assert.equal(left.availability, "error");
assert.equal(shouldRestartObservationSource(left), true);
assert.equal(shouldRestartObservationSource(right), false);
});