176 lines
5.7 KiB
JavaScript
176 lines
5.7 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import { after, before, test } from "node:test";
|
|
|
|
import { createServer } from "vite";
|
|
|
|
let server;
|
|
let admission;
|
|
let rerunPresentationStatus;
|
|
let recordedMediaPresentationState;
|
|
|
|
before(async () => {
|
|
server = await createServer({
|
|
appType: "custom",
|
|
logLevel: "silent",
|
|
server: { middlewareMode: true },
|
|
});
|
|
admission = await server.ssrLoadModule(
|
|
"/src/core/observation/recordedSessionAdmission.ts",
|
|
);
|
|
({ rerunPresentationStatus } = await server.ssrLoadModule(
|
|
"/src/components/RerunViewport.tsx",
|
|
));
|
|
({ recordedMediaPresentationState } = await server.ssrLoadModule(
|
|
"/src/components/RecordedFmp4Player.tsx",
|
|
));
|
|
});
|
|
|
|
after(async () => {
|
|
await server?.close();
|
|
});
|
|
|
|
function camera(phase, byteLength = 1_024) {
|
|
return { phase, byteLength, message: null };
|
|
}
|
|
|
|
test("recorded session admits RRD and every declared camera as one atomic generation", () => {
|
|
const ids = ["camera.left", "camera.right"];
|
|
const oneCamera = {
|
|
"camera.left": camera("ready"),
|
|
"camera.right": camera("pending"),
|
|
};
|
|
const partialGate = admission.recordedSessionAdmissionPhase("ready", ids, oneCamera);
|
|
assert.equal(partialGate, "loading");
|
|
assert.equal(rerunPresentationStatus("ready", partialGate, true), "loading");
|
|
assert.equal(
|
|
recordedMediaPresentationState("ready", "generation", "generation", false, partialGate),
|
|
"loading",
|
|
);
|
|
|
|
const completeGate = admission.recordedSessionAdmissionPhase("ready", ids, {
|
|
...oneCamera,
|
|
"camera.right": camera("ready"),
|
|
});
|
|
assert.equal(completeGate, "ready");
|
|
assert.equal(rerunPresentationStatus("ready", completeGate, true), "ready");
|
|
assert.equal(
|
|
recordedMediaPresentationState("ready", "generation", "generation", false, completeGate),
|
|
"ready",
|
|
);
|
|
});
|
|
|
|
test("any RRD or camera failure closes the complete recorded session", () => {
|
|
const ids = ["camera.left", "camera.right"];
|
|
const cameras = {
|
|
"camera.left": camera("ready"),
|
|
"camera.right": camera("error"),
|
|
};
|
|
assert.equal(admission.recordedSessionAdmissionPhase("ready", ids, cameras), "error");
|
|
assert.equal(admission.recordedSessionAdmissionPhase("error", ids, {
|
|
...cameras,
|
|
"camera.right": camera("ready"),
|
|
}), "error");
|
|
assert.equal(rerunPresentationStatus("ready", "error", true), "error");
|
|
assert.equal(
|
|
recordedMediaPresentationState("ready", "generation", "generation", false, "error"),
|
|
"error",
|
|
);
|
|
});
|
|
|
|
test("camera admission enforces independent 16/256/512 MiB limits before fetching", () => {
|
|
const {
|
|
MAX_RECORDED_CAMERA_SOURCES,
|
|
MAX_RECORDED_MEDIA_SOURCE_BYTES,
|
|
MAX_RECORDED_SESSION_CAMERA_BYTES,
|
|
recordedCameraDescriptorPreflight,
|
|
} = admission;
|
|
assert.equal(MAX_RECORDED_CAMERA_SOURCES, 16);
|
|
assert.equal(MAX_RECORDED_MEDIA_SOURCE_BYTES, 256 * 1024 * 1024);
|
|
assert.equal(MAX_RECORDED_SESSION_CAMERA_BYTES, 512 * 1024 * 1024);
|
|
assert.equal(recordedCameraDescriptorPreflight(
|
|
Array.from({ length: 2 }, (_, index) => ({
|
|
id: `camera.${index}`,
|
|
byteLength: MAX_RECORDED_MEDIA_SOURCE_BYTES,
|
|
})),
|
|
), "ready");
|
|
assert.equal(recordedCameraDescriptorPreflight(
|
|
Array.from({ length: 17 }, (_, index) => ({ id: `camera.${index}`, byteLength: 1 })),
|
|
), "error");
|
|
assert.equal(recordedCameraDescriptorPreflight([
|
|
{ id: "camera.large", byteLength: MAX_RECORDED_MEDIA_SOURCE_BYTES + 1 },
|
|
]), "error");
|
|
assert.equal(recordedCameraDescriptorPreflight(
|
|
Array.from({ length: 5 }, (_, index) => ({
|
|
id: `camera.${index}`,
|
|
byteLength: 110 * 1024 * 1024,
|
|
})),
|
|
), "error");
|
|
assert.equal(recordedCameraDescriptorPreflight([
|
|
{ id: "camera.duplicate", byteLength: 1 },
|
|
{ id: "camera.duplicate", byteLength: 1 },
|
|
]), "error");
|
|
});
|
|
|
|
test("camera preparation remains single-flight and retains the loading permit", () => {
|
|
const ids = ["camera.first", "camera.preferred"];
|
|
const cameras = {
|
|
"camera.first": camera("loading"),
|
|
"camera.preferred": camera("pending"),
|
|
};
|
|
assert.deepEqual(
|
|
admission.nextRecordedCameraPreparationIds(
|
|
ids,
|
|
cameras,
|
|
new Set(["camera.preferred"]),
|
|
),
|
|
["camera.first"],
|
|
);
|
|
assert.deepEqual(
|
|
admission.nextRecordedCameraPreparationIds(ids, {
|
|
...cameras,
|
|
"camera.first": camera("ready"),
|
|
}),
|
|
["camera.preferred"],
|
|
);
|
|
});
|
|
|
|
test("new render workers close readiness and stale callbacks cannot reopen it", () => {
|
|
const ready = { ...camera("ready"), workerGeneration: 1 };
|
|
const remounted = admission.mergeRecordedCameraAdmission(ready, {
|
|
...camera("loading"),
|
|
workerGeneration: 2,
|
|
});
|
|
assert.equal(remounted.phase, "loading");
|
|
assert.equal(remounted.workerGeneration, 2);
|
|
assert.equal(admission.mergeRecordedCameraAdmission(remounted, {
|
|
...camera("ready"),
|
|
workerGeneration: 1,
|
|
}), remounted);
|
|
const admitted = admission.mergeRecordedCameraAdmission(remounted, {
|
|
...camera("ready"),
|
|
workerGeneration: 2,
|
|
});
|
|
assert.equal(admitted.phase, "ready");
|
|
const failed = admission.mergeRecordedCameraAdmission(admitted, {
|
|
...camera("error"),
|
|
workerGeneration: 2,
|
|
});
|
|
assert.equal(failed.phase, "error");
|
|
assert.equal(admission.mergeRecordedCameraAdmission(failed, {
|
|
...camera("loading"),
|
|
workerGeneration: 3,
|
|
}), failed);
|
|
});
|
|
|
|
test("recorded player is not mounted for a camera without a scheduler permit", async () => {
|
|
const source = await readFile(
|
|
new URL("../src/components/ObservationSources.tsx", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const placeholder = source.indexOf("if (!prepareRecorded)");
|
|
const player = source.indexOf("<RecordedFmp4Player", placeholder);
|
|
assert.ok(placeholder >= 0 && player > placeholder);
|
|
assert.match(source.slice(placeholder, player), /return\s*\(/);
|
|
});
|