Validate.test.ts•14.8 kB
/* eslint-disable jest/no-identical-title */
import { defaultTestConfig } from '@prisma/config'
import { jestConsoleContext, jestContext } from '@prisma/get-platform'
import { serializeQueryEngineName } from '@prisma/internals'
import { Validate } from '../../Validate'
const ctx = jestContext.new().add(jestConsoleContext()).assemble()
const originalEnv = { ...process.env }
function restoreEnv() {
for (const key of Object.keys(process.env)) {
if (!(key in originalEnv)) {
delete process.env[key]
}
}
for (const [key, value] of Object.entries(originalEnv)) {
if (value === undefined) {
delete process.env[key]
} else {
process.env[key] = value
}
}
}
describe('validate', () => {
beforeEach(() => {
restoreEnv()
})
afterAll(() => {
restoreEnv()
})
describe('multi-schema-files', () => {
describe('valid schemas', () => {
it('should prefer single file to the multi-schema alternatives', async () => {
ctx.fixture('multi-schema-files/valid')
expect(ctx.tree()).toMatchInlineSnapshot(`
"
└── prisma/
└── schema/
└── schema1.prisma
└── schema2.prisma
└── custom.prisma
└── schema.prisma
└── custom.prisma
└── schema.prisma
"
`)
// implicit: single schema file (`schema.prisma`)
await expect(Validate.new().parse([], defaultTestConfig())).resolves.toMatchInlineSnapshot(
`"The schema at schema.prisma is valid 🚀"`,
)
// explicit: single schema file (`schema.prisma`)
await expect(
Validate.new().parse(['--schema=schema.prisma'], defaultTestConfig()),
).resolves.toMatchInlineSnapshot(`"The schema at schema.prisma is valid 🚀"`)
// explicit: single schema file (`custom.prisma`)
await expect(
Validate.new().parse(['--schema=custom.prisma'], defaultTestConfig()),
).resolves.toMatchInlineSnapshot(`"The schema at custom.prisma is valid 🚀"`)
// explicit: single schema file (`prisma/custom.prisma`)
await expect(
Validate.new().parse(['--schema=prisma/custom.prisma'], defaultTestConfig()),
).resolves.toMatchInlineSnapshot(`"The schema at prisma/custom.prisma is valid 🚀"`)
// explicit: multi schema files
await expect(
Validate.new().parse(['--schema=prisma/schema'], defaultTestConfig()),
).resolves.toMatchInlineSnapshot(`"The schemas at prisma/schema are valid 🚀"`)
await ctx.fs.removeAsync('schema.prisma')
expect(ctx.tree()).toMatchInlineSnapshot(`
"
└── prisma/
└── schema/
└── schema1.prisma
└── schema2.prisma
└── custom.prisma
└── schema.prisma
└── custom.prisma
"
`)
// implicit: single schema file (`prisma/schema.prisma`)
await expect(Validate.new().parse([], defaultTestConfig())).resolves.toMatchInlineSnapshot(
`"The schema at prisma/schema.prisma is valid 🚀"`,
)
})
})
describe('invalid schemas', () => {
it('parses multi schemas when the file containing the config blocks (`generator`, `datasource`) is valid', async () => {
ctx.fixture('multi-schema-files/invalid/valid_config_file')
expect(ctx.tree()).toMatchInlineSnapshot(`
"
└── prisma/
└── schema/
└── config.prisma
└── schema.prisma
"
`)
await expect(Validate.new().parse(['--schema=prisma/schema'], defaultTestConfig())).rejects
.toMatchInlineSnapshot(`
"Prisma schema validation - (validate wasm)
Error code: P1012
error: Argument "value" is missing.
--> prisma/schema/schema.prisma:2
|
1 | model Link {
2 | id String @id @default()
|
Validation Error Count: 1
[Context: validate]
Prisma CLI Version : 0.0.0"
`)
})
it('reports multiple errors', async () => {
ctx.fixture('multi-schema-files/invalid/multiple-errors')
expect(ctx.tree()).toMatchInlineSnapshot(`
"
└── prisma/
└── schema/
└── Blog.prisma
└── config.prisma
└── User.prisma
"
`)
await expect(Validate.new().parse(['--schema=prisma/schema'], defaultTestConfig())).rejects
.toMatchInlineSnapshot(`
"Prisma schema validation - (validate wasm)
Error code: P1012
error: Error validating model "User": Each model must have at least one unique criteria that has only required fields. Either mark a single field with \`@id\`, \`@unique\` or add a multi field criterion with \`@@id([])\` or \`@@unique([])\` to the model.
--> prisma/schema/User.prisma:1
|
|
1 | model User {
2 | id Int
3 |
4 | blogs Blog[]
5 | }
|
error: Error parsing attribute "@relation": The relation field \`owner\` on Model \`Blog\` must specify the \`fields\` argument in the @relation attribute. You can run \`prisma format\` to fix this automatically.
--> prisma/schema/Blog.prisma:5
|
4 | ownerId Int
5 | owner User
6 | }
|
error: Error parsing attribute "@relation": The relation field \`owner\` on Model \`Blog\` must specify the \`references\` argument in the @relation attribute.
--> prisma/schema/Blog.prisma:5
|
4 | ownerId Int
5 | owner User
6 | }
|
Validation Error Count: 3
[Context: validate]
Prisma CLI Version : 0.0.0"
`)
})
it('parses multi schemas when the file containing the config blocks (`generator`, `datasource`) is valid', async () => {
ctx.fixture('multi-schema-files/invalid/invalid_config_file')
// - `prisma/schema/schema_with_config.prisma` is invalid (it contains valid config + invalid models)
// - `prisma/schema/schema.prisma` is valid
expect(ctx.tree()).toMatchInlineSnapshot(`
"
└── prisma/
└── schema/
└── schema_with_config.prisma
└── schema.prisma
"
`)
await expect(Validate.new().parse(['--schema=prisma/schema'], defaultTestConfig())).rejects
.toMatchInlineSnapshot(`
"Prisma schema validation - (validate wasm)
Error code: P1012
error: Error parsing attribute "@default": The function \`now()\` cannot be used on fields of type \`Int\`.
--> prisma/schema/schema_with_config.prisma:11
|
10 | model User {
11 | id Int @id @default(now())
|
Validation Error Count: 1
[Context: validate]
Prisma CLI Version : 0.0.0"
`)
})
it('correctly reports error if config blocks (`generator`, `datasource`) are invalid', async () => {
ctx.fixture('multi-schema-files/invalid/invalid_config_blocks')
// - `prisma/schema/config.prisma` is invalid (it contains invalid attributes)
// - `prisma/schema/schema.prisma` is valid
expect(ctx.tree()).toMatchInlineSnapshot(`
"
└── prisma/
└── schema/
└── config.prisma
└── schema.prisma
"
`)
await expect(Validate.new().parse(['--schema=prisma/schema'], defaultTestConfig())).rejects
.toMatchInlineSnapshot(`
"Prisma schema validation - (validate wasm)
Error code: P1012
error: Property not known: "custom".
--> prisma/schema/config.prisma:7
|
6 | provider = "sqlite"
7 | custom = "attr"
|
Validation Error Count: 1
[Context: validate]
Prisma CLI Version : 0.0.0"
`)
})
})
})
it('should succeed if schema is valid', async () => {
ctx.fixture('example-project/prisma')
await expect(Validate.new().parse(['--schema=schema.prisma'], defaultTestConfig())).resolves.toContain('is valid')
})
it('should throw if schema is invalid', async () => {
ctx.fixture('example-project/prisma')
await expect(Validate.new().parse(['--schema=broken.prisma'], defaultTestConfig())).rejects.toThrow(
'Prisma schema validation',
)
})
it('should throw if env var is not set', async () => {
ctx.fixture('example-project/prisma')
await expect(Validate.new().parse(['--schema=env-does-not-exists.prisma'], defaultTestConfig())).rejects.toThrow(
'Environment variable not found',
)
})
it('should succeed and show a warning on stderr (preview feature deprecated)', async () => {
ctx.fixture('lint-warnings')
await expect(
Validate.new().parse(['--schema=preview-feature-deprecated.prisma'], defaultTestConfig()),
).resolves.toBeTruthy()
// stderr
expect(ctx.mocked['console.warn'].mock.calls.join('\n')).toMatchInlineSnapshot(`
"
Prisma schema warning:
- Preview feature "nativeTypes" is deprecated. The functionality can be used without specifying it as a preview feature."
`)
expect(ctx.mocked['console.error'].mock.calls.join('\n')).toMatchInlineSnapshot(`""`)
})
it('should throw with an error and show a warning on stderr (preview feature deprecated)', async () => {
ctx.fixture('lint-warnings')
await expect(
Validate.new().parse(['--schema=preview-feature-deprecated-and-error.prisma'], defaultTestConfig()),
).rejects.toThrow('P1012')
// stderr
expect(ctx.mocked['console.warn'].mock.calls.join('\n')).toMatchInlineSnapshot(`
"
Prisma schema warning:
- Preview feature "nativeTypes" is deprecated. The functionality can be used without specifying it as a preview feature."
`)
expect(ctx.mocked['console.error'].mock.calls.join('\n')).toMatchInlineSnapshot(`""`)
})
it('should succeed and NOT show a warning when PRISMA_DISABLE_WARNINGS is truthy', async () => {
ctx.fixture('lint-warnings')
process.env.PRISMA_DISABLE_WARNINGS = 'true'
await expect(
Validate.new().parse(['--schema=preview-feature-deprecated.prisma'], defaultTestConfig()),
).resolves.toBeTruthy()
// stderr
expect(ctx.mocked['console.warn'].mock.calls.join('\n')).toEqual('')
expect(ctx.mocked['console.error'].mock.calls.join('\n')).toEqual('')
})
describe('referential actions', () => {
beforeEach(() => {
ctx.fixture('referential-actions/no-action/relationMode-prisma')
})
it('should reject NoAction referential action on Postgres when relationMode = "prisma"', async () => {
expect.assertions(1)
try {
await Validate.new().parse(['--schema', './prisma/postgres.prisma'], defaultTestConfig())
} catch (e) {
expect(serializeQueryEngineName(e.message)).toMatchInlineSnapshot(`
"Prisma schema validation - (validate wasm)
Error code: P1012
error: Error validating: Invalid referential action: \`NoAction\`. Allowed values: (\`Cascade\`, \`Restrict\`, \`SetNull\`). \`NoAction\` is not implemented for Postgres when using \`relationMode = "prisma"\`, you could try using \`Restrict\` instead. Learn more at https://pris.ly/d/relation-mode
--> prisma/postgres.prisma:21
|
20 | id String @id @default(cuid())
21 | user SomeUser @relation(fields: [userId], references: [id], onUpdate: NoAction)
|
error: Error validating: Invalid referential action: \`NoAction\`. Allowed values: (\`Cascade\`, \`Restrict\`, \`SetNull\`). \`NoAction\` is not implemented for Postgres when using \`relationMode = "prisma"\`, you could try using \`Restrict\` instead. Learn more at https://pris.ly/d/relation-mode
--> prisma/postgres.prisma:28
|
27 | id String @id @default(cuid())
28 | user SomeUser @relation(fields: [userId], references: [id], onDelete: NoAction)
|
Validation Error Count: 2
[Context: validate]
Prisma CLI Version : 0.0.0"
`)
}
})
it('should reject NoAction referential action on sqlite when relationMode = "prisma"', async () => {
expect.assertions(1)
try {
await Validate.new().parse(['--schema', './prisma/postgres.prisma'], defaultTestConfig())
} catch (e) {
expect(serializeQueryEngineName(e.message)).toMatchInlineSnapshot(`
"Prisma schema validation - (validate wasm)
Error code: P1012
error: Error validating: Invalid referential action: \`NoAction\`. Allowed values: (\`Cascade\`, \`Restrict\`, \`SetNull\`). \`NoAction\` is not implemented for Postgres when using \`relationMode = "prisma"\`, you could try using \`Restrict\` instead. Learn more at https://pris.ly/d/relation-mode
--> prisma/postgres.prisma:21
|
20 | id String @id @default(cuid())
21 | user SomeUser @relation(fields: [userId], references: [id], onUpdate: NoAction)
|
error: Error validating: Invalid referential action: \`NoAction\`. Allowed values: (\`Cascade\`, \`Restrict\`, \`SetNull\`). \`NoAction\` is not implemented for Postgres when using \`relationMode = "prisma"\`, you could try using \`Restrict\` instead. Learn more at https://pris.ly/d/relation-mode
--> prisma/postgres.prisma:28
|
27 | id String @id @default(cuid())
28 | user SomeUser @relation(fields: [userId], references: [id], onDelete: NoAction)
|
Validation Error Count: 2
[Context: validate]
Prisma CLI Version : 0.0.0"
`)
}
})
it('should accept NoAction referential action on e.g. MySQL when relationMode = "prisma"', async () => {
const result = await Validate.new().parse(['--schema', './prisma/mysql.prisma'], defaultTestConfig())
expect(result).toBeTruthy()
})
})
})