103 lines
2.7 KiB
TypeScript
103 lines
2.7 KiB
TypeScript
import {type Procedure, XrpcFetchError, XrpcResponseError} from '@atproto/lex'
|
|
import {describe, expect, test} from '@jest/globals'
|
|
import {type ErrorEvent} from '@sentry/react-native'
|
|
|
|
import {
|
|
dropExpectedNetworkErrors,
|
|
isExpectedSentryNetworkError,
|
|
} from '#/logger/sentry/network-errors'
|
|
|
|
const method = {
|
|
nsid: 'app.bsky.test.getTest',
|
|
type: 'query',
|
|
} as unknown as Procedure
|
|
|
|
function xrpcResponseError(status: number) {
|
|
return new XrpcResponseError(method, new Response(null, {status}), undefined)
|
|
}
|
|
|
|
describe('isExpectedSentryNetworkError', () => {
|
|
test.each([502, 503, 504])('detects transient upstream status %s', status => {
|
|
expect(isExpectedSentryNetworkError(xrpcResponseError(status))).toBe(true)
|
|
})
|
|
|
|
test.each([429, 500])('keeps actionable HTTP status %s', status => {
|
|
expect(isExpectedSentryNetworkError(xrpcResponseError(status))).toBe(false)
|
|
})
|
|
|
|
test('checks an XRPC fetch error cause', () => {
|
|
expect(
|
|
isExpectedSentryNetworkError(
|
|
new XrpcFetchError(method, new Error('fetch failed: connection reset')),
|
|
),
|
|
).toBe(true)
|
|
})
|
|
|
|
test('keeps implementation errors wrapped by XRPC fetch errors', () => {
|
|
expect(
|
|
isExpectedSentryNetworkError(
|
|
new XrpcFetchError(
|
|
method,
|
|
new TypeError('URL.canParse is not a function'),
|
|
),
|
|
),
|
|
).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('dropExpectedNetworkErrors', () => {
|
|
test('drops automatic captures using the original exception', () => {
|
|
const event = {type: undefined} satisfies ErrorEvent
|
|
|
|
expect(
|
|
dropExpectedNetworkErrors(event, {
|
|
originalException: new Error('fetch failed: connection closed'),
|
|
}),
|
|
).toBeNull()
|
|
})
|
|
|
|
test('checks every exception in a linked error chain', () => {
|
|
const event = {
|
|
type: undefined,
|
|
exception: {
|
|
values: [
|
|
{type: 'Error', value: 'Network request failed'},
|
|
{
|
|
type: 'XrpcInternalError',
|
|
value: 'Unable to fulfill XRPC request',
|
|
},
|
|
],
|
|
},
|
|
} satisfies ErrorEvent
|
|
|
|
expect(dropExpectedNetworkErrors(event, {})).toBeNull()
|
|
})
|
|
|
|
test('drops serialized transient upstream errors', () => {
|
|
const event = {
|
|
type: undefined,
|
|
exception: {
|
|
values: [{type: 'XrpcResponseError', value: 'Upstream Timeout'}],
|
|
},
|
|
} satisfies ErrorEvent
|
|
|
|
expect(dropExpectedNetworkErrors(event, {})).toBeNull()
|
|
})
|
|
|
|
test('keeps non-network events', () => {
|
|
const event = {
|
|
type: undefined,
|
|
exception: {
|
|
values: [
|
|
{
|
|
type: 'XrpcFetchError',
|
|
value: 'URL.canParse is not a function',
|
|
},
|
|
],
|
|
},
|
|
} satisfies ErrorEvent
|
|
|
|
expect(dropExpectedNetworkErrors(event, {})).toBe(event)
|
|
})
|
|
})
|