graphql-shield-node23 7.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/cjs/constructors.js +134 -0
  3. package/dist/cjs/generator.js +205 -0
  4. package/dist/cjs/index.js +15 -0
  5. package/dist/cjs/package.json +1 -0
  6. package/dist/cjs/rules.js +402 -0
  7. package/dist/cjs/shield.js +52 -0
  8. package/dist/cjs/types.js +2 -0
  9. package/dist/cjs/utils.js +97 -0
  10. package/dist/cjs/validation.js +84 -0
  11. package/dist/esm/constructors.js +124 -0
  12. package/dist/esm/generator.js +201 -0
  13. package/dist/esm/index.js +2 -0
  14. package/dist/esm/rules.js +366 -0
  15. package/dist/esm/shield.js +45 -0
  16. package/dist/esm/types.js +1 -0
  17. package/dist/esm/utils.js +88 -0
  18. package/dist/esm/validation.js +79 -0
  19. package/dist/package.json +47 -0
  20. package/dist/typings/constructors.d.cts +91 -0
  21. package/dist/typings/constructors.d.ts +91 -0
  22. package/dist/typings/generator.d.cts +11 -0
  23. package/dist/typings/generator.d.ts +11 -0
  24. package/dist/typings/index.d.cts +3 -0
  25. package/dist/typings/index.d.ts +3 -0
  26. package/dist/typings/rules.d.cts +159 -0
  27. package/dist/typings/rules.d.ts +159 -0
  28. package/dist/typings/shield.d.cts +11 -0
  29. package/dist/typings/shield.d.ts +11 -0
  30. package/dist/typings/types.d.cts +64 -0
  31. package/dist/typings/types.d.ts +64 -0
  32. package/dist/typings/utils.d.cts +52 -0
  33. package/dist/typings/utils.d.ts +52 -0
  34. package/dist/typings/validation.d.cts +19 -0
  35. package/dist/typings/validation.d.ts +19 -0
  36. package/package.json +67 -0
  37. package/src/constructors.ts +157 -0
  38. package/src/generator.ts +294 -0
  39. package/src/index.ts +13 -0
  40. package/src/rules.ts +521 -0
  41. package/src/shield.ts +53 -0
  42. package/src/types.ts +94 -0
  43. package/src/utils.ts +101 -0
  44. package/src/validation.ts +90 -0
  45. package/tests/__snapshots__/input.test.ts.snap +7 -0
  46. package/tests/cache.test.ts +545 -0
  47. package/tests/constructors.test.ts +136 -0
  48. package/tests/fallback.test.ts +618 -0
  49. package/tests/fragments.test.ts +113 -0
  50. package/tests/generator.test.ts +356 -0
  51. package/tests/input.test.ts +63 -0
  52. package/tests/integration.test.ts +65 -0
  53. package/tests/logic.test.ts +530 -0
  54. package/tests/utils.test.ts +55 -0
  55. package/tests/validation.test.ts +139 -0
  56. package/tsconfig.json +10 -0
@@ -0,0 +1,545 @@
1
+ import { graphql } from 'graphql'
2
+ import { applyMiddleware } from 'graphql-middleware'
3
+ import { makeExecutableSchema } from '@graphql-tools/schema'
4
+ import { shield, rule } from '../src/index'
5
+ import { IHashFunction } from '../src/types'
6
+
7
+ describe('caching:', () => {
8
+ test('Strict cache - Rule is called multiple times, based on different parent.', async () => {
9
+ /* Schema */
10
+
11
+ const typeDefs = `
12
+ type Query {
13
+ test: [Test!]!
14
+ }
15
+
16
+ type Test {
17
+ value: String!
18
+ }
19
+ `
20
+ const resolvers = {
21
+ Query: {
22
+ test: () => [
23
+ { value: 'pass-A' },
24
+ { value: 'pass-A' },
25
+ { value: 'pass-B' },
26
+ ],
27
+ },
28
+ }
29
+
30
+ const schema = makeExecutableSchema({
31
+ typeDefs,
32
+ resolvers,
33
+ })
34
+
35
+ /* Permissions */
36
+
37
+ const allowMock = jest.fn().mockResolvedValue(true)
38
+ const permissions = shield({
39
+ Test: rule({ cache: 'strict' })(allowMock),
40
+ })
41
+
42
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
43
+
44
+ /* Execution */
45
+
46
+ const query = `
47
+ query {
48
+ test {
49
+ value
50
+ }
51
+ }
52
+ `
53
+ const res = await graphql({
54
+ schema: schemaWithPermissions,
55
+ source: query,
56
+ contextValue: {},
57
+ })
58
+
59
+ expect(res).toEqual({
60
+ data: {
61
+ test: [{ value: 'pass-A' }, { value: 'pass-A' }, { value: 'pass-B' }],
62
+ },
63
+ })
64
+ expect(allowMock).toBeCalledTimes(2)
65
+ })
66
+
67
+ test('Strict cache - Rule is called multiple times, based on different arguments.', async () => {
68
+ /* Schema */
69
+
70
+ const typeDefs = `
71
+ type Query {
72
+ a: String!
73
+ b: String!
74
+ c(arg: String): String!
75
+ d(arg: String): String!
76
+ e(arg: String): String!
77
+ }
78
+ `
79
+ const resolvers = {
80
+ Query: {
81
+ a: () => 'a',
82
+ b: () => 'b',
83
+ c: () => 'c',
84
+ d: () => 'd',
85
+ e: () => 'e',
86
+ },
87
+ }
88
+
89
+ const schema = makeExecutableSchema({
90
+ typeDefs,
91
+ resolvers,
92
+ })
93
+
94
+ /* Tests */
95
+
96
+ const allowMock = jest.fn().mockResolvedValue(true)
97
+
98
+ const permissions = shield({
99
+ Query: rule({ cache: 'strict' })(allowMock),
100
+ })
101
+
102
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
103
+
104
+ /* Execution */
105
+
106
+ const query = `
107
+ query {
108
+ a
109
+ b
110
+ c(arg: "foo")
111
+ d(arg: "bar")
112
+ e
113
+ f: c(arg: "foo")
114
+ }
115
+ `
116
+ const res = await graphql({
117
+ schema: schemaWithPermissions,
118
+ source: query,
119
+ contextValue: {},
120
+ })
121
+
122
+ /* Tests */
123
+
124
+ expect(res).toEqual({
125
+ data: {
126
+ a: 'a',
127
+ b: 'b',
128
+ c: 'c',
129
+ d: 'd',
130
+ e: 'e',
131
+ f: 'c',
132
+ },
133
+ })
134
+ expect(allowMock).toBeCalledTimes(3)
135
+ })
136
+
137
+ test('Contextual Cache - rules get executed only once if reused.', async () => {
138
+ /* Schema */
139
+
140
+ const typeDefs = `
141
+ type Query {
142
+ a: String!
143
+ b: String!
144
+ c(arg: String): String!
145
+ d(arg: String): String!
146
+ e(arg: String): String!
147
+ }
148
+ `
149
+ const resolvers = {
150
+ Query: {
151
+ a: () => 'a',
152
+ b: () => 'b',
153
+ c: () => 'c',
154
+ d: () => 'd',
155
+ e: () => 'e',
156
+ },
157
+ }
158
+
159
+ const schema = makeExecutableSchema({
160
+ typeDefs,
161
+ resolvers,
162
+ })
163
+
164
+ /* Permissions */
165
+
166
+ const ruleOneMock = jest.fn().mockResolvedValue(true)
167
+ const ruleOne = rule({ cache: 'contextual' })(ruleOneMock)
168
+
169
+ const ruleTwoMock = jest.fn().mockResolvedValue(true)
170
+ const ruleTwo = rule({ cache: 'contextual' })(ruleTwoMock)
171
+
172
+ const permissions = shield({
173
+ Query: {
174
+ a: ruleOne,
175
+ b: ruleOne,
176
+ c: ruleOne,
177
+ d: ruleTwo,
178
+ e: ruleTwo,
179
+ },
180
+ })
181
+
182
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
183
+
184
+ // Execution
185
+ const query = `
186
+ query {
187
+ a
188
+ b
189
+ c(arg: "foo")
190
+ d(arg: "bar")
191
+ e
192
+ }
193
+ `
194
+ const res = await graphql({
195
+ schema: schemaWithPermissions,
196
+ source: query,
197
+ contextValue: {},
198
+ })
199
+
200
+ /* Tests */
201
+
202
+ expect(res).toEqual({
203
+ data: {
204
+ a: 'a',
205
+ b: 'b',
206
+ c: 'c',
207
+ d: 'd',
208
+ e: 'e',
209
+ },
210
+ })
211
+ expect(ruleOneMock).toBeCalledTimes(1)
212
+ expect(ruleTwoMock).toBeCalledTimes(1)
213
+ })
214
+
215
+ test('No Cache - rule is reexecuted every time.', async () => {
216
+ /* Schema */
217
+
218
+ const typeDefs = `
219
+ type Query {
220
+ a: String!
221
+ b: String!
222
+ c(arg: String): String!
223
+ d(arg: String): String!
224
+ e(arg: String): String!
225
+ }
226
+ `
227
+ const resolvers = {
228
+ Query: {
229
+ a: () => 'a',
230
+ b: () => 'b',
231
+ c: () => 'c',
232
+ d: () => 'd',
233
+ e: () => 'e',
234
+ },
235
+ }
236
+
237
+ const schema = makeExecutableSchema({
238
+ typeDefs,
239
+ resolvers,
240
+ })
241
+
242
+ /* Permissions */
243
+
244
+ const allowMock = jest.fn().mockResolvedValue(true)
245
+ const allow = rule({ cache: 'no_cache' })(allowMock)
246
+
247
+ const permissions = shield({
248
+ Query: allow,
249
+ })
250
+
251
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
252
+
253
+ /* Execution */
254
+
255
+ const query = `
256
+ query {
257
+ a
258
+ b
259
+ c(arg: "foo")
260
+ d(arg: "bar")
261
+ e
262
+ }
263
+ `
264
+ const res = await graphql({
265
+ schema: schemaWithPermissions,
266
+ source: query,
267
+ contextValue: {},
268
+ })
269
+
270
+ /* Tests */
271
+
272
+ expect(res).toEqual({
273
+ data: {
274
+ a: 'a',
275
+ b: 'b',
276
+ c: 'c',
277
+ d: 'd',
278
+ e: 'e',
279
+ },
280
+ })
281
+ expect(allowMock).toBeCalledTimes(5)
282
+ })
283
+
284
+ test('Custom cache key function - Rule is called based on custom cache key', async () => {
285
+ /* Schema */
286
+ const typeDefs = `
287
+ type Query {
288
+ a(arg: String): String!
289
+ b(arg: String): String!
290
+ }
291
+ `
292
+ const resolvers = {
293
+ Query: {
294
+ a: () => 'a',
295
+ b: () => 'b',
296
+ },
297
+ }
298
+
299
+ const schema = makeExecutableSchema({
300
+ typeDefs,
301
+ resolvers,
302
+ })
303
+
304
+ /* Tests */
305
+
306
+ const allowMock = jest.fn().mockResolvedValue(true)
307
+
308
+ const permissions = shield({
309
+ Query: rule({
310
+ cache: (parent, args, ctx, info) => {
311
+ return JSON.stringify(args)
312
+ },
313
+ })(allowMock),
314
+ })
315
+
316
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
317
+
318
+ /* Execution */
319
+
320
+ const query = `
321
+ query {
322
+ a(arg: "foo")
323
+ b(arg: "bar")
324
+ a2: a(arg: "foo")
325
+ a3: a(arg: "boo")
326
+ }
327
+ `
328
+ const res = await graphql({
329
+ schema: schemaWithPermissions,
330
+ source: query,
331
+ contextValue: {},
332
+ })
333
+
334
+ /* Tests */
335
+
336
+ expect(res).toEqual({
337
+ data: {
338
+ a: 'a',
339
+ b: 'b',
340
+ a2: 'a',
341
+ a3: 'a',
342
+ },
343
+ })
344
+ expect(allowMock).toBeCalledTimes(3)
345
+ })
346
+ })
347
+
348
+ test('Customize hash function', async () => {
349
+ /* Schema */
350
+ const typeDefs = `
351
+ type Query {
352
+ a(arg: String): String!
353
+ b(arg: String): String!
354
+ }
355
+ `
356
+ const resolvers = {
357
+ Query: {
358
+ a: () => 'a',
359
+ b: () => 'b',
360
+ },
361
+ }
362
+
363
+ const schema = makeExecutableSchema({
364
+ typeDefs,
365
+ resolvers,
366
+ })
367
+
368
+ /* Tests */
369
+
370
+ const allowMock = jest.fn().mockResolvedValue(true)
371
+
372
+ const hashFunction: IHashFunction = jest.fn((opts) => JSON.stringify(opts))
373
+
374
+ const permissions = shield(
375
+ {
376
+ Query: rule({ cache: 'strict' })(allowMock),
377
+ },
378
+ {
379
+ hashFunction,
380
+ },
381
+ )
382
+
383
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
384
+
385
+ /* Execution */
386
+
387
+ const query = `
388
+ query {
389
+ a(arg: "foo")
390
+ b(arg: "bar")
391
+ }
392
+ `
393
+ const res = await graphql({
394
+ schema: schemaWithPermissions,
395
+ source: query,
396
+ contextValue: {},
397
+ })
398
+
399
+ /* Tests */
400
+
401
+ expect(res).toEqual({
402
+ data: {
403
+ a: 'a',
404
+ b: 'b',
405
+ },
406
+ })
407
+ expect(allowMock).toBeCalledTimes(2)
408
+ expect(hashFunction).toHaveBeenCalledTimes(2)
409
+ expect(hashFunction).toHaveBeenCalledWith({
410
+ parent: undefined,
411
+ args: { arg: 'foo' },
412
+ })
413
+ expect(hashFunction).toHaveBeenCalledWith({
414
+ parent: undefined,
415
+ args: { arg: 'bar' },
416
+ })
417
+ })
418
+
419
+ describe('legacy cache:', () => {
420
+ test('Strict cache - Rule is called multiple times, based on different parent.', async () => {
421
+ /* Schema */
422
+
423
+ const typeDefs = `
424
+ type Query {
425
+ test: [Test!]!
426
+ }
427
+
428
+ type Test {
429
+ value: String!
430
+ }
431
+ `
432
+ const resolvers = {
433
+ Query: {
434
+ test: () => [
435
+ { value: 'pass-A' },
436
+ { value: 'pass-A' },
437
+ { value: 'pass-B' },
438
+ ],
439
+ },
440
+ }
441
+
442
+ const schema = makeExecutableSchema({
443
+ typeDefs,
444
+ resolvers,
445
+ })
446
+
447
+ /* Permissions */
448
+
449
+ const allowMock = jest.fn().mockResolvedValue(true)
450
+ const permissions = shield({
451
+ Test: rule({ cache: true })(allowMock),
452
+ })
453
+
454
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
455
+
456
+ /* Execution */
457
+
458
+ const query = `
459
+ query {
460
+ test {
461
+ value
462
+ }
463
+ }
464
+ `
465
+ const res = await graphql({
466
+ schema: schemaWithPermissions,
467
+ source: query,
468
+ contextValue: {},
469
+ })
470
+
471
+ /* Tests */
472
+
473
+ expect(res).toEqual({
474
+ data: {
475
+ test: [{ value: 'pass-A' }, { value: 'pass-A' }, { value: 'pass-B' }],
476
+ },
477
+ })
478
+ expect(allowMock).toBeCalledTimes(2)
479
+ })
480
+
481
+ test('No Cache - rule is reexecuted every time.', async () => {
482
+ /* Schema */
483
+
484
+ const typeDefs = `
485
+ type Query {
486
+ a: String!
487
+ b: String!
488
+ c(arg: String): String!
489
+ d(arg: String): String!
490
+ e(arg: String): String!
491
+ }
492
+ `
493
+ const resolvers = {
494
+ Query: {
495
+ a: () => 'a',
496
+ b: () => 'b',
497
+ c: () => 'c',
498
+ d: () => 'd',
499
+ e: () => 'e',
500
+ },
501
+ }
502
+
503
+ const schema = makeExecutableSchema({
504
+ typeDefs,
505
+ resolvers,
506
+ })
507
+
508
+ /* Permissions */
509
+ const allowMock = jest.fn().mockResolvedValue(true)
510
+ const allow = rule({ cache: false })(allowMock)
511
+
512
+ const permissions = shield({
513
+ Query: allow,
514
+ })
515
+
516
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
517
+
518
+ // Execution
519
+ const query = `
520
+ query {
521
+ a
522
+ b
523
+ c(arg: "foo")
524
+ d(arg: "bar")
525
+ e
526
+ }
527
+ `
528
+ const res = await graphql({
529
+ schema: schemaWithPermissions,
530
+ source: query,
531
+ contextValue: {},
532
+ })
533
+
534
+ expect(res).toEqual({
535
+ data: {
536
+ a: 'a',
537
+ b: 'b',
538
+ c: 'c',
539
+ d: 'd',
540
+ e: 'e',
541
+ },
542
+ })
543
+ expect(allowMock).toBeCalledTimes(5)
544
+ })
545
+ })
@@ -0,0 +1,136 @@
1
+ import * as Yup from 'yup'
2
+ import { rule, and, or, not, allow, deny, inputRule, chain } from '../src/constructors'
3
+ import { RuleAnd, RuleOr, RuleNot, RuleTrue, RuleFalse, Rule, InputRule, RuleChain } from '../src/rules'
4
+
5
+ describe('rule constructor', () => {
6
+ beforeEach(() => {
7
+ jest.restoreAllMocks()
8
+ })
9
+
10
+ test('correctly constructs from no arguments', async () => {
11
+ /* Mocks */
12
+
13
+ const n = Math.random()
14
+ jest.spyOn(Math, 'random').mockReturnValue(n)
15
+
16
+ /* Tests */
17
+
18
+ const func = () => true
19
+
20
+ expect(rule()(func)).toEqual(new Rule(n.toString(), func, {}))
21
+ })
22
+
23
+ test('correctly constructs with name and options', async () => {
24
+ const func = () => true
25
+
26
+ expect(
27
+ rule('name', {
28
+ cache: 'contextual',
29
+ fragment: 'fragment',
30
+ })(func),
31
+ ).toEqual(new Rule('name', func, { cache: 'contextual', fragment: 'fragment' }))
32
+ })
33
+
34
+ test('correctly constructs with name but no options', async () => {
35
+ const func = () => true
36
+
37
+ expect(rule('name')(func)).toEqual(new Rule('name', func, {}))
38
+ })
39
+
40
+ test('correctly constructs with options', async () => {
41
+ /* Mocks */
42
+ const n = Math.random()
43
+ jest.spyOn(Math, 'random').mockReturnValue(n)
44
+
45
+ /* Tests */
46
+
47
+ const func = () => true
48
+
49
+ expect(
50
+ rule({
51
+ cache: 'contextual',
52
+ fragment: 'fragment',
53
+ })(func),
54
+ ).toEqual(
55
+ new Rule(n.toString(), func, {
56
+ cache: 'contextual',
57
+ fragment: 'fragment',
58
+ }),
59
+ )
60
+ })
61
+ })
62
+
63
+ describe('input rules constructor', () => {
64
+ test('correnctly constructs an input rule with name', async () => {
65
+ const name = Math.random().toString()
66
+ let schema: Yup.ObjectSchema<{}>
67
+
68
+ const rule = inputRule(name)((yup) => {
69
+ schema = yup.object().shape({}).required()
70
+ return schema
71
+ })
72
+ expect(JSON.stringify(rule)).toEqual(JSON.stringify(new InputRule(name, () => schema)))
73
+ })
74
+
75
+ test('correnctly constructs an input rule', async () => {
76
+ const n = Math.random()
77
+ jest.spyOn(Math, 'random').mockReturnValue(n)
78
+
79
+ let schema: Yup.ObjectSchema<{}>
80
+
81
+ const rule = inputRule()((yup) => {
82
+ schema = yup.object().shape({}).required()
83
+ return schema
84
+ })
85
+ expect(JSON.stringify(rule)).toEqual(JSON.stringify(new InputRule(n.toString(), () => schema)))
86
+ })
87
+
88
+ test('correctly contructs an input rule with validation options', async () => {
89
+ const n = Math.random()
90
+ jest.spyOn(Math, 'random').mockReturnValue(n)
91
+
92
+ let schema: Yup.ObjectSchema<{}>
93
+ let options = { abortEarly: false }
94
+
95
+ const rule = inputRule()((yup) => {
96
+ schema = yup.object().shape({}).required()
97
+ return schema
98
+ }, options)
99
+ expect(JSON.stringify(rule)).toEqual(JSON.stringify(new InputRule(n.toString(), () => schema, options)))
100
+ })
101
+ })
102
+
103
+ describe('logic rules constructors', () => {
104
+ test('and correctly constructs rule and', async () => {
105
+ const ruleA = rule()(() => true)
106
+ const ruleB = rule()(() => true)
107
+ expect(and(ruleA, ruleB)).toEqual(new RuleAnd([ruleA, ruleB]))
108
+ })
109
+
110
+ test('chain correctly constructs rule chain', async () => {
111
+ const ruleA = rule()(() => true)
112
+ const ruleB = rule()(() => true)
113
+ expect(chain(ruleA, ruleB)).toEqual(new RuleChain([ruleA, ruleB]))
114
+ })
115
+
116
+ test('or correctly constructs rule or', async () => {
117
+ const ruleA = rule()(() => true)
118
+ const ruleB = rule()(() => true)
119
+ expect(or(ruleA, ruleB)).toEqual(new RuleOr([ruleA, ruleB]))
120
+ })
121
+
122
+ test('not correctly constructs rule not', async () => {
123
+ const ruleA = rule()(() => true)
124
+ expect(not(ruleA)).toEqual(new RuleNot(ruleA))
125
+ })
126
+ })
127
+
128
+ describe('basic rules', () => {
129
+ test('rule allow', async () => {
130
+ expect(allow).toEqual(new RuleTrue())
131
+ })
132
+
133
+ test('rule deny', async () => {
134
+ expect(deny).toEqual(new RuleFalse())
135
+ })
136
+ })