@expo/entity 0.24.0 → 0.25.0

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 (113) hide show
  1. package/build/ComposedEntityCacheAdapter.d.ts +19 -0
  2. package/build/ComposedEntityCacheAdapter.js +66 -0
  3. package/build/ComposedEntityCacheAdapter.js.map +1 -0
  4. package/build/ComposedSecondaryEntityCache.d.ts +15 -0
  5. package/build/ComposedSecondaryEntityCache.js +37 -0
  6. package/build/ComposedSecondaryEntityCache.js.map +1 -0
  7. package/build/Entity.js +6 -6
  8. package/build/Entity.js.map +1 -1
  9. package/build/EntityAssociationLoader.js +4 -4
  10. package/build/EntityAssociationLoader.js.map +1 -1
  11. package/build/EntityLoader.d.ts +3 -2
  12. package/build/EntityLoader.js +5 -4
  13. package/build/EntityLoader.js.map +1 -1
  14. package/build/EntityLoaderFactory.d.ts +2 -2
  15. package/build/EntityLoaderFactory.js +2 -2
  16. package/build/EntityLoaderFactory.js.map +1 -1
  17. package/build/EntityMutationInfo.d.ts +12 -3
  18. package/build/EntityMutator.d.ts +5 -4
  19. package/build/EntityMutator.js +31 -24
  20. package/build/EntityMutator.js.map +1 -1
  21. package/build/EntityMutatorFactory.d.ts +4 -4
  22. package/build/EntityMutatorFactory.js +6 -6
  23. package/build/EntityMutatorFactory.js.map +1 -1
  24. package/build/EntityPrivacyPolicy.d.ts +15 -4
  25. package/build/EntityPrivacyPolicy.js +14 -14
  26. package/build/EntityPrivacyPolicy.js.map +1 -1
  27. package/build/IEntityGenericCacher.d.ts +2 -2
  28. package/build/ReadonlyEntity.js +1 -1
  29. package/build/ReadonlyEntity.js.map +1 -1
  30. package/build/ViewerScopedEntityLoaderFactory.d.ts +2 -2
  31. package/build/ViewerScopedEntityLoaderFactory.js +2 -2
  32. package/build/ViewerScopedEntityLoaderFactory.js.map +1 -1
  33. package/build/ViewerScopedEntityMutatorFactory.d.ts +4 -4
  34. package/build/ViewerScopedEntityMutatorFactory.js +6 -6
  35. package/build/ViewerScopedEntityMutatorFactory.js.map +1 -1
  36. package/build/__tests__/ComposedCacheAdapter-test.d.ts +1 -0
  37. package/build/__tests__/ComposedCacheAdapter-test.js +198 -0
  38. package/build/__tests__/ComposedCacheAdapter-test.js.map +1 -0
  39. package/build/__tests__/ComposedSecondaryEntityCache-test.d.ts +1 -0
  40. package/build/__tests__/ComposedSecondaryEntityCache-test.js +65 -0
  41. package/build/__tests__/ComposedSecondaryEntityCache-test.js.map +1 -0
  42. package/build/__tests__/EntityCommonUseCases-test.js +1 -1
  43. package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
  44. package/build/__tests__/EntityEdges-test.js +260 -37
  45. package/build/__tests__/EntityEdges-test.js.map +1 -1
  46. package/build/__tests__/EntityLoader-constructor-test.js +2 -1
  47. package/build/__tests__/EntityLoader-constructor-test.js.map +1 -1
  48. package/build/__tests__/EntityLoader-test.js +19 -11
  49. package/build/__tests__/EntityLoader-test.js.map +1 -1
  50. package/build/__tests__/EntityMutator-test.js +96 -43
  51. package/build/__tests__/EntityMutator-test.js.map +1 -1
  52. package/build/__tests__/EntityPrivacyPolicy-test.js +23 -12
  53. package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
  54. package/build/__tests__/EntitySecondaryCacheLoader-test.js +1 -1
  55. package/build/__tests__/EntitySecondaryCacheLoader-test.js.map +1 -1
  56. package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js +3 -2
  57. package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js.map +1 -1
  58. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js +3 -2
  59. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js.map +1 -1
  60. package/build/rules/AlwaysAllowPrivacyPolicyRule.d.ts +2 -1
  61. package/build/rules/AlwaysAllowPrivacyPolicyRule.js +1 -1
  62. package/build/rules/AlwaysAllowPrivacyPolicyRule.js.map +1 -1
  63. package/build/rules/AlwaysDenyPrivacyPolicyRule.d.ts +2 -1
  64. package/build/rules/AlwaysDenyPrivacyPolicyRule.js +1 -1
  65. package/build/rules/AlwaysDenyPrivacyPolicyRule.js.map +1 -1
  66. package/build/rules/AlwaysSkipPrivacyPolicyRule.d.ts +2 -1
  67. package/build/rules/AlwaysSkipPrivacyPolicyRule.js +1 -1
  68. package/build/rules/AlwaysSkipPrivacyPolicyRule.js.map +1 -1
  69. package/build/rules/PrivacyPolicyRule.d.ts +2 -1
  70. package/build/rules/PrivacyPolicyRule.js.map +1 -1
  71. package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js +1 -0
  72. package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js.map +1 -1
  73. package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js +1 -0
  74. package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js.map +1 -1
  75. package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js +1 -0
  76. package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js.map +1 -1
  77. package/build/utils/testing/PrivacyPolicyRuleTestUtils.d.ts +2 -0
  78. package/build/utils/testing/PrivacyPolicyRuleTestUtils.js +6 -6
  79. package/build/utils/testing/PrivacyPolicyRuleTestUtils.js.map +1 -1
  80. package/package.json +1 -1
  81. package/src/ComposedEntityCacheAdapter.ts +86 -0
  82. package/src/ComposedSecondaryEntityCache.ts +63 -0
  83. package/src/Entity.ts +6 -4
  84. package/src/EntityAssociationLoader.ts +4 -4
  85. package/src/EntityLoader.ts +5 -1
  86. package/src/EntityLoaderFactory.ts +4 -2
  87. package/src/EntityMutationInfo.ts +13 -3
  88. package/src/EntityMutator.ts +44 -21
  89. package/src/EntityMutatorFactory.ts +10 -4
  90. package/src/EntityPrivacyPolicy.ts +31 -1
  91. package/src/IEntityGenericCacher.ts +2 -2
  92. package/src/ReadonlyEntity.ts +1 -1
  93. package/src/ViewerScopedEntityLoaderFactory.ts +8 -3
  94. package/src/ViewerScopedEntityMutatorFactory.ts +22 -7
  95. package/src/__tests__/ComposedCacheAdapter-test.ts +280 -0
  96. package/src/__tests__/ComposedSecondaryEntityCache-test.ts +101 -0
  97. package/src/__tests__/EntityCommonUseCases-test.ts +2 -1
  98. package/src/__tests__/EntityEdges-test.ts +286 -45
  99. package/src/__tests__/EntityLoader-constructor-test.ts +3 -1
  100. package/src/__tests__/EntityLoader-test.ts +26 -1
  101. package/src/__tests__/EntityMutator-test.ts +99 -37
  102. package/src/__tests__/EntityPrivacyPolicy-test.ts +66 -7
  103. package/src/__tests__/EntitySecondaryCacheLoader-test.ts +1 -0
  104. package/src/__tests__/ViewerScopedEntityLoaderFactory-test.ts +4 -2
  105. package/src/__tests__/ViewerScopedEntityMutatorFactory-test.ts +6 -2
  106. package/src/rules/AlwaysAllowPrivacyPolicyRule.ts +2 -0
  107. package/src/rules/AlwaysDenyPrivacyPolicyRule.ts +2 -0
  108. package/src/rules/AlwaysSkipPrivacyPolicyRule.ts +2 -0
  109. package/src/rules/PrivacyPolicyRule.ts +2 -0
  110. package/src/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.ts +2 -0
  111. package/src/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.ts +2 -0
  112. package/src/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.ts +2 -0
  113. package/src/utils/testing/PrivacyPolicyRuleTestUtils.ts +14 -6
@@ -5,10 +5,13 @@ import { EntityEdgeDeletionBehavior } from '../EntityFieldDefinition';
5
5
  import { UUIDField } from '../EntityFields';
6
6
  import { EntityTriggerMutationInfo, EntityMutationType } from '../EntityMutationInfo';
7
7
  import { EntityMutationTrigger } from '../EntityMutationTriggerConfiguration';
8
- import EntityPrivacyPolicy from '../EntityPrivacyPolicy';
9
- import { EntityTransactionalQueryContext } from '../EntityQueryContext';
8
+ import EntityPrivacyPolicy, {
9
+ EntityPrivacyPolicyEvaluationContext,
10
+ EntityAuthorizationAction,
11
+ } from '../EntityPrivacyPolicy';
12
+ import { EntityTransactionalQueryContext, EntityQueryContext } from '../EntityQueryContext';
10
13
  import { CacheStatus } from '../internal/ReadThroughEntityCache';
11
- import AlwaysAllowPrivacyPolicyRule from '../rules/AlwaysAllowPrivacyPolicyRule';
14
+ import PrivacyPolicyRule, { RuleEvaluationResult } from '../rules/PrivacyPolicyRule';
12
15
  import TestViewerContext from '../testfixtures/TestViewerContext';
13
16
  import { InMemoryFullCacheStubCacheAdapter } from '../utils/testing/StubCacheAdapter';
14
17
  import { createUnitTestEntityCompanionProvider } from '../utils/testing/createUnitTestEntityCompanionProvider';
@@ -27,35 +30,83 @@ interface GrandChildFields {
27
30
  parent_id: string;
28
31
  }
29
32
 
30
- class TestEntityPrivacyPolicy extends EntityPrivacyPolicy<
31
- any,
32
- string,
33
- TestViewerContext,
34
- any,
35
- any
36
- > {
37
- protected override readonly readRules = [
38
- new AlwaysAllowPrivacyPolicyRule<any, string, TestViewerContext, any, any>(),
39
- ];
40
- protected override readonly createRules = [
41
- new AlwaysAllowPrivacyPolicyRule<any, string, TestViewerContext, any, any>(),
42
- ];
43
- protected override readonly updateRules = [
44
- new AlwaysAllowPrivacyPolicyRule<any, string, TestViewerContext, any, any>(),
45
- ];
46
- protected override readonly deleteRules = [
47
- new AlwaysAllowPrivacyPolicyRule<any, string, TestViewerContext, any, any>(),
48
- ];
49
- }
50
-
51
33
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
52
34
  const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) => {
53
35
  const triggerExecutionCounts = {
54
- parent: 0,
55
- child: 0,
56
- grandchild: 0,
36
+ ParentEntity: 0,
37
+ ChildEntity: 0,
38
+ GrandChildEntity: 0,
39
+ };
40
+
41
+ const privacyPolicyEvaluationRecords = {
42
+ shouldRecord: false,
43
+ ParentEntity: {
44
+ [EntityAuthorizationAction.CREATE]: [],
45
+ [EntityAuthorizationAction.READ]: [],
46
+ [EntityAuthorizationAction.UPDATE]: [],
47
+ [EntityAuthorizationAction.DELETE]: [],
48
+ },
49
+ ChildEntity: {
50
+ [EntityAuthorizationAction.CREATE]: [],
51
+ [EntityAuthorizationAction.READ]: [],
52
+ [EntityAuthorizationAction.UPDATE]: [],
53
+ [EntityAuthorizationAction.DELETE]: [],
54
+ },
55
+ GrandChildEntity: {
56
+ [EntityAuthorizationAction.CREATE]: [],
57
+ [EntityAuthorizationAction.READ]: [],
58
+ [EntityAuthorizationAction.UPDATE]: [],
59
+ [EntityAuthorizationAction.DELETE]: [],
60
+ },
57
61
  };
58
62
 
63
+ class AlwaysAllowPrivacyPolicyRuleThatRecords extends PrivacyPolicyRule<
64
+ any,
65
+ string,
66
+ TestViewerContext,
67
+ any,
68
+ any
69
+ > {
70
+ constructor(private readonly action: EntityAuthorizationAction) {
71
+ super();
72
+ }
73
+
74
+ async evaluateAsync(
75
+ _viewerContext: TestViewerContext,
76
+ _queryContext: EntityQueryContext,
77
+ evaluationContext: EntityPrivacyPolicyEvaluationContext,
78
+ entity: any
79
+ ): Promise<RuleEvaluationResult> {
80
+ if (privacyPolicyEvaluationRecords.shouldRecord) {
81
+ (privacyPolicyEvaluationRecords as any)[entity.constructor.name][this.action].push(
82
+ evaluationContext
83
+ );
84
+ }
85
+ return RuleEvaluationResult.ALLOW;
86
+ }
87
+ }
88
+
89
+ class TestEntityPrivacyPolicy extends EntityPrivacyPolicy<
90
+ any,
91
+ string,
92
+ TestViewerContext,
93
+ any,
94
+ any
95
+ > {
96
+ protected override readonly readRules = [
97
+ new AlwaysAllowPrivacyPolicyRuleThatRecords(EntityAuthorizationAction.READ),
98
+ ];
99
+ protected override readonly createRules = [
100
+ new AlwaysAllowPrivacyPolicyRuleThatRecords(EntityAuthorizationAction.CREATE),
101
+ ];
102
+ protected override readonly updateRules = [
103
+ new AlwaysAllowPrivacyPolicyRuleThatRecords(EntityAuthorizationAction.UPDATE),
104
+ ];
105
+ protected override readonly deleteRules = [
106
+ new AlwaysAllowPrivacyPolicyRuleThatRecords(EntityAuthorizationAction.DELETE),
107
+ ];
108
+ }
109
+
59
110
  class ParentCheckInfoTrigger extends EntityMutationTrigger<
60
111
  ParentFields,
61
112
  string,
@@ -76,7 +127,7 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
76
127
  throw new Error('Parent entity should not have casade delete cause');
77
128
  }
78
129
 
79
- triggerExecutionCounts.parent++;
130
+ triggerExecutionCounts.ParentEntity++;
80
131
  }
81
132
  }
82
133
 
@@ -111,7 +162,7 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
111
162
  throw new Error('Child entity should not have two-level casade delete cause');
112
163
  }
113
164
 
114
- triggerExecutionCounts.child++;
165
+ triggerExecutionCounts.ChildEntity++;
115
166
  }
116
167
  }
117
168
 
@@ -165,7 +216,7 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
165
216
  throw new Error('GrandChild entity should not have three-level casade delete cause');
166
217
  }
167
218
 
168
- triggerExecutionCounts.grandchild++;
219
+ triggerExecutionCounts.GrandChildEntity++;
169
220
  }
170
221
  }
171
222
 
@@ -297,14 +348,20 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
297
348
  ChildEntity,
298
349
  GrandChildEntity,
299
350
  triggerExecutionCounts,
351
+ privacyPolicyEvaluationRecords,
300
352
  };
301
353
  };
302
354
 
303
355
  describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
304
356
  describe('EntityEdgeDeletionBehavior.CASCADE_DELETE', () => {
305
357
  it('deletes', async () => {
306
- const { ParentEntity, ChildEntity, GrandChildEntity, triggerExecutionCounts } =
307
- makeEntityClasses(EntityEdgeDeletionBehavior.CASCADE_DELETE);
358
+ const {
359
+ ParentEntity,
360
+ ChildEntity,
361
+ GrandChildEntity,
362
+ triggerExecutionCounts,
363
+ privacyPolicyEvaluationRecords,
364
+ } = makeEntityClasses(EntityEdgeDeletionBehavior.CASCADE_DELETE);
308
365
  const companionProvider = createUnitTestEntityCompanionProvider();
309
366
  const viewerContext = new TestViewerContext(companionProvider);
310
367
 
@@ -326,7 +383,9 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
326
383
  GrandChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(grandchild.getID())
327
384
  ).resolves.not.toBeNull();
328
385
 
386
+ privacyPolicyEvaluationRecords.shouldRecord = true;
329
387
  await ParentEntity.enforceDeleteAsync(parent);
388
+ privacyPolicyEvaluationRecords.shouldRecord = false;
330
389
 
331
390
  await expect(
332
391
  ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID())
@@ -340,17 +399,82 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
340
399
 
341
400
  // two calls for each trigger, one beforeDelete, one afterDelete
342
401
  expect(triggerExecutionCounts).toMatchObject({
343
- parent: 2,
344
- child: 2,
345
- grandchild: 2,
402
+ ParentEntity: 2,
403
+ ChildEntity: 2,
404
+ GrandChildEntity: 2,
405
+ });
406
+
407
+ expect(privacyPolicyEvaluationRecords).toMatchObject({
408
+ ParentEntity: {
409
+ [EntityAuthorizationAction.CREATE]: [],
410
+ [EntityAuthorizationAction.READ]: [],
411
+ [EntityAuthorizationAction.UPDATE]: [],
412
+ // one DELETE auth action for parent (since it's being deleted)
413
+ [EntityAuthorizationAction.DELETE]: [{ cascadingDeleteCause: null }],
414
+ },
415
+ ChildEntity: {
416
+ [EntityAuthorizationAction.CREATE]: [],
417
+ // one READ auth action for child in order to delete via cascade
418
+ [EntityAuthorizationAction.READ]: [
419
+ {
420
+ cascadingDeleteCause: {
421
+ entity: expect.any(ParentEntity),
422
+ cascadingDeleteCause: null,
423
+ },
424
+ },
425
+ ],
426
+ [EntityAuthorizationAction.UPDATE]: [],
427
+ // one DELETE auth action for child (since it's being deleted via cascade)
428
+ [EntityAuthorizationAction.DELETE]: [
429
+ {
430
+ cascadingDeleteCause: {
431
+ entity: expect.any(ParentEntity),
432
+ cascadingDeleteCause: null,
433
+ },
434
+ },
435
+ ],
436
+ },
437
+ GrandChildEntity: {
438
+ [EntityAuthorizationAction.CREATE]: [],
439
+ // one READ auth action for grandchild in order to delete via cascade
440
+ [EntityAuthorizationAction.READ]: [
441
+ {
442
+ cascadingDeleteCause: {
443
+ entity: expect.any(ChildEntity),
444
+ cascadingDeleteCause: {
445
+ entity: expect.any(ParentEntity),
446
+ cascadingDeleteCause: null,
447
+ },
448
+ },
449
+ },
450
+ ],
451
+ [EntityAuthorizationAction.UPDATE]: [],
452
+ // one DELETE auth action for grandchild (since it's being deleted via cascade)
453
+ [EntityAuthorizationAction.DELETE]: [
454
+ {
455
+ cascadingDeleteCause: {
456
+ entity: expect.any(ChildEntity),
457
+ cascadingDeleteCause: {
458
+ entity: expect.any(ParentEntity),
459
+ cascadingDeleteCause: null,
460
+ },
461
+ },
462
+ },
463
+ ],
464
+ },
346
465
  });
347
466
  });
348
467
  });
349
468
 
350
469
  describe('EntityEdgeDeletionBehavior.SET_NULL', () => {
351
470
  it('sets null', async () => {
352
- const { ParentEntity, ChildEntity, GrandChildEntity, triggerExecutionCounts } =
353
- makeEntityClasses(EntityEdgeDeletionBehavior.SET_NULL);
471
+ const {
472
+ ParentEntity,
473
+ ChildEntity,
474
+ GrandChildEntity,
475
+ triggerExecutionCounts,
476
+ privacyPolicyEvaluationRecords,
477
+ } = makeEntityClasses(EntityEdgeDeletionBehavior.SET_NULL);
354
478
 
355
479
  const companionProvider = createUnitTestEntityCompanionProvider();
356
480
  const viewerContext = new TestViewerContext(companionProvider);
@@ -373,7 +497,9 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
373
497
  GrandChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(grandchild.getID())
374
498
  ).resolves.not.toBeNull();
375
499
 
500
+ privacyPolicyEvaluationRecords.shouldRecord = true;
376
501
  await ParentEntity.enforceDeleteAsync(parent);
502
+ privacyPolicyEvaluationRecords.shouldRecord = false;
377
503
 
378
504
  await expect(
379
505
  ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID())
@@ -392,17 +518,70 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
392
518
  // two calls for only parent trigger, one beforeDelete, one afterDelete
393
519
  // when using set null the descendants aren't deleted
394
520
  expect(triggerExecutionCounts).toMatchObject({
395
- parent: 2,
396
- child: 0,
397
- grandchild: 0,
521
+ ParentEntity: 2,
522
+ ChildEntity: 0,
523
+ GrandChildEntity: 0,
524
+ });
525
+
526
+ expect(privacyPolicyEvaluationRecords).toMatchObject({
527
+ ParentEntity: {
528
+ [EntityAuthorizationAction.CREATE]: [],
529
+ [EntityAuthorizationAction.READ]: [],
530
+ [EntityAuthorizationAction.UPDATE]: [],
531
+ // one DELETE auth action for parent (since it's being deleted)
532
+ [EntityAuthorizationAction.DELETE]: [{ cascadingDeleteCause: null }],
533
+ },
534
+ ChildEntity: {
535
+ [EntityAuthorizationAction.CREATE]: [],
536
+
537
+ // two READs auth action for Child during parent deletion:
538
+ // 1. Read to initiate the SET_NULL (to update the entity)
539
+ // 1. Read automatically post-update
540
+ // no other entities are read since it is not cascaded past first entity
541
+ [EntityAuthorizationAction.READ]: [
542
+ {
543
+ cascadingDeleteCause: {
544
+ entity: expect.any(ParentEntity),
545
+ cascadingDeleteCause: null,
546
+ },
547
+ },
548
+ {
549
+ cascadingDeleteCause: {
550
+ entity: expect.any(ParentEntity),
551
+ cascadingDeleteCause: null,
552
+ },
553
+ },
554
+ ],
555
+ // one UPDATE to set null
556
+ [EntityAuthorizationAction.UPDATE]: [
557
+ {
558
+ cascadingDeleteCause: {
559
+ entity: expect.any(ParentEntity),
560
+ cascadingDeleteCause: null,
561
+ },
562
+ },
563
+ ],
564
+ [EntityAuthorizationAction.DELETE]: [],
565
+ },
566
+ GrandChildEntity: {
567
+ [EntityAuthorizationAction.CREATE]: [],
568
+ [EntityAuthorizationAction.READ]: [],
569
+ [EntityAuthorizationAction.UPDATE]: [],
570
+ [EntityAuthorizationAction.DELETE]: [],
571
+ },
398
572
  });
399
573
  });
400
574
  });
401
575
 
402
576
  describe('EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE', () => {
403
577
  it('invalidates the cache', async () => {
404
- const { ParentEntity, ChildEntity, GrandChildEntity, triggerExecutionCounts } =
405
- makeEntityClasses(EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE);
578
+ const {
579
+ ParentEntity,
580
+ ChildEntity,
581
+ GrandChildEntity,
582
+ triggerExecutionCounts,
583
+ privacyPolicyEvaluationRecords,
584
+ } = makeEntityClasses(EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE);
406
585
 
407
586
  const companionProvider = createUnitTestEntityCompanionProvider();
408
587
  const viewerContext = new TestViewerContext(companionProvider);
@@ -447,7 +626,9 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
447
626
  ]);
448
627
  expect(grandChildCachedBefore.get(child.getID())?.status).toEqual(CacheStatus.HIT);
449
628
 
629
+ privacyPolicyEvaluationRecords.shouldRecord = true;
450
630
  await ParentEntity.enforceDeleteAsync(parent);
631
+ privacyPolicyEvaluationRecords.shouldRecord = false;
451
632
 
452
633
  const childCachedAfter = await childCacheAdapter.loadManyAsync('parent_id', [parent.getID()]);
453
634
  expect(childCachedAfter.get(parent.getID())?.status).toEqual(CacheStatus.MISS);
@@ -469,9 +650,69 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
469
650
 
470
651
  // two calls for each trigger, one beforeDelete, one afterDelete
471
652
  expect(triggerExecutionCounts).toMatchObject({
472
- parent: 2,
473
- child: 2,
474
- grandchild: 2,
653
+ ParentEntity: 2,
654
+ ChildEntity: 2,
655
+ GrandChildEntity: 2,
656
+ });
657
+
658
+ expect(privacyPolicyEvaluationRecords).toMatchObject({
659
+ ParentEntity: {
660
+ [EntityAuthorizationAction.CREATE]: [],
661
+ [EntityAuthorizationAction.READ]: [],
662
+ [EntityAuthorizationAction.UPDATE]: [],
663
+ // one DELETE auth action for parent (since it's being deleted)
664
+ [EntityAuthorizationAction.DELETE]: [{ cascadingDeleteCause: null }],
665
+ },
666
+ ChildEntity: {
667
+ [EntityAuthorizationAction.CREATE]: [],
668
+ // one READ auth action for child in order to delete via cascade
669
+ [EntityAuthorizationAction.READ]: [
670
+ {
671
+ cascadingDeleteCause: {
672
+ entity: expect.any(ParentEntity),
673
+ cascadingDeleteCause: null,
674
+ },
675
+ },
676
+ ],
677
+ [EntityAuthorizationAction.UPDATE]: [],
678
+ // one DELETE auth action for child (since it's being deleted via cascade)
679
+ [EntityAuthorizationAction.DELETE]: [
680
+ {
681
+ cascadingDeleteCause: {
682
+ entity: expect.any(ParentEntity),
683
+ cascadingDeleteCause: null,
684
+ },
685
+ },
686
+ ],
687
+ },
688
+ GrandChildEntity: {
689
+ [EntityAuthorizationAction.CREATE]: [],
690
+ // one READ auth action for grandchild in order to delete via cascade
691
+ [EntityAuthorizationAction.READ]: [
692
+ {
693
+ cascadingDeleteCause: {
694
+ entity: expect.any(ChildEntity),
695
+ cascadingDeleteCause: {
696
+ entity: expect.any(ParentEntity),
697
+ cascadingDeleteCause: null,
698
+ },
699
+ },
700
+ },
701
+ ],
702
+ [EntityAuthorizationAction.UPDATE]: [],
703
+ // one DELETE auth action for grandchild (since it's being deleted via cascade)
704
+ [EntityAuthorizationAction.DELETE]: [
705
+ {
706
+ cascadingDeleteCause: {
707
+ entity: expect.any(ChildEntity),
708
+ cascadingDeleteCause: {
709
+ entity: expect.any(ParentEntity),
710
+ cascadingDeleteCause: null,
711
+ },
712
+ },
713
+ },
714
+ ],
715
+ },
475
716
  });
476
717
  });
477
718
  });
@@ -5,7 +5,7 @@ import { EntityCompanionDefinition } from '../EntityCompanionProvider';
5
5
  import EntityConfiguration from '../EntityConfiguration';
6
6
  import { StringField } from '../EntityFields';
7
7
  import EntityLoader from '../EntityLoader';
8
- import EntityPrivacyPolicy from '../EntityPrivacyPolicy';
8
+ import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
9
9
  import ViewerContext from '../ViewerContext';
10
10
  import EntityDataManager from '../internal/EntityDataManager';
11
11
  import ReadThroughEntityCache from '../internal/ReadThroughEntityCache';
@@ -118,6 +118,7 @@ export const testEntityCompanion = new EntityCompanionDefinition({
118
118
  describe(EntityLoader, () => {
119
119
  it('handles thrown errors and literals from constructor', async () => {
120
120
  const viewerContext = instance(mock(ViewerContext));
121
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
121
122
  const metricsAdapter = instance(mock<IEntityMetricsAdapter>());
122
123
  const queryContext = StubQueryContextProvider.getQueryContext();
123
124
 
@@ -154,6 +155,7 @@ describe(EntityLoader, () => {
154
155
  const entityLoader = new EntityLoader(
155
156
  viewerContext,
156
157
  queryContext,
158
+ privacyPolicyEvaluationContext,
157
159
  testEntityConfiguration,
158
160
  TestEntity,
159
161
  privacyPolicy,
@@ -3,6 +3,7 @@ import { mock, instance, verify, spy, deepEqual, anyOfClass, anything, when } fr
3
3
  import { v4 as uuidv4 } from 'uuid';
4
4
 
5
5
  import EntityLoader from '../EntityLoader';
6
+ import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
6
7
  import ViewerContext from '../ViewerContext';
7
8
  import { enforceResultsAsync } from '../entityUtils';
8
9
  import EntityDataManager from '../internal/EntityDataManager';
@@ -21,6 +22,7 @@ describe(EntityLoader, () => {
21
22
  it('loads entities', async () => {
22
23
  const dateToInsert = new Date();
23
24
  const viewerContext = instance(mock(ViewerContext));
25
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
24
26
  const metricsAdapter = instance(mock<IEntityMetricsAdapter>());
25
27
  const queryContext = StubQueryContextProvider.getQueryContext();
26
28
 
@@ -69,6 +71,7 @@ describe(EntityLoader, () => {
69
71
  const entityLoader = new EntityLoader(
70
72
  viewerContext,
71
73
  queryContext,
74
+ privacyPolicyEvaluationContext,
72
75
  testEntityConfiguration,
73
76
  TestEntity,
74
77
  privacyPolicy,
@@ -113,6 +116,7 @@ describe(EntityLoader, () => {
113
116
  const privacyPolicy = new TestEntityPrivacyPolicy();
114
117
  const spiedPrivacyPolicy = spy(privacyPolicy);
115
118
  const viewerContext = instance(mock(ViewerContext));
119
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
116
120
  const metricsAdapter = instance(mock<IEntityMetricsAdapter>());
117
121
  const queryContext = StubQueryContextProvider.getQueryContext();
118
122
 
@@ -169,6 +173,7 @@ describe(EntityLoader, () => {
169
173
  const entityLoader = new EntityLoader(
170
174
  viewerContext,
171
175
  queryContext,
176
+ privacyPolicyEvaluationContext,
172
177
  testEntityConfiguration,
173
178
  TestEntity,
174
179
  privacyPolicy,
@@ -192,6 +197,7 @@ describe(EntityLoader, () => {
192
197
  spiedPrivacyPolicy.authorizeReadAsync(
193
198
  viewerContext,
194
199
  queryContext,
200
+ privacyPolicyEvaluationContext,
195
201
  anyOfClass(TestEntity),
196
202
  anything()
197
203
  )
@@ -209,6 +215,7 @@ describe(EntityLoader, () => {
209
215
  const spiedPrivacyPolicy = spy(privacyPolicy);
210
216
 
211
217
  const viewerContext = instance(mock(ViewerContext));
218
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
212
219
  const metricsAdapter = instance(mock<IEntityMetricsAdapter>());
213
220
  const queryContext = StubQueryContextProvider.getQueryContext();
214
221
 
@@ -247,6 +254,7 @@ describe(EntityLoader, () => {
247
254
  const entityLoader = new EntityLoader(
248
255
  viewerContext,
249
256
  queryContext,
257
+ privacyPolicyEvaluationContext,
250
258
  testEntityConfiguration,
251
259
  TestEntity,
252
260
  privacyPolicy,
@@ -255,12 +263,19 @@ describe(EntityLoader, () => {
255
263
  );
256
264
  const entity = await enforceAsyncResult(entityLoader.loadByIDAsync(id1));
257
265
  verify(
258
- spiedPrivacyPolicy.authorizeReadAsync(viewerContext, queryContext, entity, anything())
266
+ spiedPrivacyPolicy.authorizeReadAsync(
267
+ viewerContext,
268
+ queryContext,
269
+ privacyPolicyEvaluationContext,
270
+ entity,
271
+ anything()
272
+ )
259
273
  ).once();
260
274
  });
261
275
 
262
276
  it('invalidates upon invalidate one', async () => {
263
277
  const viewerContext = instance(mock(ViewerContext));
278
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
264
279
  const metricsAdapter = instance(mock<IEntityMetricsAdapter>());
265
280
  const queryContext = StubQueryContextProvider.getQueryContext();
266
281
  const privacyPolicy = instance(mock(TestEntityPrivacyPolicy));
@@ -271,6 +286,7 @@ describe(EntityLoader, () => {
271
286
  const entityLoader = new EntityLoader(
272
287
  viewerContext,
273
288
  queryContext,
289
+ privacyPolicyEvaluationContext,
274
290
  testEntityConfiguration,
275
291
  TestEntity,
276
292
  privacyPolicy,
@@ -286,6 +302,7 @@ describe(EntityLoader, () => {
286
302
 
287
303
  it('invalidates upon invalidate by field', async () => {
288
304
  const viewerContext = instance(mock(ViewerContext));
305
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
289
306
  const metricsAdapter = instance(mock<IEntityMetricsAdapter>());
290
307
  const queryContext = StubQueryContextProvider.getQueryContext();
291
308
  const privacyPolicy = instance(mock(TestEntityPrivacyPolicy));
@@ -296,6 +313,7 @@ describe(EntityLoader, () => {
296
313
  const entityLoader = new EntityLoader(
297
314
  viewerContext,
298
315
  queryContext,
316
+ privacyPolicyEvaluationContext,
299
317
  testEntityConfiguration,
300
318
  TestEntity,
301
319
  privacyPolicy,
@@ -310,6 +328,7 @@ describe(EntityLoader, () => {
310
328
 
311
329
  it('invalidates upon invalidate by entity', async () => {
312
330
  const viewerContext = instance(mock(ViewerContext));
331
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
313
332
  const metricsAdapter = instance(mock<IEntityMetricsAdapter>());
314
333
  const queryContext = StubQueryContextProvider.getQueryContext();
315
334
  const privacyPolicy = instance(mock(TestEntityPrivacyPolicy));
@@ -324,6 +343,7 @@ describe(EntityLoader, () => {
324
343
  const entityLoader = new EntityLoader(
325
344
  viewerContext,
326
345
  queryContext,
346
+ privacyPolicyEvaluationContext,
327
347
  testEntityConfiguration,
328
348
  TestEntity,
329
349
  privacyPolicy,
@@ -338,6 +358,7 @@ describe(EntityLoader, () => {
338
358
 
339
359
  it('returns error result when not allowed', async () => {
340
360
  const viewerContext = instance(mock(ViewerContext));
361
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
341
362
  const metricsAdapter = instance(mock<IEntityMetricsAdapter>());
342
363
  const queryContext = StubQueryContextProvider.getQueryContext();
343
364
  const privacyPolicyMock = mock(TestEntityPrivacyPolicy);
@@ -354,6 +375,7 @@ describe(EntityLoader, () => {
354
375
  privacyPolicyMock.authorizeReadAsync(
355
376
  viewerContext,
356
377
  queryContext,
378
+ privacyPolicyEvaluationContext,
357
379
  anyOfClass(TestEntity),
358
380
  anything()
359
381
  )
@@ -365,6 +387,7 @@ describe(EntityLoader, () => {
365
387
  const entityLoader = new EntityLoader(
366
388
  viewerContext,
367
389
  queryContext,
390
+ privacyPolicyEvaluationContext,
368
391
  testEntityConfiguration,
369
392
  TestEntity,
370
393
  privacyPolicy,
@@ -380,6 +403,7 @@ describe(EntityLoader, () => {
380
403
 
381
404
  it('throws upon database adapter error', async () => {
382
405
  const viewerContext = instance(mock(ViewerContext));
406
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
383
407
  const metricsAdapter = instance(mock<IEntityMetricsAdapter>());
384
408
  const queryContext = StubQueryContextProvider.getQueryContext();
385
409
  const privacyPolicy = instance(mock(TestEntityPrivacyPolicy));
@@ -396,6 +420,7 @@ describe(EntityLoader, () => {
396
420
  const entityLoader = new EntityLoader(
397
421
  viewerContext,
398
422
  queryContext,
423
+ privacyPolicyEvaluationContext,
399
424
  testEntityConfiguration,
400
425
  TestEntity,
401
426
  privacyPolicy,