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:
ckohen
2025-09-10 02:23:28 -07:00
committed by GitHub
parent 5d5a6945e4
commit f1bcff46b6
8 changed files with 263 additions and 6 deletions

View File

@@ -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);
});
});
});