返回 DeepSeek-Reasonix
transcriptContentOwnership.ts
根目录 / desktop / frontend / src / __tests__ / helpers / transcriptContentOwnership.ts
1 import assert from "node:assert/strict";
2 import { TranscriptStore } from "../../lib/transcriptStore";
3 import type { HistoryContentChunk, HistorySlice } from "../../lib/types";
4
5 function deferred<T>() {
6 let resolve!: (value: T) => void;
7 const promise = new Promise<T>(done => { resolve = done; });
8 return { promise, resolve };
9 }
10
11 export async function verifyTranscriptContentOwnership() {
12 const slice = (revision: number): HistorySlice => ({
13 entries: [{ entryId: "m:answer", turn: 1, order: 0,
14 message: { role: "assistant", content: `preview-${revision}` },
15 refs: [{ entryId: "m:answer", field: "content", size: 100, chunks: 1, revision, digest: `cut-${revision}` }] }],
16 revision, revisionKnown: true, digest: `cut-${revision}`, stale: false,
17 nextCursor: "", hasOlder: false, totalTurns: 1, startTurn: 1, endTurn: 1,
18 });
19 const chunk = (data: string): HistoryContentChunk => ({
20 entryId: "m:answer", field: "content", chunk: 0, chunks: 1, data, done: true, stale: false,
21 });
22 function fixture() {
23 const requests: { revision: number; gate: ReturnType<typeof deferred<HistoryContentChunk>> }[] = [];
24 const started = [deferred<void>(), deferred<void>()];
25 const page = deferred<HistorySlice>();
26 const store = new TranscriptStore({
27 HistorySliceForTab: () => page.promise,
28 HistoryContentForTab: async (_tab, ref) => {
29 const gate = deferred<HistoryContentChunk>();
30 requests.push({ revision: ref.revision, gate });
31 started[requests.length - 1]?.resolve();
32 return gate.promise;
33 },
34 });
35 store.noteSessionBinding("tab", "/session", "session-generation-1");
36 store.installSlice("tab", "/session", slice(1));
37 const read = () => store.requestFullContent("tab", "m:answer", "content");
38 return { store, requests, page, read, started };
39 }
40 {
41 const { store, requests, read } = fixture();
42 const pending = read();
43 assert.equal(store.noteSessionBinding("tab", "/session", "session-generation-1"), false);
44 assert.ok(store.peek("tab", "/session"), "the same binding retains its resident window");
45 assert.equal(store.noteSessionBinding("tab", "/session", "session-generation-2"), true);
46 assert.equal(store.peek("tab", "/session"), undefined, "same-path rebind invalidates the old cache before reading");
47 store.installSlice("tab", "/session", slice(2));
48 requests[0].gate.resolve(chunk("obsolete"));
49 assert.equal(await pending, undefined, "a binding replacement cannot hand old body reads to the new session");
50 assert.equal(requests.length, 1);
51 }
52 {
53 const { store, requests, read, started } = fixture();
54 const pending = read();
55 store.installSlice("tab", "/session", slice(2));
56 requests[0].gate.resolve(chunk("obsolete"));
57 await started[1].promise;
58 assert.equal(requests[1]?.revision, 2, "Follow installation hands the original alias to the new cut");
59 requests[1].gate.resolve(chunk("current"));
60 assert.equal(await pending, "current");
61 assert.equal(await read(), "current", "resolved body remains resident");
62 }
63 {
64 const { store, requests, read } = fixture();
65 const pending = read();
66 store.evictTab("tab");
67 store.installSlice("tab", "/session", slice(2));
68 requests[0].gate.resolve(chunk("obsolete"));
69 assert.equal(await pending, undefined, "eviction is terminal even when the same tab reopens");
70 assert.equal(requests.length, 1, "the old owner never starts a read for the new owner");
71 assert.equal(store.peek("tab", "/session")?.items[0]?.kind, "assistant");
72 }
73 {
74 const { store, requests, page, read, started } = fixture();
75 const reload = store.loadLatest("tab", "/session");
76 const pending = read();
77 assert.equal(requests.length, 0, "a body requested during replacement waits for the new page");
78 page.resolve(slice(2));
79 await reload;
80 await started[0].promise;
81 assert.equal(requests[0]?.revision, 2, "replacement does not read an old ref under a new generation");
82 requests[0].gate.resolve(chunk("fresh"));
83 assert.equal(await pending, "fresh");
84 }
85 {
86 const { store, requests, read, started } = fixture();
87 const pending = read();
88 store.installSlice("tab", "/session", slice(2));
89 requests[0].gate.resolve(chunk("first"));
90 await started[1].promise;
91 store.installSlice("tab", "/session", slice(3));
92 requests[1].gate.resolve(chunk("second"));
93 assert.equal(await pending, undefined, "repeated replacement cannot create an unbounded retry loop");
94 assert.equal(requests.length, 2, "one content request has at most one generation handoff");
95 }
96 }
97
97 lines TYPESCRIPT