返回 DeepSeek-Reasonix
session-diagnostics.test.ts
根目录 / desktop / frontend / src / __tests__ / session-diagnostics.test.ts
1 // Run: tsx src/__tests__/session-diagnostics.test.ts
2 //
3 // Phase F session-switch/history diagnostics: activation lifecycle timings,
4 // history page counters (index hit/miss, stale), mounted-row counts, provider
5 // registration, and the bounded activation log.
6
7 import {
8 activationFailureClass,
9 activationLog,
10 noteActivationRequested,
11 noteActivationSettled,
12 noteActivationStarted,
13 noteHistoryPage,
14 noteNavigationComposerEnabled,
15 noteNavigationFirstPaint,
16 noteNavigationHistoryReadable,
17 noteNavigationHistoryRequested,
18 noteNavigationIdentityPublished,
19 noteNavigationRequested,
20 noteNavigationRuntimeReady,
21 noteTranscriptRowCounts,
22 registerMarkdownWorkerDiagnostics,
23 registerTranscriptCacheDiagnostics,
24 resetSessionDiagnostics,
25 sessionPipelineDiagnostics,
26 } from "../lib/sessionDiagnostics";
27
28 let passed = 0;
29 let failed = 0;
30
31 function ok(cond: boolean, label: string) {
32 if (cond) {
33 process.stdout.write(` PASS ${label}\n`);
34 passed += 1;
35 } else {
36 process.stdout.write(` FAIL ${label}\n`);
37 failed += 1;
38 }
39 }
40
41 resetSessionDiagnostics();
42
43 // --- local navigation readable/runtime milestones ---
44 {
45 noteNavigationRequested(7);
46 noteNavigationIdentityPublished(7, "tab-fast");
47 noteNavigationHistoryRequested(7, true);
48 noteNavigationHistoryReadable(7, true);
49 noteNavigationFirstPaint(7);
50 noteNavigationRuntimeReady(7, true);
51 noteNavigationComposerEnabled("tab-fast");
52 const navigation = sessionPipelineDiagnostics().navigation;
53 ok(navigation?.tabId === "tab-fast", "navigation carries the selected tab identity");
54 ok(navigation?.historyCacheHit === true, "navigation records the bounded cache hit");
55 ok(navigation?.runtimeReattached === true, "navigation records runtime reattachment");
56 ok((navigation?.clickToIdentityMs ?? -1) >= 0, "click→identity is derived");
57 ok((navigation?.clickToFirstHistoryMs ?? -1) >= 0, "click→first history is derived");
58 ok((navigation?.clickToFirstPaintMs ?? -1) >= 0, "click→first paint is derived");
59 ok((navigation?.clickToRuntimeReadyMs ?? -1) >= 0, "click→runtime ready is derived");
60 ok((navigation?.clickToComposerEnabledMs ?? -1) >= 0, "click→composer enabled is derived");
61 }
62
63 // --- activation lifecycle with derived phase timings ---
64 {
65 noteActivationRequested("r1");
66 noteActivationStarted("r1", "tab_a");
67 noteActivationSettled("r1", "ready");
68 const snapshot = sessionPipelineDiagnostics();
69 const a = snapshot.activation;
70 ok(Boolean(a), "last activation present in snapshot");
71 ok(a?.tabId === "tab_a", "activation carries the tab id");
72 ok(a?.ticketToStartingMs !== undefined && a.ticketToStartingMs >= 0, "ticket→starting derived");
73 ok(a?.startingToReadyMs !== undefined && a.startingToReadyMs >= 0, "starting→ready derived");
74 ok(a?.totalMs !== undefined && a.totalMs >= (a?.startingToReadyMs ?? 0), "total covers the phases");
75 ok(a?.failureClass === undefined, "ready activation has no failure class");
76 }
77
78 // --- failed activation records only the failure class, never the message ---
79 {
80 noteActivationRequested("r2");
81 noteActivationSettled("r2", "failed", "dial tcp 10.0.0.1: connect: connection timeout");
82 const a = sessionPipelineDiagnostics().activation;
83 ok(a?.outcome === "failed", "failed outcome recorded");
84 ok(a?.failureClass === "timeout", "timeout classified");
85 ok(activationFailureClass("authorization cancelled by user") === "cancelled", "cancel class");
86 ok(activationFailureClass("cursor is stale") === "stale", "stale class");
87 ok(activationFailureClass("session file not found") === "missing", "missing class");
88 ok(activationFailureClass("weird backend state") === "other", "other class");
89 ok(activationFailureClass("") === "unknown", "empty error is unknown");
90 }
91
92 // --- first settle wins (a stale terminal event can't rewrite the outcome) ---
93 {
94 noteActivationRequested("r3");
95 noteActivationSettled("r3", "cancelled");
96 noteActivationSettled("r3", "ready");
97 const log = activationLog();
98 ok(log.find((a) => a.requestId === "r3")?.outcome === "cancelled", "first terminal phase wins");
99 }
100
101 // --- history page counters: index hits/misses and stale ---
102 {
103 noteHistoryPage({ entries: 120, inlineBytes: 64_000, durationMs: 3, stale: false, source: "live-index" });
104 noteHistoryPage({ entries: 40, inlineBytes: 10_000, durationMs: 12, stale: false, source: "scan" });
105 noteHistoryPage({ entries: 0, inlineBytes: 0, durationMs: 1, stale: true, source: "index" });
106 const h = sessionPipelineDiagnostics().history;
107 ok(h?.pages === 3, "all pages counted");
108 ok(h?.staleCount === 1, "stale pages counted");
109 ok(h?.indexHits === 2, "index + live-index count as hits");
110 ok(h?.indexMisses === 1, "scan counts as a miss");
111 ok(h?.entries === 0 && h?.stale === true, "last page stats win");
112 }
113
114 // --- mounted rows + registered providers ---
115 {
116 noteTranscriptRowCounts(30, 412);
117 registerMarkdownWorkerDiagnostics(() => ({ pending: 1, completed: 9, avgParseMs: 4, maxParseMs: 11, fallbackActive: false, workerFailures: 0 }));
118 registerTranscriptCacheDiagnostics(() => ({
119 residentSessions: 2,
120 maxResidentSessions: 3,
121 bodyBytes: 1024,
122 bodyBudgetBytes: 32 << 20,
123 markdownBytes: 2048,
124 markdownBudgetBytes: 16 << 20,
125 historyEvictions: 1,
126 markdownEvictions: 0,
127 reclaimedPages: 2,
128 residentWindowEntries: 96,
129 windowMaxPages: 3,
130 }));
131 const snapshot = sessionPipelineDiagnostics();
132 ok(snapshot.mountedRows?.mounted === 30 && snapshot.mountedRows.total === 412, "mounted row counts flow through");
133 ok(snapshot.markdownWorker?.completed === 9, "markdown worker provider flows through");
134 ok(snapshot.transcriptCache?.residentSessions === 2 && snapshot.transcriptCache.historyEvictions === 1, "cache provider flows through");
135 }
136
137 // --- a throwing provider never breaks the snapshot ---
138 {
139 registerMarkdownWorkerDiagnostics(() => {
140 throw new Error("boom");
141 });
142 const snapshot = sessionPipelineDiagnostics();
143 ok(snapshot.markdownWorker === undefined && snapshot.transcriptCache !== undefined, "throwing provider is isolated");
144 }
145
146 // --- activation log is bounded ---
147 {
148 resetSessionDiagnostics();
149 for (let i = 0; i < 200; i += 1) noteActivationRequested(`flood-${i}`);
150 const log = activationLog();
151 ok(log.length === 128, `activation log bounded at 128 (got ${log.length})`);
152 ok(log[log.length - 1].requestId === "flood-199", "newest entries kept");
153 }
154
155 // --- reset clears everything ---
156 {
157 resetSessionDiagnostics();
158 const snapshot = sessionPipelineDiagnostics();
159 ok(snapshot.activation === undefined && snapshot.navigation === undefined && snapshot.history === undefined, "reset drops activation/navigation/history");
160 ok(activationLog().length === 0, "reset clears the log");
161 }
162
163 process.stdout.write(`\n${passed} passed, ${failed} failed\n`);
164 if (failed > 0) process.exit(1);
165
165 lines TYPESCRIPT