Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions packages/testtools/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
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 crypto from 'node:crypto';
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) {
Expand Down Expand Up @@ -86,3 +90,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(', ')}`,
);
}
}
25 changes: 25 additions & 0 deletions tests/regression/test/v2-migrated/issue-177.test.ts
Original file line number Diff line number Diff line change
@@ -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',
);
});
15 changes: 15 additions & 0 deletions tests/regression/test/v2-migrated/issue-389.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
63 changes: 63 additions & 0 deletions tests/regression/test/v2-migrated/issue-392.test.ts
Original file line number Diff line number Diff line change
@@ -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?
}
`,
);
});
20 changes: 20 additions & 0 deletions tests/regression/test/v2-migrated/issue-416.test.ts
Original file line number Diff line number Diff line change
@@ -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}")
}
`,
);
});
29 changes: 29 additions & 0 deletions tests/regression/test/v2-migrated/issue-509.test.ts
Original file line number Diff line number Diff line change
@@ -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)
}
`,
);
});
59 changes: 59 additions & 0 deletions tests/regression/test/v2-migrated/issue-609.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
25 changes: 25 additions & 0 deletions tests/regression/test/v2-migrated/issue-632.test.ts
Original file line number Diff line number Diff line change
@@ -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 },
);
});
11 changes: 11 additions & 0 deletions tests/regression/test/v2-migrated/issue-646.test.ts
Original file line number Diff line number Diff line change
@@ -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)
}
`);
});