From 876e01392112ed369cde37cb77ca983126f2d881 Mon Sep 17 00:00:00 2001 From: Yiming Date: Sun, 22 Oct 2023 16:52:56 -0400 Subject: [PATCH 1/2] fix: incorrect policy injection for nested to-one relation inside a to-many parent (#777) --- .../src/enhancements/policy/policy-utils.ts | 2 +- tests/integration/tests/cli/plugins.test.ts | 2 +- .../with-policy/nested-to-many.test.ts | 52 ++++++++++++++++++- .../tests/regression/issue-764.test.ts | 49 +++++++++++++++++ 4 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 tests/integration/tests/regression/issue-764.test.ts diff --git a/packages/runtime/src/enhancements/policy/policy-utils.ts b/packages/runtime/src/enhancements/policy/policy-utils.ts index 12c8c57be..98c49957c 100644 --- a/packages/runtime/src/enhancements/policy/policy-utils.ts +++ b/packages/runtime/src/enhancements/policy/policy-utils.ts @@ -515,7 +515,7 @@ export class PolicyUtil { throw this.unknownError(`missing backLink field ${currField.backLink} in ${currField.type}`); } - if (backLinkField.isArray) { + if (backLinkField.isArray && !mutating) { // many-side of relationship, wrap with "some" query currQuery[currField.backLink] = { some: { ...visitWhere } }; } else { diff --git a/tests/integration/tests/cli/plugins.test.ts b/tests/integration/tests/cli/plugins.test.ts index d2461c4ec..005a0f69b 100644 --- a/tests/integration/tests/cli/plugins.test.ts +++ b/tests/integration/tests/cli/plugins.test.ts @@ -71,7 +71,7 @@ describe('CLI Plugins Tests', () => { 'zod@3.21.1', 'react', 'swr', - '@tanstack/react-query', + '@tanstack/react-query@^4.0.0', '@trpc/server', '@prisma/client@^4.0.0', `${path.join(__dirname, '../../../../.build/zenstackhq-language-' + ver + '.tgz')}`, diff --git a/tests/integration/tests/enhancements/with-policy/nested-to-many.test.ts b/tests/integration/tests/enhancements/with-policy/nested-to-many.test.ts index fa7059faa..b112aeeb1 100644 --- a/tests/integration/tests/enhancements/with-policy/nested-to-many.test.ts +++ b/tests/integration/tests/enhancements/with-policy/nested-to-many.test.ts @@ -284,7 +284,7 @@ describe('With Policy:nested to-many', () => { expect(r.m2).toEqual(expect.arrayContaining([expect.objectContaining({ id: '2', value: 3 })])); }); - it('update with create', async () => { + it('update with create from one to many', async () => { const { withPolicy } = await loadSchema( ` model M1 { @@ -341,6 +341,56 @@ describe('With Policy:nested to-many', () => { expect(r.m2).toHaveLength(3); }); + it('update with create from many to one', async () => { + const { withPolicy } = await loadSchema( + ` + model M1 { + id String @id @default(uuid()) + value Int + m2 M2[] + + @@allow('read', true) + @@allow('create', value > 0) + @@allow('update', value > 1) + } + + model M2 { + id String @id @default(uuid()) + m1 M1? @relation(fields: [m1Id], references:[id]) + m1Id String? + + @@allow('all', true) + } + ` + ); + + const db = withPolicy(); + + await db.m2.create({ data: { id: '1' } }); + + await expect( + db.m2.update({ + where: { id: '1' }, + data: { + m1: { + create: { value: 0 }, + }, + }, + }) + ).toBeRejectedByPolicy(); + + await expect( + db.m2.update({ + where: { id: '1' }, + data: { + m1: { + create: { value: 1 }, + }, + }, + }) + ).toResolveTruthy(); + }); + it('update with delete', async () => { const { withPolicy, prisma } = await loadSchema( ` diff --git a/tests/integration/tests/regression/issue-764.test.ts b/tests/integration/tests/regression/issue-764.test.ts new file mode 100644 index 000000000..8e64a15d8 --- /dev/null +++ b/tests/integration/tests/regression/issue-764.test.ts @@ -0,0 +1,49 @@ +import { loadSchema } from '@zenstackhq/testtools'; + +describe('Regression: issue 764', () => { + it('regression', async () => { + const { prisma, enhance } = await loadSchema( + ` + model User { + id Int @id @default(autoincrement()) + name String + + post Post? @relation(fields: [postId], references: [id]) + postId Int? + + @@allow('all', true) + } + + model Post { + id Int @id @default(autoincrement()) + title String + User User[] + + @@allow('all', true) + } + ` + ); + + const db = enhance(); + + const user = await prisma.user.create({ + data: { name: 'Me' }, + }); + + await db.user.update({ + where: { id: user.id }, + data: { + post: { + upsert: { + create: { + title: 'Hello World', + }, + update: { + title: 'Hello World', + }, + }, + }, + }, + }); + }); +}); From e41fc747c5a8389d820820c5f8fd95ee13717160 Mon Sep 17 00:00:00 2001 From: Yiming Date: Sun, 22 Oct 2023 19:31:09 -0400 Subject: [PATCH 2/2] fix: deal with payload field value with undefined (#778) --- .../src/enhancements/nested-write-visitor.ts | 14 ++++--- .../src/enhancements/policy/policy-utils.ts | 6 ++- .../tests/regression/issue-765.test.ts | 37 +++++++++++++++++++ 3 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 tests/integration/tests/regression/issue-765.test.ts diff --git a/packages/runtime/src/enhancements/nested-write-visitor.ts b/packages/runtime/src/enhancements/nested-write-visitor.ts index 226773242..667ebf5f2 100644 --- a/packages/runtime/src/enhancements/nested-write-visitor.ts +++ b/packages/runtime/src/enhancements/nested-write-visitor.ts @@ -323,12 +323,14 @@ export class NestedWriteVisitor { } if (fieldInfo.isDataModel) { - // recurse into nested payloads - for (const [subAction, subData] of Object.entries(payload[field])) { - if (this.isPrismaWriteAction(subAction) && subData) { - await this.doVisit(fieldInfo.type, subAction, subData, payload[field], fieldInfo, [ - ...nestingPath, - ]); + if (payload[field]) { + // recurse into nested payloads + for (const [subAction, subData] of Object.entries(payload[field])) { + if (this.isPrismaWriteAction(subAction) && subData) { + await this.doVisit(fieldInfo.type, subAction, subData, payload[field], fieldInfo, [ + ...nestingPath, + ]); + } } } } else { diff --git a/packages/runtime/src/enhancements/policy/policy-utils.ts b/packages/runtime/src/enhancements/policy/policy-utils.ts index 98c49957c..2e72e1de6 100644 --- a/packages/runtime/src/enhancements/policy/policy-utils.ts +++ b/packages/runtime/src/enhancements/policy/policy-utils.ts @@ -466,8 +466,10 @@ export class PolicyUtil { ) { // multi-field unique constraint, flatten it delete args[field]; - for (const [f, v] of Object.entries(value)) { - args[f] = v; + if (value) { + for (const [f, v] of Object.entries(value)) { + args[f] = v; + } } } } diff --git a/tests/integration/tests/regression/issue-765.test.ts b/tests/integration/tests/regression/issue-765.test.ts new file mode 100644 index 000000000..f29ef1a5a --- /dev/null +++ b/tests/integration/tests/regression/issue-765.test.ts @@ -0,0 +1,37 @@ +import { loadSchema } from '@zenstackhq/testtools'; + +describe('Regression: issue 765', () => { + it('regression', async () => { + const { enhance } = await loadSchema( + ` + model User { + id Int @id @default(autoincrement()) + name String + + post Post? @relation(fields: [postId], references: [id]) + postId Int? + + @@allow('all', true) + } + + model Post { + id Int @id @default(autoincrement()) + title String + User User[] + + @@allow('all', true) + } + ` + ); + + const db = enhance(); + const r = await db.user.create({ + data: { + name: 'Me', + post: undefined, + }, + }); + expect(r.name).toBe('Me'); + expect(r.post).toBeUndefined(); + }); +});