diff --git a/packages/api/src/router/testSupport/prepareUser.ts b/packages/api/src/router/testSupport/prepareUser.ts index ce7a8b544..05ddc42c3 100644 --- a/packages/api/src/router/testSupport/prepareUser.ts +++ b/packages/api/src/router/testSupport/prepareUser.ts @@ -1,4 +1,5 @@ import { clerkClient } from "@clerk/nextjs/server"; +import { isClerkAPIResponseError } from "@clerk/shared"; import { aiLogger } from "@oakai/logger"; import { waitUntil } from "@vercel/functions"; import os from "os"; @@ -75,24 +76,50 @@ const generateEmailAddress = (personaName: keyof typeof personas) => { return `${parts.join("+")}@thenational.academy`; }; -const deleteLastUsedTestUser = async () => { - const users = await clerkClient.users.getUserList({ - orderBy: "+last_active_at", +const deleteOldTestUser = async () => { + const result = await clerkClient.users.getUserList({ limit: 500, }); const NUMBERS_USER = /\d{5,10}.*@/; // jim+010203@thenational.academy - const lastUsedTestUser = users.data.find((u) => { + const testUsers = result.data.filter((u) => { const email = u.primaryEmailAddress?.emailAddress ?? ""; return email.startsWith("test+") || email.match(NUMBERS_USER); }); - if (lastUsedTestUser) { - log.info( - "Deleting oldest test user", - lastUsedTestUser.primaryEmailAddress?.emailAddress, - ); - await clerkClient.users.deleteUser(lastUsedTestUser.id); + if (testUsers.length < 100) { + log.info(`less than 100 test users. Skipping cleanup.`); + return; + } + + const users = testUsers.sort( + (a, b) => + new Date(a.lastActiveAt ?? a.createdAt).getTime() - + new Date(b.lastActiveAt ?? b.createdAt).getTime(), + ); + + // If multiple personas are created at the same time and both try to delete the + // oldest user they will conflict. Add some randomness to reduce conflicts + const randomOffset = Math.floor(Math.random() * 8); + const userToDelete = users[randomOffset]; + + if (userToDelete) { + try { + await clerkClient.users.deleteUser(userToDelete.id); + log.info( + "Deleted old test user", + userToDelete.primaryEmailAddress?.emailAddress, + ); + } catch (e) { + if (isClerkAPIResponseError(e) && e.status === 404) { + log.info( + `${userToDelete.primaryEmailAddress?.emailAddress} already deleted, retrying`, + ); + deleteOldTestUser(); + } else { + throw e; + } + } } }; @@ -134,7 +161,7 @@ const findOrCreateUser = async ( }, }); - waitUntil(deleteLastUsedTestUser()); + waitUntil(deleteOldTestUser()); return newUser; };