Files
orca/src/shared/crash-reporting.test.ts
T
2026-06-16 18:05:15 -07:00

182 lines
5.6 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
formatCrashReportText,
formatUncapturedCrashReportText,
isCrashReportReason,
sanitizeCrashReportBreadcrumbs,
sanitizeCrashReportDetails,
sanitizeCrashReportString,
type CrashReportRecord
} from './crash-reporting'
describe('crash-reporting shared helpers', () => {
it('redacts paths and common secret-shaped strings', () => {
const text =
'file /Users/alice/My Project/.env /tmp/build log C:\\Users\\bob\\My Project token=abc123 ghp_abcdefghijklmnopqrstuvwxyz'
expect(sanitizeCrashReportString(text)).toBe(
'file [redacted-path] [redacted-path] [redacted-path] token=[redacted] [redacted-secret]'
)
})
it('keeps details on a strict primitive allowlist', () => {
const longStack = [
'Error: boom',
...Array.from(
{ length: 80 },
(_, index) => `at Component${index} (/Users/alice/project/src/file-${index}.tsx:1:1)`
)
].join('\n')
expect(
sanitizeCrashReportDetails({
name: 'GPU /home/alice/repo',
code: 9,
crashed: true,
missing: null,
error_stack: longStack,
nested: { nope: true },
infinite: Number.POSITIVE_INFINITY
})
).toEqual({
name: 'GPU [redacted-path]',
code: 9,
crashed: true,
missing: null,
error_stack: expect.stringContaining('[redacted-path]')
})
expect(
String(sanitizeCrashReportDetails({ error_stack: longStack }).error_stack).length
).toBeGreaterThan(240)
})
it('sanitizes breadcrumb data and caps to the latest thirty entries', () => {
const breadcrumbs = sanitizeCrashReportBreadcrumbs(
Array.from({ length: 32 }, (_, index) => ({
createdAt: `2026-05-16T01:${String(index).padStart(2, '0')}:00.000Z`,
name: `event_${index}`,
data: {
path: '/Users/alice/project',
ok: true,
nested: { ignored: true }
}
}))
)
expect(breadcrumbs).toHaveLength(30)
expect(breadcrumbs?.[0].name).toBe('event_2')
expect(breadcrumbs?.[0].data).toEqual({
path: '[redacted-path]',
ok: true
})
})
it('recognizes crash reasons captured by Electron process-gone events', () => {
expect(isCrashReportReason('abnormal-exit')).toBe(true)
expect(isCrashReportReason('crashed')).toBe(true)
expect(isCrashReportReason('launch-failed')).toBe(true)
expect(isCrashReportReason('memory-eviction')).toBe(true)
expect(isCrashReportReason('clean-exit')).toBe(false)
})
it('formats reports without route or URL fields', () => {
const report: CrashReportRecord = {
id: 'crash-1',
createdAt: '2026-05-16T01:00:00.000Z',
status: 'pending',
source: 'renderer',
processType: 'renderer',
reason: 'crashed',
exitCode: 5,
appVersion: '1.0.0',
platform: 'darwin',
osRelease: '25.0.0',
arch: 'arm64',
electronVersion: '41.0.0',
chromeVersion: '141.0.0',
details: { reason: 'native crash' },
breadcrumbs: [
{
createdAt: '2026-05-16T00:59:30.000Z',
name: 'agent_state_changed',
data: { agentType: 'codex', state: 'working' }
}
]
}
const text = formatCrashReportText(report, 'saw /Users/me/project', {
status: 'uploaded',
ticketId: 'ticketabcdefghijklmnop',
bundleSubmissionId: 'bundleabcdefghijklmnop',
bytes: 1024,
spanCount: 12
})
expect(text).toContain('[Crash Report]')
expect(text).toContain('Recent activity:')
expect(text).toContain('agent_state_changed')
expect(text).toContain('Diagnostic log:')
expect(text).toContain('ticketabcdefghijklmnop')
expect(text.indexOf('Diagnostic log:')).toBeLessThan(text.indexOf('Details:'))
expect(text).toContain('User notes:')
expect(text).toContain('[redacted-path]')
expect(text).not.toContain('Route:')
expect(text).not.toContain('\nURL:')
})
it('caps formatted reports to the crash endpoint limit', () => {
const report: CrashReportRecord = {
id: 'crash-oversized',
createdAt: '2026-05-16T01:00:00.000Z',
status: 'pending',
source: 'renderer',
processType: 'renderer',
reason: 'crashed',
exitCode: 5,
appVersion: '1.0.0',
platform: 'darwin',
osRelease: '25.0.0',
arch: 'arm64',
electronVersion: '41.0.0',
chromeVersion: '141.0.0',
details: Object.fromEntries(
Array.from({ length: 400 }, (_, index) => [`detail_${index}`, 'x'.repeat(240)])
),
breadcrumbs: []
}
const text = formatCrashReportText(report)
expect(text.length).toBeLessThanOrEqual(64_000)
expect(text).toContain('[Crash report truncated to fit feedback endpoint limits.]')
})
it('formats uncaptured crash reports so users can still submit from Help', () => {
const text = formatUncapturedCrashReportText(
{
createdAt: '2026-05-16T01:00:00.000Z',
appVersion: '1.0.0',
platform: 'darwin',
osRelease: '25.0.0',
arch: 'arm64',
electronVersion: '41.0.0',
chromeVersion: '141.0.0'
},
'happened after opening /Users/me/project',
{
status: 'not_uploaded',
reason: 'diagnostic upload endpoint is not configured for this build',
bundleSubmissionId: 'bundleabcdefghijklmnop',
bytes: 2048,
spanCount: 3
}
)
expect(text).toContain('Report ID: not captured')
expect(text).toContain('Reason: no captured crash report')
expect(text).toContain('Diagnostic log:')
expect(text).toContain('Status: not uploaded')
expect(text).toContain('[redacted-path]')
})
})