mirror of https://github.com/renovatebot/renovate
163 lines
4.2 KiB
TypeScript
163 lines
4.2 KiB
TypeScript
import { RequestError } from 'got';
|
|
import { getRetryAfter, wrapWithRetry } from './retry-after';
|
|
|
|
function requestError(
|
|
response: {
|
|
statusCode?: number;
|
|
headers?: Record<string, string | string[]>;
|
|
} | null = null,
|
|
) {
|
|
const err = new RequestError('request error', {}, null as never);
|
|
if (response) {
|
|
(err as any).response = response;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
describe('util/http/retry-after', () => {
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
describe('wrapWithRetry', () => {
|
|
it('works', async () => {
|
|
const task = jest.fn(() => Promise.resolve(42));
|
|
const res = await wrapWithRetry(task, 'foobar', () => null, 60);
|
|
expect(res).toBe(42);
|
|
expect(task).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('throws', async () => {
|
|
const task = jest.fn(() => Promise.reject(new Error('error')));
|
|
|
|
await expect(
|
|
wrapWithRetry(task, 'http://example.com', () => null, 60),
|
|
).rejects.toThrow('error');
|
|
|
|
expect(task).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('retries', async () => {
|
|
const task = jest
|
|
.fn()
|
|
.mockRejectedValueOnce(new Error('error-1'))
|
|
.mockRejectedValueOnce(new Error('error-2'))
|
|
.mockResolvedValueOnce(42);
|
|
|
|
const p = wrapWithRetry(task, 'http://example.com', () => 1, 60);
|
|
await jest.advanceTimersByTimeAsync(2000);
|
|
|
|
const res = await p;
|
|
expect(res).toBe(42);
|
|
expect(task).toHaveBeenCalledTimes(3);
|
|
});
|
|
|
|
it('gives up after max retries', async () => {
|
|
const task = jest
|
|
.fn()
|
|
.mockRejectedValueOnce('error-1')
|
|
.mockRejectedValueOnce('error-2')
|
|
.mockRejectedValueOnce('error-3')
|
|
.mockRejectedValue('error-4');
|
|
|
|
const p = wrapWithRetry(task, 'http://example.com', () => 1, 60).catch(
|
|
(err) => err,
|
|
);
|
|
await jest.advanceTimersByTimeAsync(2000);
|
|
|
|
await expect(p).resolves.toBe('error-3');
|
|
expect(task).toHaveBeenCalledTimes(3);
|
|
});
|
|
|
|
it('gives up when delay exceeds maxRetryAfter', async () => {
|
|
const task = jest.fn().mockRejectedValue('error');
|
|
|
|
const p = wrapWithRetry(task, 'http://example.com', () => 61, 60).catch(
|
|
(err) => err,
|
|
);
|
|
|
|
await expect(p).resolves.toBe('error');
|
|
expect(task).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('getRetryAfter', () => {
|
|
it('returns null for non-RequestError', () => {
|
|
expect(getRetryAfter(new Error())).toBeNull();
|
|
});
|
|
|
|
it('returns null for RequestError without response', () => {
|
|
expect(getRetryAfter(requestError())).toBeNull();
|
|
});
|
|
|
|
it('returns null for status other than 429', () => {
|
|
const err = new RequestError('request-error', {}, null as never);
|
|
(err as any).response = { statusCode: 302 };
|
|
expect(getRetryAfter(requestError({ statusCode: 302 }))).toBeNull();
|
|
});
|
|
|
|
it('returns null missing "retry-after" header', () => {
|
|
expect(
|
|
getRetryAfter(requestError({ statusCode: 429, headers: {} })),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('returns null for non-integer "retry-after" header', () => {
|
|
expect(
|
|
getRetryAfter(
|
|
requestError({
|
|
statusCode: 429,
|
|
headers: {
|
|
'retry-after': 'Wed, 21 Oct 2015 07:28:00 GMT',
|
|
},
|
|
}),
|
|
),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('returns delay in seconds from date', () => {
|
|
jest.setSystemTime(new Date('2020-01-01T00:00:00Z'));
|
|
expect(
|
|
getRetryAfter(
|
|
requestError({
|
|
statusCode: 429,
|
|
headers: {
|
|
'retry-after': 'Wed, 01 Jan 2020 00:00:42 GMT',
|
|
},
|
|
}),
|
|
),
|
|
).toBe(42);
|
|
});
|
|
|
|
it('returns delay in seconds from number', () => {
|
|
expect(
|
|
getRetryAfter(
|
|
requestError({
|
|
statusCode: 429,
|
|
headers: {
|
|
'retry-after': '42',
|
|
},
|
|
}),
|
|
),
|
|
).toBe(42);
|
|
});
|
|
|
|
it('returns null for invalid header value', () => {
|
|
expect(
|
|
getRetryAfter(
|
|
requestError({
|
|
statusCode: 429,
|
|
headers: {
|
|
'retry-after': 'invalid',
|
|
},
|
|
}),
|
|
),
|
|
).toBeNull();
|
|
});
|
|
});
|
|
});
|