diff --git a/packages/core/src/utils/errors.test.ts b/packages/core/src/utils/errors.test.ts index c7f8c5f287..8ee1bcfd7a 100644 --- a/packages/core/src/utils/errors.test.ts +++ b/packages/core/src/utils/errors.test.ts @@ -5,7 +5,13 @@ */ import { describe, it, expect } from 'vitest'; -import { isAuthenticationError, UnauthorizedError } from './errors.js'; +import { + isAuthenticationError, + UnauthorizedError, + toFriendlyError, + BadRequestError, + ForbiddenError, +} from './errors.js'; describe('isAuthenticationError', () => { it('should detect error with code: 401 property (MCP SDK style)', () => { @@ -40,3 +46,132 @@ describe('isAuthenticationError', () => { expect(isAuthenticationError(new Error('Status code: 401'))).toBe(true); }); }); + +describe('toFriendlyError', () => { + it('should return BadRequestError for 400', () => { + const error = { + response: { + data: { + error: { + code: 400, + message: 'Bad Request', + }, + }, + }, + }; + const result = toFriendlyError(error); + expect(result).toBeInstanceOf(BadRequestError); + expect((result as BadRequestError).message).toBe('Bad Request'); + }); + + it('should return UnauthorizedError for 401', () => { + const error = { + response: { + data: { + error: { + code: 401, + message: 'Unauthorized', + }, + }, + }, + }; + const result = toFriendlyError(error); + expect(result).toBeInstanceOf(UnauthorizedError); + expect((result as UnauthorizedError).message).toBe('Unauthorized'); + }); + + it('should return ForbiddenError for 403', () => { + const error = { + response: { + data: { + error: { + code: 403, + message: 'Forbidden', + }, + }, + }, + }; + const result = toFriendlyError(error); + expect(result).toBeInstanceOf(ForbiddenError); + expect((result as ForbiddenError).message).toBe('Forbidden'); + }); + + it('should parse stringified JSON data', () => { + const error = { + response: { + data: JSON.stringify({ + error: { + code: 400, + message: 'Parsed Message', + }, + }), + }, + }; + const result = toFriendlyError(error); + expect(result).toBeInstanceOf(BadRequestError); + expect((result as BadRequestError).message).toBe('Parsed Message'); + }); + + it('should return original error if response data is undefined', () => { + const error = { + response: { + data: undefined, + }, + }; + expect(toFriendlyError(error)).toBe(error); + }); + + it('should return original error if error object is missing in data', () => { + const error = { + response: { + data: { + somethingElse: 'value', + }, + }, + }; + expect(toFriendlyError(error)).toBe(error); + }); + + it('should return original error if error code or message is missing', () => { + const errorNoCode = { + response: { + data: { + error: { + message: 'No Code', + }, + }, + }, + }; + expect(toFriendlyError(errorNoCode)).toBe(errorNoCode); + + const errorNoMessage = { + response: { + data: { + error: { + code: 400, + }, + }, + }, + }; + expect(toFriendlyError(errorNoMessage)).toBe(errorNoMessage); + }); + + it('should return original error for unknown codes', () => { + const error = { + response: { + data: { + error: { + code: 500, + message: 'Internal Server Error', + }, + }, + }, + }; + expect(toFriendlyError(error)).toBe(error); + }); + + it('should return original error if not a Gaxios error object', () => { + const error = new Error('Regular Error'); + expect(toFriendlyError(error)).toBe(error); + }); +}); diff --git a/packages/core/src/utils/errors.ts b/packages/core/src/utils/errors.ts index a91fafb922..8db1153d92 100644 --- a/packages/core/src/utils/errors.ts +++ b/packages/core/src/utils/errors.ts @@ -92,7 +92,7 @@ export function toFriendlyError(error: unknown): unknown { if (error && typeof error === 'object' && 'response' in error) { const gaxiosError = error as GaxiosError; const data = parseResponseData(gaxiosError); - if (data.error && data.error.message && data.error.code) { + if (data && data.error && data.error.message && data.error.code) { switch (data.error.code) { case 400: return new BadRequestError(data.error.message); @@ -110,12 +110,16 @@ export function toFriendlyError(error: unknown): unknown { return error; } -function parseResponseData(error: GaxiosError): ResponseData { +function parseResponseData(error: GaxiosError): ResponseData | undefined { // Inexplicably, Gaxios sometimes doesn't JSONify the response data. if (typeof error.response?.data === 'string') { - return JSON.parse(error.response?.data) as ResponseData; + try { + return JSON.parse(error.response?.data) as ResponseData; + } catch { + return undefined; + } } - return error.response?.data as ResponseData; + return error.response?.data as ResponseData | undefined; } /**