From 592fa39401ca128e270082b6d215294d655bfd99 Mon Sep 17 00:00:00 2001 From: Tyler Brockmeyer <95704252+tbrockmeyer-a@users.noreply.github.com> Date: Fri, 7 Oct 2022 12:07:37 -0500 Subject: [PATCH] fix wildcard rules are not reusable (#1459) --- packages/graphql-shield/src/generator.ts | 4 +- .../graphql-shield/tests/generator.test.ts | 85 +++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/packages/graphql-shield/src/generator.ts b/packages/graphql-shield/src/generator.ts index abf925664..a3991ffab 100644 --- a/packages/graphql-shield/src/generator.ts +++ b/packages/graphql-shield/src/generator.ts @@ -139,10 +139,10 @@ function applyRuleToType( /* Extract default type wildcard if any and remove it for validation */ const defaultTypeRule = rules['*'] - delete rules['*'] + const {'*': _, ...rulesWithoutWildcard} = rules; /* Validation */ - const fieldErrors = Object.keys(rules) + const fieldErrors = Object.keys(rulesWithoutWildcard) .filter((type) => !Object.prototype.hasOwnProperty.call(fieldMap, type)) .map((field) => `${type.name}.${field}`) .join(', ') diff --git a/packages/graphql-shield/tests/generator.test.ts b/packages/graphql-shield/tests/generator.test.ts index c795ee11a..9c61b0e69 100644 --- a/packages/graphql-shield/tests/generator.test.ts +++ b/packages/graphql-shield/tests/generator.test.ts @@ -268,4 +268,89 @@ describe('generates correct middleware', () => { expect(defaultQueryMock).toBeCalledTimes(1) expect(defaultTypeMock).toBeCalledTimes(2) }) + + test('correctly allows multiple uses of the same wildcard rule', async () => { + /* Schema */ + + const typeDefs = ` + type Query { + a: String + b: String + type: Type + } + type Type { + field1: String + field2: String + } + ` + + const resolvers = { + Query: { + a: () => 'a', + b: () => 'b', + type: () => ({ + field1: 'field1', + field2: 'field2', + }), + }, + } + + const schema = makeExecutableSchema({ typeDefs, resolvers }) + + /* Permissions */ + + const allowMock = jest.fn().mockResolvedValue(true) + const defaultQueryMock = jest.fn().mockResolvedValue(true) + const defaultTypeMock = jest.fn().mockResolvedValue(true) + + const permissions = shield({ + Query: { + a: rule({ cache: 'no_cache' })(allowMock), + type: rule({ cache: 'no_cache' })(jest.fn().mockResolvedValue(true)), + '*': rule({ cache: 'no_cache' })(defaultQueryMock), + }, + Type: { + '*': rule({ cache: 'no_cache' })(defaultTypeMock), + }, + }) + + /* First usage */ + applyMiddleware(schema, permissions) + + /* Second usage */ + const schemaWithPermissions = applyMiddleware(schema, permissions) + + /* Execution */ + const query = ` + query { + a + b + type { + field1 + field2 + } + } + ` + + const res = await graphql({ + schema: schemaWithPermissions, + source: query, + }) + + /* Tests */ + + expect(res).toEqual({ + data: { + a: 'a', + b: 'b', + type: { + field1: 'field1', + field2: 'field2', + }, + }, + }) + expect(allowMock).toBeCalledTimes(1) + expect(defaultQueryMock).toBeCalledTimes(1) + expect(defaultTypeMock).toBeCalledTimes(2) + }) })