mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-16 19:43:29 +01:00
feat(rest): callbacks for timeout and retry backoff (#11067)
* feat(rest): callbacks for timeout and retry backoff * test: add tests for callback utils * test: fix typo Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com> * fix(retryBackoff): efficient math * docs: minor tweaks * docs: captalisation --------- Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com> Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
This commit is contained in:
@@ -16,6 +16,7 @@ const api = new REST();
|
||||
|
||||
let mockAgent: MockAgent;
|
||||
let mockPool: Interceptable;
|
||||
let serverOutage = true;
|
||||
|
||||
beforeEach(() => {
|
||||
mockAgent = new MockAgent();
|
||||
@@ -138,3 +139,57 @@ test('Handle unexpected 429', async () => {
|
||||
expect(await unexpectedLimit).toStrictEqual({ test: true });
|
||||
expect(performance.now()).toBeGreaterThanOrEqual(previous + 1_000);
|
||||
});
|
||||
|
||||
test('server responding too slow', async () => {
|
||||
const api2 = new REST({ timeout: 1 }).setToken('A-Very-Really-Real-Token');
|
||||
|
||||
api2.setAgent(mockAgent);
|
||||
|
||||
mockPool
|
||||
.intercept({
|
||||
path: callbackPath,
|
||||
method: 'POST',
|
||||
})
|
||||
.reply(200, '')
|
||||
.delay(100)
|
||||
.times(10);
|
||||
|
||||
const promise = api2.post('/interactions/1234567890123456789/totallyarealtoken/callback', {
|
||||
auth: false,
|
||||
body: { type: 4, data: { content: 'Reply' } },
|
||||
});
|
||||
|
||||
await expect(promise).rejects.toThrowError('aborted');
|
||||
}, 1_000);
|
||||
|
||||
test('Handle temp server outage', async () => {
|
||||
mockPool
|
||||
.intercept({
|
||||
path: callbackPath,
|
||||
method: 'POST',
|
||||
})
|
||||
.reply(() => {
|
||||
if (serverOutage) {
|
||||
serverOutage = false;
|
||||
|
||||
return {
|
||||
statusCode: 500,
|
||||
data: '',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
data: { test: true },
|
||||
responseOptions,
|
||||
};
|
||||
})
|
||||
.times(2);
|
||||
|
||||
expect(
|
||||
await api.post('/interactions/1234567890123456789/totallyarealtoken/callback', {
|
||||
auth: false,
|
||||
body: { type: 4, data: { content: 'Reply' } },
|
||||
}),
|
||||
).toStrictEqual({ test: true });
|
||||
});
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/* eslint-disable unicorn/consistent-function-scoping */
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import type { GetRateLimitOffsetFunction, GetRetryBackoffFunction, GetTimeoutFunction } from '../src/index.js';
|
||||
import { makeURLSearchParams } from '../src/index.js';
|
||||
import { normalizeRateLimitOffset, normalizeRetryBackoff, normalizeTimeout } from '../src/lib/utils/utils.js';
|
||||
|
||||
describe('makeURLSearchParams', () => {
|
||||
test('GIVEN undefined THEN returns empty URLSearchParams', () => {
|
||||
@@ -86,3 +89,94 @@ describe('makeURLSearchParams', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('option normalization functions', () => {
|
||||
describe('rate limit offset', () => {
|
||||
const func: GetRateLimitOffsetFunction = (route) => {
|
||||
if (route === '/negative') return -150;
|
||||
if (route === '/high') return 150;
|
||||
return 50;
|
||||
};
|
||||
|
||||
test('offset as number', () => {
|
||||
expect(normalizeRateLimitOffset(-150, '/negative')).toEqual(0);
|
||||
expect(normalizeRateLimitOffset(150, '/high')).toEqual(150);
|
||||
expect(normalizeRateLimitOffset(50, '/normal')).toEqual(50);
|
||||
});
|
||||
|
||||
test('offset as function', () => {
|
||||
expect(normalizeRateLimitOffset(func, '/negative')).toEqual(0);
|
||||
expect(normalizeRateLimitOffset(func, '/high')).toEqual(150);
|
||||
expect(normalizeRateLimitOffset(func, '/normal')).toEqual(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('retry backoff', () => {
|
||||
const body = {
|
||||
content: 'yo',
|
||||
};
|
||||
const func: GetRetryBackoffFunction = (_route, statusCode, retryCount) => {
|
||||
if (statusCode === null) return 0;
|
||||
if (statusCode === 502) return 50;
|
||||
if (retryCount === 0) return 0;
|
||||
if (retryCount === 1) return 150;
|
||||
if (retryCount === 2) return 500;
|
||||
return null;
|
||||
};
|
||||
|
||||
test('retry backoff as number', () => {
|
||||
expect(normalizeRetryBackoff(0, '/test', null, 0, body)).toEqual(0);
|
||||
expect(normalizeRetryBackoff(0, '/test', null, 1, body)).toEqual(0);
|
||||
expect(normalizeRetryBackoff(0, '/test', null, 2, body)).toEqual(0);
|
||||
expect(normalizeRetryBackoff(50, '/test', null, 0, body)).toEqual(50);
|
||||
expect(normalizeRetryBackoff(50, '/test', null, 1, body)).toEqual(100);
|
||||
expect(normalizeRetryBackoff(50, '/test', null, 2, body)).toEqual(200);
|
||||
});
|
||||
|
||||
test('retry backoff as function', () => {
|
||||
expect(normalizeRetryBackoff(func, '/test', null, 0, body)).toEqual(0);
|
||||
expect(normalizeRetryBackoff(func, '/test', 502, 0, body)).toEqual(50);
|
||||
expect(normalizeRetryBackoff(func, '/test', 500, 0, body)).toEqual(0);
|
||||
expect(normalizeRetryBackoff(func, '/test', 500, 1, body)).toEqual(150);
|
||||
expect(normalizeRetryBackoff(func, '/test', 500, 2, body)).toEqual(500);
|
||||
expect(normalizeRetryBackoff(func, '/test', 500, 3, body)).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('timeout', () => {
|
||||
const body1 = {
|
||||
attachments: [{ id: 1 }],
|
||||
};
|
||||
const body2 = {
|
||||
content: 'yo',
|
||||
};
|
||||
const func: GetTimeoutFunction = (route, body) => {
|
||||
if (
|
||||
typeof body === 'object' &&
|
||||
body &&
|
||||
'attachments' in body &&
|
||||
Array.isArray(body.attachments) &&
|
||||
body.attachments.length
|
||||
) {
|
||||
return 1_000;
|
||||
}
|
||||
|
||||
if (route === '/negative') return -150;
|
||||
if (route === '/high') return 150;
|
||||
return 50;
|
||||
};
|
||||
|
||||
test('timeout as number', () => {
|
||||
expect(normalizeTimeout(-150, '/negative', body1)).toEqual(0);
|
||||
expect(normalizeTimeout(150, '/high', body1)).toEqual(150);
|
||||
expect(normalizeTimeout(50, '/normal', body1)).toEqual(50);
|
||||
});
|
||||
|
||||
test('timeout as function', () => {
|
||||
expect(normalizeTimeout(func, '/negative', body1)).toEqual(1_000);
|
||||
expect(normalizeTimeout(func, '/negative', body2)).toEqual(0);
|
||||
expect(normalizeTimeout(func, '/high', body2)).toEqual(150);
|
||||
expect(normalizeTimeout(func, '/normal', body2)).toEqual(50);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user