返回 DeepSeek-Reasonix
transcript-outline-store.test.ts
根目录 / desktop / frontend / src / __tests__ / transcript-outline-store.test.ts
1 import assert from "node:assert/strict";
2 import { TranscriptOutlineStore, type OutlineRead } from "../lib/transcriptOutlineStore";
3 import type { HistoryOutlinePage, HistoryOutlineRequest } from "../generated/desktopContract.generated";
4
5 const requests: HistoryOutlineRequest[] = [];
6 const page = (start = 1): HistoryOutlinePage => ({ status: "ready", generation: "cut", snapshotSequence: 12,
7 coverageSequence: 12, totalTurns: 25000, nextTurn: start + 128, done: false,
8 entries: Array.from({ length: 128 }, (_, i) => ({ messageId: `u${start + i}`, turn: start + i, position: start + i, prompt: `question ${start + i}` })) });
9 const read: OutlineRead = async (_tab, req) => { requests.push(req); return page(req.startTurn); };
10 const store = new TranscriptOutlineStore();
11 const off = store.activate("tab", "session", read, 25000);
12 await store.refresh("tab");
13 assert.equal(store.getView("tab").totalTurns, 25000);
14 assert.equal(requests.length, 1, "initial read is one page, not the whole history");
15 await Promise.all([store.ensure("tab", 20001), store.ensure("tab", 20001)]);
16 assert.equal(requests.length, 2, "range requests deduplicate");
17 assert.equal(requests[1].generation, "cut");
18 assert.equal(requests[1].snapshotSequence, 12);
19 for (let turn = 1000; turn < 10000; turn += 1000) await store.ensure("tab", turn);
20 assert.ok(store.getView("tab").entries.size <= 6 * 128, "summary cache is bounded");
21 assert.equal(store.getView("tab").totalTurns, 25000, "eviction preserves the rail extent");
22 await store.ensure("tab", 1);
23 assert.equal(store.getView("tab").entries.get(1)?.messageId, "u1");
24 off();
25
26 let resolve!: (page: HistoryOutlinePage) => void;
27 const stale = new Promise<HistoryOutlinePage>(done => { resolve = done; });
28 const oldOff = store.activate("same-tab", "old-session", () => stale, 2);
29 const pending = store.refresh("same-tab");
30 await Promise.resolve();
31 const newOff = store.activate("same-tab", "new-session", async () => ({ ...page(), totalTurns: 1, entries: [] }), 1);
32 await store.refresh("same-tab");
33 resolve(page()); await pending; oldOff();
34 assert.equal(store.getView("same-tab").totalTurns, 1, "old response and cleanup cannot replace the new session");
35 newOff();
36
37 const failing = store.activate("failed", "session", async () => { throw new Error("network"); }, 5);
38 await store.refresh("failed");
39 assert.equal(store.getView("failed").mode, "error");
40 assert.equal(store.getView("failed").totalTurns, 5);
41 failing();
42 const unsupported = store.activate("old-host", "session", async () => ({ ...page(), status: "unsupported", entries: [] }), 5);
43 await store.refresh("old-host");
44 assert.equal(store.getView("old-host").mode, "unsupported"); unsupported();
45 console.log("durable outline: sparse pages, fixed cut, eviction, generation and capability passed");
46
47 {
48 let fail = true;
49 const reads: number[] = [];
50 const cache = new TranscriptOutlineStore();
51 const release = cache.activate("retry-page", "session", async (_tab, req) => {
52 reads.push(req.startTurn ?? 1);
53 if (req.startTurn === 129 && fail) throw new Error("page offline");
54 return page(req.startTurn);
55 }, 25000);
56 try {
57 await cache.refresh("retry-page");
58 await cache.ensure("retry-page", 129);
59 await cache.ensure("retry-page", 257);
60 assert.equal(cache.getView("retry-page").mode, "error", "another successful page cannot hide a failed page");
61 fail = false;
62 await cache.retry("retry-page");
63 assert.equal(cache.getView("retry-page").entries.get(129)?.messageId, "u129", "retry reloads the failed range even when the cut is unchanged");
64 assert.equal(cache.getView("retry-page").mode, "ready");
65 assert.equal(reads.filter(start => start === 129).length, 2);
66 } finally { release(); }
67 }
68 {
69 let cut = 12;
70 let finish!: (value: HistoryOutlinePage) => void;
71 const cache = new TranscriptOutlineStore();
72 const release = cache.activate("cut-race", "session", async (_tab, req) => {
73 if (req.startTurn === 129 && req.snapshotSequence === 12) return new Promise(resolve => { finish = resolve; });
74 return { ...page(req.startTurn), snapshotSequence: cut };
75 }, 25000);
76 try {
77 await cache.refresh("cut-race");
78 const old = cache.ensure("cut-race", 129);
79 await Promise.resolve(); await Promise.resolve();
80 cut = 13;
81 await cache.refresh("cut-race");
82 finish(page(129)); await old;
83 await cache.ensure("cut-race", 129);
84 assert.equal(cache.getView("cut-race").snapshotSequence, 13);
85 assert.equal(cache.getView("cut-race").mode, "ready", "an obsolete page cannot poison the fresh directory");
86 assert.equal(cache.getView("cut-race").entries.get(129)?.messageId, "u129");
87 } finally { release(); }
88 }
89 {
90 let finish!: (value: HistoryOutlinePage) => void;
91 let reads = 0;
92 const gate = new Promise<HistoryOutlinePage>(resolve => { finish = resolve; });
93 const cache = new TranscriptOutlineStore();
94 const release = cache.activate("slow", "session", () => { reads++; return gate; }, 25000);
95 try {
96 const first = cache.refresh("slow");
97 await Promise.resolve();
98 const second = cache.refresh("slow");
99 await Promise.resolve();
100 assert.equal(reads, 1, "slow refresh is shared instead of continually invalidated");
101 finish(page()); await Promise.all([first, second]);
102 assert.equal(cache.getView("slow").mode, "ready");
103 } finally { release(); }
104 }
105
105 lines TYPESCRIPT