返回 DeepSeek-Reasonix
diagnostics_v2.test.ts
根目录 / workers / crash-report / src / diagnostics_v2.test.ts
1 import { describe, expect, it } from "vitest";
2 // @ts-expect-error Node 22+ provides node:sqlite; Worker production code does not import it.
3 import { DatabaseSync } from "node:sqlite";
4 import worker, {
5 CLI_TELEMETRY_SCHEMA_SQL,
6 Report,
7 } from "./index";
8 import { isDevelopmentReport, newestReleaseVersion } from "./report_classification";
9 import {
10 crashGroups,
11 diagnosticFacets,
12 diagnosticWindowWhere,
13 groupDiagnosticSummary,
14 reportAggregateStatements,
15 } from "./diagnostics_v2";
16 import type { Env } from "./env";
17 import diagnosticsMigrationSQL from "../migrate-diagnostics-v2.sql?raw";
18 import freshSchemaSQL from "../schema.sql?raw";
19 import firebaseCrashMigrationSQL from "../migrate-firebase-crash.sql?raw";
20 import firebaseCrashCapacityMigrationSQL from "../migrate-firebase-crash-capacity.sql?raw";
21 import {
22 classifyDiagnosticsV2Schema,
23 diagnosticsV2SchemaEntries,
24 diagnosticsV2SchemaQuery,
25 parseWranglerRows,
26 } from "../scripts/apply-diagnostics-v2.mjs";
27 import {
28 classifyFirebaseCrashSchema,
29 firebaseCrashSchemaQuery,
30 } from "../scripts/apply-firebase-crash.mjs";
31
32 const oldReport = {
33 kind: "crash",
34 version: "v1.19.0",
35 os: "windows",
36 arch: "amd64",
37 message: "legacy payload",
38 } as const;
39
40 describe("diagnostics v2 compatibility and privacy", () => {
41 it("fails closed on active partial state and ignores retired metric-user tables", () => {
42 expect(classifyDiagnosticsV2Schema([]).state).toBe("absent");
43 const completeRows = (diagnosticsV2SchemaEntries as readonly string[]).map((entry: string) => {
44 const split = entry.indexOf(":");
45 return { kind: entry.slice(0, split), name: entry.slice(split + 1) };
46 });
47 expect(classifyDiagnosticsV2Schema(completeRows).state).toBe("complete");
48 const partial = classifyDiagnosticsV2Schema(completeRows.slice(1));
49 expect(partial.state).toBe("partial");
50 expect(partial.missing).toEqual([diagnosticsV2SchemaEntries[0]]);
51 expect(parseWranglerRows(JSON.stringify([{ results: completeRows }]))).toEqual(completeRows);
52 expect(classifyDiagnosticsV2Schema([
53 ...completeRows,
54 { kind: "column", name: "metric_users.arch" },
55 { kind: "column", name: "cli_metric_users.arch" },
56 ]).state).toBe("complete");
57 expect(diagnosticsV2SchemaEntries.some((entry) => entry.includes("metric_users"))).toBe(false);
58 expect(diagnosticsV2SchemaQuery).not.toMatch(/metric_users/);
59 });
60
61 it("accepts old reports plus Windows and Linux runtime diagnostics", () => {
62 expect(Report.safeParse(oldReport).success).toBe(true);
63 expect(Report.safeParse({
64 ...oldReport,
65 kind: "exception",
66 schemaVersion: 3,
67 source: "desktop.session_migration",
68 label: "transcript.initialization",
69 errorType: "TranscriptInitializationError",
70 errorMessage: "Transcript initialization failed during legacy session migration.",
71 topFrame: "internal/transcript.NewProjection",
72 fingerprintHint: "desktop.session_migration.transcript_initialization.duplicate_record_identity",
73 message: "[transcript initialization]\n\nstage: legacy_import\nclassification: duplicate_record_identity",
74 }).success).toBe(true);
75 expect(Report.safeParse({
76 ...oldReport,
77 installId: "b".repeat(32),
78 channel: "stable",
79 device: { osVersion: "Windows 10", osBuild: 17763, osRevision: 6293 },
80 webview2: {
81 kind: "browser_process_exited",
82 reason: "integrity_failure",
83 exitCode: -1073740760,
84 processDescription: "Browser",
85 failureSourceModule: "inject.dll",
86 runtimeVersion: "132.0.2957.140",
87 gpuDisabled: false,
88 recovery: "not_applicable",
89 },
90 }).success).toBe(true);
91 expect(Report.safeParse({
92 ...oldReport,
93 os: "linux",
94 device: {
95 distroId: "ubuntu", distroVersion: "24.04", kernelVersion: "6.8.0",
96 sessionType: "wayland",
97 },
98 webRuntime: {
99 engine: "webkitgtk", kind: "web_process_terminated", reason: "crashed",
100 runtimeVersion: "2.44.2", gpuMode: "always", recovery: "reload_succeeded",
101 },
102 }).success).toBe(true);
103 });
104
105 it("rejects full failure-source paths and puts channel=test in development", () => {
106 expect(Report.safeParse({
107 ...oldReport,
108 webview2: {
109 kind: "browser_process_exited",
110 reason: "integrity_failure",
111 failureSourceModule: "C:\\Users\\alice\\inject.dll",
112 runtimeVersion: "132",
113 gpuDisabled: false,
114 recovery: "not_applicable",
115 },
116 }).success).toBe(false);
117 expect(isDevelopmentReport({
118 ...oldReport,
119 source: "frontend.global",
120 label: "window.error",
121 errorType: "Error",
122 errorMessage: "boom",
123 topFrame: "at render (assets/index.js:1:2)",
124 version: "v1.23.0",
125 channel: "test",
126 })).toBe(true);
127 });
128
129 it("keeps fresh, migrated, and runtime-bootstrap schemas aligned", () => {
130 const legacy = `
131 CREATE TABLE reports (id INTEGER PRIMARY KEY);
132 CREATE TABLE pings (
133 date TEXT NOT NULL, install_id TEXT NOT NULL, version TEXT NOT NULL, os TEXT NOT NULL,
134 arch TEXT NOT NULL, os_version TEXT NOT NULL DEFAULT '', opens INTEGER NOT NULL DEFAULT 1,
135 PRIMARY KEY (date, install_id)
136 );
137 CREATE TABLE cli_pings (
138 date TEXT NOT NULL, install_id TEXT NOT NULL, version TEXT NOT NULL, os TEXT NOT NULL,
139 arch TEXT NOT NULL, os_version TEXT NOT NULL DEFAULT '', opens INTEGER NOT NULL DEFAULT 1,
140 PRIMARY KEY (date, install_id)
141 );
142 `;
143 const columns = (db: DatabaseSync, table: string) =>
144 db.prepare(`PRAGMA table_info(${table})`).all().map((row: Record<string, unknown>) => String(row.name));
145 const fresh = new DatabaseSync(":memory:");
146 const migrated = new DatabaseSync(":memory:");
147 const runtimeBootstrap = new DatabaseSync(":memory:");
148 try {
149 fresh.exec(freshSchemaSQL);
150 migrated.exec(legacy);
151 migrated.exec(diagnosticsMigrationSQL);
152 runtimeBootstrap.exec(CLI_TELEMETRY_SCHEMA_SQL.join(";\n"));
153 const additiveColumns: Record<string, string[]> = {
154 reports: ["webview2", "web_runtime"],
155 pings: ["os_build", "os_revision", "distro_id", "session_type", "runtime_engine", "gpu_mode"],
156 cli_pings: ["os_build", "os_revision", "distro_id", "session_type", "runtime_engine", "gpu_mode"],
157 };
158 for (const [table, expected] of Object.entries(additiveColumns)) {
159 expect(columns(migrated, table)).toEqual(expect.arrayContaining(expected));
160 expect(columns(fresh, table)).toEqual(expect.arrayContaining(expected));
161 }
162 expect(columns(fresh, "reports")).not.toContain("install_id");
163 for (const table of ["report_daily", "report_installations", "report_event_dimensions", "diagnostics_meta"]) {
164 expect(columns(migrated, table)).toEqual(columns(fresh, table));
165 }
166 for (const table of ["cli_pings"]) {
167 expect(columns(runtimeBootstrap, table)).toEqual(columns(fresh, table));
168 }
169 expect(classifyDiagnosticsV2Schema(
170 migrated.prepare(diagnosticsV2SchemaQuery).all(),
171 ).state).toBe("complete");
172 expect(classifyDiagnosticsV2Schema(
173 fresh.prepare(diagnosticsV2SchemaQuery).all(),
174 ).state).toBe("complete");
175 } finally {
176 fresh.close();
177 migrated.close();
178 runtimeBootstrap.close();
179 }
180 expect(diagnosticsMigrationSQL).not.toMatch(/\bDROP\b/);
181 expect(diagnosticsMigrationSQL).not.toMatch(/ALTER TABLE (?:metric_users|cli_metric_users)\b/);
182 });
183
184 it("uses a date-leading index for platform impact denominators", () => {
185 const db = new DatabaseSync(":memory:");
186 try {
187 db.exec(freshSchemaSQL);
188 const plan = db.prepare(
189 "EXPLAIN QUERY PLAN SELECT COUNT(DISTINCT install_id) FROM pings WHERE date >= date('now', '-29 day') AND os = 'linux' AND distro_id = 'ubuntu'",
190 ).all().map((row: Record<string, unknown>) => String(row.detail)).join("\n");
191 expect(plan).toMatch(/SEARCH pings USING INDEX/);
192 expect(plan).not.toMatch(/SCAN pings/);
193 } finally {
194 db.close();
195 }
196 });
197
198 it("keeps the Firebase outbox migration additive and aligned with fresh installs", () => {
199 const migrated = new DatabaseSync(":memory:");
200 const fresh = new DatabaseSync(":memory:");
201 try {
202 migrated.exec("CREATE TABLE groups (fingerprint TEXT PRIMARY KEY, last_seen TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'open')");
203 migrated.exec(firebaseCrashMigrationSQL);
204 expect(classifyFirebaseCrashSchema(migrated.prepare(firebaseCrashSchemaQuery).all()).state).toBe("partial");
205 migrated.exec(firebaseCrashCapacityMigrationSQL);
206 fresh.exec(freshSchemaSQL);
207 expect(classifyFirebaseCrashSchema(migrated.prepare(firebaseCrashSchemaQuery).all()).state).toBe("complete");
208 expect(classifyFirebaseCrashSchema(fresh.prepare(firebaseCrashSchemaQuery).all()).state).toBe("complete");
209 expect(firebaseCrashMigrationSQL).not.toMatch(/\b(?:DROP|ALTER)\b/);
210 expect(firebaseCrashCapacityMigrationSQL).not.toMatch(/\b(?:DROP|ALTER)\b/);
211 } finally {
212 migrated.close();
213 fresh.close();
214 }
215 });
216 });
217
218 describe("stats window and release baseline", () => {
219 it("uses an inclusive calendar window for diagnostic groups", () => {
220 expect(diagnosticWindowWhere(7)).toBe("date(last_seen) >= date('now', '-6 day')");
221 expect(diagnosticWindowWhere(30)).toBe("date(last_seen) >= date('now', '-29 day')");
222 });
223
224 it("does not promote prerelease or synthetic non-semver labels", () => {
225 expect(newestReleaseVersion(["v1.19.4", "v1.20.0-beta.1", "dev", "v9.9.9-test"])).toBe("v1.19.4");
226 });
227 });
228
229 describe("diagnostics v2 storage consistency", () => {
230 it("commits every report write through one D1 batch", async () => {
231 let batchCalls = 0;
232 let directRuns = 0;
233 let committed: Array<{ sql: string }> = [];
234 const db = {
235 prepare(sql: string) {
236 const statement = {
237 sql,
238 bind() { return statement; },
239 async first() { return null; },
240 async run() { directRuns++; return {}; },
241 };
242 return statement;
243 },
244 async batch(statements: Array<{ sql: string }>) {
245 batchCalls++;
246 committed = statements;
247 return [];
248 },
249 } as unknown as D1Database;
250 const env = {
251 DB: db,
252 RATE_LIMITER: { async limit() { return { success: true }; } },
253 } as unknown as Env;
254 const body = JSON.stringify({
255 installId: "a".repeat(32), kind: "crash", version: "v1.23.0",
256 os: "windows", arch: "amd64", message: "browser process exited",
257 });
258 const response = await worker.fetch(new Request("https://crash.reasonix.io/v1/report", {
259 method: "POST",
260 headers: {
261 "content-type": "application/json",
262 "content-length": String(new TextEncoder().encode(body).byteLength),
263 "cf-connecting-ip": "127.0.0.1",
264 },
265 body,
266 }), env);
267 expect(response.status).toBe(202);
268 expect(batchCalls).toBe(1);
269 expect(directRuns).toBe(0);
270 expect(committed.map((statement) => statement.sql)).toEqual([
271 expect.stringContaining("INSERT INTO report_events"),
272 expect.stringContaining("automatic_regression"),
273 expect.stringContaining("INSERT INTO groups"),
274 expect.stringContaining("INSERT INTO reports"),
275 expect.stringContaining("INSERT INTO report_daily"),
276 expect.stringContaining("INSERT INTO report_installations"),
277 expect.stringContaining("INSERT INTO report_event_dimensions"),
278 expect.stringContaining("INSERT INTO report_attribution_daily"),
279 expect.stringContaining("DELETE FROM reports"),
280 ]);
281 expect(committed.every((statement) => !statement.sql.includes("firebase_crash_"))).toBe(true);
282 });
283
284 it("projects a repeated D1 eventId only once", async () => {
285 const projected = new Set<string>();
286 let batchCalls = 0;
287 const db = {
288 prepare(sql: string) {
289 let binds: unknown[] = [];
290 const statement = {
291 sql,
292 bind(...values: unknown[]) { binds = values; return statement; },
293 async first() {
294 return sql.includes("SELECT event_id FROM report_events") && projected.has(String(binds[0]))
295 ? { event_id: binds[0] }
296 : null;
297 },
298 async run() { return {}; },
299 binds() { return binds; },
300 };
301 return statement;
302 },
303 async batch(statements: Array<{ sql: string; binds(): unknown[] }>) {
304 batchCalls++;
305 const event = statements.find((statement) => statement.sql.includes("INSERT INTO report_events"));
306 if (event) projected.add(String(event.binds()[0]));
307 return [];
308 },
309 } as unknown as D1Database;
310 const env = {
311 DB: db,
312 RATE_LIMITER: { async limit() { return { success: true }; } },
313 } as unknown as Env;
314 const body = JSON.stringify({
315 eventId: "e".repeat(32), installId: "a".repeat(32), kind: "crash",
316 version: "v1.23.0", os: "windows", arch: "amd64", message: "same event",
317 });
318 const request = () => new Request("https://crash.reasonix.io/v1/report", {
319 method: "POST",
320 headers: {
321 "content-type": "application/json",
322 "content-length": String(new TextEncoder().encode(body).byteLength),
323 "cf-connecting-ip": "127.0.0.1",
324 },
325 body,
326 });
327
328 expect((await worker.fetch(request(), env)).status).toBe(202);
329 expect((await worker.fetch(request(), env)).status).toBe(202);
330 expect(batchCalls).toBe(1);
331 expect(projected).toEqual(new Set(["e".repeat(32)]));
332 });
333
334 it("preserves separate GPU and runtime event dimensions for one installation", () => {
335 type BoundStatement = { sql: string; binds: unknown[] };
336 const statements: BoundStatement[] = [];
337 const d1 = {
338 prepare(sql: string) {
339 const statement = {
340 sql,
341 binds: [] as unknown[],
342 bind(...binds: unknown[]) { statement.binds = binds; statements.push(statement); return statement; },
343 };
344 return statement;
345 },
346 } as unknown as D1Database;
347 const report = {
348 installId: "a".repeat(32), version: "v1.23.0", os: "windows", arch: "amd64",
349 device: { osBuild: 17763, osRevision: 6293 },
350 };
351 const webview = {
352 engine: "webview2",
353 runtimeVersion: "132", kind: "gpu_process_exited", reason: "unexpected",
354 exitCode: 1, recovery: "not_applicable", gpuMode: "enabled",
355 };
356 reportAggregateStatements(d1, report, "f".repeat(64), "stable", webview);
357 reportAggregateStatements(d1, report, "f".repeat(64), "stable", {
358 ...webview, runtimeVersion: "133", gpuMode: "disabled",
359 });
360 const facts = statements.filter((statement) => statement.sql.includes("INSERT INTO report_event_dimensions"));
361 const db = new DatabaseSync(":memory:");
362 try {
363 db.exec(freshSchemaSQL);
364 for (const fact of facts) db.prepare(fact.sql).run(...fact.binds as []);
365 expect(db.prepare(
366 "SELECT runtime_version, gpu_mode, events FROM report_event_dimensions ORDER BY runtime_version",
367 ).all()).toEqual([
368 { runtime_version: "132", gpu_mode: "enabled", events: 1 },
369 { runtime_version: "133", gpu_mode: "disabled", events: 1 },
370 ]);
371 } finally {
372 db.close();
373 }
374 });
375
376 it("preserves unidentified event dimensions without inventing an affected installation", () => {
377 type BoundStatement = { sql: string; binds: unknown[] };
378 const statements: BoundStatement[] = [];
379 const d1 = {
380 prepare(sql: string) {
381 const statement = {
382 sql,
383 binds: [] as unknown[],
384 bind(...binds: unknown[]) { statement.binds = binds; statements.push(statement); return statement; },
385 };
386 return statement;
387 },
388 } as unknown as D1Database;
389 reportAggregateStatements(d1, {
390 version: "v1.23.0", os: "linux", arch: "amd64",
391 device: { distroId: "ubuntu", distroVersion: "24.04", sessionType: "wayland" },
392 }, "f".repeat(64), "stable", {
393 engine: "webkitgtk", runtimeVersion: "2.44", kind: "web_process_terminated", reason: "crashed",
394 recovery: "reload_failed", gpuMode: "always",
395 });
396 const db = new DatabaseSync(":memory:");
397 try {
398 db.exec(freshSchemaSQL);
399 for (const statement of statements) db.prepare(statement.sql).run(...statement.binds as []);
400 expect(db.prepare("SELECT events, identified_events FROM report_daily").get()).toEqual({
401 events: 1, identified_events: 0,
402 });
403 expect(db.prepare(
404 "SELECT install_id, distro_id, recovery, events FROM report_event_dimensions",
405 ).get()).toEqual({ install_id: "", distro_id: "ubuntu", recovery: "reload_failed", events: 1 });
406 expect(db.prepare("SELECT COUNT(*) AS count FROM report_installations").get()).toEqual({ count: 0 });
407 } finally {
408 db.close();
409 }
410 });
411
412 it("uses the same dimensions for filtered events, identity coverage, and installations", async () => {
413 let querySQL = "";
414 let queryBinds: unknown[] = [];
415 const row = {
416 fingerprint: "f".repeat(64), status: "open", severity: "high", regressed_at: "",
417 first_version: "v1.23.0", count: 2, seen: "2026-08-10", title: "renderer exited",
418 last_version: "v1.23.0", last_channel: "stable", affected_installs: 1,
419 window_events: 2, identified_events: 1, active_build_installs: 10,
420 dimension_base_installs: 10, dimension_covered_installs: 10, kind: "exception",
421 source: "web.runtime.native", label: "renderer_process_exited", error_type: "", top_frame: "",
422 last_os: "windows", last_arch: "amd64",
423 };
424 const db = {
425 prepare(sql: string) {
426 querySQL = sql;
427 const statement = {
428 bind(...binds: unknown[]) { queryBinds = binds; return statement; },
429 async all() { return { results: [row] }; },
430 };
431 return statement;
432 },
433 } as unknown as D1Database;
434 const result = await crashGroups({ DB: db } as unknown as Env, {
435 status: "", source: "", version: "", os: "", platform: "", osBuild: "17763", arch: "",
436 channel: "", runtimeVersion: "", failureKind: "", failureReason: "", recovery: "", gpu: "",
437 newLatest: false, regressed: false, windowDays: 7,
438 }, "");
439 expect(querySQL).toContain("COUNT(DISTINCT NULLIF(install_id, '')) AS affected_installs");
440 expect(querySQL).toContain("SUM(events) AS window_events");
441 expect(querySQL).toContain("SUM(CASE WHEN install_id <> '' THEN events ELSE 0 END) AS identified_events");
442 expect(querySQL).toContain("os_build = ?");
443 expect(querySQL).toContain("COALESCE(diagnostics.window_events, 0) > 0");
444 expect(querySQL).not.toContain("FROM report_daily WHERE");
445 expect(queryBinds).toContain(17763);
446 expect(result.results[0]).toMatchObject({ identity_coverage: 0.5, impact_rate: 0.1 });
447 });
448
449 it("orders the SQL limit and returned groups by affected installations first", async () => {
450 let querySQL = "";
451 const row = (fingerprint: string, severity: string, affectedInstalls: number) => ({
452 fingerprint, status: "open", severity, regressed_at: "", first_version: "v1.23.0",
453 count: 100, seen: "2026-08-10", title: "browser process exited", last_version: "v1.23.0",
454 last_channel: "stable", affected_installs: affectedInstalls, window_events: 100,
455 identified_events: 100, active_build_installs: 0, kind: "crash", source: "desktop.webview2",
456 label: "browser_process_exited", error_type: "", top_frame: "", last_os: "windows", last_arch: "amd64",
457 });
458 const db = {
459 prepare(sql: string) {
460 querySQL = sql;
461 return { async all() { return { results: [row("b".repeat(64), "critical", 1), row("a".repeat(64), "low", 20)] }; } };
462 },
463 } as unknown as D1Database;
464 const result = await crashGroups({ DB: db } as unknown as Env, {
465 status: "", source: "", version: "", os: "", platform: "", osBuild: "", arch: "", channel: "",
466 runtimeVersion: "", failureKind: "", failureReason: "", recovery: "", gpu: "",
467 newLatest: false, regressed: false, windowDays: 7,
468 }, "");
469 expect(querySQL).toContain("FROM report_daily");
470 expect(querySQL.indexOf("affected_installs DESC")).toBeLessThan(querySQL.indexOf("CASE WHEN status = 'open'"));
471 expect(result.results.map((group) => group.fingerprint)).toEqual(["a".repeat(64), "b".repeat(64)]);
472 });
473
474 it("qualifies the development fingerprint in the joined diagnostics query", async () => {
475 const sqlite = new DatabaseSync(":memory:");
476 try {
477 sqlite.exec(freshSchemaSQL);
478 const db = {
479 prepare(sql: string) {
480 const statement = sqlite.prepare(sql);
481 return { async all() { return { results: statement.all() }; } };
482 },
483 } as unknown as D1Database;
484 await expect(crashGroups({ DB: db } as unknown as Env, {
485 status: "", source: "", version: "", os: "", platform: "", osBuild: "", arch: "", channel: "",
486 runtimeVersion: "", failureKind: "", failureReason: "", recovery: "", gpu: "",
487 newLatest: false, regressed: false, windowDays: 30,
488 }, "")).resolves.toMatchObject({ results: [] });
489 } finally {
490 sqlite.close();
491 }
492 });
493
494 it("shares the unfiltered ping denominator across diagnostics metrics", async () => {
495 let querySQL = "";
496 const db = {
497 prepare(sql: string) {
498 querySQL = sql;
499 return { async all() { return { results: [] }; } };
500 },
501 } as unknown as D1Database;
502 await crashGroups({ DB: db } as unknown as Env, {
503 status: "", source: "", version: "", os: "", platform: "", osBuild: "", arch: "", channel: "",
504 runtimeVersion: "", failureKind: "", failureReason: "", recovery: "", gpu: "",
505 newLatest: false, regressed: false, windowDays: 30,
506 }, "");
507 expect(querySQL.match(/FROM pings WHERE/g)).toHaveLength(1);
508 expect(querySQL).toContain("CROSS JOIN (SELECT COUNT(DISTINCT install_id) AS installs FROM pings");
509 });
510
511 it("caches diagnostic facets per D1 binding and reports query timing labels", async () => {
512 let prepareCalls = 0;
513 const observations: string[] = [];
514 const db = {
515 prepare() {
516 prepareCalls++;
517 return { async all() { return { results: [] }; } };
518 },
519 } as unknown as D1Database;
520 const env = { DB: db } as unknown as Env;
521 await diagnosticFacets(env, 30, (label) => observations.push(label));
522 const firstCallCount = prepareCalls;
523 await diagnosticFacets(env, 30, (label) => observations.push(label));
524 expect(firstCallCount).toBe(17);
525 expect(prepareCalls).toBe(firstCallCount);
526 expect(observations).toContain("diagnostic_facets.cache");
527 });
528
529 it("uses daily crash rollups when no diagnostic dimensions are filtered", async () => {
530 const sqlite = new DatabaseSync(":memory:");
531 sqlite.exec(freshSchemaSQL);
532 const fingerprint = "a".repeat(64);
533 sqlite.prepare(
534 `INSERT INTO groups (fingerprint, kind, count, first_seen, last_seen, first_version, last_version, title)
535 VALUES (?1, 'crash', 4, datetime('now'), datetime('now'), 'v1.0.0', 'v1.0.0', 'boom')`,
536 ).run(fingerprint);
537 sqlite.prepare(
538 `INSERT INTO report_daily (date, fingerprint, events, identified_events)
539 VALUES (date('now'), ?1, 4, 3)`,
540 ).run(fingerprint);
541 sqlite.prepare(
542 `INSERT INTO report_installations (date, fingerprint, install_id, version, os, arch)
543 VALUES (date('now'), ?1, 'install-1', 'v1.0.0', 'windows', 'amd64')`,
544 ).run(fingerprint);
545 const d1 = {
546 prepare(sql: string) {
547 const statement = sqlite.prepare(sql);
548 const wrapper: any = {
549 bind(...values: unknown[]) { wrapper.values = values; return wrapper; },
550 values: [] as unknown[],
551 async all() { return { results: statement.all(...wrapper.values) }; },
552 };
553 return wrapper;
554 },
555 } as unknown as D1Database;
556 try {
557 const result = await crashGroups({ DB: d1 } as unknown as Env, {
558 status: "", source: "", version: "", os: "", platform: "", osBuild: "", arch: "", channel: "",
559 runtimeVersion: "", failureKind: "", failureReason: "", recovery: "", gpu: "",
560 newLatest: false, regressed: false, windowDays: 30,
561 }, "");
562 expect(result.results[0]).toMatchObject({
563 fingerprint, affected_installs: 1, window_events: 4, identified_events: 3,
564 });
565 } finally {
566 sqlite.close();
567 }
568 });
569
570 it("materializes one group detail window for all diagnostic distributions", async () => {
571 let distributionSQL = "";
572 const db = {
573 prepare(sql: string) {
574 if (sql.includes("WITH window AS MATERIALIZED")) distributionSQL = sql;
575 const statement = {
576 bind() { return statement; },
577 async first() { return { window_events: 1, identified_events: 1, affected_installs: 1 }; },
578 async all() { return { results: [] }; },
579 };
580 return statement;
581 },
582 } as unknown as D1Database;
583 await groupDiagnosticSummary({ DB: db } as unknown as Env, "b".repeat(64));
584 expect(distributionSQL).toContain("WITH window AS MATERIALIZED");
585 expect(distributionSQL.match(/FROM report_event_dimensions/g)).toHaveLength(1);
586 expect(distributionSQL).toContain("ROW_NUMBER() OVER (PARTITION BY facet");
587 expect(distributionSQL).toContain("WHERE rank <= 20");
588 });
589
590 it("bounds each group facet and reads totals from the daily rollups", async () => {
591 const sqlite = new DatabaseSync(":memory:");
592 sqlite.exec(freshSchemaSQL);
593 const fingerprint = "c".repeat(64);
594 sqlite.prepare(
595 `INSERT INTO report_daily (date, fingerprint, events, identified_events)
596 VALUES (date('now'), ?1, 40, 40)`,
597 ).run(fingerprint);
598 sqlite.prepare(
599 `INSERT INTO report_installations (date, fingerprint, install_id, version, os, arch)
600 VALUES (date('now'), ?1, 'install-1', 'v1.0.0', 'windows', 'amd64')`,
601 ).run(fingerprint);
602 const fact = sqlite.prepare(
603 `INSERT INTO report_event_dimensions
604 (date, fingerprint, install_id, version, os, arch, os_build, os_revision,
605 distro_id, distro_version, kernel_version, session_type, channel,
606 runtime_engine, runtime_version, failure_kind, failure_reason, exit_code, recovery, gpu_mode, events)
607 VALUES (date('now'), ?1, ?2, 'v1.0.0', 'windows', 'amd64', 1, 1, '', '', '', '', 'stable',
608 'webview2', ?3, 'browser_process_exited', 'unexpected', '1', '', 'unknown', 1)`,
609 );
610 for (let i = 0; i < 40; i++) fact.run(fingerprint, `install-${i}`, `runtime-${i}`);
611 const db = {
612 prepare(sql: string) {
613 const statement = sqlite.prepare(sql);
614 const wrapper: any = {
615 values: [] as unknown[],
616 bind(...values: unknown[]) { wrapper.values = values; return wrapper; },
617 async first() { return statement.get(...wrapper.values); },
618 async all() { return { results: statement.all(...wrapper.values) }; },
619 };
620 return wrapper;
621 },
622 } as unknown as D1Database;
623 try {
624 const result = await groupDiagnosticSummary({ DB: db } as unknown as Env, fingerprint);
625 expect(result).toMatchObject({ windowEvents: 40, identifiedEvents: 40, affectedInstalls: 1 });
626 const byFacet = new Map<string, number>();
627 for (const row of result.distributions) byFacet.set(row.facet, (byFacet.get(row.facet) ?? 0) + 1);
628 expect(Math.max(...byFacet.values())).toBeLessThanOrEqual(20);
629 } finally {
630 sqlite.close();
631 }
632 });
633 });
634
634 lines TYPESCRIPT