UNPKG

@seriouslag/httpclient

Version:
349 lines (290 loc) 9.46 kB
import { ExponentialBackoffOptions, ExponentialBackoffRequestStrategy, HttpResponse, } from '../index'; import { Request } from '../Adaptors'; const successfulResponseData: HttpResponse<string> = { data: 'data', status: 200, headers: {}, statusText: 'success', }; const failedResponseData: HttpResponse<undefined> = { data: undefined, status: 400, headers: {}, statusText: 'bad model', }; const tooManyRequestsResponseData: HttpResponse<undefined> = { data: undefined, status: 429, headers: {}, statusText: 'too many requests', }; describe('ExponentialBackoffRequestStrategy', () => { beforeEach(() => { jest.resetModules(); jest.resetAllMocks(); }); it('be defined', () => { expect(new ExponentialBackoffRequestStrategy()).toBeDefined(); }); it('default maxRetryCount', () => { const strategy = new ExponentialBackoffRequestStrategy(); expect((strategy as any).maxRetryCount).toEqual(5); }); it('default delayFirstRequest', () => { const strategy = new ExponentialBackoffRequestStrategy(); expect((strategy as any).delayFirstRequest).toEqual(false); }); it('default baseDelay', () => { const strategy = new ExponentialBackoffRequestStrategy(); expect((strategy as any).baseDelay).toEqual(100); }); it('default factor', () => { const strategy = new ExponentialBackoffRequestStrategy(); expect((strategy as any).factor).toEqual(2); }); it('default maxDelay', () => { const strategy = new ExponentialBackoffRequestStrategy(); expect((strategy as any).maxDelay).toEqual(-1); }); it('accept options', () => { const maxRetryCount = 10; const delayFirstRequest = true; const baseDelay = 10; const factor = 3; const maxDelay = 100; const options: ExponentialBackoffOptions = { maxRetryCount, delayFirstRequest, baseDelay, factor, maxDelay, }; const strategy = new ExponentialBackoffRequestStrategy(options); expect((strategy as any).maxRetryCount).toEqual(maxRetryCount); expect((strategy as any).delayFirstRequest).toEqual(delayFirstRequest); expect((strategy as any).baseDelay).toEqual(baseDelay); expect((strategy as any).factor).toEqual(factor); expect((strategy as any).maxDelay).toEqual(maxDelay); }); it('request once on a success response', async () => { expect.assertions(2); const strategy = new ExponentialBackoffRequestStrategy(); const doFn = jest.fn(() => Promise.resolve(successfulResponseData)); const request: Request<any> = { do: doFn, }; const response = await strategy.request(request); expect(successfulResponseData.data).toEqual(response.data); expect(doFn).toHaveBeenCalledTimes(1); }); it('request until successful, 1 failed, 1 success, 5 max', async () => { expect.assertions(2); const strategy = new ExponentialBackoffRequestStrategy({ maxRetryCount: 5, factor: 0, baseDelay: 0, }); let requestCount = 0; const doFn = jest.fn(() => { if (requestCount === 0) { requestCount += 1; return Promise.resolve(failedResponseData); } requestCount += 1; return Promise.resolve(successfulResponseData); }); const request: Request<any> = { do: doFn, }; const response = await strategy.request(request); expect(response.data).toEqual(successfulResponseData.data); expect(doFn).toHaveBeenCalledTimes(2); }); it('request until maxRetryCount, 10 failed, 0 success, 10 max', async () => { expect.assertions(2); const maxRetryCount = 10; const strategy = new ExponentialBackoffRequestStrategy({ maxRetryCount, factor: 0, baseDelay: 0, }); const doFn = jest.fn(() => { return Promise.resolve(failedResponseData); }); const request: Request<any> = { do: doFn, }; const response = await strategy.request(request); expect(response.data).toEqual(failedResponseData.data); expect(doFn).toHaveBeenCalledTimes(maxRetryCount); }); it('request until hits TOO_MANY_REQUESTS_STATUS, 3 failed, 1 TOO_MANY..., 5 max', async () => { expect.assertions(2); const strategy = new ExponentialBackoffRequestStrategy({ maxRetryCount: 5, factor: 0, baseDelay: 0, }); let requestCount = 0; const doFn = jest.fn(() => { if (requestCount === 3) { requestCount += 1; return Promise.resolve(tooManyRequestsResponseData); } requestCount += 1; return Promise.resolve(failedResponseData); }); const request: Request<any> = { do: doFn, }; const response = await strategy.request(request); expect(response.data).toEqual(tooManyRequestsResponseData.data); expect(doFn).toHaveBeenCalledTimes(4); }); it('request forever if a zero is passed for maxRetryCount, 99 failed, 1 success..., 0 max', async () => { expect.assertions(2); const maxRetryCount = 0; const strategy = new ExponentialBackoffRequestStrategy({ maxRetryCount, factor: 0, baseDelay: 0, }); let requestCount = 0; const doFn = jest.fn(() => { if (requestCount === 99) { requestCount += 1; return Promise.resolve(successfulResponseData); } requestCount += 1; return Promise.resolve(failedResponseData); }); const request: Request<any> = { do: doFn, }; const response = await strategy.request(request); expect(response.data).toEqual(successfulResponseData.data); expect(doFn).toHaveBeenCalledTimes(100); }); it('first request is delayed with baseDelay if delayFirstRequest is passed', async () => { expect.assertions(2); const delayFirstRequest = true; const baseDelay = 1000; const strategy = new ExponentialBackoffRequestStrategy({ delayFirstRequest, baseDelay, }); const doFn = jest.fn(() => Promise.resolve(successfulResponseData)); const request: Request<any> = { do: doFn, }; const then = Date.now(); await strategy.request(request); const now = Date.now(); expect(now).toBeGreaterThan(then); expect(now - then).toBeGreaterThanOrEqual(baseDelay); }); it('request will backoff based on baseDelay and factor, 3 failed, 1 success..., 5 max', async () => { expect.assertions(2); const delayFirstRequest = false; const baseDelay = 100; const factor = 1.25; const maxRetryCount = 5; const strategy = new ExponentialBackoffRequestStrategy({ delayFirstRequest, baseDelay, factor, maxRetryCount, }); let requestCount = 0; const doFn = jest.fn(() => { if (requestCount === 3) { requestCount += 1; return Promise.resolve(successfulResponseData); } requestCount += 1; return Promise.resolve(failedResponseData); }); const request: Request<any> = { do: doFn, }; /** * 1st request: 0s * 2nd request: 100ms - baseDelay 100 * 3rd request: 125ms - baseDelay 100 * factor 1.25 * retryCount 1 * 4th request: 250ms - baseDelay 100 * factor 1.25 * retryCount 2 * total: 475ms */ const then = Date.now(); await strategy.request(request); const now = Date.now(); expect(now).toBeGreaterThan(then); const firstRequestTime = 0; const secondRequestTime = baseDelay; const thirdRequestTime = baseDelay * factor * 1; const forthRequestTime = baseDelay * factor * 2; // buffer is used to variance of tests const bufferTime = 25; const totalTime = firstRequestTime + secondRequestTime + thirdRequestTime + forthRequestTime + bufferTime; expect(now - then).toBeGreaterThanOrEqual(totalTime); }); it('request will backoff based on maxDelay if maxDelay is lower than the retry algorithm, 3 failed, 1 success..., 5 max', async () => { expect.assertions(2); const delayFirstRequest = false; const baseDelay = 100; const factor = 1.25; const maxRetryCount = 5; const maxDelay = 10; const strategy = new ExponentialBackoffRequestStrategy({ delayFirstRequest, baseDelay, factor, maxRetryCount, maxDelay, }); let requestCount = 0; const doFn = jest.fn(() => { if (requestCount === 3) { requestCount += 1; return Promise.resolve(successfulResponseData); } requestCount += 1; return Promise.resolve(failedResponseData); }); const request: Request<any> = { do: doFn, }; /** * 1st request: 0s * 2nd request: 10ms - baseDelay 100 {maxDelay 10} * 3rd request: 10ms - baseDelay 100 * factor 1.25 * retryCount 1 {maxDelay 10} * 4th request: 10ms - baseDelay 100 * factor 1.25 * retryCount 2 {maxDelay 10} * total: ~30ms */ const then = Date.now(); await strategy.request(request); const now = Date.now(); const firstRequestTime = 0; const secondRequestTime = maxDelay; const thirdRequestTime = maxDelay; const forthRequestTime = maxDelay; // buffer is used to variance of tests const bufferTime = 5; const totalTime = firstRequestTime + secondRequestTime + thirdRequestTime + forthRequestTime - bufferTime; expect(now).toBeGreaterThan(then); expect(now - then).toBeGreaterThanOrEqual(totalTime); }); });