import { XrpcResponseError } from '@atproto/lex' import { com } from '@pds-moover/lexicons' import { BlobRateLimitError } from '@pds-moover/moover' import { beforeEach, describe, expect, it, vi } from 'vitest' const captureException = vi.fn() vi.mock('@sentry/sveltekit', () => ({ captureException: (...args: unknown[]) => captureException(...args), })) const { isExpectedError, reportError } = await import('./sentryReport') /** A real XrpcResponseError, because the filter narrows with `instanceof`. */ function xrpcError(status: number, error: string) { return new XrpcResponseError( com.atproto.server.describeServer.main, new Response(null, { status }), { encoding: 'application/json', body: { error, message: error }, } as never, ) } describe('isExpectedError', () => { it('treats user-facing conditions as expected', () => { expect(isExpectedError(xrpcError(401, 'AuthFactorTokenRequired'))).toBe(true) expect(isExpectedError(xrpcError(401, 'AuthenticationRequired'))).toBe(true) expect(isExpectedError(xrpcError(400, 'InvalidToken'))).toBe(true) expect(isExpectedError(xrpcError(400, 'ExpiredToken'))).toBe(true) expect(isExpectedError(xrpcError(400, 'InvalidInviteCode'))).toBe(true) expect(isExpectedError(xrpcError(400, 'HandleNotAvailable'))).toBe(true) expect(isExpectedError(xrpcError(429, 'RateLimitExceeded'))).toBe(true) expect(isExpectedError(new BlobRateLimitError({ limit: 1, remaining: 0 } as never, 3, 7))).toBe( true, ) const aborted = new Error('The operation was aborted') aborted.name = 'AbortError' expect(isExpectedError(aborted)).toBe(true) }) it('treats everything else as unexpected', () => { expect(isExpectedError(new Error('boom'))).toBe(false) expect(isExpectedError(new TypeError('Failed to fetch'))).toBe(false) expect(isExpectedError(xrpcError(500, 'InternalServerError'))).toBe(false) expect(isExpectedError(xrpcError(400, 'RepoNotFound'))).toBe(false) expect(isExpectedError('a string')).toBe(false) expect(isExpectedError(undefined)).toBe(false) }) }) describe('reportError', () => { beforeEach(() => captureException.mockReset()) it('skips expected errors', () => { expect(reportError(xrpcError(401, 'AuthFactorTokenRequired'), 'login')).toBe(false) expect(captureException).not.toHaveBeenCalled() }) it('captures unexpected errors tagged with the op and the xrpc code', () => { const error = xrpcError(500, 'InternalServerError') expect(reportError(error, 'migration.run', { extra: { step: 'blobs' } })).toBe(true) expect(captureException).toHaveBeenCalledWith(error, { level: 'error', tags: { op: 'migration.run' }, extra: { step: 'blobs', xrpc_error: 'InternalServerError', xrpc_status: 500 }, }) }) it('passes the level through for best-effort work', () => { const error = new Error('resolver down') reportError(error, 'handle.check', { level: 'warning' }) expect(captureException).toHaveBeenCalledWith(error, { level: 'warning', tags: { op: 'handle.check' }, extra: {}, }) }) })