From f913d7a1d610a804f4592b6ef4e5bc0a02e9c113 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sat, 4 Oct 2025 22:13:26 -0700 Subject: [PATCH 1/3] chore: migrate some v2 regression cases --- packages/testtools/src/schema.ts | 44 +++++++++++++ .../test/v2-migrated/issue-177.test.ts | 25 ++++++++ .../test/v2-migrated/issue-389.test.ts | 15 +++++ .../test/v2-migrated/issue-392.test.ts | 63 +++++++++++++++++++ .../test/v2-migrated/issue-416.test.ts | 20 ++++++ .../test/v2-migrated/issue-509.test.ts | 29 +++++++++ .../test/v2-migrated/issue-609.test.ts | 59 +++++++++++++++++ .../test/v2-migrated/issue-632.test.ts | 25 ++++++++ .../test/v2-migrated/issue-646.test.ts | 11 ++++ turbo.json | 2 +- 10 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 tests/regression/test/v2-migrated/issue-177.test.ts create mode 100644 tests/regression/test/v2-migrated/issue-389.test.ts create mode 100644 tests/regression/test/v2-migrated/issue-392.test.ts create mode 100644 tests/regression/test/v2-migrated/issue-416.test.ts create mode 100644 tests/regression/test/v2-migrated/issue-509.test.ts create mode 100644 tests/regression/test/v2-migrated/issue-609.test.ts create mode 100644 tests/regression/test/v2-migrated/issue-632.test.ts create mode 100644 tests/regression/test/v2-migrated/issue-646.test.ts diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index ee57a0c5..a2274de4 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -1,10 +1,13 @@ +import { invariant } from '@zenstackhq/common-helpers'; import { loadDocument } from '@zenstackhq/language'; import { TsSchemaGenerator } from '@zenstackhq/sdk'; import type { SchemaDef } from '@zenstackhq/sdk/schema'; import { execSync } from 'node:child_process'; import fs from 'node:fs'; +import os from 'node:os'; import path from 'node:path'; import { match } from 'ts-pattern'; +import { expect } from 'vitest'; import { createTestProject } from './project'; function makePrelude(provider: 'sqlite' | 'postgresql', dbUrl?: string) { @@ -86,3 +89,44 @@ export async function generateTsSchemaInPlace(schemaPath: string) { await generator.generate(result.model, workDir); return compileAndLoad(workDir); } + +export async function loadSchema(schema: string) { + if (!schema.includes('datasource ')) { + schema = `${makePrelude('sqlite')}\n\n${schema}`; + } + + // create a temp file + const tempFile = path.join(os.tmpdir(), `zenstack-schema-${crypto.randomUUID()}.zmodel`); + fs.writeFileSync(tempFile, schema); + const r = await loadDocument(tempFile); + expect(r).toSatisfy( + (r) => r.success, + `Failed to load schema: ${(r as any).errors?.map((e: any) => e.toString()).join(', ')}`, + ); + invariant(r.success); + return r.model; +} + +export async function loadSchemaWithError(schema: string, error: string | RegExp) { + if (!schema.includes('datasource ')) { + schema = `${makePrelude('sqlite')}\n\n${schema}`; + } + + // create a temp file + const tempFile = path.join(os.tmpdir(), `zenstack-schema-${crypto.randomUUID()}.zmodel`); + fs.writeFileSync(tempFile, schema); + const r = await loadDocument(tempFile); + expect(r.success).toBe(false); + invariant(!r.success); + if (typeof error === 'string') { + expect(r).toSatisfy( + (r) => r.errors.some((e: any) => e.toString().toLowerCase().includes(error.toLowerCase())), + `Expected error message to include "${error}" but got: ${r.errors.map((e: any) => e.toString()).join(', ')}`, + ); + } else { + expect(r).toSatisfy( + (r) => r.errors.some((e: any) => error.test(e)), + `Expected error message to match "${error}" but got: ${r.errors.map((e: any) => e.toString()).join(', ')}`, + ); + } +} diff --git a/tests/regression/test/v2-migrated/issue-177.test.ts b/tests/regression/test/v2-migrated/issue-177.test.ts new file mode 100644 index 00000000..4c8558fb --- /dev/null +++ b/tests/regression/test/v2-migrated/issue-177.test.ts @@ -0,0 +1,25 @@ +import { loadSchemaWithError } from '@zenstackhq/testtools'; +import { it } from 'vitest'; + +it('verifies issue 177', async () => { + await loadSchemaWithError( + ` + model Foo { + id String @id @default(cuid()) + + bar Bar @relation(fields: [barId1, barId2], references: [id1, id2]) + barId1 String? + barId2 String + } + + model Bar { + id1 String @default(cuid()) + id2 String @default(cuid()) + foos Foo[] + + @@id([id1, id2]) + } + `, + 'relation "bar" is not optional, but field "barId1" is optional', + ); +}); diff --git a/tests/regression/test/v2-migrated/issue-389.test.ts b/tests/regression/test/v2-migrated/issue-389.test.ts new file mode 100644 index 00000000..20777b2e --- /dev/null +++ b/tests/regression/test/v2-migrated/issue-389.test.ts @@ -0,0 +1,15 @@ +import { expect, it } from 'vitest'; +import { createPolicyTestClient } from '@zenstackhq/testtools'; + +it('verifies issue 389', async () => { + const db = await createPolicyTestClient(` + model model { + id String @id @default(uuid()) + value Int + @@allow('read', true) + @@allow('create', value > 0) + } + `); + await expect(db.model.create({ data: { value: 0 } })).toBeRejectedByPolicy(); + await expect(db.model.create({ data: { value: 1 } })).toResolveTruthy(); +}); diff --git a/tests/regression/test/v2-migrated/issue-392.test.ts b/tests/regression/test/v2-migrated/issue-392.test.ts new file mode 100644 index 00000000..bf8acfb4 --- /dev/null +++ b/tests/regression/test/v2-migrated/issue-392.test.ts @@ -0,0 +1,63 @@ +import { loadDocument } from '@zenstackhq/language'; +import { it } from 'vitest'; + +it('verifies issue 392', async () => { + await loadDocument( + ` + model M1 { + m2_id String @id + m2 M2 @relation(fields: [m2_id], references: [id]) + } + + model M2 { + id String @id + m1 M1? + } + `, + ); + + await loadDocument( + ` + model M1 { + id String @id + m2_id String @unique + m2 M2 @relation(fields: [m2_id], references: [id]) + } + + model M2 { + id String @id + m1 M1? + } + `, + ); + + await loadDocument( + ` + model M1 { + m2_id String + m2 M2 @relation(fields: [m2_id], references: [id]) + @@id([m2_id]) + } + + model M2 { + id String @id + m1 M1? + } + `, + ); + + await loadDocument( + ` + model M1 { + m2_id String + m2 M2 @relation(fields: [m2_id], references: [id]) + @@unique([m2_id]) + } + + model M2 { + id String @id + m1 M1? + } + `, + ); +}); diff --git a/tests/regression/test/v2-migrated/issue-416.test.ts b/tests/regression/test/v2-migrated/issue-416.test.ts new file mode 100644 index 00000000..75776394 --- /dev/null +++ b/tests/regression/test/v2-migrated/issue-416.test.ts @@ -0,0 +1,20 @@ +import { loadSchema } from '@zenstackhq/testtools'; +import { it } from 'vitest'; + +it('verifies issue 416', async () => { + await loadSchema( + ` +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Example { + id Int @id + doubleQuote String @default("s\\"1") + singleQuote String @default('s\\'1') + json Json @default("{\\"theme\\": \\"light\\", \\"consoleDrawer\\": false}") +} + `, + ); +}); diff --git a/tests/regression/test/v2-migrated/issue-509.test.ts b/tests/regression/test/v2-migrated/issue-509.test.ts new file mode 100644 index 00000000..ceefb2f5 --- /dev/null +++ b/tests/regression/test/v2-migrated/issue-509.test.ts @@ -0,0 +1,29 @@ +import { loadDocument } from '@zenstackhq/language'; +import { it } from 'vitest'; + +it('verifies issue 509', async () => { + await loadDocument( + ` + model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + posts Post[] + } + + model Post { + id Int @id @default(autoincrement()) + title String + content String? + published Boolean @default(false) + author User? @relation(fields: [authorId], references: [id]) + authorId Int? + + deleted Boolean @default(false) @omit + + @@allow('all', true) + @@deny('read', deleted) + } + `, + ); +}); diff --git a/tests/regression/test/v2-migrated/issue-609.test.ts b/tests/regression/test/v2-migrated/issue-609.test.ts new file mode 100644 index 00000000..b25314c6 --- /dev/null +++ b/tests/regression/test/v2-migrated/issue-609.test.ts @@ -0,0 +1,59 @@ +import { createPolicyTestClient } from '@zenstackhq/testtools'; +import { expect, it } from 'vitest'; + +it('verifies issue 609', async () => { + const db = await createPolicyTestClient( + ` +model User { + id String @id @default(cuid()) + comments Comment[] +} + +model Comment { + id String @id @default(cuid()) + parentCommentId String? + replies Comment[] @relation("CommentToComment") + parent Comment? @relation("CommentToComment", fields: [parentCommentId], references: [id]) + comment String + author User @relation(fields: [authorId], references: [id]) + authorId String + + @@allow('read,create', true) + @@allow('update,delete', auth() == author) +} + `, + { usePrismaPush: true }, + ); + + const rawDb = db.$unuseAll(); + + await rawDb.user.create({ + data: { + id: '1', + comments: { + create: { + id: '1', + comment: 'Comment 1', + }, + }, + }, + }); + + await rawDb.user.create({ + data: { + id: '2', + }, + }); + + // connecting a child comment from a different user to a parent comment should succeed + const dbAuth = db.$setAuth({ id: '2' }); + await expect( + dbAuth.comment.create({ + data: { + comment: 'Comment 2', + author: { connect: { id: '2' } }, + parent: { connect: { id: '1' } }, + }, + }), + ).toResolveTruthy(); +}); diff --git a/tests/regression/test/v2-migrated/issue-632.test.ts b/tests/regression/test/v2-migrated/issue-632.test.ts new file mode 100644 index 00000000..99d251d2 --- /dev/null +++ b/tests/regression/test/v2-migrated/issue-632.test.ts @@ -0,0 +1,25 @@ +import { createTestClient } from '@zenstackhq/testtools'; +import { it } from 'vitest'; + +it('verifies issue 632', async () => { + await createTestClient( + ` +enum InventoryUnit { + DIGITAL + FL_OZ + GRAMS + MILLILITERS + OUNCES + UNIT + UNLIMITED +} + +model TwoEnumsOneModelTest { + id String @id @default(cuid()) + inventoryUnit InventoryUnit @default(UNIT) + inputUnit InventoryUnit @default(UNIT) +} +`, + { provider: 'postgresql', usePrismaPush: true }, + ); +}); diff --git a/tests/regression/test/v2-migrated/issue-646.test.ts b/tests/regression/test/v2-migrated/issue-646.test.ts new file mode 100644 index 00000000..12daf25b --- /dev/null +++ b/tests/regression/test/v2-migrated/issue-646.test.ts @@ -0,0 +1,11 @@ +import { loadSchema } from '@zenstackhq/testtools'; +import { it } from 'vitest'; + +it('verifies issue 646', async () => { + await loadSchema(` +model Example { + id Int @id + epsilon Decimal @default(0.00000001) +} + `); +}); diff --git a/turbo.json b/turbo.json index ab312f31..94796728 100644 --- a/turbo.json +++ b/turbo.json @@ -7,7 +7,7 @@ "outputs": ["dist/**"] }, "watch": { - "dependsOn": ["^build"], + "dependsOn": ["^watch"], "inputs": ["src/**", "zenstack/*.zmodel"], "outputs": [] }, From be52876b9967b9d3a9be9bdc979295474f01c972 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sat, 4 Oct 2025 22:17:06 -0700 Subject: [PATCH 2/3] update --- turbo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index 94796728..ab312f31 100644 --- a/turbo.json +++ b/turbo.json @@ -7,7 +7,7 @@ "outputs": ["dist/**"] }, "watch": { - "dependsOn": ["^watch"], + "dependsOn": ["^build"], "inputs": ["src/**", "zenstack/*.zmodel"], "outputs": [] }, From cd034a9bfd44c5650ff8b2f921ae61f03035a16a Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sat, 4 Oct 2025 22:20:00 -0700 Subject: [PATCH 3/3] update --- packages/testtools/src/schema.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index a2274de4..fd234378 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -3,6 +3,7 @@ import { loadDocument } from '@zenstackhq/language'; import { TsSchemaGenerator } from '@zenstackhq/sdk'; import type { SchemaDef } from '@zenstackhq/sdk/schema'; import { execSync } from 'node:child_process'; +import crypto from 'node:crypto'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path';