fix(uptime): сбросить снимок при смене пары и сохранить историю

This commit is contained in:
Denozordec committed 2026-10-07 15:52:58 +07:00
1 parent 8627459bfa
commit e4f951175c
12 files changed
+142 -20

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
import { test } from "node:test"
import assert from "node:assert/strict"
import { emptySpeedProbeObservations } from "@mmapp/contracts"
import { publishSpeedProbeSnapshots } from "./speed-probe-snapshot"
const probe = { id: "p", srcServerId: "1", dstServerId: "2", enabled: true, ...emptySpeedProbeObservations, lastTxAvgMbps: 0 as number | null, lastRxAvgMbps: 80 as number | null, lastStatus: "done" as "done" | "error" | null, lastRunAt: "2026-10-07T00:00:00Z" as string | null }
test("same pair preserves latest zero and snapshot instead of readonly values copied into a stale draft", () => {
const next = { ...probe, enabled: false, lastTxAvgMbps: 999, lastRxAvgMbps: 999 }
const [saved] = publishSpeedProbeSnapshots([probe], [next])
assert.equal(saved.enabled, false)
assert.equal(saved.lastTxAvgMbps, 0)
assert.equal(saved.lastRxAvgMbps, 80)
assert.equal(saved.lastStatus, "done")
assert.equal(next.lastTxAvgMbps, 999)
})
for (const field of ["srcServerId", "dstServerId", "id"] as const) test(`changed ${field} starts with unknown measurements`, () => {
const [saved] = publishSpeedProbeSnapshots([probe], [{ ...probe, [field]: "3" }])
for (const key of Object.keys(emptySpeedProbeObservations)) assert.equal(saved[key as keyof typeof emptySpeedProbeObservations], null)
assert.equal(probe.lastRxAvgMbps, 80)
})
test("new row and reordered existing rows retain correct ownership without mutation", () => {
const next = { ...probe, id: "new" }
const saved = publishSpeedProbeSnapshots([probe], [next, probe])
assert.equal(saved[0].lastStatus, null)
assert.equal(saved[1].lastTxAvgMbps, 0)
assert.deepEqual(saved.map(p => p.id), ["new", "p"])
assert.equal(next.lastRxAvgMbps, 80)
})
+15
View File
@@ -0,0 +1,15 @@
import { emptySpeedProbeObservations, type SpeedProbeRead } from "@mmapp/contracts"
type Observations = Pick<SpeedProbeRead, keyof typeof emptySpeedProbeObservations>
type Pair = { id: string; srcServerId: string; dstServerId: string }
/** Publish confirmed configuration while preserving the latest snapshot only for the same pair. */
export function publishSpeedProbeSnapshots<T extends Pair & Partial<Observations>>(previous: readonly T[], next: readonly T[]) {
const byId = new Map(previous.map(row => [row.id, row]))
return next.map(row => {
const old = byId.get(row.id)
if (!old || old.srcServerId !== row.srcServerId || old.dstServerId !== row.dstServerId) {
return { ...row, ...emptySpeedProbeObservations }
}
const observations = Object.fromEntries(Object.keys(emptySpeedProbeObservations).map(key => [key, old[key as keyof Observations] ?? null])) as Observations
return { ...row, ...observations }
})
}