feat(k1): complete primary acquisition lifecycle
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
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/);
|
||||
});
|
||||
Reference in New Issue
Block a user