Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(client): Errors are obfuscated by interactive transactions gh-12862 #15602

Closed
wants to merge 12 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from './getTestSuiteInfo'
import { DatasourceInfo, setupTestSuiteDatabase, setupTestSuiteFiles, setupTestSuiteSchema } from './setupTestSuiteEnv'
import type { TestSuiteMeta } from './setupTestSuiteMatrix'
import { ClientMeta, ClientRuntime } from './types'
import { AlterStatementCallback, ClientMeta, ClientRuntime } from './types'

/**
* Does the necessary setup to get a test suite client ready to run.
Expand All @@ -26,12 +26,14 @@ export async function setupTestSuiteClient({
skipDb,
datasourceInfo,
clientMeta,
alterStatementCallback,
}: {
suiteMeta: TestSuiteMeta
suiteConfig: NamedTestSuiteConfig
skipDb?: boolean
datasourceInfo: DatasourceInfo
clientMeta: ClientMeta
alterStatementCallback?: AlterStatementCallback
}) {
const suiteFolderPath = getTestSuiteFolderPath(suiteMeta, suiteConfig)
const previewFeatures = getTestSuitePreviewFeatures(suiteConfig.matrixOptions)
Expand All @@ -44,7 +46,7 @@ export async function setupTestSuiteClient({
await setupTestSuiteSchema(suiteMeta, suiteConfig, schema)
if (!skipDb) {
process.env[datasourceInfo.envVarName] = datasourceInfo.databaseUrl
await setupTestSuiteDatabase(suiteMeta, suiteConfig)
await setupTestSuiteDatabase(suiteMeta, suiteConfig, [], alterStatementCallback)
}

process.env[datasourceInfo.envVarName] = datasourceInfo.dataProxyUrl ?? datasourceInfo.databaseUrl
Expand Down
25 changes: 24 additions & 1 deletion packages/client/tests/functional/_utils/setupTestSuiteEnv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import { match } from 'ts-pattern'
import { Script } from 'vm'

import { DbDrop } from '../../../../migrate/src/commands/DbDrop'
import { DbExecute } from '../../../../migrate/src/commands/DbExecute'
import { DbPush } from '../../../../migrate/src/commands/DbPush'
import type { NamedTestSuiteConfig } from './getTestSuiteInfo'
import { getTestSuiteFolderPath, getTestSuiteSchemaPath } from './getTestSuiteInfo'
import { Providers } from './providers'
import { ProviderFlavor, ProviderFlavors } from './relationMode/ProviderFlavor'
import type { TestSuiteMeta } from './setupTestSuiteMatrix'
import { ClientMeta } from './types'
import { AlterStatementCallback, ClientMeta } from './types'

const DB_NAME_VAR = 'PRISMA_DB_NAME'

Expand Down Expand Up @@ -108,12 +109,34 @@ export async function setupTestSuiteDatabase(
suiteMeta: TestSuiteMeta,
suiteConfig: NamedTestSuiteConfig,
errors: Error[] = [],
alterStatementCallback?: AlterStatementCallback,
) {
const schemaPath = getTestSuiteSchemaPath(suiteMeta, suiteConfig)

try {
const consoleInfoMock = jest.spyOn(console, 'info').mockImplementation()
await DbPush.new().parse(['--schema', schemaPath, '--force-reset', '--skip-generate'])

if (alterStatementCallback) {
const prismaDir = path.dirname(schemaPath)
const timestamp = new Date().getTime()
const provider = suiteConfig.matrixOptions['provider'] as Providers

await fs.promises.mkdir(`${prismaDir}/migrations/${timestamp}`, { recursive: true })
await fs.promises.writeFile(`${prismaDir}/migrations/migration_lock.toml`, `provider = "${provider}"`)
await fs.promises.writeFile(
`${prismaDir}/migrations/${timestamp}/migration.sql`,
alterStatementCallback(provider),
)

await DbExecute.new().parse([
'--file',
`${prismaDir}/migrations/${timestamp}/migration.sql`,
'--schema',
`${schemaPath}`,
])
}

consoleInfoMock.mockRestore()
} catch (e) {
errors.push(e as Error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ function setupTestSuiteMatrix(
skipDb: options?.skipDb,
datasourceInfo,
clientMeta,
alterStatementCallback: options?.alterStatementCallback,
})

globalThis['newPrismaClient'] = (...args) => {
Expand Down
4 changes: 4 additions & 0 deletions packages/client/tests/functional/_utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export type MatrixOptions = {
runtimes: ClientRuntime[]
reason: string
}
// SQL Migraiton to apply after inital generated migration
alterStatementCallback?: AlterStatementCallback
}

export type NewPrismaClient<T extends new (...args: any) => any> = (
Expand All @@ -23,3 +25,5 @@ export type ClientMeta = {
dataProxy: boolean
runtime: 'node' | 'edge'
}

export type AlterStatementCallback = (provider: Providers) => string
3 changes: 3 additions & 0 deletions packages/client/tests/functional/issues/12862/_matrix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { defineMatrix } from '../../_utils/defineMatrix'

export default defineMatrix(() => [[{ provider: 'postgresql' }]])
35 changes: 35 additions & 0 deletions packages/client/tests/functional/issues/12862/prisma/_schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { idForProvider } from '../../../_utils/idForProvider'
import testMatrix from '../_matrix'

export default testMatrix.setupSchema(({ provider }) => {
return /* Prisma */ `
generator client {
provider = "prisma-client-js"
previewFeatures = ["interactiveTransactions"]
}

datasource db {
provider = "${provider}"
url = env("DATABASE_URI_${provider}")
}

model User {
id ${idForProvider(provider)}
email String @unique
name String?
posts Post[]
}

model Post {
id ${idForProvider(provider)}
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
content String?
published Boolean @default(false)
viewCount Int @default(0)
author User? @relation(fields: [authorId], references: [id])
authorId String?
}
`
})
85 changes: 85 additions & 0 deletions packages/client/tests/functional/issues/12862/tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { faker } from '@faker-js/faker'

import testMatrix from './_matrix'
// @ts-ignore
import type { PrismaClient } from './node_modules/@prisma/client'

declare let prisma: PrismaClient

// https://github.com/prisma/prisma/issues/12862
testMatrix.setupTestSuite(
() => {
test('should propagate the correct error when a method fails', async () => {
const user = await prisma.user.create({
data: {
email: faker.internet.email(),
name: faker.name.firstName(),
},
})

await expect(
prisma.post.create({
data: {
authorId: user.id,
title: faker.lorem.sentence(),
viewCount: -1, // should fail, must be >= 0
},
}),
).rejects.toThrowError('violates check constraint \\"post_viewcount_check\\"')
})

test('should propagate the correct error when a method fails inside an transaction', async () => {
const user = await prisma.user.create({
data: {
email: faker.internet.email(),
name: faker.name.firstName(),
},
})

await expect(
prisma.$transaction([
prisma.post.create({
data: {
authorId: user.id,
title: faker.lorem.sentence(),
viewCount: -1, // should fail, must be >= 0
},
}),
]),
).rejects.toThrowError('violates check constraint \\"post_viewcount_check\\"')
})

test('should propagate the correct error when a method fails inside an interactive transaction', async () => {
await expect(
prisma.$transaction(async (client) => {
const user = await client.user.create({
data: {
email: faker.internet.email(),
name: faker.name.firstName(),
},
})

const post = await client.post.create({
data: {
authorId: user.id,
title: faker.lorem.sentence(),
viewCount: -1, // should fail, must be >= 0
},
})

return post
}),
).rejects.toThrowError('violates check constraint \\"post_viewcount_check\\"')
})
},
{
optOut: {
from: ['cockroachdb', 'mongodb', 'mysql', 'sqlite', 'sqlserver'],
reason: 'Issue relates to postgresql only',
},
alterStatementCallback: () => `
ALTER TABLE "Post"
ADD CONSTRAINT Post_viewCount_check CHECK ("viewCount" >= 0);
`,
},
)