-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Expand file tree
/
Copy patherrors.test.ts
More file actions
181 lines (157 loc) · 6.14 KB
/
Copy patherrors.test.ts
File metadata and controls
181 lines (157 loc) · 6.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import { describeError, findCause, getPostgresErrorCode, toError } from './errors.js'
describe('toError', () => {
it('returns the same Error when given an Error', () => {
const err = new Error('test')
expect(toError(err)).toBe(err)
})
it('wraps a string into an Error', () => {
const err = toError('msg')
expect(err).toBeInstanceOf(Error)
expect(err.message).toBe('msg')
})
it('wraps a number into an Error', () => {
const err = toError(42)
expect(err).toBeInstanceOf(Error)
expect(err.message).toBe('42')
})
it('wraps null into an Error', () => {
const err = toError(null)
expect(err).toBeInstanceOf(Error)
expect(err.message).toBe('null')
})
it('wraps undefined into an Error', () => {
const err = toError(undefined)
expect(err).toBeInstanceOf(Error)
expect(err.message).toBe('undefined')
})
})
describe('getPostgresErrorCode', () => {
it('reads code from Error.code', () => {
const err = new Error('fail') as Error & { code: string }
err.code = '23505'
expect(getPostgresErrorCode(err)).toBe('23505')
})
it('reads code from plain object', () => {
expect(getPostgresErrorCode({ code: '23505' })).toBe('23505')
})
it('reads code from Error.cause', () => {
const err = new Error('fail', { cause: { code: '23505' } })
expect(getPostgresErrorCode(err)).toBe('23505')
})
it('walks nested Error causes', () => {
const pgErr = new Error('unique_violation') as Error & { code: string }
pgErr.code = '23505'
const err = new Error('outer', { cause: new Error('inner', { cause: pgErr }) })
expect(getPostgresErrorCode(err)).toBe('23505')
})
it('returns undefined for non-errors', () => {
expect(getPostgresErrorCode(undefined)).toBeUndefined()
expect(getPostgresErrorCode(null)).toBeUndefined()
expect(getPostgresErrorCode('23505')).toBeUndefined()
})
it('returns undefined when no code is present', () => {
expect(getPostgresErrorCode(new Error('no code'))).toBeUndefined()
})
it('does not loop forever on circular cause chains', () => {
const err1 = new Error('a')
const err2 = new Error('b', { cause: err1 })
// Create circular reference
;(err1 as { cause?: unknown }).cause = err2
expect(getPostgresErrorCode(err1)).toBeUndefined()
})
})
describe('describeError', () => {
it('reports name and message for a plain error, omitting causeChain', () => {
const described = describeError(new Error('boom'))
expect(described).toEqual({ name: 'Error', message: 'boom' })
expect(described.causeChain).toBeUndefined()
})
it('surfaces the deepest cause for a wrapped driver error', () => {
const driver = Object.assign(new Error('read ECONNRESET'), {
code: 'ECONNRESET',
errno: 'ECONNRESET',
syscall: 'read',
})
const wrapped = new Error('Failed query: select ...', { cause: driver })
const described = describeError(wrapped)
expect(described.message).toBe('read ECONNRESET')
expect(described.code).toBe('ECONNRESET')
expect(described.errno).toBe('ECONNRESET')
expect(described.syscall).toBe('read')
expect(described.causeChain).toEqual([
'Error: Failed query: select ...',
'Error: read ECONNRESET',
])
})
it('redacts driver-appended bound parameter values from every reported message', () => {
const driver = Object.assign(
new Error('cannot execute SELECT FOR UPDATE in a read-only transaction'),
{
code: '25006',
}
)
const wrapped = new Error(
'Failed query: select "storage_used_bytes" from "workspace" where id = $1 limit $2 for update\nparams: ws-secret-id,1',
{ cause: driver }
)
const described = describeError(wrapped)
expect(described.code).toBe('25006')
expect(described.causeChain?.[0]).toBe(
'Error: Failed query: select "storage_used_bytes" from "workspace" where id = $1 limit $2 for update\nparams: [redacted]'
)
expect(JSON.stringify(described)).not.toContain('ws-secret-id')
})
it('redacts bound parameter values from an unwrapped driver error message', () => {
const described = describeError(new Error('Failed query: select 1\nparams: ws-secret-id'))
expect(described.message).toBe('Failed query: select 1\nparams: [redacted]')
})
it('always returns the cause for unclassified errors (AbortError)', () => {
const aborted = Object.assign(new Error('The operation was aborted'), { name: 'AbortError' })
expect(describeError(aborted)).toEqual({
name: 'AbortError',
message: 'The operation was aborted',
})
})
it('falls back to a populated description for non-Error input without throwing', () => {
expect(describeError('just a string')).toEqual({ name: 'Error', message: 'just a string' })
expect(() => describeError({ weird: true })).not.toThrow()
})
it('stops at depth 10 and does not loop on a cyclic cause', () => {
const a = new Error('a')
const b = new Error('b')
;(a as { cause?: unknown }).cause = b
;(b as { cause?: unknown }).cause = a
let described: ReturnType<typeof describeError> | undefined
expect(() => {
described = describeError(a)
}).not.toThrow()
expect(described?.causeChain?.length).toBeLessThanOrEqual(10)
})
})
describe('findCause', () => {
class Marker extends Error {}
const isMarker = (value: unknown): value is Marker => value instanceof Marker
it('returns the error itself when it matches', () => {
const marker = new Marker('hit')
expect(findCause(marker, isMarker)).toBe(marker)
})
it('finds a match further down the cause chain', () => {
const marker = new Marker('deep')
const wrapped = new Error('outer', { cause: new Error('middle', { cause: marker }) })
expect(findCause(wrapped, isMarker)).toBe(marker)
})
it('returns undefined when nothing matches', () => {
expect(findCause(new Error('plain'), isMarker)).toBeUndefined()
})
it('survives a cyclic chain', () => {
const a = new Error('a')
const b = new Error('b')
Object.assign(a, { cause: b })
Object.assign(b, { cause: a })
expect(findCause(a, isMarker)).toBeUndefined()
})
})