@expo/entity 0.53.0 → 0.55.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 (83) hide show
  1. package/build/src/ComposedEntityCacheAdapter.js.map +1 -1
  2. package/build/src/ComposedSecondaryEntityCache.js.map +1 -1
  3. package/build/src/EntityDatabaseAdapter.js.map +1 -1
  4. package/build/src/EntityFieldDefinition.js.map +1 -1
  5. package/build/src/EntityLoaderUtils.js +14 -18
  6. package/build/src/EntityLoaderUtils.js.map +1 -1
  7. package/build/src/GenericEntityCacheAdapter.js.map +1 -1
  8. package/build/src/GenericSecondaryEntityCache.js.map +1 -1
  9. package/build/src/entityUtils.js +7 -2
  10. package/build/src/entityUtils.js.map +1 -1
  11. package/build/src/errors/EntityCacheAdapterError.d.ts +2 -2
  12. package/build/src/errors/EntityCacheAdapterError.js +12 -2
  13. package/build/src/errors/EntityCacheAdapterError.js.map +1 -1
  14. package/build/src/errors/EntityDatabaseAdapterError.d.ts +24 -24
  15. package/build/src/errors/EntityDatabaseAdapterError.js +111 -24
  16. package/build/src/errors/EntityDatabaseAdapterError.js.map +1 -1
  17. package/build/src/errors/EntityError.d.ts +2 -4
  18. package/build/src/errors/EntityError.js +5 -8
  19. package/build/src/errors/EntityError.js.map +1 -1
  20. package/build/src/errors/EntityInvalidFieldValueError.d.ts +2 -2
  21. package/build/src/errors/EntityInvalidFieldValueError.js +9 -2
  22. package/build/src/errors/EntityInvalidFieldValueError.js.map +1 -1
  23. package/build/src/errors/EntityNotAuthorizedError.d.ts +2 -2
  24. package/build/src/errors/EntityNotAuthorizedError.js +9 -2
  25. package/build/src/errors/EntityNotAuthorizedError.js.map +1 -1
  26. package/build/src/errors/EntityNotFoundError.d.ts +2 -2
  27. package/build/src/errors/EntityNotFoundError.js +9 -2
  28. package/build/src/errors/EntityNotFoundError.js.map +1 -1
  29. package/build/src/index.d.ts +4 -0
  30. package/build/src/index.js +4 -0
  31. package/build/src/index.js.map +1 -1
  32. package/build/src/internal/CompositeFieldHolder.js.map +1 -1
  33. package/build/src/internal/CompositeFieldValueMap.js.map +1 -1
  34. package/build/src/internal/SingleFieldHolder.js.map +1 -1
  35. package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.d.ts +10 -0
  36. package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.js +19 -0
  37. package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.js.map +1 -0
  38. package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.d.ts +10 -0
  39. package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.js +19 -0
  40. package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.js.map +1 -0
  41. package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.d.ts +66 -0
  42. package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.js +75 -0
  43. package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.js.map +1 -0
  44. package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.d.ts +12 -0
  45. package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.js +23 -0
  46. package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.js.map +1 -0
  47. package/build/src/utils/EntityPrivacyUtils.js +34 -15
  48. package/build/src/utils/EntityPrivacyUtils.js.map +1 -1
  49. package/package.json +10 -13
  50. package/src/ComposedEntityCacheAdapter.ts +1 -2
  51. package/src/ComposedSecondaryEntityCache.ts +4 -3
  52. package/src/EntityDatabaseAdapter.ts +3 -2
  53. package/src/EntityFieldDefinition.ts +1 -2
  54. package/src/EntityLoaderUtils.ts +17 -20
  55. package/src/GenericEntityCacheAdapter.ts +1 -2
  56. package/src/GenericSecondaryEntityCache.ts +1 -2
  57. package/src/__tests__/ComposedCacheAdapter-test.ts +4 -3
  58. package/src/__tests__/EntityFields-test.ts +2 -8
  59. package/src/entityUtils.ts +7 -4
  60. package/src/errors/EntityCacheAdapterError.ts +16 -3
  61. package/src/errors/EntityDatabaseAdapterError.ts +137 -25
  62. package/src/errors/EntityError.ts +7 -8
  63. package/src/errors/EntityInvalidFieldValueError.ts +11 -2
  64. package/src/errors/EntityNotAuthorizedError.ts +11 -2
  65. package/src/errors/EntityNotFoundError.ts +11 -2
  66. package/src/errors/__tests__/EntityDatabaseAdapterError-test.ts +68 -11
  67. package/src/errors/__tests__/EntityError-test.ts +36 -0
  68. package/src/index.ts +4 -0
  69. package/src/internal/CompositeFieldHolder.ts +7 -10
  70. package/src/internal/CompositeFieldValueMap.ts +1 -2
  71. package/src/internal/SingleFieldHolder.ts +10 -6
  72. package/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.ts +47 -0
  73. package/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.ts +47 -0
  74. package/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.ts +177 -0
  75. package/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.ts +46 -0
  76. package/src/rules/__tests__/AllowIfAllSubRulesAllowPrivacyPolicyRule-test.ts +64 -0
  77. package/src/rules/__tests__/AllowIfAnySubRuleAllowsPrivacyPolicyRule-test.ts +64 -0
  78. package/src/rules/__tests__/AllowIfInParentCascadeDeletionPrivacyPolicyRule-test.ts +258 -0
  79. package/src/rules/__tests__/EvaluateIfEntityFieldPredicatePrivacyPolicyRule-test.ts +47 -0
  80. package/src/utils/EntityPrivacyUtils.ts +61 -20
  81. package/src/utils/__testfixtures__/StubCacheAdapter.ts +2 -4
  82. package/src/utils/__testfixtures__/StubDatabaseAdapter.ts +1 -1
  83. package/src/utils/__tests__/EntityPrivacyUtils-test.ts +295 -0
@@ -0,0 +1,64 @@
1
+ import { anything, instance, mock } from 'ts-mockito';
2
+
3
+ import { EntityPrivacyPolicyEvaluationContext } from '../../EntityPrivacyPolicy';
4
+ import { EntityQueryContext } from '../../EntityQueryContext';
5
+ import { ViewerContext } from '../../ViewerContext';
6
+ import { describePrivacyPolicyRule } from '../../utils/__testfixtures__/PrivacyPolicyRuleTestUtils';
7
+ import { AllowIfAllSubRulesAllowPrivacyPolicyRule } from '../AllowIfAllSubRulesAllowPrivacyPolicyRule';
8
+ import { AlwaysAllowPrivacyPolicyRule } from '../AlwaysAllowPrivacyPolicyRule';
9
+ import { AlwaysDenyPrivacyPolicyRule } from '../AlwaysDenyPrivacyPolicyRule';
10
+ import { AlwaysSkipPrivacyPolicyRule } from '../AlwaysSkipPrivacyPolicyRule';
11
+
12
+ describePrivacyPolicyRule(
13
+ new AllowIfAllSubRulesAllowPrivacyPolicyRule([
14
+ new AlwaysAllowPrivacyPolicyRule(),
15
+ new AlwaysSkipPrivacyPolicyRule(),
16
+ ]),
17
+ {
18
+ skipCases: [
19
+ {
20
+ viewerContext: instance(mock(ViewerContext)),
21
+ queryContext: instance(mock(EntityQueryContext)),
22
+ evaluationContext:
23
+ instance(mock<EntityPrivacyPolicyEvaluationContext<any, any, any, any, any>>()),
24
+ entity: anything(),
25
+ },
26
+ ],
27
+ },
28
+ );
29
+
30
+ describePrivacyPolicyRule(
31
+ new AllowIfAllSubRulesAllowPrivacyPolicyRule([
32
+ new AlwaysAllowPrivacyPolicyRule(),
33
+ new AlwaysDenyPrivacyPolicyRule(),
34
+ ]),
35
+ {
36
+ skipCases: [
37
+ {
38
+ viewerContext: instance(mock(ViewerContext)),
39
+ queryContext: instance(mock(EntityQueryContext)),
40
+ evaluationContext:
41
+ instance(mock<EntityPrivacyPolicyEvaluationContext<any, any, any, any, any>>()),
42
+ entity: anything(),
43
+ },
44
+ ],
45
+ },
46
+ );
47
+
48
+ describePrivacyPolicyRule(
49
+ new AllowIfAllSubRulesAllowPrivacyPolicyRule([
50
+ new AlwaysAllowPrivacyPolicyRule(),
51
+ new AlwaysAllowPrivacyPolicyRule(),
52
+ ]),
53
+ {
54
+ allowCases: [
55
+ {
56
+ viewerContext: instance(mock(ViewerContext)),
57
+ queryContext: instance(mock(EntityQueryContext)),
58
+ evaluationContext:
59
+ instance(mock<EntityPrivacyPolicyEvaluationContext<any, any, any, any, any>>()),
60
+ entity: anything(),
61
+ },
62
+ ],
63
+ },
64
+ );
@@ -0,0 +1,64 @@
1
+ import { anything, instance, mock } from 'ts-mockito';
2
+
3
+ import { EntityPrivacyPolicyEvaluationContext } from '../../EntityPrivacyPolicy';
4
+ import { EntityQueryContext } from '../../EntityQueryContext';
5
+ import { ViewerContext } from '../../ViewerContext';
6
+ import { describePrivacyPolicyRule } from '../../utils/__testfixtures__/PrivacyPolicyRuleTestUtils';
7
+ import { AllowIfAnySubRuleAllowsPrivacyPolicyRule } from '../AllowIfAnySubRuleAllowsPrivacyPolicyRule';
8
+ import { AlwaysAllowPrivacyPolicyRule } from '../AlwaysAllowPrivacyPolicyRule';
9
+ import { AlwaysDenyPrivacyPolicyRule } from '../AlwaysDenyPrivacyPolicyRule';
10
+ import { AlwaysSkipPrivacyPolicyRule } from '../AlwaysSkipPrivacyPolicyRule';
11
+
12
+ describePrivacyPolicyRule(
13
+ new AllowIfAnySubRuleAllowsPrivacyPolicyRule([
14
+ new AlwaysAllowPrivacyPolicyRule(),
15
+ new AlwaysSkipPrivacyPolicyRule(),
16
+ ]),
17
+ {
18
+ allowCases: [
19
+ {
20
+ viewerContext: instance(mock(ViewerContext)),
21
+ queryContext: instance(mock(EntityQueryContext)),
22
+ evaluationContext:
23
+ instance(mock<EntityPrivacyPolicyEvaluationContext<any, any, any, any, any>>()),
24
+ entity: anything(),
25
+ },
26
+ ],
27
+ },
28
+ );
29
+
30
+ describePrivacyPolicyRule(
31
+ new AllowIfAnySubRuleAllowsPrivacyPolicyRule([
32
+ new AlwaysAllowPrivacyPolicyRule(),
33
+ new AlwaysDenyPrivacyPolicyRule(),
34
+ ]),
35
+ {
36
+ allowCases: [
37
+ {
38
+ viewerContext: instance(mock(ViewerContext)),
39
+ queryContext: instance(mock(EntityQueryContext)),
40
+ evaluationContext:
41
+ instance(mock<EntityPrivacyPolicyEvaluationContext<any, any, any, any, any>>()),
42
+ entity: anything(),
43
+ },
44
+ ],
45
+ },
46
+ );
47
+
48
+ describePrivacyPolicyRule(
49
+ new AllowIfAnySubRuleAllowsPrivacyPolicyRule([
50
+ new AlwaysSkipPrivacyPolicyRule(),
51
+ new AlwaysSkipPrivacyPolicyRule(),
52
+ ]),
53
+ {
54
+ skipCases: [
55
+ {
56
+ viewerContext: instance(mock(ViewerContext)),
57
+ queryContext: instance(mock(EntityQueryContext)),
58
+ evaluationContext:
59
+ instance(mock<EntityPrivacyPolicyEvaluationContext<any, any, any, any, any>>()),
60
+ entity: anything(),
61
+ },
62
+ ],
63
+ },
64
+ );
@@ -0,0 +1,258 @@
1
+ import { instance, mock, when } from 'ts-mockito';
2
+
3
+ import { EntityCompanionDefinition } from '../../EntityCompanionProvider';
4
+ import { EntityPrivacyPolicy } from '../../EntityPrivacyPolicy';
5
+ import { EntityQueryContext } from '../../EntityQueryContext';
6
+ import { ReadonlyEntity } from '../../ReadonlyEntity';
7
+ import { ViewerContext } from '../../ViewerContext';
8
+ import { describePrivacyPolicyRule } from '../../utils/__testfixtures__/PrivacyPolicyRuleTestUtils';
9
+ import { AllowIfInParentCascadeDeletionPrivacyPolicyRule } from '../AllowIfInParentCascadeDeletionPrivacyPolicyRule';
10
+
11
+ // Define test field types
12
+ type ParentFields = {
13
+ id: string;
14
+ name: string;
15
+ };
16
+
17
+ type ChildFields = {
18
+ id: string;
19
+ parent_id: string | null;
20
+ parent_name: string | null;
21
+ };
22
+
23
+ // Create a mock privacy policy for the parent entity
24
+ class TestParentPrivacyPolicy extends EntityPrivacyPolicy<
25
+ ParentFields,
26
+ 'id',
27
+ ViewerContext,
28
+ TestParentEntity
29
+ > {
30
+ protected override readonly readRules = [];
31
+ protected override readonly createRules = [];
32
+ protected override readonly updateRules = [];
33
+ protected override readonly deleteRules = [];
34
+ }
35
+
36
+ // Create a mock parent entity class with required static method
37
+ class TestParentEntity extends ReadonlyEntity<ParentFields, 'id', ViewerContext> {
38
+ static defineCompanionDefinition(): EntityCompanionDefinition<
39
+ ParentFields,
40
+ 'id',
41
+ ViewerContext,
42
+ TestParentEntity,
43
+ TestParentPrivacyPolicy
44
+ > {
45
+ throw new Error('Not implemented for test');
46
+ }
47
+ }
48
+
49
+ // Create a mock child entity class
50
+ class TestChildEntity extends ReadonlyEntity<ChildFields, 'id', ViewerContext> {}
51
+
52
+ // Mock parent entities
53
+ const parentEntityMock = mock(TestParentEntity);
54
+ when(parentEntityMock.getID()).thenReturn('5');
55
+ const parentEntity = instance(parentEntityMock);
56
+ Object.setPrototypeOf(parentEntity, TestParentEntity.prototype);
57
+
58
+ const otherParentEntityMock = mock(TestParentEntity);
59
+ when(otherParentEntityMock.getID()).thenReturn('6');
60
+ const otherParentEntity = instance(otherParentEntityMock);
61
+ Object.setPrototypeOf(otherParentEntity, TestParentEntity.prototype);
62
+
63
+ // Mock a non-parent entity (different class)
64
+ class UnrelatedOtherEntity extends ReadonlyEntity<{ id: string }, 'id', ViewerContext> {}
65
+ const unrelatedOtherEntityMock = mock(UnrelatedOtherEntity);
66
+ Object.setPrototypeOf(unrelatedOtherEntityMock, UnrelatedOtherEntity.prototype);
67
+
68
+ // Mock child entities
69
+ const childEntityMock = mock(TestChildEntity);
70
+ when(childEntityMock.getField('parent_id')).thenReturn('5');
71
+
72
+ const childEntityMockWithNullifiedField = mock(TestChildEntity);
73
+ when(childEntityMockWithNullifiedField.getField('parent_id')).thenReturn(null);
74
+
75
+ const childEntityDifferentParentMock = mock(TestChildEntity);
76
+ when(childEntityDifferentParentMock.getField('parent_id')).thenReturn('6');
77
+
78
+ describePrivacyPolicyRule(
79
+ new AllowIfInParentCascadeDeletionPrivacyPolicyRule<
80
+ ChildFields,
81
+ 'id',
82
+ ViewerContext,
83
+ TestChildEntity,
84
+ ParentFields,
85
+ 'id',
86
+ TestParentEntity,
87
+ TestParentPrivacyPolicy
88
+ >({
89
+ fieldIdentifyingParentEntity: 'parent_id',
90
+ parentEntityClass: TestParentEntity,
91
+ }),
92
+ {
93
+ allowCases: [
94
+ // parent id matches parent being deleted, field not yet nullified
95
+ {
96
+ viewerContext: instance(mock(ViewerContext)),
97
+ queryContext: instance(mock(EntityQueryContext)),
98
+ evaluationContext: {
99
+ previousValue: instance(childEntityMock),
100
+ cascadingDeleteCause: {
101
+ entity: parentEntity,
102
+ cascadingDeleteCause: null,
103
+ },
104
+ },
105
+ entity: instance(childEntityMock),
106
+ },
107
+ // parent id matches parent being deleted, field null in current version but filled in previous version
108
+ {
109
+ viewerContext: instance(mock(ViewerContext)),
110
+ queryContext: instance(mock(EntityQueryContext)),
111
+ evaluationContext: {
112
+ previousValue: instance(childEntityMock),
113
+ cascadingDeleteCause: {
114
+ entity: parentEntity,
115
+ cascadingDeleteCause: null,
116
+ },
117
+ },
118
+ entity: instance(childEntityMockWithNullifiedField),
119
+ },
120
+ ],
121
+ skipCases: [
122
+ // no cascading delete
123
+ {
124
+ viewerContext: instance(mock(ViewerContext)),
125
+ queryContext: instance(mock(EntityQueryContext)),
126
+ evaluationContext: {
127
+ previousValue: null,
128
+ cascadingDeleteCause: null,
129
+ },
130
+ entity: instance(childEntityMock),
131
+ },
132
+ // cascading delete not from parent entity class
133
+ {
134
+ viewerContext: instance(mock(ViewerContext)),
135
+ queryContext: instance(mock(EntityQueryContext)),
136
+ evaluationContext: {
137
+ previousValue: null,
138
+ cascadingDeleteCause: {
139
+ entity: instance(unrelatedOtherEntityMock),
140
+ cascadingDeleteCause: null,
141
+ },
142
+ },
143
+ entity: instance(childEntityMock),
144
+ },
145
+ // cascading delete from different parent, field not nullified
146
+ {
147
+ viewerContext: instance(mock(ViewerContext)),
148
+ queryContext: instance(mock(EntityQueryContext)),
149
+ evaluationContext: {
150
+ previousValue: null,
151
+ cascadingDeleteCause: {
152
+ entity: otherParentEntity,
153
+ cascadingDeleteCause: null,
154
+ },
155
+ },
156
+ entity: instance(childEntityMock),
157
+ },
158
+ // entity belongs to different parent
159
+ {
160
+ viewerContext: instance(mock(ViewerContext)),
161
+ queryContext: instance(mock(EntityQueryContext)),
162
+ evaluationContext: {
163
+ previousValue: null,
164
+ cascadingDeleteCause: {
165
+ entity: parentEntity,
166
+ cascadingDeleteCause: null,
167
+ },
168
+ },
169
+ entity: instance(childEntityDifferentParentMock),
170
+ },
171
+ // parent id field undefined (null) and no previous value
172
+ {
173
+ viewerContext: instance(mock(ViewerContext)),
174
+ queryContext: instance(mock(EntityQueryContext)),
175
+ evaluationContext: {
176
+ previousValue: null,
177
+ cascadingDeleteCause: {
178
+ entity: parentEntity,
179
+ cascadingDeleteCause: null,
180
+ },
181
+ },
182
+ entity: instance(childEntityMockWithNullifiedField),
183
+ },
184
+ // parent id now null but previous value different parent
185
+ {
186
+ viewerContext: instance(mock(ViewerContext)),
187
+ queryContext: instance(mock(EntityQueryContext)),
188
+ evaluationContext: {
189
+ previousValue: instance(childEntityDifferentParentMock),
190
+ cascadingDeleteCause: {
191
+ entity: parentEntity,
192
+ cascadingDeleteCause: null,
193
+ },
194
+ },
195
+ entity: instance(childEntityMockWithNullifiedField),
196
+ },
197
+ ],
198
+ },
199
+ );
200
+
201
+ // Test with custom lookup field (parentEntityLookupByField)
202
+ const parentEntityWithNameMock = mock(TestParentEntity);
203
+ when(parentEntityWithNameMock.getField('name')).thenReturn('test-name');
204
+ const parentEntityWithName = instance(parentEntityWithNameMock);
205
+ Object.setPrototypeOf(parentEntityWithName, TestParentEntity.prototype);
206
+
207
+ const childEntityWithNameRefMock = mock(TestChildEntity);
208
+ when(childEntityWithNameRefMock.getField('parent_name')).thenReturn('test-name');
209
+
210
+ const childEntityWithNameRefMockWithNullifiedField = mock(TestChildEntity);
211
+ when(childEntityWithNameRefMockWithNullifiedField.getField('parent_name')).thenReturn(null);
212
+
213
+ describePrivacyPolicyRule(
214
+ new AllowIfInParentCascadeDeletionPrivacyPolicyRule<
215
+ ChildFields,
216
+ 'id',
217
+ ViewerContext,
218
+ TestChildEntity,
219
+ ParentFields,
220
+ 'id',
221
+ TestParentEntity,
222
+ TestParentPrivacyPolicy
223
+ >({
224
+ fieldIdentifyingParentEntity: 'parent_name',
225
+ parentEntityClass: TestParentEntity,
226
+ parentEntityLookupByField: 'name',
227
+ }),
228
+ {
229
+ allowCases: [
230
+ // parent name matches parent being deleted, field not yet nullified
231
+ {
232
+ viewerContext: instance(mock(ViewerContext)),
233
+ queryContext: instance(mock(EntityQueryContext)),
234
+ evaluationContext: {
235
+ previousValue: instance(childEntityWithNameRefMock),
236
+ cascadingDeleteCause: {
237
+ entity: parentEntityWithName,
238
+ cascadingDeleteCause: null,
239
+ },
240
+ },
241
+ entity: instance(childEntityWithNameRefMock),
242
+ },
243
+ // parent name matches parent being deleted, field null in current version but filled in previous version
244
+ {
245
+ viewerContext: instance(mock(ViewerContext)),
246
+ queryContext: instance(mock(EntityQueryContext)),
247
+ evaluationContext: {
248
+ previousValue: instance(childEntityWithNameRefMock),
249
+ cascadingDeleteCause: {
250
+ entity: parentEntityWithName,
251
+ cascadingDeleteCause: null,
252
+ },
253
+ },
254
+ entity: instance(childEntityWithNameRefMockWithNullifiedField),
255
+ },
256
+ ],
257
+ },
258
+ );
@@ -0,0 +1,47 @@
1
+ import { mock, instance, when } from 'ts-mockito';
2
+
3
+ import { EntityPrivacyPolicyEvaluationContext } from '../../EntityPrivacyPolicy';
4
+ import { EntityQueryContext } from '../../EntityQueryContext';
5
+ import { ViewerContext } from '../../ViewerContext';
6
+ import { describePrivacyPolicyRule } from '../../utils/__testfixtures__/PrivacyPolicyRuleTestUtils';
7
+ import { TestEntity, TestFields } from '../../utils/__testfixtures__/TestEntity';
8
+ import { AlwaysAllowPrivacyPolicyRule } from '../AlwaysAllowPrivacyPolicyRule';
9
+ import { EvaluateIfEntityFieldPredicatePrivacyPolicyRule } from '../EvaluateIfEntityFieldPredicatePrivacyPolicyRule';
10
+
11
+ const entityBlahMock = mock(TestEntity);
12
+ when(entityBlahMock.getField('testIndexedField')).thenReturn('1');
13
+ const entityBlah = instance(entityBlahMock);
14
+
15
+ const entityFooMock = mock(TestEntity);
16
+ when(entityFooMock.getField('testIndexedField')).thenReturn('2');
17
+ const entityFoo = instance(entityFooMock);
18
+
19
+ describePrivacyPolicyRule<TestFields, 'customIdField', ViewerContext, TestEntity>(
20
+ new EvaluateIfEntityFieldPredicatePrivacyPolicyRule<
21
+ TestFields,
22
+ 'customIdField',
23
+ ViewerContext,
24
+ TestEntity,
25
+ 'testIndexedField'
26
+ >('testIndexedField', (val) => val === '1', new AlwaysAllowPrivacyPolicyRule()),
27
+ {
28
+ allowCases: [
29
+ {
30
+ viewerContext: instance(mock(ViewerContext)),
31
+ queryContext: instance(mock(EntityQueryContext)),
32
+ evaluationContext:
33
+ instance(mock<EntityPrivacyPolicyEvaluationContext<any, any, any, any, any>>()),
34
+ entity: entityBlah,
35
+ },
36
+ ],
37
+ skipCases: [
38
+ {
39
+ viewerContext: instance(mock(ViewerContext)),
40
+ queryContext: instance(mock(EntityQueryContext)),
41
+ evaluationContext:
42
+ instance(mock<EntityPrivacyPolicyEvaluationContext<any, any, any, any, any>>()),
43
+ entity: entityFoo,
44
+ },
45
+ ],
46
+ },
47
+ );
@@ -5,9 +5,9 @@ import {
5
5
  EntityEdgeDeletionAuthorizationInferenceBehavior,
6
6
  EntityEdgeDeletionBehavior,
7
7
  } from '../EntityFieldDefinition';
8
- import { EntityCascadingDeletionInfo } from '../EntityMutationInfo';
9
- import { EntityPrivacyPolicy } from '../EntityPrivacyPolicy';
8
+ import { EntityPrivacyPolicy, EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
10
9
  import { EntityQueryContext } from '../EntityQueryContext';
10
+ import { ReadonlyEntity } from '../ReadonlyEntity';
11
11
  import { ViewerContext } from '../ViewerContext';
12
12
  import { failedResults, partitionArray } from '../entityUtils';
13
13
  import { EntityNotAuthorizedError } from '../errors/EntityNotAuthorizedError';
@@ -74,7 +74,7 @@ export async function canViewerUpdateAsync<
74
74
  const result = await canViewerUpdateInternalAsync(
75
75
  entityClass,
76
76
  sourceEntity,
77
- /* cascadingDeleteCause */ null,
77
+ { previousValue: sourceEntity, cascadingDeleteCause: null },
78
78
  queryContext,
79
79
  );
80
80
  return result.allowed;
@@ -121,7 +121,7 @@ export async function getCanViewerUpdateResultAsync<
121
121
  return await canViewerUpdateInternalAsync(
122
122
  entityClass,
123
123
  sourceEntity,
124
- /* cascadingDeleteCause */ null,
124
+ { previousValue: sourceEntity, cascadingDeleteCause: null },
125
125
  queryContext,
126
126
  );
127
127
  }
@@ -149,7 +149,13 @@ async function canViewerUpdateInternalAsync<
149
149
  TSelectedFields
150
150
  >,
151
151
  sourceEntity: TEntity,
152
- cascadingDeleteCause: EntityCascadingDeletionInfo | null,
152
+ evaluationContext: EntityPrivacyPolicyEvaluationContext<
153
+ TFields,
154
+ TIDField,
155
+ TViewerContext,
156
+ TEntity,
157
+ TSelectedFields
158
+ >,
153
159
  queryContext: EntityQueryContext,
154
160
  ): Promise<EntityPrivacyEvaluationResult> {
155
161
  const companion = sourceEntity
@@ -160,7 +166,7 @@ async function canViewerUpdateInternalAsync<
160
166
  privacyPolicy.authorizeUpdateAsync(
161
167
  sourceEntity.getViewerContext(),
162
168
  queryContext,
163
- { previousValue: null, cascadingDeleteCause },
169
+ evaluationContext,
164
170
  sourceEntity,
165
171
  companion.getMetricsAdapter(),
166
172
  ),
@@ -217,7 +223,7 @@ export async function canViewerDeleteAsync<
217
223
  const result = await canViewerDeleteInternalAsync(
218
224
  entityClass,
219
225
  sourceEntity,
220
- /* cascadingDeleteCause */ null,
226
+ { previousValue: null, cascadingDeleteCause: null },
221
227
  queryContext,
222
228
  );
223
229
  return result.allowed;
@@ -264,7 +270,7 @@ export async function getCanViewerDeleteResultAsync<
264
270
  return await canViewerDeleteInternalAsync(
265
271
  entityClass,
266
272
  sourceEntity,
267
- /* cascadingDeleteCause */ null,
273
+ { previousValue: null, cascadingDeleteCause: null },
268
274
  queryContext,
269
275
  );
270
276
  }
@@ -292,7 +298,13 @@ async function canViewerDeleteInternalAsync<
292
298
  TSelectedFields
293
299
  >,
294
300
  sourceEntity: TEntity,
295
- cascadingDeleteCause: EntityCascadingDeletionInfo | null,
301
+ evaluationContext: EntityPrivacyPolicyEvaluationContext<
302
+ TFields,
303
+ TIDField,
304
+ TViewerContext,
305
+ TEntity,
306
+ TSelectedFields
307
+ >,
296
308
  queryContext: EntityQueryContext,
297
309
  ): Promise<EntityPrivacyEvaluationResult> {
298
310
  const viewerContext = sourceEntity.getViewerContext();
@@ -306,7 +318,7 @@ async function canViewerDeleteInternalAsync<
306
318
  privacyPolicy.authorizeDeleteAsync(
307
319
  sourceEntity.getViewerContext(),
308
320
  queryContext,
309
- { previousValue: null, cascadingDeleteCause },
321
+ evaluationContext,
310
322
  sourceEntity,
311
323
  viewerScopedCompanion.getMetricsAdapter(),
312
324
  ),
@@ -321,7 +333,7 @@ async function canViewerDeleteInternalAsync<
321
333
 
322
334
  const newCascadingDeleteCause = {
323
335
  entity: sourceEntity,
324
- cascadingDeleteCause,
336
+ cascadingDeleteCause: evaluationContext.cascadingDeleteCause,
325
337
  };
326
338
 
327
339
  // Take entity X which is proposed to be deleted, look at inbound edges (entities that reference X).
@@ -366,7 +378,7 @@ async function canViewerDeleteInternalAsync<
366
378
  const edgeDeletionPermissionInferenceBehavior =
367
379
  association.edgeDeletionAuthorizationInferenceBehavior;
368
380
 
369
- let entityResultsToCheckForInboundEdge: readonly Result<any>[];
381
+ let entityResultsToCheckForInboundEdge: readonly Result<ReadonlyEntity<any, any, any, any>>[];
370
382
 
371
383
  if (
372
384
  edgeDeletionPermissionInferenceBehavior ===
@@ -419,7 +431,7 @@ async function canViewerDeleteInternalAsync<
419
431
  canViewerDeleteInternalAsync(
420
432
  inboundEdge,
421
433
  entity,
422
- newCascadingDeleteCause,
434
+ { previousValue: null, cascadingDeleteCause: newCascadingDeleteCause },
423
435
  queryContext,
424
436
  ),
425
437
  ),
@@ -435,14 +447,43 @@ async function canViewerDeleteInternalAsync<
435
447
 
436
448
  case EntityEdgeDeletionBehavior.SET_NULL:
437
449
  case EntityEdgeDeletionBehavior.SET_NULL_INVALIDATE_CACHE_ONLY: {
450
+ // create synthetic entities with the reference field set to null to properly evaluate
451
+ // privacy policy as it would be after the cascading SET NULL operation
452
+ const previousAndSyntheticEntitiesForInboundEdge = entitiesForInboundEdge.map(
453
+ (entity) => {
454
+ const entityLoader = viewerContext
455
+ .getViewerScopedEntityCompanionForClass(inboundEdge)
456
+ .getLoaderFactory()
457
+ .forLoad(queryContext, {
458
+ previousValue: entity,
459
+ cascadingDeleteCause: newCascadingDeleteCause,
460
+ });
461
+
462
+ const allFields = entity.getAllDatabaseFields();
463
+ const syntheticFields = {
464
+ ...allFields,
465
+ [fieldName]: null,
466
+ };
467
+
468
+ return {
469
+ previousValue: entity,
470
+ syntheticallyUpdatedValue: entityLoader.utils.constructEntity(syntheticFields),
471
+ };
472
+ },
473
+ );
474
+
438
475
  const canUpdateEvaluationResults = await Promise.all(
439
- entitiesForInboundEdge.map((entity) =>
440
- canViewerUpdateInternalAsync(
441
- inboundEdge,
442
- entity,
443
- newCascadingDeleteCause,
444
- queryContext,
445
- ),
476
+ previousAndSyntheticEntitiesForInboundEdge.map(
477
+ ({ previousValue, syntheticallyUpdatedValue }) =>
478
+ canViewerUpdateInternalAsync(
479
+ inboundEdge,
480
+ syntheticallyUpdatedValue,
481
+ {
482
+ previousValue,
483
+ cascadingDeleteCause: newCascadingDeleteCause,
484
+ },
485
+ queryContext,
486
+ ),
446
487
  ),
447
488
  );
448
489
 
@@ -17,8 +17,7 @@ export class NoCacheStubCacheAdapterProvider implements IEntityCacheAdapterProvi
17
17
  export class NoCacheStubCacheAdapter<
18
18
  TFields extends Record<string, any>,
19
19
  TIDField extends keyof TFields,
20
- > implements IEntityCacheAdapter<TFields, TIDField>
21
- {
20
+ > implements IEntityCacheAdapter<TFields, TIDField> {
22
21
  public async loadManyAsync<
23
22
  TLoadKey extends IEntityLoadKey<TFields, TIDField, TSerializedLoadValue, TLoadValue>,
24
23
  TSerializedLoadValue,
@@ -74,8 +73,7 @@ export class InMemoryFullCacheStubCacheAdapterProvider implements IEntityCacheAd
74
73
  export class InMemoryFullCacheStubCacheAdapter<
75
74
  TFields extends Record<string, any>,
76
75
  TIDField extends keyof TFields,
77
- > implements IEntityCacheAdapter<TFields, TIDField>
78
- {
76
+ > implements IEntityCacheAdapter<TFields, TIDField> {
79
77
  constructor(
80
78
  private readonly entityConfiguration: EntityConfiguration<TFields, TIDField>,
81
79
  private readonly cache: Map<string, Readonly<TFields> | typeof DOES_NOT_EXIST>,
@@ -1,5 +1,5 @@
1
1
  import invariant from 'invariant';
2
- import { uuidv7 } from 'uuidv7';
2
+ import { v7 as uuidv7 } from 'uuid';
3
3
 
4
4
  import { EntityConfiguration } from '../../EntityConfiguration';
5
5
  import {