@seriouslag/httpclient
Version:
Typed wrapper HttpClient for axios
349 lines (290 loc) • 9.46 kB
text/typescript
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);
});
});