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
7 changes: 4 additions & 3 deletions packages/language/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,11 @@ export function getRecursiveBases(
return result;
}
seen.add(decl);
decl.mixins.forEach((mixin) => {
// avoid using mixin.ref since this function can be called before linking
const bases = [...decl.mixins, ...(isDataModel(decl) && decl.baseModel ? [decl.baseModel] : [])];
bases.forEach((base) => {
// avoid using .ref since this function can be called before linking
const baseDecl = decl.$container.declarations.find(
(d): d is TypeDef => isTypeDef(d) && d.name === mixin.$refText,
(d): d is TypeDef | DataModel => isTypeDef(d) || (isDataModel(d) && d.name === base.$refText),
);
if (baseDecl) {
if (!includeDelegate && isDelegateModel(baseDecl)) {
Expand Down
4 changes: 3 additions & 1 deletion packages/runtime/src/client/crud/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,9 @@ export class InputValidator<Schema extends SchemaDef> {
...(fieldDef.array
? {
// to-many relations can be ordered, skipped, taken, and cursor-located
orderBy: z.lazy(() => this.makeOrderBySchema(fieldDef.type, true, false)).optional(),
orderBy: z
.lazy(() => this.orArray(this.makeOrderBySchema(fieldDef.type, true, false), true))
.optional(),
skip: this.makeSkipSchema().optional(),
take: this.makeTakeSchema().optional(),
cursor: this.makeCursorSchema(fieldDef.type).optional(),
Expand Down
2 changes: 1 addition & 1 deletion packages/testtools/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export async function generateTsSchema(

if (extraSourceFiles) {
for (const [fileName, content] of Object.entries(extraSourceFiles)) {
const filePath = path.resolve(workDir, `${fileName}.ts`);
const filePath = path.resolve(workDir, !fileName.endsWith('.ts') ? `${fileName}.ts` : fileName);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, content);
}
Expand Down
51 changes: 51 additions & 0 deletions tests/regression/test/v2-migrated/issue-1647.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { loadSchema } from '@zenstackhq/testtools';
import { describe, it } from 'vitest';

// TODO: multi-schema support
describe.skip('Regression for issue 1647', () => {
it('inherits @@schema by default', async () => {
await loadSchema(
`
model Asset {
id Int @id
type String
@@delegate(type)
@@schema('public')
}

model Post extends Asset {
title String
}
`,
);
});

it('respects sub model @@schema overrides', async () => {
await loadSchema(
`
datasource db {
provider = 'postgresql'
url = env('DATABASE_URL')
schemas = ['public', 'post']
}

generator client {
provider = 'prisma-client-js'
previewFeatures = ['multiSchema']
}

model Asset {
id Int @id
type String
@@delegate(type)
@@schema('public')
}

model Post extends Asset {
title String
@@schema('post')
}
`,
);
});
});
42 changes: 42 additions & 0 deletions tests/regression/test/v2-migrated/issue-1648.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { createPolicyTestClient } from '@zenstackhq/testtools';
import { expect, it } from 'vitest';

it('verifies issue 1648', async () => {
const db = await createPolicyTestClient(
`
model User {
id Int @id @default(autoincrement())
profile Profile?
posts Post[]
}

model Profile {
id Int @id @default(autoincrement())
someText String
user User @relation(fields: [userId], references: [id])
userId Int @unique
}

model Post {
id Int @id @default(autoincrement())
title String

userId Int
user User @relation(fields: [userId], references: [id])

// this will always be true, even if the someText field is "canUpdate"
@@deny("post-update", user.profile.someText != "canUpdate")

@@allow("all", true)
}
`,
);

await db.$unuseAll().user.create({ data: { id: 1, profile: { create: { someText: 'canUpdate' } } } });
await db.$unuseAll().user.create({ data: { id: 2, profile: { create: { someText: 'nothing' } } } });
await db.$unuseAll().post.create({ data: { id: 1, title: 'Post1', userId: 1 } });
await db.$unuseAll().post.create({ data: { id: 2, title: 'Post2', userId: 2 } });

await expect(db.post.update({ where: { id: 1 }, data: { title: 'Post1-1' } })).toResolveTruthy();
await expect(db.post.update({ where: { id: 2 }, data: { title: 'Post2-2' } })).toBeRejectedByPolicy();
});
80 changes: 80 additions & 0 deletions tests/regression/test/v2-migrated/issue-1674.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { createPolicyTestClient } from '@zenstackhq/testtools';
import { expect, it } from 'vitest';

it('verifies issue 1674', async () => {
const db = await createPolicyTestClient(
`
model User {
id String @id @default(cuid())
email String @unique @email @length(6, 32)
posts Post[]

// everybody can signup
@@allow('create', true)

// full access by self
@@allow('all', auth() == this)
}

model Blog {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

post Post? @relation(fields: [postId], references: [id], onDelete: Cascade)
postId String?
}

model Post {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String @length(1, 256)
content String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String

blogs Blog[]

type String

@@delegate(type)
}

model PostA extends Post {
}

model PostB extends Post {
}
`,
);

const user = await db.$unuseAll().user.create({
data: { email: 'abc@def.com' },
});

const blog = await db.$unuseAll().blog.create({
data: {},
});

const authDb = db.$setAuth(user);
await expect(
authDb.postA.create({
data: {
content: 'content',
title: 'title',
blogs: {
connect: {
id: blog.id,
},
},
author: {
connect: {
id: user.id,
},
},
},
}),
).toBeRejectedByPolicy();
});
29 changes: 29 additions & 0 deletions tests/regression/test/v2-migrated/issue-1681.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { createTestClient } from '@zenstackhq/testtools';
import { expect, it } from 'vitest';

it('verifies issue 1681', async () => {
const db = await createTestClient(
`
model User {
id Int @id @default(autoincrement())
posts Post[]
@@allow('all', true)
}

model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int @default(auth().id)
@@allow('all', true)
}
`,
);

const authDb = db.$setAuth({ id: 1 });
const user = await db.user.create({ data: {} });
await expect(authDb.post.createMany({ data: [{ title: 'Post1' }] })).resolves.toMatchObject({ count: 1 });

const r = await authDb.post.createManyAndReturn({ data: [{ title: 'Post2' }] });
expect(r[0].authorId).toBe(user.id);
});
18 changes: 18 additions & 0 deletions tests/regression/test/v2-migrated/issue-1693.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { loadSchema } from '@zenstackhq/testtools';
import { it } from 'vitest';

it('verifies issue 1693', async () => {
await loadSchema(
`
model Animal {
id String @id @default(uuid())
animalType String @default("")
@@delegate(animalType)
}

model Dog extends Animal {
name String
}
`,
);
});
21 changes: 21 additions & 0 deletions tests/regression/test/v2-migrated/issue-1695.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { loadSchema } from '@zenstackhq/testtools';
import { it } from 'vitest';

it('verifies issue 1695', async () => {
await loadSchema(
`
type SoftDelete {
deleted Int @default(0)
}

model MyModel with SoftDelete {
id String @id @default(cuid())
name String

@@deny('update', deleted != 0)
@@deny('post-update', deleted != 0)
@@deny('read', this.deleted != 0)
}
`,
);
});
72 changes: 72 additions & 0 deletions tests/regression/test/v2-migrated/issue-1698.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { createTestClient } from '@zenstackhq/testtools';
import { expect, it } from 'vitest';

it('verifies issue 1698', async () => {
const db = await createTestClient(
`
model House {
id Int @id @default(autoincrement())
doorTypeId Int
door Door @relation(fields: [doorTypeId], references: [id])
houseType String
@@delegate(houseType)
}

model PrivateHouse extends House {
size Int
}

model Skyscraper extends House {
height Int
}

model Door {
id Int @id @default(autoincrement())
color String
doorType String
houses House[]
@@delegate(doorType)
}

model IronDoor extends Door {
strength Int
}

model WoodenDoor extends Door {
texture String
}
`,
);

const door1 = await db.ironDoor.create({
data: { strength: 100, color: 'blue' },
});
console.log(door1);

const door2 = await db.woodenDoor.create({
data: { texture: 'pine', color: 'red' },
});
console.log(door2);

const house1 = await db.privateHouse.create({
data: { size: 5000, door: { connect: { id: door1.id } } },
});
console.log(house1);

const house2 = await db.skyscraper.create({
data: { height: 3000, door: { connect: { id: door2.id } } },
});
console.log(house2);

const r1 = await db.privateHouse.findFirst({ include: { door: true } });
console.log(r1);
expect(r1).toMatchObject({
door: { color: 'blue', strength: 100 },
});

const r2 = (await db.skyscraper.findMany({ include: { door: true } }))[0];
console.log(r2);
expect(r2).toMatchObject({
door: { color: 'red', texture: 'pine' },
});
});
Loading