get-users-paginated.test.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. import { randomUUID } from 'crypto'
  2. import { afterAll, expect, test } from 'vitest'
  3. import { getPaginatedUsersSQL } from '../../../src/sql/studio/auth/get-users-paginated'
  4. import { cleanupRoot, createDatabaseWithAuthSchema, createTestDatabase } from '../../db/utils'
  5. afterAll(async () => {
  6. await cleanupRoot()
  7. })
  8. const withTestDatabase = (
  9. name: string,
  10. fn: (db: Awaited<ReturnType<typeof createTestDatabase>>) => Promise<void>
  11. ) => {
  12. test(name, async () => {
  13. const db = await createTestDatabase()
  14. // Initialize minimal auth schema needed for user pagination tests
  15. await createDatabaseWithAuthSchema(db, { includeIdentities: true })
  16. try {
  17. await fn(db)
  18. } finally {
  19. await db.cleanup()
  20. }
  21. })
  22. }
  23. withTestDatabase('returns paginated users with default settings', async ({ executeQuery }) => {
  24. // Insert 10 users with different created_at times
  25. for (let i = 0; i < 10; i++) {
  26. await executeQuery(`
  27. INSERT INTO auth.users (id, email, instance_id, created_at)
  28. VALUES (
  29. '${randomUUID()}',
  30. 'user${i}@supabase.io',
  31. '00000000-0000-0000-0000-000000000000',
  32. NOW() + INTERVAL '${i} minutes'
  33. )
  34. `)
  35. }
  36. // Get first page (default limit 50, page 0)
  37. const sql = getPaginatedUsersSQL({ sort: 'created_at', order: 'desc' })
  38. const result = await executeQuery<Array<{ email: string }>>(sql)
  39. expect(result.length).toBe(10)
  40. // Should be ordered by created_at desc, so user9 should be first
  41. expect(result[0].email).toBe('user9@supabase.io')
  42. expect(result[9].email).toBe('user0@supabase.io')
  43. })
  44. withTestDatabase('respects custom limit and pagination', async ({ executeQuery }) => {
  45. // Insert 30 users
  46. for (let i = 0; i < 30; i++) {
  47. await executeQuery(`
  48. INSERT INTO auth.users (id, email, instance_id, created_at)
  49. VALUES (
  50. '${randomUUID()}',
  51. 'user${String(i).padStart(2, '0')}@supabase.io',
  52. '00000000-0000-0000-0000-000000000000',
  53. NOW() + INTERVAL '${i} minutes'
  54. )
  55. `)
  56. }
  57. // Get page 1 with limit 10 (should skip first 10, get next 10)
  58. const sql = getPaginatedUsersSQL({ sort: 'created_at', order: 'desc', limit: 10, page: 1 })
  59. const result = await executeQuery<Array<{ email: string }>>(sql)
  60. expect(result.length).toBe(10)
  61. // Page 1 should start at user19 (0-indexed, skipping first 10)
  62. expect(result[0].email).toBe('user19@supabase.io')
  63. expect(result[9].email).toBe('user10@supabase.io')
  64. })
  65. withTestDatabase('sorts by email in ascending order', async ({ executeQuery }) => {
  66. await executeQuery(`
  67. INSERT INTO auth.users (id, email, instance_id, created_at)
  68. VALUES
  69. ('${randomUUID()}', 'charlie@supabase.io', '00000000-0000-0000-0000-000000000000', NOW()),
  70. ('${randomUUID()}', 'alice@supabase.io', '00000000-0000-0000-0000-000000000000', NOW()),
  71. ('${randomUUID()}', 'bob@supabase.io', '00000000-0000-0000-0000-000000000000', NOW())
  72. `)
  73. const sql = getPaginatedUsersSQL({ sort: 'email', order: 'asc', limit: 10 })
  74. const result = await executeQuery<Array<{ email: string }>>(sql)
  75. expect(result.length).toBe(3)
  76. expect(result[0].email).toBe('alice@supabase.io')
  77. expect(result[1].email).toBe('bob@supabase.io')
  78. expect(result[2].email).toBe('charlie@supabase.io')
  79. })
  80. withTestDatabase('filters by keywords across multiple fields', async ({ executeQuery }) => {
  81. const searchUserId = randomUUID()
  82. await executeQuery(`
  83. INSERT INTO auth.users (id, email, phone, instance_id, raw_user_meta_data, created_at)
  84. VALUES
  85. ('${searchUserId}', 'john.doe@supabase.io', NULL, '00000000-0000-0000-0000-000000000000', '{"full_name": "John Doe"}', NOW()),
  86. ('${randomUUID()}', 'jane.smith@supabase.io', '+1234567890', '00000000-0000-0000-0000-000000000000', '{"full_name": "Jane Smith"}', NOW()),
  87. ('${randomUUID()}', 'bob.jones@supabase.io', '+9876543210', '00000000-0000-0000-0000-000000000000', '{"full_name": "Bob Jones"}', NOW())
  88. `)
  89. // Search by keyword "john" - should find john.doe by email
  90. const emailSql = getPaginatedUsersSQL({ sort: 'created_at', order: 'desc', keywords: 'john' })
  91. const emailResult = await executeQuery<Array<{ email: string }>>(emailSql)
  92. expect(emailResult.length).toBe(1)
  93. expect(emailResult[0].email).toBe('john.doe@supabase.io')
  94. // Search by phone
  95. const phoneSql = getPaginatedUsersSQL({ sort: 'created_at', order: 'desc', keywords: '1234' })
  96. const phoneResult = await executeQuery<Array<{ phone: string }>>(phoneSql)
  97. expect(phoneResult.length).toBe(1)
  98. expect(phoneResult[0].phone).toBe('+1234567890')
  99. // Search by ID prefix
  100. const idPrefix = searchUserId.substring(0, 8)
  101. const idSql = getPaginatedUsersSQL({ sort: 'created_at', order: 'desc', keywords: idPrefix })
  102. const idResult = await executeQuery<Array<{ email: string }>>(idSql)
  103. expect(idResult.length).toBe(1)
  104. expect(idResult[0].email).toBe('john.doe@supabase.io')
  105. })
  106. withTestDatabase('filters verified and unverified users', async ({ executeQuery }) => {
  107. const now = new Date().toISOString()
  108. await executeQuery(`
  109. INSERT INTO auth.users (id, email, instance_id, email_confirmed_at, created_at)
  110. VALUES
  111. ('${randomUUID()}', 'verified1@supabase.io', '00000000-0000-0000-0000-000000000000', '${now}', NOW()),
  112. ('${randomUUID()}', 'verified2@supabase.io', '00000000-0000-0000-0000-000000000000', '${now}', NOW()),
  113. ('${randomUUID()}', 'unverified@supabase.io', '00000000-0000-0000-0000-000000000000', NULL, NOW())
  114. `)
  115. // Filter verified users
  116. const verifiedSql = getPaginatedUsersSQL({
  117. sort: 'created_at',
  118. order: 'desc',
  119. verified: 'verified',
  120. })
  121. const verifiedResult = await executeQuery<Array<{ email: string }>>(verifiedSql)
  122. expect(verifiedResult.length).toBe(2)
  123. // Filter unverified users
  124. const unverifiedSql = getPaginatedUsersSQL({
  125. sort: 'created_at',
  126. order: 'desc',
  127. verified: 'unverified',
  128. })
  129. const unverifiedResult = await executeQuery<Array<{ email: string }>>(unverifiedSql)
  130. expect(unverifiedResult.length).toBe(1)
  131. expect(unverifiedResult[0].email).toBe('unverified@supabase.io')
  132. })
  133. withTestDatabase('filters anonymous users', async ({ executeQuery }) => {
  134. await executeQuery(`
  135. INSERT INTO auth.users (id, email, instance_id, is_anonymous, created_at)
  136. VALUES
  137. ('${randomUUID()}', 'anon1@supabase.io', '00000000-0000-0000-0000-000000000000', true, NOW()),
  138. ('${randomUUID()}', 'anon2@supabase.io', '00000000-0000-0000-0000-000000000000', true, NOW()),
  139. ('${randomUUID()}', 'regular@supabase.io', '00000000-0000-0000-0000-000000000000', false, NOW())
  140. `)
  141. const sql = getPaginatedUsersSQL({ sort: 'created_at', order: 'desc', verified: 'anonymous' })
  142. const result = await executeQuery<Array<{ email: string }>>(sql)
  143. expect(result.length).toBe(2)
  144. })
  145. withTestDatabase('filters by providers', async ({ executeQuery }) => {
  146. const user1 = randomUUID()
  147. const user2 = randomUUID()
  148. const user3 = randomUUID()
  149. await executeQuery(`
  150. INSERT INTO auth.users (id, email, instance_id, raw_app_meta_data, created_at)
  151. VALUES
  152. ('${user1}', 'google1@supabase.io', '00000000-0000-0000-0000-000000000000', '{"providers": ["google"]}', NOW()),
  153. ('${user2}', 'google2@supabase.io', '00000000-0000-0000-0000-000000000000', '{"providers": ["google"]}', NOW()),
  154. ('${user3}', 'github@supabase.io', '00000000-0000-0000-0000-000000000000', '{"providers": ["github"]}', NOW())
  155. `)
  156. // Also insert identities for the users
  157. await executeQuery(`
  158. INSERT INTO auth.identities (id, user_id, provider, identity_data, created_at)
  159. VALUES
  160. ('google1', '${user1}', 'google', '{}', NOW()),
  161. ('google2', '${user2}', 'google', '{}', NOW()),
  162. ('github1', '${user3}', 'github', '{}', NOW())
  163. `)
  164. // Filter by google provider
  165. const googleSql = getPaginatedUsersSQL({
  166. sort: 'created_at',
  167. order: 'desc',
  168. providers: ['google'],
  169. })
  170. const googleResult = await executeQuery<Array<{ email: string }>>(googleSql)
  171. expect(googleResult.length).toBe(2)
  172. // Filter by multiple providers
  173. const multipleSql = getPaginatedUsersSQL({
  174. sort: 'created_at',
  175. order: 'desc',
  176. providers: ['google', 'github'],
  177. })
  178. const multipleResult = await executeQuery<Array<{ email: string }>>(multipleSql)
  179. expect(multipleResult.length).toBe(3)
  180. })
  181. withTestDatabase('combines multiple filters', async ({ executeQuery }) => {
  182. const now = new Date().toISOString()
  183. await executeQuery(`
  184. INSERT INTO auth.users (id, email, instance_id, email_confirmed_at, raw_app_meta_data, created_at)
  185. VALUES
  186. ('${randomUUID()}', 'verified.google@supabase.io', '00000000-0000-0000-0000-000000000000', '${now}', '{"providers": ["google"]}', NOW()),
  187. ('${randomUUID()}', 'verified.github@supabase.io', '00000000-0000-0000-0000-000000000000', '${now}', '{"providers": ["github"]}', NOW()),
  188. ('${randomUUID()}', 'unverified.google@supabase.io', '00000000-0000-0000-0000-000000000000', NULL, '{"providers": ["google"]}', NOW())
  189. `)
  190. // Combine verified filter with provider and keyword
  191. const sql = getPaginatedUsersSQL({
  192. sort: 'created_at',
  193. order: 'desc',
  194. keywords: 'verified',
  195. verified: 'verified',
  196. providers: ['google'],
  197. })
  198. const result = await executeQuery<Array<{ email: string }>>(sql)
  199. expect(result.length).toBe(1)
  200. expect(result[0].email).toBe('verified.google@supabase.io')
  201. })
  202. withTestDatabase('optimized email search works correctly', async ({ executeQuery }) => {
  203. await executeQuery(`
  204. INSERT INTO auth.users (id, email, instance_id, created_at)
  205. VALUES
  206. ('${randomUUID()}', 'alice@supabase.io', '00000000-0000-0000-0000-000000000000', NOW()),
  207. ('${randomUUID()}', 'alicia@supabase.io', '00000000-0000-0000-0000-000000000000', NOW()),
  208. ('${randomUUID()}', 'bob@supabase.io', '00000000-0000-0000-0000-000000000000', NOW())
  209. `)
  210. // Optimized search by email prefix
  211. const sql = getPaginatedUsersSQL({
  212. sort: 'created_at',
  213. order: 'desc',
  214. column: 'email',
  215. keywords: 'ali',
  216. limit: 10,
  217. })
  218. const result = await executeQuery<Array<{ email: string }>>(sql)
  219. expect(result.length).toBe(2)
  220. expect(result[0].email).toBe('alice@supabase.io')
  221. expect(result[1].email).toBe('alicia@supabase.io')
  222. })
  223. withTestDatabase('optimized phone search works correctly', async ({ executeQuery }) => {
  224. await executeQuery(`
  225. INSERT INTO auth.users (id, phone, instance_id, created_at)
  226. VALUES
  227. ('${randomUUID()}', '+1234567890', '00000000-0000-0000-0000-000000000000', NOW()),
  228. ('${randomUUID()}', '+1234999999', '00000000-0000-0000-0000-000000000000', NOW()),
  229. ('${randomUUID()}', '+9876543210', '00000000-0000-0000-0000-000000000000', NOW())
  230. `)
  231. // Optimized search by phone prefix
  232. const sql = getPaginatedUsersSQL({
  233. sort: 'created_at',
  234. order: 'desc',
  235. column: 'phone',
  236. keywords: '+1234',
  237. limit: 10,
  238. })
  239. const result = await executeQuery<Array<{ phone: string }>>(sql)
  240. expect(result.length).toBe(2)
  241. })
  242. withTestDatabase('optimized id search works correctly', async ({ executeQuery }) => {
  243. const userId1 = 'abc12300-0000-4000-8000-000000000001'
  244. const userId2 = 'abc12300-0000-4000-8000-000000000002'
  245. const userId3 = 'def45600-0000-4000-8000-000000000003'
  246. await executeQuery(`
  247. INSERT INTO auth.users (id, email, instance_id, created_at)
  248. VALUES
  249. ('${userId1}', 'user1@supabase.io', '00000000-0000-0000-0000-000000000000', NOW()),
  250. ('${userId2}', 'user2@supabase.io', '00000000-0000-0000-0000-000000000000', NOW()),
  251. ('${userId3}', 'user3@supabase.io', '00000000-0000-0000-0000-000000000000', NOW())
  252. `)
  253. // Optimized search by id prefix
  254. const sql = getPaginatedUsersSQL({
  255. sort: 'created_at',
  256. order: 'desc',
  257. column: 'id',
  258. keywords: 'abc123',
  259. limit: 10,
  260. })
  261. const result = await executeQuery<Array<{ id: string }>>(sql)
  262. expect(result.length).toBe(2)
  263. expect(result[0].id).toBe(userId1)
  264. expect(result[1].id).toBe(userId2)
  265. // Exact UUID match
  266. const exactSql = getPaginatedUsersSQL({
  267. sort: 'created_at',
  268. order: 'desc',
  269. column: 'id',
  270. keywords: userId1,
  271. limit: 10,
  272. })
  273. const exactResult = await executeQuery<Array<{ id: string }>>(exactSql)
  274. expect(exactResult.length).toBe(1)
  275. expect(exactResult[0].id).toBe(userId1)
  276. })
  277. withTestDatabase('includes provider information from identities', async ({ executeQuery }) => {
  278. const userId = randomUUID()
  279. await executeQuery(`
  280. INSERT INTO auth.users (id, email, instance_id, created_at)
  281. VALUES ('${userId}', 'user@supabase.io', '00000000-0000-0000-0000-000000000000', NOW())
  282. `)
  283. // Insert multiple identities for the user
  284. await executeQuery(`
  285. INSERT INTO auth.identities (id, user_id, provider, identity_data, created_at)
  286. VALUES
  287. ('google1', '${userId}', 'google', '{}', NOW()),
  288. ('github1', '${userId}', 'github', '{}', NOW())
  289. `)
  290. const sql = getPaginatedUsersSQL({ sort: 'created_at', order: 'desc', limit: 10 })
  291. const result = await executeQuery<Array<{ email: string; providers: string[] }>>(sql)
  292. expect(result.length).toBe(1)
  293. expect(result[0].email).toBe('user@supabase.io')
  294. expect(result[0].providers).toHaveLength(2)
  295. expect(result[0].providers).toContain('google')
  296. expect(result[0].providers).toContain('github')
  297. })