auth-tenant-pool.integration.test.ts78 lines · main
| 1 | /** |
| 2 | * Customer auth (Better-Auth) against REAL DoltGres — sprint plan S2.1 + S2.3. |
| 3 | * |
| 4 | * The riskiest change in the plan: re-platforming the per-tenant Better-Auth |
| 5 | * instance off postgres.js + schema-per-project onto pg + database-per-project, |
| 6 | * with citext replaced by text + a lower(email) unique index. This test PROVES |
| 7 | * the whole customer-login loop actually works on DoltGres: |
| 8 | * provision project DB → create auth tables → sign up → sign in (DIFFERENT |
| 9 | * email case) → case-insensitive uniqueness is enforced. |
| 10 | * |
| 11 | * Skips when BRIVEN_DATA_PLANE_URL is unset. |
| 12 | */ |
| 13 | import { afterAll, beforeAll, describe, expect, test } from 'bun:test'; |
| 14 | |
| 15 | import { |
| 16 | closeProjectDbPools, |
| 17 | dropProjectDatabase, |
| 18 | provisionProjectDatabase, |
| 19 | runInProjectDatabase, |
| 20 | } from '../db/data-plane.js'; |
| 21 | import { renderAuthProvisioningSql } from './auth-provisioning.js'; |
| 22 | import { clearAuthInstancePool, getAuthInstance } from './auth-tenant-pool.js'; |
| 23 | |
| 24 | const PROJECT_ID = `p_authpool${Date.now().toString(36)}`; |
| 25 | const PASSWORD = 'correct-horse-battery-staple'; |
| 26 | const HAS_DB = Boolean(process.env.BRIVEN_DATA_PLANE_URL); |
| 27 | |
| 28 | // Acceptance test for the customer-auth rebuild: S2.1a (pg + db-per-project), |
| 29 | // S2.3 (citext→text + lower(email) unique), S2.1b (Better-Auth table schema). |
| 30 | // Skips when no DoltGres is configured. |
| 31 | describe.skipIf(!HAS_DB)('customer auth on real DoltGres (S2.1 + S2.3)', () => { |
| 32 | beforeAll(async () => { |
| 33 | await provisionProjectDatabase(PROJECT_ID); |
| 34 | // Create the auth tables exactly as the "enable auth" route does. |
| 35 | await runInProjectDatabase(PROJECT_ID, async (tx) => { |
| 36 | await tx.unsafe('SET dolt_transaction_commit = 1'); |
| 37 | for (const stmt of renderAuthProvisioningSql()) { |
| 38 | await tx.unsafe(stmt); |
| 39 | } |
| 40 | }); |
| 41 | }); |
| 42 | |
| 43 | afterAll(async () => { |
| 44 | await clearAuthInstancePool().catch(() => {}); |
| 45 | await dropProjectDatabase(PROJECT_ID).catch(() => {}); |
| 46 | await closeProjectDbPools().catch(() => {}); |
| 47 | }); |
| 48 | |
| 49 | test('sign up, then sign in with a DIFFERENT email case', async () => { |
| 50 | const inst = await getAuthInstance(PROJECT_ID); |
| 51 | |
| 52 | const signup = await inst.betterAuth.api.signUpEmail({ |
| 53 | body: { email: 'Mixed@Case.com', password: PASSWORD, name: 'Test User' }, |
| 54 | }); |
| 55 | expect(signup).toBeTruthy(); |
| 56 | expect(signup.user?.id).toBeTruthy(); |
| 57 | |
| 58 | // Sign in with a different-case email — must resolve to the same account. |
| 59 | const signin = await inst.betterAuth.api.signInEmail({ |
| 60 | body: { email: 'mixed@case.com', password: PASSWORD }, |
| 61 | }); |
| 62 | expect(signin).toBeTruthy(); |
| 63 | expect(signin.user?.id).toBe(signup.user.id); |
| 64 | }); |
| 65 | |
| 66 | test('case-variant email cannot create a duplicate account', async () => { |
| 67 | const inst = await getAuthInstance(PROJECT_ID); |
| 68 | let threw = false; |
| 69 | try { |
| 70 | await inst.betterAuth.api.signUpEmail({ |
| 71 | body: { email: 'MIXED@CASE.COM', password: PASSWORD, name: 'Dup' }, |
| 72 | }); |
| 73 | } catch { |
| 74 | threw = true; |
| 75 | } |
| 76 | expect(threw).toBe(true); |
| 77 | }); |
| 78 | }); |