From abec0f1f388e6f45af4b7052b09777e44e1f24ba Mon Sep 17 00:00:00 2001 From: Marco Link Date: Tue, 26 Sep 2023 15:48:14 +0200 Subject: [PATCH] chore: more throtteling tests --- src/rate-limit-throttle.ts | 16 +++++++++++++--- src/rate-limit.ts | 2 +- test/unit/rate-limit-throttle.spec.ts | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/rate-limit-throttle.ts b/src/rate-limit-throttle.ts index d7e14f1..11f76c5 100644 --- a/src/rate-limit-throttle.ts +++ b/src/rate-limit-throttle.ts @@ -8,7 +8,16 @@ type ThrottleType = 'auto' | string const PERCENTAGE_REGEX = /(?\d+)(%)/ -function calculateLimit(type: ThrottleType, max = 7) { +const HEADERS = { + // @desc The maximum amount of requests which can be made in a second. + RATE_LIMIT: 'x-contentful-ratelimit-second-limit', + // @desc The number of seconds until the next request can be made. + RATE_LIMIT_RESET: 'x-contentful-ratelimit-second-reset', + // @desc The remaining amount of requests which can be made until the next secondly reset. + RATE_LIMIT_REMAINING: 'x-contentful-ratelimit-second-remaining', +} as const + +export function calculateLimit(type: ThrottleType, max = 7) { let limit = max if (PERCENTAGE_REGEX.test(type)) { @@ -47,14 +56,15 @@ export default (axiosInstance: AxiosInstance, type: ThrottleType | number = 'aut const responseInterceptorId = axiosInstance.interceptors.response.use( (response) => { + // If we haven't yet calculated the limit based on the headers, do so now if ( !isCalculated && isString(type) && (type === 'auto' || PERCENTAGE_REGEX.test(type)) && response.headers && - response.headers['x-contentful-ratelimit-second-limit'] + response.headers[HEADERS.RATE_LIMIT] ) { - const rawLimit = parseInt(response.headers['x-contentful-ratelimit-second-limit']) + const rawLimit = parseInt(response.headers[HEADERS.RATE_LIMIT]) const nextLimit = calculateLimit(type, rawLimit) if (nextLimit !== limit) { diff --git a/src/rate-limit.ts b/src/rate-limit.ts index 907020a..37b08cf 100644 --- a/src/rate-limit.ts +++ b/src/rate-limit.ts @@ -58,7 +58,7 @@ export default function rateLimit(instance: AxiosInstance, maxRetry = 5): void { } else if (response.status === 429) { // 429 errors are exceeded rate limit exceptions retryErrorType = 'Rate limit' - // all headers are lowercased by axios https://github.com/mzabriskie/axios/issues/413 + // all headers are lower-cased by axios https://github.com/mzabriskie/axios/issues/413 if (response.headers && error.response.headers['x-contentful-ratelimit-reset']) { wait = response.headers['x-contentful-ratelimit-reset'] } diff --git a/test/unit/rate-limit-throttle.spec.ts b/test/unit/rate-limit-throttle.spec.ts index 5f8be74..64fce53 100644 --- a/test/unit/rate-limit-throttle.spec.ts +++ b/test/unit/rate-limit-throttle.spec.ts @@ -4,6 +4,7 @@ import axios from 'axios' import MockAdapter from 'axios-mock-adapter' import { AxiosInstance } from '../../src' import createHttpClient from '../../src/create-http-client' +import { calculateLimit } from '../../src/rate-limit-throttle' const logHandlerStub = vi.fn() @@ -141,3 +142,19 @@ describe('throttle to rate limit axios interceptor', () => { }, ) }) + +describe('a calculate limit function', () => { + describe('with type "auto"', () => { + it('always returns the given max limit', () => { + expect(calculateLimit('auto', 10)).toEqual(10) + expect(calculateLimit('auto', 1)).toEqual(1) + }) + }) + describe('with %', () => { + it('always returns % of max limit', () => { + expect(calculateLimit('0%', 10)).toEqual(1) + expect(calculateLimit('50%', 10)).toEqual(5) + expect(calculateLimit('100%', 10)).toEqual(10) + }) + }) +})