@expo/entity 0.24.0 → 0.25.2
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.
- package/build/ComposedEntityCacheAdapter.d.ts +19 -0
- package/build/ComposedEntityCacheAdapter.js +66 -0
- package/build/ComposedEntityCacheAdapter.js.map +1 -0
- package/build/ComposedSecondaryEntityCache.d.ts +15 -0
- package/build/ComposedSecondaryEntityCache.js +37 -0
- package/build/ComposedSecondaryEntityCache.js.map +1 -0
- package/build/Entity.js +6 -6
- package/build/Entity.js.map +1 -1
- package/build/EntityAssociationLoader.js +4 -4
- package/build/EntityAssociationLoader.js.map +1 -1
- package/build/EntityLoader.d.ts +3 -2
- package/build/EntityLoader.js +5 -4
- package/build/EntityLoader.js.map +1 -1
- package/build/EntityLoaderFactory.d.ts +2 -2
- package/build/EntityLoaderFactory.js +2 -2
- package/build/EntityLoaderFactory.js.map +1 -1
- package/build/EntityMutationInfo.d.ts +12 -3
- package/build/EntityMutator.d.ts +5 -4
- package/build/EntityMutator.js +31 -24
- package/build/EntityMutator.js.map +1 -1
- package/build/EntityMutatorFactory.d.ts +4 -4
- package/build/EntityMutatorFactory.js +6 -6
- package/build/EntityMutatorFactory.js.map +1 -1
- package/build/EntityPrivacyPolicy.d.ts +15 -4
- package/build/EntityPrivacyPolicy.js +14 -14
- package/build/EntityPrivacyPolicy.js.map +1 -1
- package/build/IEntityGenericCacher.d.ts +2 -2
- package/build/ReadonlyEntity.js +1 -1
- package/build/ReadonlyEntity.js.map +1 -1
- package/build/ViewerScopedEntityLoaderFactory.d.ts +2 -2
- package/build/ViewerScopedEntityLoaderFactory.js +2 -2
- package/build/ViewerScopedEntityLoaderFactory.js.map +1 -1
- package/build/ViewerScopedEntityMutatorFactory.d.ts +4 -4
- package/build/ViewerScopedEntityMutatorFactory.js +6 -6
- package/build/ViewerScopedEntityMutatorFactory.js.map +1 -1
- package/build/__tests__/ComposedCacheAdapter-test.d.ts +1 -0
- package/build/__tests__/ComposedCacheAdapter-test.js +198 -0
- package/build/__tests__/ComposedCacheAdapter-test.js.map +1 -0
- package/build/__tests__/ComposedSecondaryEntityCache-test.d.ts +1 -0
- package/build/__tests__/ComposedSecondaryEntityCache-test.js +65 -0
- package/build/__tests__/ComposedSecondaryEntityCache-test.js.map +1 -0
- package/build/__tests__/EntityCommonUseCases-test.js +1 -1
- package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
- package/build/__tests__/EntityEdges-test.js +260 -37
- package/build/__tests__/EntityEdges-test.js.map +1 -1
- package/build/__tests__/EntityLoader-constructor-test.js +2 -1
- package/build/__tests__/EntityLoader-constructor-test.js.map +1 -1
- package/build/__tests__/EntityLoader-test.js +19 -11
- package/build/__tests__/EntityLoader-test.js.map +1 -1
- package/build/__tests__/EntityMutator-test.js +96 -43
- package/build/__tests__/EntityMutator-test.js.map +1 -1
- package/build/__tests__/EntityPrivacyPolicy-test.js +23 -12
- package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
- package/build/__tests__/EntitySecondaryCacheLoader-test.js +1 -1
- package/build/__tests__/EntitySecondaryCacheLoader-test.js.map +1 -1
- package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js +3 -2
- package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js.map +1 -1
- package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js +3 -2
- package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js.map +1 -1
- package/build/index.d.ts +2 -0
- package/build/index.js +5 -1
- package/build/index.js.map +1 -1
- package/build/rules/AlwaysAllowPrivacyPolicyRule.d.ts +2 -1
- package/build/rules/AlwaysAllowPrivacyPolicyRule.js +1 -1
- package/build/rules/AlwaysAllowPrivacyPolicyRule.js.map +1 -1
- package/build/rules/AlwaysDenyPrivacyPolicyRule.d.ts +2 -1
- package/build/rules/AlwaysDenyPrivacyPolicyRule.js +1 -1
- package/build/rules/AlwaysDenyPrivacyPolicyRule.js.map +1 -1
- package/build/rules/AlwaysSkipPrivacyPolicyRule.d.ts +2 -1
- package/build/rules/AlwaysSkipPrivacyPolicyRule.js +1 -1
- package/build/rules/AlwaysSkipPrivacyPolicyRule.js.map +1 -1
- package/build/rules/PrivacyPolicyRule.d.ts +2 -1
- package/build/rules/PrivacyPolicyRule.js.map +1 -1
- package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js +1 -0
- package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js.map +1 -1
- package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js +1 -0
- package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js.map +1 -1
- package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js +1 -0
- package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js.map +1 -1
- package/build/utils/testing/PrivacyPolicyRuleTestUtils.d.ts +2 -0
- package/build/utils/testing/PrivacyPolicyRuleTestUtils.js +6 -6
- package/build/utils/testing/PrivacyPolicyRuleTestUtils.js.map +1 -1
- package/package.json +1 -1
- package/src/ComposedEntityCacheAdapter.ts +86 -0
- package/src/ComposedSecondaryEntityCache.ts +63 -0
- package/src/Entity.ts +6 -4
- package/src/EntityAssociationLoader.ts +4 -4
- package/src/EntityLoader.ts +5 -1
- package/src/EntityLoaderFactory.ts +4 -2
- package/src/EntityMutationInfo.ts +13 -3
- package/src/EntityMutator.ts +44 -21
- package/src/EntityMutatorFactory.ts +10 -4
- package/src/EntityPrivacyPolicy.ts +31 -1
- package/src/IEntityGenericCacher.ts +2 -2
- package/src/ReadonlyEntity.ts +1 -1
- package/src/ViewerScopedEntityLoaderFactory.ts +8 -3
- package/src/ViewerScopedEntityMutatorFactory.ts +22 -7
- package/src/__tests__/ComposedCacheAdapter-test.ts +280 -0
- package/src/__tests__/ComposedSecondaryEntityCache-test.ts +101 -0
- package/src/__tests__/EntityCommonUseCases-test.ts +2 -1
- package/src/__tests__/EntityEdges-test.ts +286 -45
- package/src/__tests__/EntityLoader-constructor-test.ts +3 -1
- package/src/__tests__/EntityLoader-test.ts +26 -1
- package/src/__tests__/EntityMutator-test.ts +99 -37
- package/src/__tests__/EntityPrivacyPolicy-test.ts +66 -7
- package/src/__tests__/EntitySecondaryCacheLoader-test.ts +1 -0
- package/src/__tests__/ViewerScopedEntityLoaderFactory-test.ts +4 -2
- package/src/__tests__/ViewerScopedEntityMutatorFactory-test.ts +6 -2
- package/src/index.ts +2 -0
- package/src/rules/AlwaysAllowPrivacyPolicyRule.ts +2 -0
- package/src/rules/AlwaysDenyPrivacyPolicyRule.ts +2 -0
- package/src/rules/AlwaysSkipPrivacyPolicyRule.ts +2 -0
- package/src/rules/PrivacyPolicyRule.ts +2 -0
- package/src/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.ts +2 -0
- package/src/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.ts +2 -0
- package/src/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.ts +2 -0
- 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
|
|
9
|
-
|
|
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
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
307
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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 {
|
|
353
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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 {
|
|
405
|
-
|
|
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
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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(
|
|
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,
|