Skip to content

Commit f13b92f

Browse files
committed
chore(policy): more test cases and update
1 parent f01bcde commit f13b92f

File tree

4 files changed

+616
-71
lines changed

4 files changed

+616
-71
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
</a>
2222
</div>
2323

24-
> V3 is currently in alpha phase and not ready for production use. Feedback and bug reports are greatly appreciated. Please visit this dedicated [discord channel](https://discord.com/channels/1035538056146595961/1352359627525718056) for chat and support.
24+
> V3 is currently in beta phase and not ready for production use. Feedback and bug reports are greatly appreciated. Please visit this dedicated [discord channel](https://discord.com/channels/1035538056146595961/1352359627525718056) for chat and support.
2525
2626
# What's ZenStack
2727

packages/runtime/src/client/crud/operations/base.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
281281
);
282282
Object.assign(createFields, parentFkFields);
283283
} else {
284-
parentUpdateTask = (entity) => {
284+
parentUpdateTask = async (entity) => {
285285
const query = kysely
286286
.updateTable(fromRelation.model)
287287
.set(
@@ -300,7 +300,10 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
300300
operation: 'update',
301301
}),
302302
);
303-
return this.executeQuery(kysely, query, 'update');
303+
const result = await this.executeQuery(kysely, query, 'update');
304+
if (!result.numAffectedRows) {
305+
throw new NotFoundError(fromRelation.model);
306+
}
304307
};
305308
}
306309
}
@@ -1551,8 +1554,11 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
15511554
fromRelation.field,
15521555
);
15531556
let updateResult: QueryResult<unknown>;
1557+
let updateModel: GetModels<Schema>;
15541558

15551559
if (ownedByModel) {
1560+
updateModel = fromRelation.model;
1561+
15561562
// set parent fk directly
15571563
invariant(_data.length === 1, 'only one entity can be connected');
15581564
const target = await this.readUnique(kysely, model, {
@@ -1581,6 +1587,8 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
15811587
);
15821588
updateResult = await this.executeQuery(kysely, query, 'connect');
15831589
} else {
1590+
updateModel = model;
1591+
15841592
// disconnect current if it's a one-one relation
15851593
const relationFieldDef = this.requireField(fromRelation.model, fromRelation.field);
15861594

@@ -1621,9 +1629,9 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
16211629
}
16221630

16231631
// validate connect result
1624-
if (_data.length > updateResult.numAffectedRows!) {
1632+
if (!updateResult.numAffectedRows || _data.length > updateResult.numAffectedRows) {
16251633
// some entities were not connected
1626-
throw new NotFoundError(model);
1634+
throw new NotFoundError(updateModel);
16271635
}
16281636
}
16291637
}
@@ -1735,7 +1743,10 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
17351743
operation: 'update',
17361744
}),
17371745
);
1738-
await this.executeQuery(kysely, query, 'disconnect');
1746+
const result = await this.executeQuery(kysely, query, 'disconnect');
1747+
if (!result.numAffectedRows) {
1748+
throw new NotFoundError(fromRelation.model);
1749+
}
17391750
} else {
17401751
// disconnect
17411752
const query = kysely
@@ -1859,7 +1870,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
18591870
const r = await this.executeQuery(kysely, query, 'connect');
18601871

18611872
// validate result
1862-
if (_data.length > r.numAffectedRows!) {
1873+
if (!r.numAffectedRows || _data.length > r.numAffectedRows) {
18631874
// some entities were not connected
18641875
throw new NotFoundError(model);
18651876
}
@@ -1892,9 +1903,12 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
18921903
}
18931904

18941905
let deleteResult: { count: number };
1906+
let deleteFromModel: GetModels<Schema>;
18951907
const m2m = getManyToManyRelation(this.schema, fromRelation.model, fromRelation.field);
18961908

18971909
if (m2m) {
1910+
deleteFromModel = model;
1911+
18981912
// handle many-to-many relation
18991913
const fieldDef = this.requireField(fromRelation.model, fromRelation.field);
19001914
invariant(fieldDef.relation?.opposite);
@@ -1919,11 +1933,13 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
19191933
);
19201934

19211935
if (ownedByModel) {
1936+
deleteFromModel = fromRelation.model;
1937+
19221938
const fromEntity = await this.readUnique(kysely, fromRelation.model as GetModels<Schema>, {
19231939
where: fromRelation.ids,
19241940
});
19251941
if (!fromEntity) {
1926-
throw new NotFoundError(model);
1942+
throw new NotFoundError(fromRelation.model);
19271943
}
19281944

19291945
const fieldDef = this.requireField(fromRelation.model, fromRelation.field);
@@ -1938,6 +1954,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
19381954
],
19391955
});
19401956
} else {
1957+
deleteFromModel = model;
19411958
deleteResult = await this.delete(kysely, model, {
19421959
AND: [
19431960
Object.fromEntries(keyPairs.map(({ fk, pk }) => [fk, fromRelation.ids[pk]])),
@@ -1952,7 +1969,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
19521969
// validate result
19531970
if (throwForNotFound && expectedDeleteCount > deleteResult.count) {
19541971
// some entities were not deleted
1955-
throw new NotFoundError(model);
1972+
throw new NotFoundError(deleteFromModel);
19561973
}
19571974
}
19581975

packages/runtime/test/policy/crud/create.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,71 @@ model Profile {
206206

207207
await expect(db.$setAuth({ id: 4 }).profile.create({ data: { id: 2, userId: 4 } })).toBeRejectedByPolicy();
208208
});
209+
210+
it('works with nested create owner side', async () => {
211+
const db = await createPolicyTestClient(
212+
`
213+
model User {
214+
id Int @id
215+
profile Profile?
216+
@@allow('all', true)
217+
}
218+
219+
model Profile {
220+
id Int @id
221+
user User? @relation(fields: [userId], references: [id])
222+
userId Int? @unique
223+
224+
@@deny('all', auth() == null)
225+
@@allow('create', user.id == auth().id)
226+
@@allow('read', true)
227+
}
228+
`,
229+
);
230+
231+
await expect(db.user.create({ data: { id: 1, profile: { create: { id: 1 } } } })).toBeRejectedByPolicy();
232+
await expect(
233+
db
234+
.$setAuth({ id: 1 })
235+
.user.create({ data: { id: 1, profile: { create: { id: 1 } } }, include: { profile: true } }),
236+
).resolves.toMatchObject({
237+
id: 1,
238+
profile: {
239+
id: 1,
240+
},
241+
});
242+
});
243+
244+
it('works with nested create non-owner side', async () => {
245+
const db = await createPolicyTestClient(
246+
`
247+
model User {
248+
id Int @id
249+
profile Profile?
250+
@@deny('all', auth() == null)
251+
@@allow('create', this.id == auth().id)
252+
@@allow('read', true)
253+
}
254+
255+
model Profile {
256+
id Int @id
257+
user User? @relation(fields: [userId], references: [id])
258+
userId Int? @unique
259+
@@allow('all', true)
260+
}
261+
`,
262+
);
263+
264+
await expect(db.profile.create({ data: { id: 1, user: { create: { id: 1 } } } })).toBeRejectedByPolicy();
265+
await expect(
266+
db
267+
.$setAuth({ id: 1 })
268+
.profile.create({ data: { id: 1, user: { create: { id: 1 } } }, include: { user: true } }),
269+
).resolves.toMatchObject({
270+
id: 1,
271+
user: {
272+
id: 1,
273+
},
274+
});
275+
});
209276
});

0 commit comments

Comments
 (0)