Capture the current unreleased K1 connection, recovery, lifecycle, viewer, and test work as a single known-bad baseline for subsequent fixes.
219 lines
6.8 KiB
JavaScript
219 lines
6.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { after, before, test } from "node:test";
|
|
|
|
import { createServer } from "vite";
|
|
|
|
let server;
|
|
let selectMonotonicXgridsState;
|
|
|
|
before(async () => {
|
|
server = await createServer({
|
|
appType: "custom",
|
|
logLevel: "silent",
|
|
server: { middlewareMode: true },
|
|
});
|
|
({ selectMonotonicXgridsState } = await server.ssrLoadModule(
|
|
"@xgrids-k1/frontend/stateOrdering.ts",
|
|
));
|
|
});
|
|
|
|
after(async () => {
|
|
await server?.close();
|
|
});
|
|
|
|
function snapshot(revision, generation, phase = "streaming", sessionId = "device-session-a") {
|
|
return {
|
|
phase: "connected",
|
|
message: `${phase}:${revision}:${generation}`,
|
|
device_session: sessionId === null
|
|
? null
|
|
: {
|
|
device_session_id: sessionId,
|
|
device_id: `device-for-${sessionId}`,
|
|
connectivity: "connected",
|
|
},
|
|
camera_preview: {
|
|
phase,
|
|
revision,
|
|
generation,
|
|
active_source_id: generation === null ? null : "sensor.camera.left",
|
|
delivery: null,
|
|
},
|
|
};
|
|
}
|
|
|
|
function stampedSnapshot({
|
|
runtimeStartedAt = "2026-08-06T10:00:00Z",
|
|
runtimeStartedMonotonicNs = "1000000",
|
|
runtimeId = "runtime-a",
|
|
snapshotRevision = 1,
|
|
cameraRevision = snapshotRevision,
|
|
generation = 1,
|
|
} = {}) {
|
|
return {
|
|
...snapshot(cameraRevision, generation),
|
|
snapshot_runtime_started_at_utc: runtimeStartedAt,
|
|
snapshot_runtime_started_monotonic_ns: runtimeStartedMonotonicNs,
|
|
snapshot_runtime_id: runtimeId,
|
|
snapshot_revision: snapshotRevision,
|
|
};
|
|
}
|
|
|
|
test("uses the process snapshot revision before camera-local counters", () => {
|
|
const current = stampedSnapshot({ snapshotRevision: 8, cameraRevision: 2 });
|
|
const stale = stampedSnapshot({ snapshotRevision: 7, cameraRevision: 99 });
|
|
const newer = stampedSnapshot({ snapshotRevision: 9, cameraRevision: 1 });
|
|
|
|
assert.equal(selectMonotonicXgridsState(current, stale), current);
|
|
assert.equal(selectMonotonicXgridsState(current, newer), newer);
|
|
});
|
|
|
|
test("accepts a newer runtime and rejects a delayed snapshot from the old runtime", () => {
|
|
const oldRuntime = stampedSnapshot({
|
|
runtimeStartedAt: "2026-08-06T10:00:00Z",
|
|
runtimeStartedMonotonicNs: "1000000",
|
|
runtimeId: "runtime-old",
|
|
snapshotRevision: 300,
|
|
});
|
|
const newRuntime = stampedSnapshot({
|
|
runtimeStartedAt: "2026-08-06T10:05:00Z",
|
|
runtimeStartedMonotonicNs: "2000000",
|
|
runtimeId: "runtime-new",
|
|
snapshotRevision: 1,
|
|
});
|
|
const delayedOldRuntime = stampedSnapshot({
|
|
runtimeStartedAt: "2026-08-06T10:00:00Z",
|
|
runtimeStartedMonotonicNs: "1000000",
|
|
runtimeId: "runtime-old",
|
|
snapshotRevision: 301,
|
|
});
|
|
|
|
assert.equal(selectMonotonicXgridsState(oldRuntime, newRuntime), newRuntime);
|
|
assert.equal(
|
|
selectMonotonicXgridsState(newRuntime, delayedOldRuntime),
|
|
newRuntime,
|
|
);
|
|
});
|
|
|
|
test("orders restarts by monotonic time even when UTC moves backwards", () => {
|
|
const oldRuntime = stampedSnapshot({
|
|
runtimeStartedAt: "2026-08-06T10:05:00Z",
|
|
runtimeStartedMonotonicNs: "2000000",
|
|
runtimeId: "runtime-old",
|
|
snapshotRevision: 900,
|
|
});
|
|
const newRuntime = stampedSnapshot({
|
|
runtimeStartedAt: "2026-08-06T09:55:00Z",
|
|
runtimeStartedMonotonicNs: "3000000",
|
|
runtimeId: "runtime-new",
|
|
snapshotRevision: 1,
|
|
});
|
|
|
|
assert.equal(selectMonotonicXgridsState(oldRuntime, newRuntime), newRuntime);
|
|
assert.equal(selectMonotonicXgridsState(newRuntime, oldRuntime), newRuntime);
|
|
});
|
|
|
|
test("orders two runtimes sharing the same UTC millisecond", () => {
|
|
const first = stampedSnapshot({
|
|
runtimeStartedMonotonicNs: "4000000",
|
|
runtimeId: "runtime-first",
|
|
});
|
|
const second = stampedSnapshot({
|
|
runtimeStartedMonotonicNs: "4000001",
|
|
runtimeId: "runtime-second",
|
|
});
|
|
|
|
assert.equal(selectMonotonicXgridsState(first, second), second);
|
|
});
|
|
|
|
test("a malformed monotonic stamp cannot replace valid runtime authority", () => {
|
|
const current = stampedSnapshot({
|
|
runtimeStartedMonotonicNs: "5000000",
|
|
runtimeId: "runtime-current",
|
|
});
|
|
const malformed = stampedSnapshot({
|
|
runtimeStartedAt: "2026-08-06T11:00:00Z",
|
|
runtimeStartedMonotonicNs: "not-a-number",
|
|
runtimeId: "runtime-malformed",
|
|
snapshotRevision: 9999,
|
|
});
|
|
|
|
assert.equal(selectMonotonicXgridsState(current, malformed), current);
|
|
});
|
|
|
|
test("does not let an unstamped legacy response replace stamped authority", () => {
|
|
const current = stampedSnapshot({ snapshotRevision: 8 });
|
|
const legacy = snapshot(99, 99);
|
|
|
|
assert.equal(selectMonotonicXgridsState(current, legacy), current);
|
|
assert.equal(selectMonotonicXgridsState(legacy, current), current);
|
|
});
|
|
|
|
test("accepts the first camera preview snapshot", () => {
|
|
const incoming = snapshot(1, 1);
|
|
assert.equal(selectMonotonicXgridsState(null, incoming), incoming);
|
|
});
|
|
|
|
test("rejects a lower revision even when its generation is higher", () => {
|
|
const current = snapshot(8, 3);
|
|
const stale = snapshot(7, 99);
|
|
assert.equal(selectMonotonicXgridsState(current, stale), current);
|
|
});
|
|
|
|
test("uses generation only as a tie-breaker for equal revisions", () => {
|
|
const current = snapshot(8, 3);
|
|
const stale = snapshot(8, 2);
|
|
const equal = snapshot(8, 3, "selected");
|
|
|
|
assert.equal(selectMonotonicXgridsState(current, stale), current);
|
|
assert.equal(selectMonotonicXgridsState(current, equal), equal);
|
|
});
|
|
|
|
test("accepts a newer stop revision with a null generation", () => {
|
|
const current = snapshot(8, 3);
|
|
const stopped = snapshot(9, null, "idle");
|
|
|
|
assert.equal(selectMonotonicXgridsState(current, stopped), stopped);
|
|
});
|
|
|
|
test("rejects an unversioned camera snapshot after a versioned one", () => {
|
|
const current = snapshot(8, 3);
|
|
const stale = {
|
|
phase: "connected",
|
|
device_session: current.device_session,
|
|
};
|
|
|
|
assert.equal(selectMonotonicXgridsState(current, stale), current);
|
|
});
|
|
|
|
test("accepts revision reset when the authoritative device session changes", () => {
|
|
const current = snapshot(18, 7, "streaming", "device-session-a");
|
|
const nextDevice = snapshot(0, null, "idle", "device-session-b");
|
|
|
|
assert.equal(selectMonotonicXgridsState(current, nextDevice), nextDevice);
|
|
});
|
|
|
|
test("accepts backend reset that clears and later recreates the device session", () => {
|
|
const current = snapshot(18, 7, "streaming", "device-session-a");
|
|
const reset = snapshot(0, null, "idle", null);
|
|
const reconnected = snapshot(0, null, "idle", "device-session-c");
|
|
|
|
assert.equal(selectMonotonicXgridsState(current, reset), reset);
|
|
assert.equal(selectMonotonicXgridsState(reset, reconnected), reconnected);
|
|
});
|
|
|
|
test("rejects the entire stale atomic snapshot instead of merging unrelated fields", () => {
|
|
const current = {
|
|
...snapshot(8, 3),
|
|
metrics: { point_count: 2_500 },
|
|
};
|
|
const stale = {
|
|
...snapshot(7, 2),
|
|
metrics: { point_count: 9_999 },
|
|
};
|
|
|
|
const accepted = selectMonotonicXgridsState(current, stale);
|
|
assert.equal(accepted, current);
|
|
assert.equal(accepted.metrics.point_count, 2_500);
|
|
});
|