diff --git a/backend/src/common/retryer.js b/backend/src/common/retryer.js index bbef173f..68672779 100644 --- a/backend/src/common/retryer.js +++ b/backend/src/common/retryer.js @@ -25,12 +25,14 @@ const retryer = async (fetcher, variables, retries = 0) => { if (!RETRIES) { throw new CustomError("No GitHub API tokens found", CustomError.NO_TOKENS); } + if (retries > RETRIES) { throw new CustomError( "Downtime due to GitHub API rate limiting", CustomError.MAX_RETRY, ); } + try { // try to fetch with the first token since RETRIES is 0 index i'm adding +1 let response = await fetcher( @@ -39,12 +41,16 @@ const retryer = async (fetcher, variables, retries = 0) => { retries, ); - // prettier-ignore - const isRateExceeded = response.data.errors && response.data.errors[0].type === "RATE_LIMITED"; + // react on both type and message-based rate-limit signals. + const errors = response?.data?.errors; + const errorType = errors?.[0]?.type; + const errorMsg = errors?.[0]?.message || ""; + const isRateLimited = + (errors && errorType === "RATE_LIMITED") || /rate limit/i.test(errorMsg); // if rate limit is hit increase the RETRIES and recursively call the retryer // with username, and current RETRIES - if (isRateExceeded) { + if (isRateLimited) { logger.log(`PAT_${retries + 1} Failed due to rate limiting`); retries++; // directly return from the function diff --git a/backend/tests/retryer.test.js b/backend/tests/retryer.test.js index b0b4bd79..76630039 100644 --- a/backend/tests/retryer.test.js +++ b/backend/tests/retryer.test.js @@ -25,6 +25,27 @@ const fetcherFailOnSecondTry = jest.fn((_vars, _token, retries) => { }); }); +const fetcherFailWithMessageBasedRateLimitErr = jest.fn( + (_vars, _token, retries) => { + return new Promise((res) => { + // faking rate limit + if (retries < 1) { + return res({ + data: { + errors: [ + { + type: "ASDF", + message: "API rate limit already exceeded for user ID 11111111", + }, + ], + }, + }); + } + return res({ data: "ok" }); + }); + }, +); + describe("Test Retryer", () => { it("retryer should return value and have zero retries on first try", async () => { let res = await retryer(fetcher, {}); @@ -40,6 +61,13 @@ describe("Test Retryer", () => { expect(res).toStrictEqual({ data: "ok" }); }); + it("retryer should return value and have 2 retries with message based rate limit error", async () => { + let res = await retryer(fetcherFailWithMessageBasedRateLimitErr, {}); + + expect(fetcherFailWithMessageBasedRateLimitErr).toBeCalledTimes(2); + expect(res).toStrictEqual({ data: "ok" }); + }); + it("retryer should throw specific error if maximum retries reached", async () => { try { await retryer(fetcherFail, {});