| 1 | import { PrismaClient } from '@prisma/client'
|
| 2 | import { PrismaPg } from '@prisma/adapter-pg'
|
| 3 | import jwt from 'jsonwebtoken'
|
| 4 | import bcrypt from 'bcryptjs'
|
| 5 | import { expect } from 'vitest'
|
| 6 |
|
| 7 | export function buildPrisma(): PrismaClient {
|
| 8 | const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! })
|
| 9 | return new PrismaClient({ adapter })
|
| 10 | }
|
| 11 |
|
| 12 | export async function cleanupTestDb(prisma: PrismaClient) {
|
| 13 | await prisma.$executeRawUnsafe('TRUNCATE users CASCADE')
|
| 14 | }
|
| 15 |
|
| 16 | export function generateTestToken(userId: number, email: string): string {
|
| 17 | return jwt.sign({ userId, email }, process.env.JWT_SECRET!, { expiresIn: '1h' })
|
| 18 | }
|
| 19 |
|
| 20 | export async function createTestUser(
|
| 21 | prisma: PrismaClient,
|
| 22 | overrides: {
|
| 23 | email?: string
|
| 24 | firstName?: string
|
| 25 | lastName?: string
|
| 26 | password?: string
|
| 27 | pushToken?: string
|
| 28 | notificationsEnabled?: boolean
|
| 29 | } = {}
|
| 30 | ) {
|
| 31 | const { password, pushToken, notificationsEnabled, ...rest } = overrides
|
| 32 | return prisma.user.create({
|
| 33 | data: {
|
| 34 | email: rest.email ?? 'test@example.com',
|
| 35 | firstName: rest.firstName ?? 'Test',
|
| 36 | lastName: rest.lastName ?? 'User',
|
| 37 | encryptedPassword: await bcrypt.hash(password ?? 'password123', 10),
|
| 38 | ...(pushToken !== undefined && { pushToken }),
|
| 39 | ...(notificationsEnabled !== undefined && { notificationsEnabled }),
|
| 40 | },
|
| 41 | })
|
| 42 | }
|
| 43 |
|
| 44 | // Fields that must never leave the API. Asserting absence at every
|
| 45 | // user-returning endpoint is the regression net for the raw-row leak: a failure
|
| 46 | // means an endpoint started handing back a Prisma row again.
|
| 47 | const SECRET_FIELDS = [
|
| 48 | 'encryptedPassword',
|
| 49 | 'passwordResetToken',
|
| 50 | 'passwordResetExpires',
|
| 51 | 'activationCode',
|
| 52 | 'activationCodeExpiresAt',
|
| 53 | 'pushToken',
|
| 54 | ] as const
|
| 55 |
|
| 56 | export function expectPublicUser(user: Record<string, unknown> | undefined) {
|
| 57 | expect(user).toBeDefined()
|
| 58 | for (const field of SECRET_FIELDS) {
|
| 59 | expect(user).not.toHaveProperty(field)
|
| 60 | }
|
| 61 | }
|