From 34d10991eb8e9097016127ce0dd3cab0c6821d76 Mon Sep 17 00:00:00 2001 From: Tim Leslie Date: Wed, 25 Aug 2021 16:21:46 +1000 Subject: [PATCH] Update relationship input types to respect graphql.isEnabled --- examples-staging/assets-cloud/schema.graphql | 2 +- examples-staging/assets-local/schema.graphql | 2 +- examples-staging/basic/schema.graphql | 4 +- examples-staging/ecommerce/schema.graphql | 10 +- .../graphql-api-endpoint/schema.graphql | 4 +- examples-staging/roles/schema.graphql | 4 +- examples-staging/sandbox/schema.graphql | 2 +- examples/blog/schema.graphql | 2 +- examples/custom-admin-ui-logo/schema.graphql | 2 +- .../custom-admin-ui-navigation/schema.graphql | 2 +- examples/custom-admin-ui-pages/schema.graphql | 2 +- examples/custom-field-view/schema.graphql | 2 +- examples/custom-field/schema.graphql | 2 +- examples/default-values/schema.graphql | 2 +- examples/document-field/schema.graphql | 2 +- examples/extend-graphql-schema/schema.graphql | 2 +- examples/json/schema.graphql | 2 +- examples/task-manager/schema.graphql | 2 +- examples/testing/schema.graphql | 2 +- examples/virtual-field/schema.graphql | 2 +- examples/with-auth/schema.graphql | 2 +- .../relationship/tests/implementation.test.ts | 2 +- .../keystone/src/lib/core/types-for-lists.ts | 117 ++++++++++++++++-- packages/keystone/src/types/next-fields.ts | 16 +-- .../api-tests/access-control/schema-utils.ts | 14 ++- tests/api-tests/access-control/schema.test.ts | 86 ++++++++++++- .../nested-mutations/create-singular.test.ts | 8 +- tests/test-projects/basic/schema.graphql | 2 +- .../crud-notifications/schema.graphql | 2 +- 29 files changed, 241 insertions(+), 62 deletions(-) diff --git a/examples-staging/assets-cloud/schema.graphql b/examples-staging/assets-cloud/schema.graphql index 6e73618ef22..746fe6f4e21 100644 --- a/examples-staging/assets-cloud/schema.graphql +++ b/examples-staging/assets-cloud/schema.graphql @@ -175,9 +175,9 @@ input AuthorUpdateInput { } input PostRelateToManyForUpdateInput { + create: [PostCreateInput!] disconnect: [PostWhereUniqueInput!] set: [PostWhereUniqueInput!] - create: [PostCreateInput!] connect: [PostWhereUniqueInput!] } diff --git a/examples-staging/assets-local/schema.graphql b/examples-staging/assets-local/schema.graphql index c6be74a2179..1666b6a7eb4 100644 --- a/examples-staging/assets-local/schema.graphql +++ b/examples-staging/assets-local/schema.graphql @@ -153,9 +153,9 @@ input AuthorUpdateInput { } input PostRelateToManyForUpdateInput { + create: [PostCreateInput!] disconnect: [PostWhereUniqueInput!] set: [PostWhereUniqueInput!] - create: [PostCreateInput!] connect: [PostWhereUniqueInput!] } diff --git a/examples-staging/basic/schema.graphql b/examples-staging/basic/schema.graphql index 2f3587b414c..afc901d0c40 100644 --- a/examples-staging/basic/schema.graphql +++ b/examples-staging/basic/schema.graphql @@ -229,16 +229,16 @@ input FileFieldInput { } input PhoneNumberRelateToManyForUpdateInput { + create: [PhoneNumberCreateInput!] disconnect: [PhoneNumberWhereUniqueInput!] set: [PhoneNumberWhereUniqueInput!] - create: [PhoneNumberCreateInput!] connect: [PhoneNumberWhereUniqueInput!] } input PostRelateToManyForUpdateInput { + create: [PostCreateInput!] disconnect: [PostWhereUniqueInput!] set: [PostWhereUniqueInput!] - create: [PostCreateInput!] connect: [PostWhereUniqueInput!] } diff --git a/examples-staging/ecommerce/schema.graphql b/examples-staging/ecommerce/schema.graphql index ede25549b39..8d3586e390b 100644 --- a/examples-staging/ecommerce/schema.graphql +++ b/examples-staging/ecommerce/schema.graphql @@ -245,16 +245,16 @@ input UserUpdateInput { } input CartItemRelateToManyForUpdateInput { + create: [CartItemCreateInput!] disconnect: [CartItemWhereUniqueInput!] set: [CartItemWhereUniqueInput!] - create: [CartItemCreateInput!] connect: [CartItemWhereUniqueInput!] } input OrderRelateToManyForUpdateInput { + create: [OrderCreateInput!] disconnect: [OrderWhereUniqueInput!] set: [OrderWhereUniqueInput!] - create: [OrderCreateInput!] connect: [OrderWhereUniqueInput!] } @@ -265,9 +265,9 @@ input RoleRelateToOneForUpdateInput { } input ProductRelateToManyForUpdateInput { + create: [ProductCreateInput!] disconnect: [ProductWhereUniqueInput!] set: [ProductWhereUniqueInput!] - create: [ProductCreateInput!] connect: [ProductWhereUniqueInput!] } @@ -622,9 +622,9 @@ input OrderUpdateInput { } input OrderItemRelateToManyForUpdateInput { + create: [OrderItemCreateInput!] disconnect: [OrderItemWhereUniqueInput!] set: [OrderItemWhereUniqueInput!] - create: [OrderItemCreateInput!] connect: [OrderItemWhereUniqueInput!] } @@ -690,9 +690,9 @@ input RoleUpdateInput { } input UserRelateToManyForUpdateInput { + create: [UserCreateInput!] disconnect: [UserWhereUniqueInput!] set: [UserWhereUniqueInput!] - create: [UserCreateInput!] connect: [UserWhereUniqueInput!] } diff --git a/examples-staging/graphql-api-endpoint/schema.graphql b/examples-staging/graphql-api-endpoint/schema.graphql index 3caa70617f4..0e8af06dfab 100644 --- a/examples-staging/graphql-api-endpoint/schema.graphql +++ b/examples-staging/graphql-api-endpoint/schema.graphql @@ -153,9 +153,9 @@ input UserUpdateInput { } input PostRelateToManyForUpdateInput { + create: [PostCreateInput!] disconnect: [PostWhereUniqueInput!] set: [PostWhereUniqueInput!] - create: [PostCreateInput!] connect: [PostWhereUniqueInput!] } @@ -227,9 +227,9 @@ input UserRelateToOneForUpdateInput { } input TagRelateToManyForUpdateInput { + create: [TagCreateInput!] disconnect: [TagWhereUniqueInput!] set: [TagWhereUniqueInput!] - create: [TagCreateInput!] connect: [TagWhereUniqueInput!] } diff --git a/examples-staging/roles/schema.graphql b/examples-staging/roles/schema.graphql index d3d71b849e7..13c4c49d096 100644 --- a/examples-staging/roles/schema.graphql +++ b/examples-staging/roles/schema.graphql @@ -208,9 +208,9 @@ input RoleRelateToOneForUpdateInput { } input TodoRelateToManyForUpdateInput { + create: [TodoCreateInput!] disconnect: [TodoWhereUniqueInput!] set: [TodoWhereUniqueInput!] - create: [TodoCreateInput!] connect: [TodoWhereUniqueInput!] } @@ -282,9 +282,9 @@ input RoleUpdateInput { } input PersonRelateToManyForUpdateInput { + create: [PersonCreateInput!] disconnect: [PersonWhereUniqueInput!] set: [PersonWhereUniqueInput!] - create: [PersonCreateInput!] connect: [PersonWhereUniqueInput!] } diff --git a/examples-staging/sandbox/schema.graphql b/examples-staging/sandbox/schema.graphql index 1b415c31622..f605c9838cd 100644 --- a/examples-staging/sandbox/schema.graphql +++ b/examples-staging/sandbox/schema.graphql @@ -121,9 +121,9 @@ input UserUpdateInput { } input TodoRelateToManyForUpdateInput { + create: [TodoCreateInput!] disconnect: [TodoWhereUniqueInput!] set: [TodoWhereUniqueInput!] - create: [TodoCreateInput!] connect: [TodoWhereUniqueInput!] } diff --git a/examples/blog/schema.graphql b/examples/blog/schema.graphql index d0edfca215f..8cd1572e23a 100644 --- a/examples/blog/schema.graphql +++ b/examples/blog/schema.graphql @@ -142,9 +142,9 @@ input AuthorUpdateInput { } input PostRelateToManyForUpdateInput { + create: [PostCreateInput!] disconnect: [PostWhereUniqueInput!] set: [PostWhereUniqueInput!] - create: [PostCreateInput!] connect: [PostWhereUniqueInput!] } diff --git a/examples/custom-admin-ui-logo/schema.graphql b/examples/custom-admin-ui-logo/schema.graphql index 24200845cd6..2a242b9df0d 100644 --- a/examples/custom-admin-ui-logo/schema.graphql +++ b/examples/custom-admin-ui-logo/schema.graphql @@ -181,9 +181,9 @@ input PersonUpdateInput { } input TaskRelateToManyForUpdateInput { + create: [TaskCreateInput!] disconnect: [TaskWhereUniqueInput!] set: [TaskWhereUniqueInput!] - create: [TaskCreateInput!] connect: [TaskWhereUniqueInput!] } diff --git a/examples/custom-admin-ui-navigation/schema.graphql b/examples/custom-admin-ui-navigation/schema.graphql index 24200845cd6..2a242b9df0d 100644 --- a/examples/custom-admin-ui-navigation/schema.graphql +++ b/examples/custom-admin-ui-navigation/schema.graphql @@ -181,9 +181,9 @@ input PersonUpdateInput { } input TaskRelateToManyForUpdateInput { + create: [TaskCreateInput!] disconnect: [TaskWhereUniqueInput!] set: [TaskWhereUniqueInput!] - create: [TaskCreateInput!] connect: [TaskWhereUniqueInput!] } diff --git a/examples/custom-admin-ui-pages/schema.graphql b/examples/custom-admin-ui-pages/schema.graphql index 24200845cd6..2a242b9df0d 100644 --- a/examples/custom-admin-ui-pages/schema.graphql +++ b/examples/custom-admin-ui-pages/schema.graphql @@ -181,9 +181,9 @@ input PersonUpdateInput { } input TaskRelateToManyForUpdateInput { + create: [TaskCreateInput!] disconnect: [TaskWhereUniqueInput!] set: [TaskWhereUniqueInput!] - create: [TaskCreateInput!] connect: [TaskWhereUniqueInput!] } diff --git a/examples/custom-field-view/schema.graphql b/examples/custom-field-view/schema.graphql index 124e26dd9dc..cd631eb0b77 100644 --- a/examples/custom-field-view/schema.graphql +++ b/examples/custom-field-view/schema.graphql @@ -184,9 +184,9 @@ input PersonUpdateInput { } input TaskRelateToManyForUpdateInput { + create: [TaskCreateInput!] disconnect: [TaskWhereUniqueInput!] set: [TaskWhereUniqueInput!] - create: [TaskCreateInput!] connect: [TaskWhereUniqueInput!] } diff --git a/examples/custom-field/schema.graphql b/examples/custom-field/schema.graphql index f165d6d76c5..99a0429db02 100644 --- a/examples/custom-field/schema.graphql +++ b/examples/custom-field/schema.graphql @@ -116,9 +116,9 @@ input AuthorUpdateInput { } input PostRelateToManyForUpdateInput { + create: [PostCreateInput!] disconnect: [PostWhereUniqueInput!] set: [PostWhereUniqueInput!] - create: [PostCreateInput!] connect: [PostWhereUniqueInput!] } diff --git a/examples/default-values/schema.graphql b/examples/default-values/schema.graphql index 24200845cd6..2a242b9df0d 100644 --- a/examples/default-values/schema.graphql +++ b/examples/default-values/schema.graphql @@ -181,9 +181,9 @@ input PersonUpdateInput { } input TaskRelateToManyForUpdateInput { + create: [TaskCreateInput!] disconnect: [TaskWhereUniqueInput!] set: [TaskWhereUniqueInput!] - create: [TaskCreateInput!] connect: [TaskWhereUniqueInput!] } diff --git a/examples/document-field/schema.graphql b/examples/document-field/schema.graphql index 6d7e7f7dc62..d36cda0ef17 100644 --- a/examples/document-field/schema.graphql +++ b/examples/document-field/schema.graphql @@ -126,9 +126,9 @@ input AuthorUpdateInput { } input PostRelateToManyForUpdateInput { + create: [PostCreateInput!] disconnect: [PostWhereUniqueInput!] set: [PostWhereUniqueInput!] - create: [PostCreateInput!] connect: [PostWhereUniqueInput!] } diff --git a/examples/extend-graphql-schema/schema.graphql b/examples/extend-graphql-schema/schema.graphql index 75d76e389ba..fbbced1cda7 100644 --- a/examples/extend-graphql-schema/schema.graphql +++ b/examples/extend-graphql-schema/schema.graphql @@ -141,9 +141,9 @@ input AuthorUpdateInput { } input PostRelateToManyForUpdateInput { + create: [PostCreateInput!] disconnect: [PostWhereUniqueInput!] set: [PostWhereUniqueInput!] - create: [PostCreateInput!] connect: [PostWhereUniqueInput!] } diff --git a/examples/json/schema.graphql b/examples/json/schema.graphql index 45b03572944..8408c6cd104 100644 --- a/examples/json/schema.graphql +++ b/examples/json/schema.graphql @@ -103,9 +103,9 @@ input PersonUpdateInput { } input PackageRelateToManyForUpdateInput { + create: [PackageCreateInput!] disconnect: [PackageWhereUniqueInput!] set: [PackageWhereUniqueInput!] - create: [PackageCreateInput!] connect: [PackageWhereUniqueInput!] } diff --git a/examples/task-manager/schema.graphql b/examples/task-manager/schema.graphql index 24200845cd6..2a242b9df0d 100644 --- a/examples/task-manager/schema.graphql +++ b/examples/task-manager/schema.graphql @@ -181,9 +181,9 @@ input PersonUpdateInput { } input TaskRelateToManyForUpdateInput { + create: [TaskCreateInput!] disconnect: [TaskWhereUniqueInput!] set: [TaskWhereUniqueInput!] - create: [TaskCreateInput!] connect: [TaskWhereUniqueInput!] } diff --git a/examples/testing/schema.graphql b/examples/testing/schema.graphql index c37b0c90fde..3729dc56313 100644 --- a/examples/testing/schema.graphql +++ b/examples/testing/schema.graphql @@ -250,9 +250,9 @@ input PersonUpdateInput { } input TaskRelateToManyForUpdateInput { + create: [TaskCreateInput!] disconnect: [TaskWhereUniqueInput!] set: [TaskWhereUniqueInput!] - create: [TaskCreateInput!] connect: [TaskWhereUniqueInput!] } diff --git a/examples/virtual-field/schema.graphql b/examples/virtual-field/schema.graphql index 1ad3971c405..e458fce1ee8 100644 --- a/examples/virtual-field/schema.graphql +++ b/examples/virtual-field/schema.graphql @@ -124,9 +124,9 @@ input AuthorUpdateInput { } input PostRelateToManyForUpdateInput { + create: [PostCreateInput!] disconnect: [PostWhereUniqueInput!] set: [PostWhereUniqueInput!] - create: [PostCreateInput!] connect: [PostWhereUniqueInput!] } diff --git a/examples/with-auth/schema.graphql b/examples/with-auth/schema.graphql index c37b0c90fde..3729dc56313 100644 --- a/examples/with-auth/schema.graphql +++ b/examples/with-auth/schema.graphql @@ -250,9 +250,9 @@ input PersonUpdateInput { } input TaskRelateToManyForUpdateInput { + create: [TaskCreateInput!] disconnect: [TaskWhereUniqueInput!] set: [TaskWhereUniqueInput!] - create: [TaskCreateInput!] connect: [TaskWhereUniqueInput!] } diff --git a/packages/keystone/src/fields/types/relationship/tests/implementation.test.ts b/packages/keystone/src/fields/types/relationship/tests/implementation.test.ts index ce9228f1ed5..a0ad7b73b6b 100644 --- a/packages/keystone/src/fields/types/relationship/tests/implementation.test.ts +++ b/packages/keystone/src/fields/types/relationship/tests/implementation.test.ts @@ -88,9 +88,9 @@ describe('Type Generation', () => { expect(printType(schema.getType('ZipRelateToManyForUpdateInput')!)).toMatchInlineSnapshot(` "input ZipRelateToManyForUpdateInput { + create: [ZipCreateInput!] disconnect: [ZipWhereUniqueInput!] set: [ZipWhereUniqueInput!] - create: [ZipCreateInput!] connect: [ZipWhereUniqueInput!] }" `); diff --git a/packages/keystone/src/lib/core/types-for-lists.ts b/packages/keystone/src/lib/core/types-for-lists.ts index 869d7161fe7..bd9eb082bc7 100644 --- a/packages/keystone/src/lib/core/types-for-lists.ts +++ b/packages/keystone/src/lib/core/types-for-lists.ts @@ -207,7 +207,21 @@ export function initialiseLists( const { fields } = lists[listKey]; return Object.fromEntries( Object.entries(fields).flatMap(([key, field]) => { - if (!field.input?.create?.arg || !field.graphql.isEnabled.create) return []; + if ( + // We're going to skip this field if... + !field.input?.create?.arg || // The field type doesn't support create, or + !field.graphql.isEnabled.create || // Create has been disabled on this field, or + (field.dbField.kind === 'relation' && // This is a relationship field and the related list... + !( + isEnabled[field.dbField.list].create || // Doesn't support create + // and also + isEnabled[field.dbField.list].query || // Doesn't support uniqueWhere (?) + isEnabled[field.dbField.list].update || + isEnabled[field.dbField.list].delete + )) + ) { + return []; + } return [[key, field.input.create.arg]] as const; }) ); @@ -220,7 +234,21 @@ export function initialiseLists( const { fields } = lists[listKey]; return Object.fromEntries( Object.entries(fields).flatMap(([key, field]) => { - if (!field.input?.update?.arg || !field.graphql.isEnabled.update) return []; + if ( + // We're going to skip this field if... + !field.input?.update?.arg || /// The field type doesn't support update, or + !field.graphql.isEnabled.update || // Update has been disabled on this field, or + (field.dbField.kind === 'relation' && // This is a relationship field and the related list... + !( + isEnabled[field.dbField.list].create || // Doesn't support create + // and also + isEnabled[field.dbField.list].query || // Doesn't support uniqueWhere (?) + isEnabled[field.dbField.list].update || + isEnabled[field.dbField.list].delete + )) + ) { + return []; + } return [[key, field.input.update.arg]] as const; }) ); @@ -262,10 +290,26 @@ export function initialiseLists( fields: () => { const list = lists[listKey]; return { - ...(list.access.create !== false && { + // Create via a relationship is only supported if this list allows create + ...(list.graphql.isEnabled.create && { create: graphql.arg({ type: graphql.list(graphql.nonNull(create)) }), }), - connect: graphql.arg({ type: graphql.list(graphql.nonNull(uniqueWhere)) }), + // Connecting to this list (via a uniqueWhere) is only supported if uniqueWhere already exists + ...((list.graphql.isEnabled.query || + list.graphql.isEnabled.update || + list.graphql.isEnabled.delete) && { + connect: graphql.arg({ type: graphql.list(graphql.nonNull(uniqueWhere)) }), + }), + ...(!( + list.graphql.isEnabled.create || + list.graphql.isEnabled.query || + list.graphql.isEnabled.update || + list.graphql.isEnabled.delete + ) && { + // We can't have an empty type, so leave a placeholder. This input type + // will never actually get used, so this will never end up in the GraphQL API. + _unused_placeholder: graphql.arg({ type: graphql.Boolean }), + }), }; }, }); @@ -274,13 +318,28 @@ export function initialiseLists( name: names.relateToManyForUpdateInputName, fields: () => { const list = lists[listKey]; + const _isEnabled = list.graphql.isEnabled; return { - disconnect: graphql.arg({ type: graphql.list(graphql.nonNull(uniqueWhere)) }), - set: graphql.arg({ type: graphql.list(graphql.nonNull(uniqueWhere)) }), - ...(list.access.create !== false && { + // Create via a relationship is only supported if this list allows create + ...(_isEnabled.create && { create: graphql.arg({ type: graphql.list(graphql.nonNull(create)) }), }), - connect: graphql.arg({ type: graphql.list(graphql.nonNull(uniqueWhere)) }), + // Connecting/disconnecting/setting to this list (via a uniqueWhere) is only supported if uniqueWhere already exists + ...((_isEnabled.query || _isEnabled.update || _isEnabled.delete) && { + disconnect: graphql.arg({ type: graphql.list(graphql.nonNull(uniqueWhere)) }), + set: graphql.arg({ type: graphql.list(graphql.nonNull(uniqueWhere)) }), + connect: graphql.arg({ type: graphql.list(graphql.nonNull(uniqueWhere)) }), + }), + ...(!( + _isEnabled.create || + _isEnabled.query || + _isEnabled.update || + _isEnabled.delete + ) && { + // We can't have an empty type, so leave a placeholder. This input type + // will never actually get used, so this will never end up in the GraphQL API. + _unused_placeholder: graphql.arg({ type: graphql.Boolean }), + }), }; }, }); @@ -290,10 +349,26 @@ export function initialiseLists( fields: () => { const list = lists[listKey]; return { - ...(list.access.create !== false && { + // Create via a relationship is only supported if this list allows create + ...(list.graphql.isEnabled.create && { create: graphql.arg({ type: create }), }), - connect: graphql.arg({ type: uniqueWhere }), + // Connecting to this list (via a uniqueWhere) is only supported if uniqueWhere already exists + ...((list.graphql.isEnabled.query || + list.graphql.isEnabled.update || + list.graphql.isEnabled.delete) && { + connect: graphql.arg({ type: uniqueWhere }), + }), + ...(!( + list.graphql.isEnabled.create || + list.graphql.isEnabled.query || + list.graphql.isEnabled.update || + list.graphql.isEnabled.delete + ) && { + // We can't have an empty type, so leave a placeholder. This input type + // will never actually get used, so this will never end up in the GraphQL API. + _unused_placeholder: graphql.arg({ type: graphql.Boolean }), + }), }; }, }); @@ -303,11 +378,27 @@ export function initialiseLists( fields: () => { const list = lists[listKey]; return { - ...(list.access.create !== false && { + // Create via a relationship is only supported if this list allows create + ...(list.graphql.isEnabled.create && { create: graphql.arg({ type: create }), }), - connect: graphql.arg({ type: uniqueWhere }), - disconnect: graphql.arg({ type: graphql.Boolean }), + // Connecting/disconnecting/setting to this list (via a uniqueWhere) is only supported if uniqueWhere already exists + ...((list.graphql.isEnabled.query || + list.graphql.isEnabled.update || + list.graphql.isEnabled.delete) && { + connect: graphql.arg({ type: uniqueWhere }), + disconnect: graphql.arg({ type: graphql.Boolean }), + }), + ...(!( + list.graphql.isEnabled.create || + list.graphql.isEnabled.query || + list.graphql.isEnabled.update || + list.graphql.isEnabled.delete + ) && { + // We can't have an empty type, so leave a placeholder. This input type + // will never actually get used, so this will never end up in the GraphQL API. + _unused_placeholder: graphql.arg({ type: graphql.Boolean }), + }), }; }, }); diff --git a/packages/keystone/src/types/next-fields.ts b/packages/keystone/src/types/next-fields.ts index b362bb1f0cb..7b0054b974d 100644 --- a/packages/keystone/src/types/next-fields.ts +++ b/packages/keystone/src/types/next-fields.ts @@ -400,25 +400,27 @@ export type TypesForList = { none: graphql.Arg; }>; create: graphql.InputObjectType<{ - connect: graphql.Arg>>; + connect?: graphql.Arg>>; create?: graphql.Arg>>; }>; update: graphql.InputObjectType<{ - disconnect: graphql.Arg>>; - set: graphql.Arg>>; - connect: graphql.Arg>>; + disconnect?: graphql.Arg< + graphql.ListType> + >; + set?: graphql.Arg>>; + connect?: graphql.Arg>>; create?: graphql.Arg>>; }>; }; one: { create: graphql.InputObjectType<{ create?: graphql.Arg; - connect: graphql.Arg; + connect?: graphql.Arg; }>; update: graphql.InputObjectType<{ create?: graphql.Arg; - connect: graphql.Arg; - disconnect: graphql.Arg; + connect?: graphql.Arg; + disconnect?: graphql.Arg; }>; }; }; diff --git a/tests/api-tests/access-control/schema-utils.ts b/tests/api-tests/access-control/schema-utils.ts index 6a0f1589be3..c877f6350dd 100644 --- a/tests/api-tests/access-control/schema-utils.ts +++ b/tests/api-tests/access-control/schema-utils.ts @@ -1,4 +1,4 @@ -import { text } from '@keystone-next/keystone/fields'; +import { relationship, text } from '@keystone-next/keystone/fields'; import { createSchema, list } from '@keystone-next/keystone'; import { statelessSessions } from '@keystone-next/keystone/session'; import { apiTestConfig } from '../utils'; @@ -116,6 +116,11 @@ const createFieldStatic = (isEnabled: FieldEnabled) => ({ [getFieldName(isEnabled)]: text({ graphql: { isEnabled } }), }); +const createRelatedFields = (isEnabled: ListEnabled) => ({ + [`${getListPrefix(isEnabled)}one`]: relationship({ ref: getListName(isEnabled), many: false }), + [`${getListPrefix(isEnabled)}many`]: relationship({ ref: getListName(isEnabled), many: true }), +}); + const lists = createSchema({}); listEnabledVariations.forEach(isEnabled => { @@ -128,6 +133,13 @@ listEnabledVariations.forEach(isEnabled => { }); }); +lists.RelatedToAll = list({ + fields: Object.assign( + {}, + ...listEnabledVariations.map(variation => createRelatedFields(variation)) + ), +}); + const config = apiTestConfig({ lists, session: statelessSessions({ secret: COOKIE_SECRET }), diff --git a/tests/api-tests/access-control/schema.test.ts b/tests/api-tests/access-control/schema.test.ts index 44b893416da..e1eafa644b8 100644 --- a/tests/api-tests/access-control/schema.test.ts +++ b/tests/api-tests/access-control/schema.test.ts @@ -39,10 +39,16 @@ describe(`Schema`, () => { let queries: string[], mutations: string[], types: string[], + typesByName: Record, fieldTypes: Record< string, { name: string; fields: Record; inputFields: Record } >; + let __schema: { + types: { name: string; fields: { name: string }[]; inputFields: { name: string }[] }[]; + queryType: { fields: { name: string }[] }; + mutationType: { fields: { name: string }[] }; + }; beforeAll(async () => { testEnv = await setupTestEnv({ config }); context = testEnv.testArgs.context; @@ -50,14 +56,11 @@ describe(`Schema`, () => { await testEnv.connect(); const data = await context.graphql.run({ query: introspectionQuery }); - const __schema: { - types: { name: string; fields: { name: string }[]; inputFields: { name: string }[] }[]; - queryType: { fields: { name: string }[] }; - mutationType: { fields: { name: string }[] }; - } = data.__schema; + __schema = data.__schema; queries = __schema.queryType.fields.map(({ name }) => name); mutations = __schema.mutationType.fields.map(({ name }) => name); types = __schema.types.map(({ name }) => name); + typesByName = Object.fromEntries(__schema.types.map(t => [t.name, t])); fieldTypes = Object.fromEntries( __schema.types.map(type => [ type.name, @@ -87,7 +90,7 @@ describe(`Schema`, () => { if ( isEnabled === undefined || isEnabled === true || - (isEnabled !== false && (isEnabled?.query || isEnabled?.update || isEnabled?.delete)) + (isEnabled !== false && (isEnabled.query || isEnabled.update || isEnabled.delete)) ) { // Filter types are also available for update/delete/create (thanks // to nested mutations) @@ -96,6 +99,77 @@ describe(`Schema`, () => { expect(types).not.toContain(gqlNames.whereUniqueInputName); } + // The relateTo types do not exist if the list has been completely + // disabled, or if all four operations have been individually disabled. + if ( + isEnabled === false || + (isEnabled !== undefined && + isEnabled !== true && + !(isEnabled.query || isEnabled.create || isEnabled.update || isEnabled.delete)) + ) { + expect(types).not.toContain(gqlNames.relateToManyForCreateInputName); + expect(types).not.toContain(gqlNames.relateToOneForCreateInputName); + expect(types).not.toContain(gqlNames.relateToManyForUpdateInputName); + expect(types).not.toContain(gqlNames.relateToOneForUpdateInputName); + } else { + expect(types).toContain(gqlNames.relateToManyForCreateInputName); + expect(types).toContain(gqlNames.relateToOneForCreateInputName); + expect(types).toContain(gqlNames.relateToManyForUpdateInputName); + expect(types).toContain(gqlNames.relateToOneForUpdateInputName); + + const createFromMany = typesByName[ + gqlNames.relateToManyForCreateInputName + ].inputFields.map(({ name }: { name: string }) => name); + const createFromOne = typesByName[gqlNames.relateToOneForCreateInputName].inputFields.map( + ({ name }: { name: string }) => name + ); + const updateFromMany = typesByName[ + gqlNames.relateToManyForUpdateInputName + ].inputFields.map(({ name }: { name: string }) => name); + const updateFromOne = typesByName[gqlNames.relateToOneForUpdateInputName].inputFields.map( + ({ name }: { name: string }) => name + ); + + expect(createFromMany).not.toContain('_unused_placeholder'); + + if (isEnabled === true || isEnabled === undefined || isEnabled.create) { + expect(createFromMany).toContain('create'); + expect(createFromOne).toContain('create'); + expect(updateFromMany).toContain('create'); + expect(updateFromOne).toContain('create'); + } else { + expect(createFromMany).not.toContain('create'); + expect(createFromOne).not.toContain('create'); + expect(updateFromMany).not.toContain('create'); + expect(updateFromOne).not.toContain('create'); + } + // The connect/disconnect/set operations are supported as long as the uniqueWhere + // exists, e.g. one of query/update/delete is supported. + if ( + isEnabled === true || + isEnabled === undefined || + isEnabled.query || + isEnabled.update || + isEnabled.delete + ) { + expect(createFromMany).toContain('connect'); + expect(createFromOne).toContain('connect'); + expect(updateFromMany).toContain('connect'); + expect(updateFromOne).toContain('connect'); + expect(updateFromMany).toContain('disconnect'); + expect(updateFromOne).toContain('disconnect'); + expect(updateFromMany).toContain('set'); + } else { + expect(createFromMany).not.toContain('connect'); + expect(createFromOne).not.toContain('connect'); + expect(updateFromMany).not.toContain('connect'); + expect(updateFromOne).not.toContain('connect'); + expect(updateFromMany).not.toContain('disconnect'); + expect(updateFromOne).not.toContain('disconnect'); + expect(updateFromMany).not.toContain('set'); + } + } + // Queries are only accessible when reading if ( isEnabled === undefined || diff --git a/tests/api-tests/relationships/nested-mutations/create-singular.test.ts b/tests/api-tests/relationships/nested-mutations/create-singular.test.ts index a4b48502e62..5c937e70d1f 100644 --- a/tests/api-tests/relationships/nested-mutations/create-singular.test.ts +++ b/tests/api-tests/relationships/nested-mutations/create-singular.test.ts @@ -38,7 +38,7 @@ const runner = setupTestRunner({ fields: { name: text(), }, - access: { read: false }, + graphql: { isEnabled: { query: false } }, }), EventToGroupNoReadHard: list({ @@ -66,7 +66,7 @@ const runner = setupTestRunner({ fields: { name: text({ graphql: { isEnabled: { filter: true } } }), }, - access: { create: false }, + graphql: { isEnabled: { create: false } }, }), EventToGroupNoCreateHard: list({ @@ -94,7 +94,7 @@ const runner = setupTestRunner({ fields: { name: text(), }, - access: { update: false }, + graphql: { isEnabled: { update: false } }, }), EventToGroupNoUpdateHard: list({ @@ -164,7 +164,7 @@ describe('no access control', () => { describe('with access control', () => { [ { name: 'GroupNoRead', allowed: true, func: 'read: () => false' }, - { name: 'GroupNoReadHard', allowed: true, func: 'read: false' }, + { name: 'GroupNoReadHard', allowed: true, func: 'query: false' }, { name: 'GroupNoCreate', allowed: false, func: 'create: () => false' }, { name: 'GroupNoCreateHard', allowed: false, func: 'create: false' }, { name: 'GroupNoUpdate', allowed: true, func: 'update: () => false' }, diff --git a/tests/test-projects/basic/schema.graphql b/tests/test-projects/basic/schema.graphql index 24200845cd6..2a242b9df0d 100644 --- a/tests/test-projects/basic/schema.graphql +++ b/tests/test-projects/basic/schema.graphql @@ -181,9 +181,9 @@ input PersonUpdateInput { } input TaskRelateToManyForUpdateInput { + create: [TaskCreateInput!] disconnect: [TaskWhereUniqueInput!] set: [TaskWhereUniqueInput!] - create: [TaskCreateInput!] connect: [TaskWhereUniqueInput!] } diff --git a/tests/test-projects/crud-notifications/schema.graphql b/tests/test-projects/crud-notifications/schema.graphql index 24200845cd6..2a242b9df0d 100644 --- a/tests/test-projects/crud-notifications/schema.graphql +++ b/tests/test-projects/crud-notifications/schema.graphql @@ -181,9 +181,9 @@ input PersonUpdateInput { } input TaskRelateToManyForUpdateInput { + create: [TaskCreateInput!] disconnect: [TaskWhereUniqueInput!] set: [TaskWhereUniqueInput!] - create: [TaskCreateInput!] connect: [TaskWhereUniqueInput!] }