fix(k1): stabilize repeated acquisition and live viewer recovery
This commit is contained in:
@@ -439,6 +439,11 @@ test("live source is confirmed only by an acquiring acquisition", () => {
|
||||
assert.equal(lifecycle.sourceStatusLabel(waiting), "Ожидание реальных данных");
|
||||
assert.equal(lifecycle.isConfirmedLiveState(acquiring), true);
|
||||
assert.equal(lifecycle.confirmedRuntimeSourceMode(acquiring), "live");
|
||||
assert.equal(lifecycle.liveStartPlan(acquiring), "already-running");
|
||||
assert.equal(
|
||||
lifecycle.controlSessionEntryPlan("scanning", false, false),
|
||||
"continue",
|
||||
);
|
||||
});
|
||||
|
||||
test("prepared acquisition resumes without another prepare and remains recoverable", () => {
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { after, before, test } from "node:test";
|
||||
|
||||
import { createServer } from "vite";
|
||||
|
||||
let server;
|
||||
let advanceLiveReceiverWatchdog;
|
||||
let initialLiveReceiverWatchdogState;
|
||||
let initialLiveReceiverRecoveryState;
|
||||
let requestLiveReceiverRecovery;
|
||||
|
||||
before(async () => {
|
||||
server = await createServer({
|
||||
appType: "custom",
|
||||
logLevel: "silent",
|
||||
server: { middlewareMode: true },
|
||||
});
|
||||
({
|
||||
advanceLiveReceiverWatchdog,
|
||||
initialLiveReceiverRecoveryState,
|
||||
initialLiveReceiverWatchdogState,
|
||||
requestLiveReceiverRecovery,
|
||||
} = await server.ssrLoadModule("/src/core/observation/liveReceiverWatchdog.ts"));
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server?.close();
|
||||
});
|
||||
|
||||
test("watchdog ignores healthy live progress", () => {
|
||||
let state = initialLiveReceiverWatchdogState();
|
||||
for (let index = 0; index < 20; index += 1) {
|
||||
const observed = advanceLiveReceiverWatchdog(state, {
|
||||
nowMs: index * 500,
|
||||
backendActivitySequence: index * 2,
|
||||
viewerRangeMaxNs: index * 1_000_000_000,
|
||||
});
|
||||
state = observed.state;
|
||||
assert.notEqual(observed.signal, "stalled");
|
||||
}
|
||||
});
|
||||
|
||||
test("watchdog detects a frozen receiver while backend publication advances", () => {
|
||||
let state = initialLiveReceiverWatchdogState();
|
||||
let signal = "none";
|
||||
let stalledForMs = 0;
|
||||
for (let index = 0; index <= 13; index += 1) {
|
||||
const observed = advanceLiveReceiverWatchdog(state, {
|
||||
nowMs: index * 500,
|
||||
backendActivitySequence: 100 + index,
|
||||
viewerRangeMaxNs: 42_000_000_000,
|
||||
});
|
||||
state = observed.state;
|
||||
signal = observed.signal === "stalled" ? observed.signal : signal;
|
||||
stalledForMs = Math.max(stalledForMs, observed.stalledForMs);
|
||||
}
|
||||
assert.equal(signal, "stalled");
|
||||
assert.ok(stalledForMs >= 5_000);
|
||||
});
|
||||
|
||||
test("one backend increment at stream finalization does not create a false stall", () => {
|
||||
let state = initialLiveReceiverWatchdogState();
|
||||
state = advanceLiveReceiverWatchdog(state, {
|
||||
nowMs: 0,
|
||||
backendActivitySequence: 10,
|
||||
viewerRangeMaxNs: 20_000_000_000,
|
||||
}).state;
|
||||
state = advanceLiveReceiverWatchdog(state, {
|
||||
nowMs: 500,
|
||||
backendActivitySequence: 11,
|
||||
viewerRangeMaxNs: 20_000_000_000,
|
||||
}).state;
|
||||
const observed = advanceLiveReceiverWatchdog(state, {
|
||||
nowMs: 10_000,
|
||||
backendActivitySequence: 11,
|
||||
viewerRangeMaxNs: 20_000_000_000,
|
||||
});
|
||||
assert.equal(observed.signal, "none");
|
||||
});
|
||||
|
||||
test("startup failures request only three bounded viewer restarts", () => {
|
||||
let state = initialLiveReceiverRecoveryState();
|
||||
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
||||
const recovery = requestLiveReceiverRecovery(state);
|
||||
assert.equal(recovery.signal, "retry");
|
||||
assert.equal(recovery.attempt, attempt);
|
||||
assert.equal(recovery.state.awaitingRecovery, true);
|
||||
state = recovery.state;
|
||||
}
|
||||
const exhausted = requestLiveReceiverRecovery(state);
|
||||
assert.equal(exhausted.signal, "exhausted");
|
||||
assert.equal(exhausted.attempt, 3);
|
||||
assert.equal(exhausted.state.awaitingRecovery, false);
|
||||
});
|
||||
@@ -129,7 +129,11 @@ test("recorded RRD bytes are never split across LogChannel.send_rrd calls", asyn
|
||||
assert.match(source, /viewer\.start\(\s*rerunViewerInitialSource\(resolvedSource\)/s);
|
||||
assert.match(
|
||||
source,
|
||||
/recordingOpened = true;[\s\S]*if \(!isRecordedSource\) clearLiveRecordingOpenTimer\(\);/,
|
||||
/recordingOpened = true;[\s\S]*clearLiveRecordingOpenTimer\(\);[\s\S]*clearLiveRecordingDiscoveryTimer\(\);/,
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/viewer\.get_active_recording_id\(\)[\s\S]*eventCode: "live_receiver_active_store_admitted"[\s\S]*admitRecording\(\{[\s\S]*application_id: "nodedc_mission_core_spatial"/,
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
@@ -141,6 +145,14 @@ test("recorded RRD bytes are never split across LogChannel.send_rrd calls", asyn
|
||||
);
|
||||
});
|
||||
|
||||
test("raw replay exercises the same streaming receiver lifecycle as a live scan", async () => {
|
||||
const source = await readFile(
|
||||
new URL("../src/workspaces/Workspaces.tsx", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
assert.match(source, /followLive=\{!recordedReplay && streamActive\}/);
|
||||
});
|
||||
|
||||
test("the complete vendor canvas host is hidden during partial and failed admission", async () => {
|
||||
const css = await readFile(new URL("../src/styles/spatial.css", import.meta.url), "utf8");
|
||||
assert.match(
|
||||
|
||||
Reference in New Issue
Block a user