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

test(client): reproduction for #16423 m:n dangling pivot table entry after deleting #16428

Merged
merged 2 commits into from
Nov 24, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { defineMatrix } from '../../_utils/defineMatrix'
import { Providers } from '../../_utils/providers'

export default defineMatrix(() => [
[
{
provider: Providers.POSTGRESQL,
relationMode: 'prisma',
},
{
provider: Providers.POSTGRESQL,
relationMode: '', // empty means default (foreignKeys)
},
{
provider: Providers.MYSQL,
relationMode: 'prisma',
},
{
provider: Providers.MYSQL,
relationMode: '', // empty means default (foreignKeys)
},
],
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import testMatrix from '../_matrix'

export default testMatrix.setupSchema(({ provider, relationMode }) => {
// if relationMode is not defined, we do not add the line
const relationModeLine = relationMode ? `relationMode = "${relationMode}"` : ''

const schemaHeader = /* Prisma */ `
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "${provider}"
url = env("DATABASE_URI_${provider}")
${relationModeLine}
}
`
return /* Prisma */ `
${schemaHeader}

model Item {
id Int @id @default(autoincrement())
categories Category[]
createdAt DateTime @default(now())
updatedAt DateTime? @updatedAt
}

model Category {
id Int @id @default(autoincrement())
items Item[]
createdAt DateTime @default(now())
updatedAt DateTime? @updatedAt
}
`
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
import testMatrix from './_matrix'

/* eslint-disable @typescript-eslint/no-unused-vars */

// @ts-ignore this is just for type checks
declare let prisma: import('@prisma/client').PrismaClient

testMatrix.setupTestSuite(
(suiteConfig, suiteMeta) => {
describe('issue 16390', () => {
afterEach(async () => {
// Start from a clean state
await prisma.item.deleteMany({})
await prisma.category.deleteMany({})
if (suiteConfig['provider'] === 'mysql') {
await prisma.$executeRaw`TRUNCATE TABLE \`_CategoryToItem\`;`
} else {
await prisma.$executeRaw`TRUNCATE TABLE "_CategoryToItem";`
}
})

afterAll(async () => {
await prisma.$disconnect()
})

test('when deleting an item, the corresponding entry in the implicit pivot table should be deleted', async () => {
// Create one category
const category = await prisma.category.create({
data: {},
})
expect(category).toMatchObject({
id: 1,
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
})

// Create one item linked to the category
const item = await prisma.item.create({
data: {
categories: {
connect: {
id: category.id,
},
},
},
include: {
categories: true,
},
})
expect(item).toMatchObject({
categories: [{ id: 1, createdAt: expect.any(Date), updatedAt: expect.any(Date) }],
id: 1,
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
})

// Check the pivot table entries
let pivotTable
if (suiteConfig['provider'] === 'mysql') {
pivotTable = await prisma.$queryRaw`SELECT * FROM \`_CategoryToItem\`;`
} else {
pivotTable = await prisma.$queryRaw`SELECT * FROM "_CategoryToItem";`
}
expect(pivotTable).toMatchInlineSnapshot(`
Array [
Object {
A: 1,
B: 1,
},
]
`)

// Delete the item
expect(
await prisma.item.delete({
where: {
id: item.id,
},
}),
).toMatchObject({
id: 1,
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
})

// Item query now returns null
expect(
await prisma.item.findUnique({
where: {
id: item.id,
},
include: {
categories: true,
},
}),
).toBeNull()

// Category has no items
expect(
await prisma.category.findUnique({
where: {
id: category.id,
},
include: {
items: true,
},
}),
).toMatchObject({
id: 1,
items: [],
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
})

// Everything looks good but....
// Let's check the pivot table

// Check the pivot table entries
let pivotTableAfterDelete
if (suiteConfig['provider'] === 'mysql') {
pivotTableAfterDelete = await prisma.$queryRaw`SELECT * FROM \`_CategoryToItem\`;`
} else {
pivotTableAfterDelete = await prisma.$queryRaw`SELECT * FROM "_CategoryToItem";`
}

// ... the pivot table entry is still there!
// This is a bug in the relationMode="prisma" emulation
if (suiteConfig.relationMode === 'prisma') {
expect(pivotTableAfterDelete).toStrictEqual([
{
A: 1,
B: 1,
},
])
} else {
// This is the expected behavior for prisma and foreignKeys
// once this bug is fixed
expect(pivotTableAfterDelete).toStrictEqual([])
}
})
})

test('when deleting a category, the corresponding entry in the implicit pivot table should be deleted', async () => {
// Create one category
const category = await prisma.category.create({
data: {},
})
expect(category).toMatchObject({
id: 2,
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
})

// Create one item linked to the category
const item = await prisma.item.create({
data: {
categories: {
connect: {
id: category.id,
},
},
},
include: {
categories: true,
},
})
expect(item).toMatchObject({
categories: [{ id: 2, createdAt: expect.any(Date), updatedAt: expect.any(Date) }],
id: 2,
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
})

// Check the pivot table entries
let pivotTable
if (suiteConfig['provider'] === 'mysql') {
pivotTable = await prisma.$queryRaw`SELECT * FROM \`_CategoryToItem\`;`
} else {
pivotTable = await prisma.$queryRaw`SELECT * FROM "_CategoryToItem";`
}
expect(pivotTable).toMatchInlineSnapshot(`
Array [
Object {
A: 2,
B: 2,
},
]
`)

// Delete the category
expect(
await prisma.category.delete({
where: {
id: item.id,
},
}),
).toMatchObject({
id: 2,
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
})

// Category query now returns null
expect(
await prisma.category.findUnique({
where: {
id: category.id,
},
include: {
items: true,
},
}),
).toBeNull()

// Item has no category
expect(
await prisma.item.findUnique({
where: {
id: item.id,
},
include: {
categories: true,
},
}),
).toMatchObject({
id: 2,
categories: [],
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
})

// Everything looks good but....
// Let's check the pivot table

// Check the pivot table entries
let pivotTableAfterDelete
if (suiteConfig['provider'] === 'mysql') {
pivotTableAfterDelete = await prisma.$queryRaw`SELECT * FROM \`_CategoryToItem\`;`
} else {
pivotTableAfterDelete = await prisma.$queryRaw`SELECT * FROM "_CategoryToItem";`
}

// ... the pivot table entry is still there!
// This is a bug in the relationMode="prisma" emulation
Jolg42 marked this conversation as resolved.
Show resolved Hide resolved
if (suiteConfig.relationMode === 'prisma') {
expect(pivotTableAfterDelete).toStrictEqual([
{
A: 2,
B: 2,
},
])
} else {
// This is the expected behavior for prisma and foreignKeys
// once this bug is fixed
expect(pivotTableAfterDelete).toStrictEqual([])
}
})
},
// Use `optOut` to opt out from testing the default selected providers
// otherwise the suite will require all providers to be specified.
{
optOut: {
from: ['sqlite', 'mongodb', 'cockroachdb', 'sqlserver'],
reason: 'Only testing postgresql and mysql',
},
},
)