| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | import { markSessionRead, mergeReadStores, repairReadBaseline, seedSessionReads, type ReadStore } from "../lib/sessionReadActivity"; |
| 4 | import { projectTreeReadActivityKey } from "../lib/projectTreeTopic"; |
| 5 | import type { ProjectNode } from "../lib/types"; |
| 6 | const node: ProjectNode = { key: "b", kind: "topic", label: "B", session: { hostId: "local", sessionId: "b" }, resultSequence: 20 }; |
| 7 | const key = projectTreeReadActivityKey(node)!; |
| 8 | const polluted = (): ReadStore => ({ version: 3, baselineAt: 1, records: { [key]: { metric: "result", value: 100, revision: 0, imported: true } } }); |
| 9 | test("only verified imported baseline is repaired, once", () => { |
| 10 | const store = polluted(), captured = store.records[key]; |
| 11 | assert.equal(repairReadBaseline(store, key, captured, { complete: false, resultSequence: 10, eventVersion: "30" }), store); |
| 12 | const fixed = repairReadBaseline(store, key, captured, { complete: true, resultSequence: 10, eventVersion: "30" }); |
| 13 | assert.equal(fixed.records[key].value, 10); |
| 14 | assert.equal(repairReadBaseline(fixed, key, fixed.records[key], { complete: true, resultSequence: 5, eventVersion: "31" }), fixed); |
| 15 | const read = markSessionRead(fixed, node); |
| 16 | assert.equal(read.records[key].value, 20); |
| 17 | assert.equal(markSessionRead(read, { ...node, resultSequence: 10 }), read); |
| 18 | }); |
| 19 | test("user read invalidates an outstanding pollution repair", () => { |
| 20 | const store = polluted(), captured = store.records[key]; |
| 21 | const read = markSessionRead(store, node); |
| 22 | assert.equal(repairReadBaseline(read, key, captured, { complete: true, resultSequence: 10, eventVersion: "30" }), read); |
| 23 | assert.equal(repairReadBaseline(read, key, read.records[key], { complete: true, resultSequence: 10, eventVersion: "30" }), read); |
| 24 | assert.equal(repairReadBaseline(read, key, read.records[key], { complete: true, resultSequence: 20, eventVersion: "60" }).records[key].value,20); |
| 25 | }); |
| 26 | test("cross-window merges accept exact repair but never lower a newer user read", () => { |
| 27 | const old = polluted(); |
| 28 | const fixed = repairReadBaseline(old, key, old.records[key], { complete: true, resultSequence: 10, eventVersion: "30" }); |
| 29 | assert.equal(mergeReadStores(old, fixed).records[key].value, 10); |
| 30 | assert.equal(mergeReadStores(fixed, old).records[key].value, 10); |
| 31 | const read = markSessionRead(old, node); |
| 32 | assert.equal(mergeReadStores(read, fixed).records[key].value, 100); |
| 33 | }); |
| 34 | |
| 35 | test("adoption changes time to result only after an authoritative baseline", () => { |
| 36 | const source = "source\0local\0old-head"; |
| 37 | const store: ReadStore = { version: 3, baselineAt: 1, records: { [source]: { metric: "time", value: 100000, revision: 1 } } }; |
| 38 | const adopted = { ...node, turnsState: "ready", identityAliases: [source], resultSequence: 10 }; |
| 39 | const seeded = seedSessionReads(store, [adopted]); |
| 40 | assert.equal(seeded.records[key].needsBaseline, true); |
| 41 | assert.equal(seeded.records[key].value, 0); |
| 42 | const verified = repairReadBaseline(seeded, key, seeded.records[key], { complete: true, resultSequence: 20, eventVersion: "60" }); |
| 43 | assert.equal(verified.records[key].value, 20); |
| 44 | assert.equal(verified.records[key].needsBaseline, false); |
| 45 | const read = markSessionRead(seeded, { ...adopted, resultSequence: 30 }); |
| 46 | assert.equal(repairReadBaseline(read, key, seeded.records[key], { complete: true, resultSequence: 20, eventVersion: "60" }), read); |
| 47 | }); |
| 48 | |
| 49 | test("a concurrent user read survives a repair delivered first", () => { |
| 50 | const old = polluted(); |
| 51 | const fixed = repairReadBaseline(old, key, old.records[key], { complete: true, resultSequence: 10, eventVersion: "30" }); |
| 52 | const read = markSessionRead(old, node); |
| 53 | assert.equal(mergeReadStores(fixed, read).records[key].value, 20); |
| 54 | }); |
| 55 |