utils.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import { randomUUID } from 'crypto'
  2. import pg, { Pool } from 'pg'
  3. import { parse as parseArray } from 'postgres-array'
  4. // Those types override are in sync with `postgres-meta` since the queries
  5. // will get executed via `execQuery` on a pg connection with the same configuration
  6. // see: https://github.com/supabase/postgres-meta/blob/ca06061b4708971628134f95e49f254c2dfdfa7d/src/lib/db.ts#L6-L23
  7. pg.types.setTypeParser(pg.types.builtins.INT8, (x) => {
  8. const asNumber = Number(x)
  9. if (Number.isSafeInteger(asNumber)) {
  10. return asNumber
  11. } else {
  12. return x
  13. }
  14. })
  15. pg.types.setTypeParser(pg.types.builtins.DATE, (x) => x)
  16. pg.types.setTypeParser(pg.types.builtins.INTERVAL, (x) => x)
  17. pg.types.setTypeParser(pg.types.builtins.TIMESTAMP, (x) => x)
  18. pg.types.setTypeParser(pg.types.builtins.TIMESTAMPTZ, (x) => x)
  19. pg.types.setTypeParser(1115, parseArray) // _timestamp
  20. pg.types.setTypeParser(1182, parseArray) // _date
  21. pg.types.setTypeParser(1185, parseArray) // _timestamptz
  22. pg.types.setTypeParser(600, (x) => x) // point
  23. pg.types.setTypeParser(1017, (x) => x) // _point
  24. const ROOT_DB_URL = process.env.DATABASE_URL ?? 'postgresql://postgres:postgres@localhost:5432'
  25. const ROOT_DB_NAME = process.env.DATABASE_NAME ?? 'postgres'
  26. // Replace postgres.js root connection with pg connection
  27. const rootPool = new Pool({
  28. connectionString: `${ROOT_DB_URL}/${ROOT_DB_NAME}`,
  29. max: 1,
  30. })
  31. export async function createTestDatabase() {
  32. const dbName = `test_${randomUUID().replace(/-/g, '_')}`
  33. try {
  34. await rootPool.query(`CREATE DATABASE ${dbName};`)
  35. const pool = new Pool({
  36. connectionString: `${ROOT_DB_URL}/${dbName}`,
  37. max: 1,
  38. idleTimeoutMillis: 20000,
  39. connectionTimeoutMillis: 10000,
  40. })
  41. return {
  42. dbName,
  43. client: 'pg' as const,
  44. executeQuery: async <T = any>(query: string): Promise<T> => {
  45. try {
  46. const res = await pool.query(query)
  47. return res.rows as T
  48. } catch (error) {
  49. if (error instanceof Error) {
  50. throw new Error(`Failed to execute query: ${error.message}`)
  51. }
  52. throw error
  53. }
  54. },
  55. cleanup: async () => {
  56. await pool.end()
  57. await rootPool.query(`DROP DATABASE ${dbName};`)
  58. },
  59. }
  60. } catch (error) {
  61. if (error instanceof Error) {
  62. throw new Error(`Failed to create test database: ${error.message}`)
  63. }
  64. throw error
  65. }
  66. }
  67. // Update cleanup function to use pg pool
  68. export async function cleanupRoot() {
  69. await rootPool.end()
  70. }
  71. export async function createDatabaseWithAuthSchema(
  72. db: Awaited<ReturnType<typeof createTestDatabase>>,
  73. options?: { includeIdentities?: boolean }
  74. ) {
  75. const { includeIdentities = false } = options || {}
  76. await db.executeQuery(`
  77. CREATE SCHEMA IF NOT EXISTS auth;
  78. CREATE TABLE IF NOT EXISTS auth.users (
  79. instance_id uuid NULL,
  80. id uuid NOT NULL UNIQUE,
  81. aud varchar(255) NULL,
  82. "role" varchar(255) NULL,
  83. email varchar(255) NULL,
  84. encrypted_password varchar(255) NULL,
  85. email_confirmed_at timestamptz NULL,
  86. invited_at timestamptz NULL,
  87. confirmation_token varchar(255) NULL,
  88. confirmation_sent_at timestamptz NULL,
  89. recovery_token varchar(255) NULL,
  90. recovery_sent_at timestamptz NULL,
  91. email_change_token varchar(255) NULL,
  92. email_change varchar(255) NULL,
  93. email_change_sent_at timestamptz NULL,
  94. last_sign_in_at timestamptz NULL,
  95. raw_app_meta_data jsonb NULL,
  96. raw_user_meta_data jsonb NULL,
  97. is_super_admin bool NULL,
  98. created_at timestamptz NULL,
  99. updated_at timestamptz NULL,
  100. phone text NULL,
  101. phone_confirmed_at timestamptz NULL,
  102. phone_change text NULL,
  103. phone_change_token varchar(255) NULL,
  104. phone_change_sent_at timestamptz NULL,
  105. confirmed_at timestamptz NULL,
  106. email_change_token_current varchar(255) NULL,
  107. email_change_confirm_status smallint NULL,
  108. banned_until timestamptz NULL,
  109. reauthentication_token varchar(255) NULL,
  110. reauthentication_sent_at timestamptz NULL,
  111. is_sso_user bool NOT NULL DEFAULT false,
  112. deleted_at timestamptz NULL,
  113. is_anonymous bool NOT NULL DEFAULT false,
  114. CONSTRAINT users_pkey PRIMARY KEY (id)
  115. );
  116. CREATE INDEX IF NOT EXISTS users_instance_id_idx ON auth.users USING btree (instance_id);
  117. CREATE INDEX IF NOT EXISTS users_instance_id_email_idx ON auth.users USING btree (instance_id, lower(email));
  118. CREATE INDEX IF NOT EXISTS confirmation_token_idx ON auth.users USING btree (confirmation_token) WHERE confirmation_token IS NOT NULL;
  119. CREATE INDEX IF NOT EXISTS recovery_token_idx ON auth.users USING btree (recovery_token) WHERE recovery_token IS NOT NULL;
  120. CREATE INDEX IF NOT EXISTS email_change_token_current_idx ON auth.users USING btree (email_change_token_current) WHERE email_change_token_current IS NOT NULL;
  121. CREATE INDEX IF NOT EXISTS email_change_token_new_idx ON auth.users USING btree (email_change_token) WHERE email_change_token IS NOT NULL;
  122. CREATE INDEX IF NOT EXISTS reauthentication_token_idx ON auth.users USING btree (reauthentication_token) WHERE reauthentication_token IS NOT NULL;
  123. CREATE INDEX IF NOT EXISTS users_is_anonymous_idx ON auth.users USING btree (is_anonymous);
  124. `)
  125. if (includeIdentities) {
  126. await db.executeQuery(`
  127. CREATE TABLE IF NOT EXISTS auth.identities (
  128. id text NOT NULL,
  129. user_id uuid NOT NULL,
  130. identity_data jsonb NOT NULL,
  131. provider text NOT NULL,
  132. last_sign_in_at timestamptz NULL,
  133. created_at timestamptz NULL,
  134. updated_at timestamptz NULL,
  135. CONSTRAINT identities_pkey PRIMARY KEY (provider, id)
  136. );
  137. CREATE INDEX IF NOT EXISTS identities_user_id_idx ON auth.identities USING btree (user_id);
  138. `)
  139. }
  140. }