From 65b0620684e541196550ba37e6857265ce028da7 Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 19 Mar 2026 22:54:46 -0500 Subject: [PATCH] fix(testing): add retry logic for testing token fetch on 429/5xx --- packages/testing/src/common/setup.ts | 32 +++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/testing/src/common/setup.ts b/packages/testing/src/common/setup.ts index 2b2fe211903..a9575ea6820 100644 --- a/packages/testing/src/common/setup.ts +++ b/packages/testing/src/common/setup.ts @@ -1,9 +1,36 @@ import { createClerkClient } from '@clerk/backend'; +import { isClerkAPIResponseError } from '@clerk/shared/error'; import { parsePublishableKey } from '@clerk/shared/keys'; import dotenv from 'dotenv'; import type { ClerkSetupOptions, ClerkSetupReturn } from './types'; +const MAX_RETRIES = 5; +const BASE_DELAY_MS = 1000; +const JITTER_MAX_MS = 500; +const MAX_RETRY_DELAY_MS = 30_000; +const RETRYABLE_STATUS_CODES = new Set([429, 502, 503, 504]); + +async function fetchWithRetry(fn: () => Promise, label: string): Promise { + for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) { + try { + return await fn(); + } catch (error) { + const isRetryable = isClerkAPIResponseError(error) && RETRYABLE_STATUS_CODES.has(error.status); + if (!isRetryable || attempt === MAX_RETRIES) { + throw error; + } + const delay = + isClerkAPIResponseError(error) && typeof error.retryAfter === 'number' + ? Math.min(error.retryAfter * 1000, MAX_RETRY_DELAY_MS) + : BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * JITTER_MAX_MS; + console.warn(`[Retry] ${error.status} for ${label}, attempt ${attempt + 1}/${MAX_RETRIES}, waiting ${Math.round(delay)}ms`); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + throw new Error('Unreachable'); +} + export const fetchEnvVars = async (options?: ClerkSetupOptions): Promise => { const { debug = false, dotenv: loadDotEnv = true, ...rest } = options || {}; @@ -44,7 +71,10 @@ export const fetchEnvVars = async (options?: ClerkSetupOptions): Promise clerkClient.testingTokens.createTestingToken(), + 'testingTokens.createTestingToken', + ); testingToken = tokenData.token; } catch (err) { console.error('Failed to fetch testing token from Clerk API.');