Skip to content

Commit

Permalink
test(client): add relationMode datasource property tests for all da…
Browse files Browse the repository at this point in the history
…tabases (#14221)

* test(client): prototype referential integrity tests

Related: #10806

Test Suites: 1 failed, 1 total
Tests:       3 failed, 45 passed, 48 total
Snapshots:   0 total
Time:        9.838 s

* exclude mongodb to see if types tests fail like locally

* wip rewrite with Alberto

* chore: update tests

* chore: used .rejects.toThrowError

* chore: started adding ref actions

* chore: mongodb only supports 'referentialIntegrity: prisma'

* chore: added support for conditional error messages

* wip

reorg with describe
revert matrix and schema changes
beforeEach check and create users with profiles

* fix referentialIntegrityLine

* add skeleton for 1:n and m:n tests

* chore: highlighted some test errors with mongodb

* chore: added create tests for 1:n relationship

* chore: added TODO comment

* WIP m:n for SQL databases

* ci: try running github actions matrix with our tests only

* ci: remove needs: detect_jobs_to_run

* ci: fix os matrix

* ci: small CI env var tweak

* ci: fix bash line

* chore: added some update/delete tests for 1:n

* chore: added comments to _matrix.ts

* postgresql m:n all referential actions

* merge main

* DEFAULT, exclude Restrict from SQL Server, fix tests

* add mongodb tests for m:n

* chore: added some additional ref actions, added support for SQL Server

* chore: fixed bug with DEFAULT not tested, added upsert test, fixed some conditional errors, found panic on SQLServer with SetDefault

* reuse sql schema for mongodb 1:1 1:n

* 1:1 test all referral actions for onDelete & clean

* chore: improved ref actions, fixed failing tests

* chore: reproduced issue #14271

* chore: split relationships in separate files

* split m:n mongodb tests into its own file

* add `enabled Boolean?` on 1-to-n

It's a reproduction of #13766

* m:n add comments where RI=prisma - Cascade resolves instead of failing in update and create

* add `published   Boolean?` to MongoDB and update tests

* 1:1 add `enabled Boolean?` and MongoDB update tests

* add `published   Boolean?` to m:n with update tests

* chore: updated sql server tests

* add comments to m:n

[skip ci]

* add a mongodb test for immutable _id

* full database matrix for CI

* split schemas into simple files

* m to n MongoDB: change tests to match current implementation expectations

* chore: add JSON support to getTestSuiteFullName

* chore: moved ref integrity utils to its own folder. Added builder pattern to conditional error. Added matrix generation. Extracted self-contained utilities out of _schema.ts file.

* chore: added sqlite support and referentialIntegrity=prisma snapshots to 1:1 and 1:n relations

* chore: pnpm-lock

* chore: removed temp folder that wasn't supposed to be committed

* chore: imported right 'checkIfEmpty' util for m:n relation

* clean and add sqlite on m to n

* m to n add RI=Prisma messages as comments + small edits

add expectation even for `toThrowError()` cases

* m:n: split 1 test for prisma/foreignKeys

* chore: added prisma snapshots and errors as comments in m:n relation (no MongoDB yet)

* chore: added mongodb and comments to _matrix

* fix: moved _referential-integrity-utils to _utils/referential-integrity to support  ERR_PNPM_NO_SCRIPT  Missing script: test:functional-code command

* update matrix for MongoDB: add SetNull

* chore: added comments to m:n

* m to n: small edits for RI=prisma

* test 1:1 / 1:n add expects for expected throws and fix some RI=prisma messages

* chore: prettier

* 1:1 handle error messages depending on RI=prisma/foreignKeys

* * simplify conditionals

* m to n: change expectations to match current state

* chore: comment skipped test

* sqlite: skip createMany in 1:n relations

* chore: added missing sqlite snapshots to 1:n relation

* 1:n add missing delete action variants

* 1:n simplify and handle expected RI=prisma tests that should succeed

* chore: added reproduction of issue #10000

* chore: updated reproduction of issue #10000

* chore: reproduce issue #12378

* 1:1 separate onUpdate: Restrict, NoAction

* 1:1 fix create test from should throw to should succeed

* ci: separate action test for each relation / file

* ci: continue-on-error: true

* fix: referential actions test params

* fix ts checks

* only run pnpm run test:functional:code filename

* fix Cannot destructure property 'onDelete' of 'suiteConfig.referentialActions' as it is undefined.

* chore: added reproduction of issue #12557

* chore: add assertions to nested child connect in 1:1

* chore: clean unused code and better describe title

* test: add test about NO ACTION behavior with DEFERRABLE constraint

* manually bump engines

* chore: rename referentialIntegrity datasource property to relationMode

* fix github actions

* fix test about 14759 and RELATION_MODE env var

* cleanup some reproduce-x test cases

* add sqlite error snapshot

* remove continue-on-error

* remove outdated comments

* ci: remove last continue-on-error: true

* test: add `SetNull` to the matrix only for relationMode=prisma

* ci: add continue-on-error: true to run all tests

* fix snapshot with lowerace table name and run SQL Server

* exclude SQL Server / SetNull when relationMode !== prisma

* fix computeMatrix for SQL Server Restrict emulation which is not implemented

* change failing tests (issue 15683) to use .failing

* fix SQL Server test

* test(reproductions): fix empty test name

* fix relationMode = prisma SQL Server error snapshot

* fix relationMode = prisma SQL Server error snapshot

* fix relationMode = prisma SQL Server error snapshot

* comment vitess docker image (unused) and put back timeout to 60s

* test: fix snapshots for field-reference

* test: fix snapshot and getTestSuiteParametersString

* ignore expected type errors

* add note about SetNull is not run for foreignKeys in the test suite

* test: tweak comment and fix 1:1 test snapshots

* Unused '@ts-expect-error' directive.

* add `--relation-mode-tests-only` to unblock merge

* test: skip NoAction for PostgreSQL and SQLite

Related prisma/prisma-engines#3274

* fix relationMode value

* fix test name + comments

* cleanup matrix and move reproduce tests to issues directory

* add test for referentialIntegrity="prisma"

Closes #15736

* cleanup comments and misleading `toThrowError()` for expected to fail tests

* chore: removed useless comment

* chore: removed useless comments

* fix imports for moved tests in issues directory

Co-authored-by: jkomyno <skiabo97@gmail.com>
Co-authored-by: Alberto Schiabel <jkomyno@users.noreply.github.com>
  • Loading branch information
3 people committed Oct 13, 2022
1 parent e8bce9d commit 99487a7
Show file tree
Hide file tree
Showing 46 changed files with 5,807 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .github/codecov.yml
@@ -1,4 +1,4 @@
# Enable/Disable PR comments
# Enable/Disable PR comments
comment: false

# About `carryforward: true`
Expand Down
79 changes: 79 additions & 0 deletions .github/workflows/test.yml
Expand Up @@ -114,6 +114,85 @@ jobs:
- run: pnpm run lint && pnpm run check-engines-override
working-directory: ./

#
# CLIENT test:functional relationMode
#
client-relationMode:
timeout-minutes: 35
runs-on: ${{ matrix.os }}

needs: detect_jobs_to_run
if: ${{ contains(needs.detect_jobs_to_run.outputs.jobs, '-all-') || contains(needs.detect_jobs_to_run.outputs.jobs, '-client-') }}

strategy:
fail-fast: false
matrix:
# os: [buildjet-4vcpu-ubuntu-2004]
relationMode: ['', 'foreignKeys', 'prisma']
os: [ubuntu-latest]
node: [16]

env:
CI: true
SKIP_GIT: true
GITHUB_CONTEXT: ${{ toJson(github) }}
TEST_POSTGRES_URI: postgres://prisma:prisma@localhost:5432/tests
TEST_MYSQL_URI: mysql://root:root@localhost:3306/tests
TEST_MSSQL_URI: mssql://SA:Pr1sm4_Pr1sm4@localhost:1433/master
TEST_MSSQL_JDBC_URI: sqlserver://localhost:1433;database=master;user=SA;password=Pr1sm4_Pr1sm4;trustServerCertificate=true;
TEST_MSSQL_JDBC_URI_MIGRATE: 'sqlserver://localhost:1433;database=tests-migrate;user=SA;password=Pr1sm4_Pr1sm4;trustServerCertificate=true;'
TEST_MONGO_URI: 'mongodb://root:prisma@localhost:27018/tests?authSource=admin'
TEST_COCKROACH_URI: 'postgresql://prisma@localhost:26257/tests'
TEST_COCKROACH_URI_MIGRATE: 'postgresql://prisma@localhost:26257/tests-migrate'
TEST_COCKROACH_SHADOWDB_URI_MIGRATE: 'postgres://prisma:prisma@localhost:26257/tests-migrate-shadowdb'
TEST_FUNCTIONAL_POSTGRES_URI: 'postgres://prisma:prisma@localhost:5432/PRISMA_DB_NAME'
TEST_FUNCTIONAL_MYSQL_URI: 'mysql://root:root@localhost:3306/PRISMA_DB_NAME'
TEST_FUNCTIONAL_MSSQL_URI: 'sqlserver://localhost:1433;database=PRISMA_DB_NAME;user=SA;password=Pr1sm4_Pr1sm4;trustServerCertificate=true;'
TEST_FUNCTIONAL_MONGO_URI: 'mongodb://root:prisma@localhost:27018/PRISMA_DB_NAME?authSource=admin'
TEST_FUNCTIONAL_COCKROACH_URI: 'postgresql://prisma@localhost:26257/PRISMA_DB_NAME'
NODE_OPTIONS: '--max-old-space-size=8096'

steps:
- uses: actions/checkout@v3

- name: Set RELATION_MODE custom test env var
run: |
echo "RELATION_MODE=${{ matrix.relationMode }}"
if [ ! -z "${{ matrix.relationMode }}" ]; then echo "RELATION_MODE=${{ matrix.relationMode }}" >> $GITHUB_ENV; fi
- run: docker-compose -f docker/docker-compose.yml up --detach postgres mysql mssql mongo cockroachdb

- uses: pnpm/action-setup@v2.2.2
with:
version: 7

- uses: actions/setup-node@v3
with:
cache: 'pnpm'
node-version: ${{ matrix.node }}

- run: bash .github/workflows/scripts/setup.sh
env:
CI: true
SKIP_GIT: true
GITHUB_CONTEXT: ${{ toJson(github) }}

- name: 1 to 1
run: pnpm run test:functional:code --relation-mode-tests-only relationMode/tests_1-to-1.ts
working-directory: packages/client

- name: 1 to n
run: pnpm run test:functional:code --relation-mode-tests-only relationMode/tests_1-to-n.ts
working-directory: packages/client

- name: m to n (SQL databases)
run: pnpm run test:functional:code --relation-mode-tests-only relationMode/tests_m-to-n.ts
working-directory: packages/client

- name: m to n (MongoDB)
run: pnpm run test:functional:code --relation-mode-tests-only relationMode/tests_m-to-n-MongoDB.ts
working-directory: packages/client

#
# CLIENT (without types test)
#
Expand Down
51 changes: 51 additions & 0 deletions docker/docker-compose.yml
Expand Up @@ -30,6 +30,57 @@ services:
ports:
- '26257:26257'

# Planetscale
# From https://github.com/prisma/prisma-engines/blob/976a00ae3c30ab9507fa742986c9c6f5327ba10f/docker-compose.yml

# vitess-test-5_7:
# image: vitess/vttestserver:mysql57@sha256:2b132a22d08b3b227d9391f8f58ed7ab5c081ca07bf0f87a0c166729124d360a
# restart: always
# ports:
# - 33577:33577
# environment:
# PORT: 33574
# KEYSPACES: "test"
# NUM_SHARDS: "1"
# MYSQL_BIND_HOST: "0.0.0.0"
# FOREIGN_KEY_MODE: "disallow"

# vitess-test-8_0:
# image: vitess/vttestserver:mysql80@sha256:9412e3d51bde38e09c3039090b5c68808e299579f12c79178a4ec316f7831889
# restart: always
# ports:
# - 33807:33807
# environment:
# PORT: 33804
# KEYSPACES: 'test'
# NUM_SHARDS: '1'
# MYSQL_BIND_HOST: '0.0.0.0'
# FOREIGN_KEY_MODE: 'disallow'

# vitess-shadow-5_7:
# image: vitess/vttestserver:mysql57@sha256:2b132a22d08b3b227d9391f8f58ed7ab5c081ca07bf0f87a0c166729124d360a
# restart: always
# ports:
# - 33578:33577
# environment:
# PORT: 33574
# KEYSPACES: "shadow"
# NUM_SHARDS: "1"
# MYSQL_BIND_HOST: "0.0.0.0"
# FOREIGN_KEY_MODE: "disallow"
#
# vitess-shadow-8_0:
# image: vitess/vttestserver:mysql80@sha256:9412e3d51bde38e09c3039090b5c68808e299579f12c79178a4ec316f7831889
# restart: always
# ports:
# - 33808:33807
# environment:
# PORT: 33804
# KEYSPACES: 'shadow'
# NUM_SHARDS: '1'
# MYSQL_BIND_HOST: '0.0.0.0'
# FOREIGN_KEY_MODE: 'disallow'

mysql:
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password --lower_case_table_names=1
Expand Down
16 changes: 16 additions & 0 deletions packages/client/helpers/functional-test/run-tests.ts
Expand Up @@ -34,6 +34,15 @@ const args = arg(
'--no-mini-proxy-default-ca': Boolean,
// Enable debug logs in the bundled Mini-Proxy server
'--mini-proxy-debug': Boolean,
// Since `relationMode` tests need to be run with 2 different values
// `foreignKeys` and `prisma`
// We run them separately in a GitHub Action matrix for now
// Also the typescript tests fail and it might not be easily fixable
// This flag is used for this purpose
'--relation-mode-tests-only': Boolean,
//
// Jest flags
//
// Passes the same flag to Jest to only run tests related to changed files
'--onlyChanged': Boolean,
// Passes the same flag to Jest to only run tests related to changed files
Expand Down Expand Up @@ -98,6 +107,13 @@ async function main(): Promise<number | void> {
}

const jestArgs = ['--testPathIgnorePatterns', 'typescript']

// See flag description above.
// If the flag is not provided we want to ignore `relationMode` tests
if (!args['--relation-mode-tests-only']) {
jestArgs.push('--testPathIgnorePatterns', 'relationMode')
}

if (args['--onlyChanged']) {
jestArgs.push('--onlyChanged')
}
Expand Down
1 change: 1 addition & 0 deletions packages/client/package.json
Expand Up @@ -134,6 +134,7 @@
"strip-indent": "3.0.0",
"ts-jest": "28.0.8",
"ts-node": "10.9.1",
"ts-pattern": "^4.0.5",
"tsd": "0.21.0",
"typescript": "4.8.4",
"yeoman-generator": "5.7.0",
Expand Down
14 changes: 12 additions & 2 deletions packages/client/tests/functional/_utils/getTestSuiteInfo.ts
Expand Up @@ -121,8 +121,18 @@ export function getTestSuiteConfigs(suiteMeta: TestSuiteMeta): NamedTestSuiteCon
function getTestSuiteParametersString(configs: Record<string, string>[]) {
return configs
.map((config) => {
const firstKey = Object.keys(config)[0]
return `${firstKey}=${config[firstKey]}`
// Note if we use the JSON.stringy name for all tests
// Some tests will error with `ENAMETOOLONG: name too long` as this is used for the directory name
// Example of failing tests: `field-reference` and `fulltext-search`
// So we scope this to the values used in `relationMode` tests
if (config.relationMode || config.onUpdate || config.onDelete) {
return Object.entries(config).map(
([key, value]) => `${key}=${value !== null && typeof value === 'object' ? JSON.stringify(value) : value}`,
)
} else {
const firstKey = Object.keys(config)[0]
return `${firstKey}=${config[firstKey]}`
}
})
.join(', ')
}
Expand Down
@@ -0,0 +1,9 @@
// @ts-ignore this is just for type checks
declare let prisma: import('@prisma/client').PrismaClient

export async function checkIfEmpty(...models: unknown[]) {
const checkEmptyArr = await prisma.$transaction(models.map((model) => prisma[model].findMany()))
checkEmptyArr.forEach((checkEmpty) => {
expect(checkEmpty).toHaveLength(0)
})
}
107 changes: 107 additions & 0 deletions packages/client/tests/functional/_utils/relationMode/computeMatrix.ts
@@ -0,0 +1,107 @@
import { Providers } from '../providers'

type ComputeMatrix = {
relationMode: 'prisma' | 'foreignKeys' | ''
providersDenyList?: Providers[]
}

export function computeMatrix({ relationMode, providersDenyList }: ComputeMatrix) {
const providersBase = [
Providers.POSTGRESQL,
Providers.COCKROACHDB,
Providers.SQLSERVER,
Providers.MYSQL,
Providers.SQLITE,
] as const
// Note: SetDefault is not implemented in the emulation (relationMode="prisma")

// Note: testing 'SetDefault' requires a relation with a scalar field having the "@default" attribute.
// If no defaults are provided for any of the scalar fields, a runtime error will be thrown.
// Also 'SetDefault' is making SQL Server crash badly for example
// See https://www.notion.so/prismaio/Phase-1-Report-on-findings-f21c7bb079c5414296286973fdcd62c2#ac4d9f6a5d3842b5b6ff5b877e7e6782

const referentialActionsBase = ['DEFAULT', 'Cascade', 'NoAction', 'Restrict', 'SetNull'] as const

const providers = providersBase.filter((provider) => !(providersDenyList || []).includes(provider))

// "foreignKeys"
//
// 'Restrict' on SQL Server is not available and it triggers a schema parsing error.
// See in our docs https://pris.ly/d/relationMode
//
// `SetNull` with non-optional relations (= our 1:1, 1:n and m:n in this project) is invalid
// when using Foreign Keys fails with migration errors on MySQL, CockroachDB and SQL Server
// A schema validation error will be added, see https://github.com/prisma/prisma/issues/14673
//
// "prisma"
//
// `NoAction` on PostgreSQL / SQLite
// We made a schema validation error for PostgreSQL and SQLite in https://github.com/prisma/prisma-engines/pull/3274
// Error code: P1012 error: Error validating: Invalid referential action: `NoAction`. Allowed values: (`Cascade`, `Restrict`, `SetNull`). `NoAction` is not implemented for sqlite when using `relationMode = "prisma"`, you could try using `Restrict` instead. Learn more at https://pris.ly/d/relationMode
//
// We skip these combinations in the matrix (= filtering them out)

const referentialActionsDenylistByProvider = {
foreignKeys: {
[Providers.SQLSERVER]: ['Restrict', 'SetNull'],
[Providers.COCKROACHDB]: ['SetNull'],
[Providers.MYSQL]: ['SetNull'],
},
prisma: {
[Providers.SQLSERVER]: ['Restrict', 'SetNull'],
[Providers.COCKROACHDB]: ['SetNull'],
[Providers.MYSQL]: ['SetNull'],
[Providers.POSTGRESQL]: ['NoAction'],
[Providers.SQLITE]: ['NoAction'],
},
}

const providersMatrix = providers.map((provider) => ({
provider,
id: 'String @id',
relationMode,
}))

const referentialActionsMatrix = providersMatrix.flatMap((entry) => {
const denyList = referentialActionsDenylistByProvider[relationMode || 'foreignKeys'][entry.provider] || []
const referentialActions = referentialActionsBase.filter((action) => !denyList.includes(action))

const referentialActionMatrixForSQL = referentialActions.map((referentialAction) => ({
...entry,
onUpdate: referentialAction,
onDelete: referentialAction,
}))

const mongoDBMatrixBase = {
provider: Providers.MONGODB,
id: 'String @id @map("_id")',
relationMode,
}
const referentialActionMatrixForMongoDB = [
{
...mongoDBMatrixBase,
onUpdate: 'DEFAULT',
onDelete: 'DEFAULT',
},
{
...mongoDBMatrixBase,
onUpdate: 'Cascade',
onDelete: 'Cascade',
},
{
...mongoDBMatrixBase,
onUpdate: 'NoAction',
onDelete: 'NoAction',
},
{
...mongoDBMatrixBase,
onUpdate: 'SetNull',
onDelete: 'SetNull',
},
]

return [...referentialActionMatrixForSQL, ...referentialActionMatrixForMongoDB]
})

return referentialActionsMatrix
}
@@ -0,0 +1,19 @@
type ComputeReferentialActionLine = {
onUpdate: string
onDelete: string
}

export function computeReferentialActionLine({ onUpdate, onDelete }: ComputeReferentialActionLine): string {
let referentialActionLine = ''
const DEFAULT = 'DEFAULT'

if (onUpdate && onUpdate !== DEFAULT) {
referentialActionLine += `, onUpdate: ${onUpdate}`
}

if (onDelete && onDelete !== DEFAULT) {
referentialActionLine += `, onDelete: ${onDelete}`
}

return referentialActionLine
}
@@ -0,0 +1,41 @@
import { match } from 'ts-pattern'

import { Providers } from '../providers'

export type ComputeSchemaHeader = {
provider: Providers
previewFeatures: string
relationMode: string
}

export function computeSchemaHeader({ provider, previewFeatures, relationMode }: ComputeSchemaHeader): string {
const USE_PLANETSCALE = false

// if relationModeLine is not defined, we do not add the line
// if relationModeLine is defined
// we add the line only if the provider is not MongoDB, since MongoDB doesn't need the setting, it's on by default
const relationModeLine = provider === Providers.MONGODB || !relationMode ? '' : `relationMode = "${relationMode}"`

const url = match(provider)
.when(
(_provider) => USE_PLANETSCALE && _provider === Providers.MYSQL,
(_provider) => `"mysql://root:root@127.0.0.1:33807/PRISMA_DB_NAME"`,
)
.with(Providers.SQLITE, (_provider) => `"file:test.db"`)
.otherwise((_provider) => `env("DATABASE_URI_${provider}")`)

const schemaHeader = /* Prisma */ `
generator client {
provider = "prisma-client-js"
previewFeatures = [${previewFeatures}]
}
datasource db {
provider = "${provider}"
url = ${url}
${relationModeLine}
}
`

return schemaHeader
}

0 comments on commit 99487a7

Please sign in to comment.