instrumentation-client.test.ts419 lines · main
1import type { Event as SentryEvent, StackFrame } from '@sentry/nextjs'
2import { describe, expect, it } from 'vitest'
3
4import {
5 isBrowserWalletExtensionError,
6 isCancellationRejection,
7 isChallengeExpiredError,
8 isUserAbortedOperation,
9} from './instrumentation-client'
10
11describe('Sentry beforeSend filtering functions', () => {
12 describe('isBrowserWalletExtensionError', () => {
13 it('returns true for Gate.io wallet extension error (gt-window-provider.js)', () => {
14 const event: SentryEvent = {
15 exception: {
16 values: [
17 {
18 type: 'TypeError',
19 value: 'en.shouldSetTallyForCurrentProvider is not a function',
20 stacktrace: {
21 frames: [
22 { filename: 'app:///_next/static/chunks/main.js' } as StackFrame,
23 { filename: 'app:///gt-window-provider.js' } as StackFrame,
24 ],
25 },
26 },
27 ],
28 },
29 }
30
31 expect(isBrowserWalletExtensionError(event)).toBe(true)
32 })
33
34 it('returns true for Gate.io BTC wallet extension error (gt-window-provider-btc.js)', () => {
35 const event: SentryEvent = {
36 exception: {
37 values: [
38 {
39 type: 'TypeError',
40 value: 'f.shouldSetTallyForCurrentProvider is not a function',
41 stacktrace: {
42 frames: [{ filename: 'app:///gt-window-provider-btc.js' } as StackFrame],
43 },
44 },
45 ],
46 },
47 }
48
49 expect(isBrowserWalletExtensionError(event)).toBe(true)
50 })
51
52 it('returns true for wallet-provider in abs_path', () => {
53 const event: SentryEvent = {
54 exception: {
55 values: [
56 {
57 type: 'Error',
58 value: 'wallet error',
59 stacktrace: {
60 frames: [
61 { abs_path: 'chrome-extension://abc123/wallet-provider.js' } as StackFrame,
62 ],
63 },
64 },
65 ],
66 },
67 }
68
69 expect(isBrowserWalletExtensionError(event)).toBe(true)
70 })
71
72 it('returns false for regular application errors', () => {
73 const event: SentryEvent = {
74 exception: {
75 values: [
76 {
77 type: 'Error',
78 value: 'Regular error',
79 stacktrace: {
80 frames: [
81 { filename: 'app:///_next/static/chunks/main.js' } as StackFrame,
82 { filename: 'app:///_next/static/chunks/pages/index.js' } as StackFrame,
83 ],
84 },
85 },
86 ],
87 },
88 }
89
90 expect(isBrowserWalletExtensionError(event)).toBe(false)
91 })
92
93 it('returns false for empty event', () => {
94 const event: SentryEvent = {}
95 expect(isBrowserWalletExtensionError(event)).toBe(false)
96 })
97
98 it('returns false when exception values are empty', () => {
99 const event: SentryEvent = {
100 exception: {
101 values: [],
102 },
103 }
104 expect(isBrowserWalletExtensionError(event)).toBe(false)
105 })
106
107 it('returns false when stacktrace frames are undefined', () => {
108 const event: SentryEvent = {
109 exception: {
110 values: [
111 {
112 type: 'Error',
113 value: 'Error without stacktrace',
114 },
115 ],
116 },
117 }
118 expect(isBrowserWalletExtensionError(event)).toBe(false)
119 })
120 })
121
122 describe('isUserAbortedOperation', () => {
123 it('returns true for "operation was aborted" error', () => {
124 const error = new Error('The operation was aborted.')
125 const event: SentryEvent = {}
126
127 expect(isUserAbortedOperation(error, event)).toBe(true)
128 })
129
130 it('returns true for "signal is aborted" error', () => {
131 const error = new Error('signal is aborted without reason')
132 const event: SentryEvent = {}
133
134 expect(isUserAbortedOperation(error, event)).toBe(true)
135 })
136
137 it('returns true for "manually canceled" error', () => {
138 const error = new Error('operation is manually canceled')
139 const event: SentryEvent = {}
140
141 expect(isUserAbortedOperation(error, event)).toBe(true)
142 })
143
144 it('returns true for "AbortError" message', () => {
145 const error = new Error('AbortError: The operation was aborted')
146 const event: SentryEvent = {}
147
148 expect(isUserAbortedOperation(error, event)).toBe(true)
149 })
150
151 it('returns true when message is in event.message (no error object)', () => {
152 const error = null
153 const event: SentryEvent = {
154 message: '[CRITICAL][sign in via EP] Failed: The operation was aborted.',
155 }
156
157 expect(isUserAbortedOperation(error, event)).toBe(true)
158 })
159
160 it('returns true for event message with "signal is aborted"', () => {
161 const event: SentryEvent = {
162 message: '[CRITICAL][sign in via EP] Failed: signal is aborted without reason',
163 }
164
165 expect(isUserAbortedOperation(undefined, event)).toBe(true)
166 })
167
168 it('returns false for regular errors', () => {
169 const error = new Error('Something went wrong')
170 const event: SentryEvent = {}
171
172 expect(isUserAbortedOperation(error, event)).toBe(false)
173 })
174
175 it('returns false for empty inputs', () => {
176 expect(isUserAbortedOperation(null, {})).toBe(false)
177 expect(isUserAbortedOperation(undefined, {})).toBe(false)
178 })
179
180 it('handles non-Error objects gracefully', () => {
181 const error = { message: 'The operation was aborted.' }
182 const event: SentryEvent = {}
183
184 // Non-Error objects should not match since we check instanceof Error
185 expect(isUserAbortedOperation(error, event)).toBe(false)
186 })
187 })
188
189 describe('isCancellationRejection', () => {
190 it('returns true for cancellation type in extra.__serialized__', () => {
191 const event: SentryEvent = {
192 extra: {
193 __serialized__: {
194 msg: 'operation is manually canceled',
195 type: 'cancelation',
196 },
197 },
198 }
199
200 expect(isCancellationRejection(event)).toBe(true)
201 })
202
203 it('returns false when type is not cancelation', () => {
204 const event: SentryEvent = {
205 extra: {
206 __serialized__: {
207 msg: 'some error',
208 type: 'error',
209 },
210 },
211 }
212
213 expect(isCancellationRejection(event)).toBe(false)
214 })
215
216 it('returns false when __serialized__ is undefined', () => {
217 const event: SentryEvent = {
218 extra: {},
219 }
220
221 expect(isCancellationRejection(event)).toBe(false)
222 })
223
224 it('returns false when extra is undefined', () => {
225 const event: SentryEvent = {}
226
227 expect(isCancellationRejection(event)).toBe(false)
228 })
229
230 it('returns false when __serialized__ has no type property', () => {
231 const event: SentryEvent = {
232 extra: {
233 __serialized__: {
234 msg: 'some message',
235 },
236 },
237 }
238
239 expect(isCancellationRejection(event)).toBe(false)
240 })
241 })
242
243 describe('isChallengeExpiredError', () => {
244 it('returns true for challenge-expired error message', () => {
245 const error = new Error('Non-Error promise rejection captured with value: challenge-expired')
246 const event: SentryEvent = {}
247
248 expect(isChallengeExpiredError(error, event)).toBe(true)
249 })
250
251 it('returns true when challenge-expired is in event.message', () => {
252 const event: SentryEvent = {
253 message: 'challenge-expired',
254 }
255
256 expect(isChallengeExpiredError(null, event)).toBe(true)
257 })
258
259 it('returns false for regular errors', () => {
260 const error = new Error('Something went wrong')
261 const event: SentryEvent = {}
262
263 expect(isChallengeExpiredError(error, event)).toBe(false)
264 })
265
266 it('returns false for empty inputs', () => {
267 expect(isChallengeExpiredError(null, {})).toBe(false)
268 expect(isChallengeExpiredError(undefined, {})).toBe(false)
269 })
270
271 it('returns false for similar but different messages', () => {
272 const error = new Error('challenge expired') // No hyphen
273 const event: SentryEvent = {}
274
275 expect(isChallengeExpiredError(error, event)).toBe(false)
276 })
277 })
278
279 describe('integration scenarios', () => {
280 it('correctly identifies BRIVEN-APP-353 pattern (cancellation rejection)', () => {
281 // Based on actual Sentry issue BRIVEN-APP-353
282 const event: SentryEvent = {
283 exception: {
284 values: [
285 {
286 type: 'UnhandledRejection',
287 value: 'Object captured as promise rejection with keys: msg, type',
288 },
289 ],
290 },
291 extra: {
292 __serialized__: {
293 msg: 'operation is manually canceled',
294 type: 'cancelation',
295 },
296 },
297 }
298
299 expect(isCancellationRejection(event)).toBe(true)
300 })
301
302 it('correctly identifies BRIVEN-APP-AFC pattern (wallet extension)', () => {
303 // Based on actual Sentry issue BRIVEN-APP-AFC
304 const event: SentryEvent = {
305 exception: {
306 values: [
307 {
308 type: 'TypeError',
309 value: 'f.shouldSetTallyForCurrentProvider is not a function',
310 stacktrace: {
311 frames: [
312 {
313 filename:
314 'node_modules/.pnpm/@sentry+browser@10.27.0/node_modules/@sentry/browser/src/helpers.ts',
315 function: 'n',
316 } as StackFrame,
317 {
318 filename: 'app:///gt-window-provider-btc.js',
319 function: 'GateWindowProvider.internalListener',
320 } as StackFrame,
321 ],
322 },
323 },
324 ],
325 },
326 }
327
328 expect(isBrowserWalletExtensionError(event)).toBe(true)
329 })
330
331 it('correctly identifies BRIVEN-APP-92A pattern (wallet extension)', () => {
332 // Based on actual Sentry issue BRIVEN-APP-92A
333 const event: SentryEvent = {
334 exception: {
335 values: [
336 {
337 type: 'TypeError',
338 value: 'en.shouldSetTallyForCurrentProvider is not a function',
339 stacktrace: {
340 frames: [
341 {
342 filename:
343 'node_modules/.pnpm/@sentry+browser@10.27.0/node_modules/@sentry/browser/src/helpers.ts',
344 function: 'n',
345 } as StackFrame,
346 {
347 filename: 'app:///gt-window-provider.js',
348 function: 'GateWindowProvider.internalListener',
349 } as StackFrame,
350 ],
351 },
352 },
353 ],
354 },
355 }
356
357 expect(isBrowserWalletExtensionError(event)).toBe(true)
358 })
359
360 it('correctly identifies BRIVEN-APP-BG6 pattern (user aborted)', () => {
361 // Based on actual Sentry issue BRIVEN-APP-BG6
362 const error = new Error('The operation was aborted.')
363 const event: SentryEvent = {
364 message: '[CRITICAL][sign in via EP] Failed: The operation was aborted.',
365 }
366
367 expect(isUserAbortedOperation(error, event)).toBe(true)
368 })
369
370 it('correctly identifies BRIVEN-APP-BG7 pattern (signal aborted)', () => {
371 // Based on actual Sentry issue BRIVEN-APP-BG7
372 const error = new Error('signal is aborted without reason')
373 const event: SentryEvent = {
374 message: '[CRITICAL][sign in via EP] Failed: signal is aborted without reason',
375 }
376
377 expect(isUserAbortedOperation(error, event)).toBe(true)
378 })
379
380 it('correctly identifies BRIVEN-APP-ACC pattern (challenge expired)', () => {
381 // Based on actual Sentry issue BRIVEN-APP-ACC
382 const error = new Error('Non-Error promise rejection captured with value: challenge-expired')
383 const event: SentryEvent = {
384 exception: {
385 values: [
386 {
387 type: 'UnhandledRejection',
388 value: 'Non-Error promise rejection captured with value: challenge-expired',
389 },
390 ],
391 },
392 }
393
394 expect(isChallengeExpiredError(error, event)).toBe(true)
395 })
396
397 it('does not filter legitimate errors', () => {
398 const error = new Error('Cannot read property "foo" of undefined')
399 const event: SentryEvent = {
400 exception: {
401 values: [
402 {
403 type: 'TypeError',
404 value: 'Cannot read property "foo" of undefined',
405 stacktrace: {
406 frames: [{ filename: 'app:///_next/static/chunks/pages/index.js' } as StackFrame],
407 },
408 },
409 ],
410 },
411 }
412
413 expect(isBrowserWalletExtensionError(event)).toBe(false)
414 expect(isUserAbortedOperation(error, event)).toBe(false)
415 expect(isCancellationRejection(event)).toBe(false)
416 expect(isChallengeExpiredError(error, event)).toBe(false)
417 })
418 })
419})