96 lines
3.1 KiB
JavaScript
96 lines
3.1 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 guard;
|
|
|
|
before(async () => {
|
|
server = await createServer({
|
|
appType: "custom",
|
|
logLevel: "silent",
|
|
server: { middlewareMode: true },
|
|
});
|
|
guard = await server.ssrLoadModule("/src/core/runtime/acquisitionGuard.ts");
|
|
});
|
|
|
|
after(async () => {
|
|
await server?.close();
|
|
});
|
|
|
|
function runtimeState(acquisitionState, cleanupPending = false) {
|
|
return {
|
|
phase: "idle",
|
|
sourceMode: "idle",
|
|
acquisition: acquisitionState === null
|
|
? null
|
|
: {
|
|
acquisitionId: "acq-1",
|
|
deviceId: "device-1",
|
|
deviceSessionId: "device-session-1",
|
|
compatibilityProfileId: "profile-1",
|
|
controlMode: "operator-manual",
|
|
state: acquisitionState,
|
|
stateRevision: 1,
|
|
operatorInstructions: [],
|
|
cleanupPending,
|
|
},
|
|
};
|
|
}
|
|
|
|
test("every nonterminal acquisition phase blocks saved and manual source replacement", () => {
|
|
for (const state of [
|
|
"preparing",
|
|
"prepared",
|
|
"awaiting_external_start",
|
|
"starting",
|
|
"acquiring",
|
|
"awaiting_external_stop",
|
|
"stopping",
|
|
"finalizing",
|
|
]) {
|
|
assert.equal(guard.isSpatialSourceSwitchBlocked(runtimeState(state)), true, state);
|
|
}
|
|
});
|
|
|
|
test("terminal or absent acquisitions allow an explicit source selection", () => {
|
|
for (const state of ["completed", "failed", "aborted", "interrupted"]) {
|
|
assert.equal(guard.isSpatialSourceSwitchBlocked(runtimeState(state)), false, state);
|
|
}
|
|
assert.equal(guard.isSpatialSourceSwitchBlocked(runtimeState(null)), false);
|
|
assert.equal(guard.isSpatialSourceSwitchBlocked(null), false);
|
|
});
|
|
|
|
test("an unknown acquisition state fails closed", () => {
|
|
assert.equal(
|
|
guard.isSpatialSourceSwitchBlocked(runtimeState("future_vendor_phase")),
|
|
true,
|
|
);
|
|
assert.match(guard.SPATIAL_SOURCE_SWITCH_BLOCKED_REASON, /Завершите текущий приём/);
|
|
});
|
|
|
|
test("terminal acquisition with retained cleanup remains source-switch blocked", () => {
|
|
assert.equal(guard.isSpatialSourceSwitchBlocked(runtimeState("failed", true)), true);
|
|
assert.equal(guard.isSpatialSourceSwitchBlocked(runtimeState("failed", false)), false);
|
|
});
|
|
|
|
test("saved-session and manual source controls share the acquisition guard", async () => {
|
|
const appSource = await readFile(new URL("../src/App.tsx", import.meta.url), "utf8");
|
|
const sessionSelectSource = await readFile(
|
|
new URL("../src/components/ObservationSessionSelect.tsx", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const observationHookSource = await readFile(
|
|
new URL("../src/core/observation/useObservationSessions.ts", import.meta.url),
|
|
"utf8",
|
|
);
|
|
|
|
assert.match(appSource, /blockedReason=\{sourceSwitchBlockedReason\}/);
|
|
assert.match(appSource, /disabled=\{sourceSwitchBlocked \|\| !sourceDraft\.trim\(\)\}/);
|
|
assert.match(appSource, /if \(sourceSwitchBlockedRef\.current\) return;/);
|
|
assert.match(sessionSelectSource, /replayEnabled: blockedReason === null/);
|
|
assert.match(observationHookSource, /!replayEnabledRef\.current/);
|
|
});
|