返回 DeepSeek-Reasonix
session-export-operation.test.ts
根目录 / desktop / frontend / src / __tests__ / session-export-operation.test.ts
1 import { noteSessionObservation, sessionObservationDiagnostics } from "../lib/sessionObservationDiagnostics";
2 import { notePromptSubmission } from "../lib/promptSubmissionDiagnostics";
3 import assert from "node:assert/strict";
4 import { JSDOM } from "jsdom";
5 import { installDesktopHostStub } from "./desktopHostStub";
6 import { exportChunks, runSessionExport } from "../lib/sessionExportOperation";
7 import { cancelSessionExport, sessionExportProgress } from "../lib/sessionExportProgress";
8 import type { SessionExportHandle } from "../generated/desktopContract.generated";
9 const dom = new JSDOM("<!doctype html><html><body></body></html>", { url: "http://localhost" });
10 Object.assign(globalThis, { window: dom.window, document: dom.window.document });
11 const body = Buffer.from("第一条问题\n工具参数和完整输出✓\n最终回答", "utf8");
12 let cancelled = 0, finished = 0;
13 let exportedObservation = "";
14 let blocked = false;
15 let release!: () => void;
16 let started!: () => void;
17 const readStarted = new Promise<void>(resolve => { started = resolve; });
18 const gate = new Promise<void>(resolve => { release = resolve; });
19 const stub = installDesktopHostStub({
20 BeginSessionExportForTarget: async (_selector: unknown, _tab: string, _format: string, _title: string, observation: string) => {
21 exportedObservation = observation;
22 return { exportId: "fixed-A", format: "clipboard", snapshot: {} };
23 },
24 ReadSessionExportChunk: async (_id: string, offset: number) => {
25 if (blocked) { started(); await gate; }
26 const end = Math.min(offset + 1, body.length);
27 return { data: body.subarray(offset, end).toString("base64"), nextOffset: end, done: end === body.length };
28 },
29 CancelSessionExport: async () => { cancelled++; },
30 FinishSessionExport: async () => { finished++; return { paths: [], records: 137, pages: 0 }; },
31 CancelSession: async () => { throw Error("export must not cancel execution"); },
32 });
33 try {
34 noteSessionObservation("session-id:shared", { action: "unsubscribe", tabId: "remote-A", generation: 1, sequence: 7 });
35 noteSessionObservation("session-id:shared", { action: "terminal", tabId: "remote-B", generation: 2, sequence: 9 });
36 assert.equal(sessionObservationDiagnostics("session-id:shared", "remote-A").events[0]?.action, "unsubscribe");
37 assert.equal(sessionObservationDiagnostics("session-id:shared", "remote-B").events.length, 1, "equal remote routes do not mix renderer bindings");
38 let text = "";
39 for await (const part of exportChunks({ exportId: "fixed-A" } as SessionExportHandle)) text += part;
40 assert.equal(text, body.toString("utf8"), "UTF-8 characters crossing every RPC boundary survive");
41 const input = { selector: { ref: { hostId: "local", sessionId: "A" } }, tabId: "tab-A", format: "clipboard", title: "A", remote: false, residentItems: 0, runningStream: false, unresolvedTools: 0 };
42 notePromptSubmission({ tabId: "tab-A", sessionId: "A", sessionGeneration: 0, promptId: "1", turnId: "turn-A", runtimeEpoch: "runtime-A", kind: "approval" }, "transport", "accepted");
43 const result = await runSessionExport(input);
44 const promptEvent = JSON.parse(exportedObservation).lifecycleDiagnostics.events.find((event: { action: string }) => event.action === "prompt-transport");
45 assert.equal(promptEvent.status, "accepted", "session exports contain the actual approval transport outcome");
46 assert.equal(promptEvent.prompt.bindingGeneration, 0, "export preserves the valid initial generation");
47 assert.equal(result.text, text);
48 assert.equal(finished, 1);
49 blocked = true;
50 const pending = runSessionExport(input);
51 await readStarted;
52 await cancelSessionExport("fixed-A");
53 release();
54 assert.equal((await pending).cancelled, true);
55 assert.equal(finished, 1, "cancelled export is never published");
56 assert.ok(cancelled >= 1);
57 assert.equal(sessionExportProgress.getSnapshot().length, 0);
58 console.log("session export operations: UTF-8 chunk boundaries, full clipboard and independent cancellation passed");
59 } finally { stub.uninstall(); dom.window.close(); }
60
60 lines TYPESCRIPT