diff --git a/packages/providers/src/fetch_json.ts b/packages/providers/src/fetch_json.ts index 9701aca52d..1c68594a82 100644 --- a/packages/providers/src/fetch_json.ts +++ b/packages/providers/src/fetch_json.ts @@ -9,7 +9,7 @@ const retryConfig = { numOfAttempts: RETRY_NUMBER, timeMultiple: BACKOFF_MULTIPLIER, retry: (e: ProviderError) => { - if ([503, 408].includes(e.cause)) { + if ([503, 500, 408].includes(e.cause)) { return true; } @@ -26,7 +26,7 @@ export interface ConnectionInfo { headers?: { [key: string]: string | number }; } -class ProviderError extends Error { +export class ProviderError extends Error { cause: number; constructor(message: string, options: any) { super(message, options); @@ -56,14 +56,19 @@ export async function fetchJsonRpc(url: string, json: JsonRpcRequest, headers: o }); const { ok, status } = res; - if (!ok) { - throw new ProviderError(await res.text(), { cause: status }); - } - if (status === 503) { - throw new ProviderError(`${url} unavailable`, { cause: status }); + if (status === 500) { + throw new ProviderError(`Internal server error`, { cause: status }); } else if (status === 408) { - throw new ProviderError('Unused connection', { cause: status }); + throw new ProviderError('Timeout error', { cause: status }); + } else if (status === 400) { + throw new ProviderError('Request validation error', { cause: status }); + } else if (status === 503) { + throw new ProviderError(`${url} unavailable`, { cause: status }); + } + + if (!ok) { + throw new ProviderError(await res.text(), { cause: status }); } return res; diff --git a/packages/providers/test/fetch_json_error.test.ts b/packages/providers/test/fetch_json_error.test.ts new file mode 100644 index 0000000000..1e65564f10 --- /dev/null +++ b/packages/providers/test/fetch_json_error.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, test, jest, afterEach } from '@jest/globals'; +import { fetchJsonRpc } from '../src/fetch_json'; +import unfetch from 'isomorphic-unfetch'; +import { ProviderError } from '../src/fetch_json'; + +jest.mock('isomorphic-unfetch'); + +describe('fetchJsonError', () => { + const RPC_URL = 'https://rpc.testnet.near.org'; + const statusRequest = { + jsonrpc: '2.0', + id: 'dontcare', + method: 'status', + params: [], + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('handles 500 Internal Server Error', async () => { + (unfetch as jest.Mock).mockReturnValue({ + ok: false, + status: 500, + text: '', + json: {}, + }); + + // @ts-expect-error test input + await expect(fetchJsonRpc(RPC_URL, statusRequest, undefined)) + .rejects + .toThrowError(new ProviderError('Internal server error', { cause: 500 })); + }); + test('handles 408 Timeout Error', async () => { + (unfetch as jest.Mock).mockReturnValue({ + ok: false, + status: 408, + text: '', + json: {}, + }); + // @ts-expect-error test input + await expect(fetchJsonRpc(RPC_URL, statusRequest, undefined)) + .rejects + .toThrowError(new ProviderError('Timeout error', { cause: 408 })); + }); + // }); + + test('handles 400 Request Validation Error', async () => { + (unfetch as jest.Mock).mockReturnValue({ + ok: false, + status: 400, + text: '', + json: {}, + }); + // @ts-expect-error test input + await expect(fetchJsonRpc(RPC_URL, statusRequest, undefined)) + .rejects + .toThrowError(new ProviderError('Request validation error', { cause: 400 })); + }); + + test('handles 503 Service Unavailable', async () => { + (unfetch as jest.Mock).mockReturnValue({ + ok: false, + status: 503, + text: '', + json: {}, + }); + + // @ts-expect-error test input + await expect(fetchJsonRpc(RPC_URL, statusRequest, undefined)) + .rejects + .toThrowError(new ProviderError(`${RPC_URL} unavailable`, { cause: 503 })); + }); +});