@expo/entity 0.54.0 → 0.57.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.
- package/build/src/AuthorizationResultBasedEntityAssociationLoader.d.ts +1 -1
- package/build/src/AuthorizationResultBasedEntityAssociationLoader.js.map +1 -1
- package/build/src/AuthorizationResultBasedEntityLoader.d.ts +18 -24
- package/build/src/AuthorizationResultBasedEntityLoader.js +37 -56
- package/build/src/AuthorizationResultBasedEntityLoader.js.map +1 -1
- package/build/src/AuthorizationResultBasedEntityMutator.js +26 -19
- package/build/src/AuthorizationResultBasedEntityMutator.js.map +1 -1
- package/build/src/EnforcingEntityCreator.d.ts +1 -1
- package/build/src/EnforcingEntityCreator.js +1 -1
- package/build/src/EnforcingEntityLoader.d.ts +1 -58
- package/build/src/EnforcingEntityLoader.js +0 -65
- package/build/src/EnforcingEntityLoader.js.map +1 -1
- package/build/src/Entity.d.ts +6 -0
- package/build/src/Entity.js +6 -0
- package/build/src/Entity.js.map +1 -1
- package/build/src/EntityCompanion.d.ts +2 -2
- package/build/src/EntityCompanion.js.map +1 -1
- package/build/src/EntityCompanionProvider.d.ts +1 -1
- package/build/src/EntityCompanionProvider.js +4 -4
- package/build/src/EntityConfiguration.d.ts +1 -1
- package/build/src/EntityConfiguration.js +1 -2
- package/build/src/EntityConfiguration.js.map +1 -1
- package/build/src/{EntityLoaderUtils.d.ts → EntityConstructionUtils.d.ts} +15 -29
- package/build/src/EntityConstructionUtils.js +118 -0
- package/build/src/EntityConstructionUtils.js.map +1 -0
- package/build/src/EntityDatabaseAdapter.d.ts +10 -108
- package/build/src/EntityDatabaseAdapter.js +14 -76
- package/build/src/EntityDatabaseAdapter.js.map +1 -1
- package/build/src/EntityFieldDefinition.d.ts +1 -1
- package/build/src/EntityInvalidationUtils.d.ts +41 -0
- package/build/src/EntityInvalidationUtils.js +71 -0
- package/build/src/EntityInvalidationUtils.js.map +1 -0
- package/build/src/EntityLoader.d.ts +0 -6
- package/build/src/EntityLoader.js +0 -7
- package/build/src/EntityLoader.js.map +1 -1
- package/build/src/EntityLoaderFactory.d.ts +4 -0
- package/build/src/EntityLoaderFactory.js +10 -3
- package/build/src/EntityLoaderFactory.js.map +1 -1
- package/build/src/EntityPrivacyPolicy.d.ts +27 -0
- package/build/src/EntityPrivacyPolicy.js +22 -1
- package/build/src/EntityPrivacyPolicy.js.map +1 -1
- package/build/src/EntitySecondaryCacheLoader.d.ts +14 -3
- package/build/src/EntitySecondaryCacheLoader.js +21 -4
- package/build/src/EntitySecondaryCacheLoader.js.map +1 -1
- package/build/src/ReadonlyEntity.d.ts +4 -5
- package/build/src/ReadonlyEntity.js +7 -8
- package/build/src/ReadonlyEntity.js.map +1 -1
- package/build/src/ViewerContext.d.ts +6 -6
- package/build/src/ViewerContext.js +8 -8
- package/build/src/ViewerScopedEntityCompanion.d.ts +1 -1
- package/build/src/ViewerScopedEntityCompanion.js.map +1 -1
- package/build/src/ViewerScopedEntityLoaderFactory.d.ts +4 -0
- package/build/src/ViewerScopedEntityLoaderFactory.js +6 -0
- package/build/src/ViewerScopedEntityLoaderFactory.js.map +1 -1
- package/build/src/errors/EntityDatabaseAdapterError.d.ts +4 -0
- package/build/src/errors/EntityDatabaseAdapterError.js +13 -1
- package/build/src/errors/EntityDatabaseAdapterError.js.map +1 -1
- package/build/src/errors/EntityError.d.ts +2 -1
- package/build/src/errors/EntityError.js +1 -0
- package/build/src/errors/EntityError.js.map +1 -1
- package/build/src/index.d.ts +6 -1
- package/build/src/index.js +6 -1
- package/build/src/index.js.map +1 -1
- package/build/src/internal/EntityDataManager.d.ts +8 -16
- package/build/src/internal/EntityDataManager.js +8 -18
- package/build/src/internal/EntityDataManager.js.map +1 -1
- package/build/src/internal/EntityFieldTransformationUtils.js.map +1 -1
- package/build/src/internal/EntityLoadInterfaces.d.ts +2 -0
- package/build/src/internal/EntityLoadInterfaces.js +2 -0
- package/build/src/internal/EntityLoadInterfaces.js.map +1 -1
- package/build/src/internal/EntityTableDataCoordinator.d.ts +2 -0
- package/build/src/internal/EntityTableDataCoordinator.js +4 -0
- package/build/src/internal/EntityTableDataCoordinator.js.map +1 -1
- package/build/src/metrics/EntityMetricsUtils.d.ts +1 -0
- package/build/src/metrics/EntityMetricsUtils.js +15 -1
- package/build/src/metrics/EntityMetricsUtils.js.map +1 -1
- package/build/src/metrics/IEntityMetricsAdapter.d.ts +4 -1
- package/build/src/metrics/IEntityMetricsAdapter.js +3 -0
- package/build/src/metrics/IEntityMetricsAdapter.js.map +1 -1
- package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.d.ts +10 -0
- package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.js +19 -0
- package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.d.ts +10 -0
- package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.js +19 -0
- package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.d.ts +66 -0
- package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.js +75 -0
- package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.d.ts +12 -0
- package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.js +23 -0
- package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/PrivacyPolicyRule.d.ts +2 -2
- package/build/src/utils/EntityPrivacyUtils.js +11 -20
- package/build/src/utils/EntityPrivacyUtils.js.map +1 -1
- package/build/src/utils/collections/maps.d.ts +2 -2
- package/build/src/utils/collections/maps.js +2 -2
- package/package.json +5 -5
- package/src/AuthorizationResultBasedEntityAssociationLoader.ts +4 -7
- package/src/AuthorizationResultBasedEntityLoader.ts +58 -88
- package/src/AuthorizationResultBasedEntityMutator.ts +35 -20
- package/src/EnforcingEntityCreator.ts +1 -1
- package/src/EnforcingEntityLoader.ts +1 -95
- package/src/Entity.ts +6 -0
- package/src/EntityCompanion.ts +2 -2
- package/src/EntityCompanionProvider.ts +4 -4
- package/src/EntityConfiguration.ts +8 -5
- package/src/EntityConstructionUtils.ts +168 -0
- package/src/EntityDatabaseAdapter.ts +32 -222
- package/src/EntityFieldDefinition.ts +1 -1
- package/src/{EntityLoaderUtils.ts → EntityInvalidationUtils.ts} +5 -96
- package/src/EntityLoader.ts +0 -16
- package/src/EntityLoaderFactory.ts +50 -10
- package/src/EntityPrivacyPolicy.ts +44 -1
- package/src/EntitySecondaryCacheLoader.ts +54 -3
- package/src/ReadonlyEntity.ts +9 -11
- package/src/ViewerContext.ts +10 -10
- package/src/ViewerScopedEntityCompanion.ts +1 -1
- package/src/ViewerScopedEntityLoaderFactory.ts +37 -0
- package/src/__tests__/AuthorizationResultBasedEntityLoader-constructor-test.ts +3 -5
- package/src/__tests__/AuthorizationResultBasedEntityLoader-test.ts +34 -419
- package/src/__tests__/ComposedCacheAdapter-test.ts +3 -3
- package/src/__tests__/EnforcingEntityLoader-test.ts +2 -134
- package/src/__tests__/EntityCompanion-test.ts +18 -0
- package/src/__tests__/EntityConfiguration-test.ts +4 -4
- package/src/__tests__/EntityDatabaseAdapter-test.ts +33 -68
- package/src/__tests__/EntityEdges-test.ts +10 -10
- package/src/__tests__/EntityLoader-test.ts +6 -4
- package/src/__tests__/EntityMutator-test.ts +27 -15
- package/src/__tests__/EntityPrivacyPolicy-test.ts +102 -0
- package/src/__tests__/EntityQueryContext-test.ts +11 -11
- package/src/__tests__/EntitySecondaryCacheLoader-test.ts +10 -5
- package/src/__tests__/EntitySelfReferentialEdges-test.ts +6 -6
- package/src/__tests__/GenericEntityCacheAdapter-test.ts +18 -15
- package/src/__tests__/GenericSecondaryEntityCache-test.ts +27 -5
- package/src/__tests__/ReadonlyEntity-test.ts +6 -4
- package/src/__tests__/ViewerContext-test.ts +4 -4
- package/src/__tests__/ViewerScopedEntityCompanion-test.ts +1 -0
- package/src/__tests__/cases/TwoEntitySameTableDisjointRows-test.ts +0 -17
- package/src/errors/EntityDatabaseAdapterError.ts +14 -0
- package/src/errors/EntityError.ts +1 -0
- package/src/errors/__tests__/EntityDatabaseAdapterError-test.ts +9 -0
- package/src/errors/__tests__/EntityError-test.ts +13 -5
- package/src/index.ts +6 -1
- package/src/internal/EntityDataManager.ts +19 -54
- package/src/internal/EntityFieldTransformationUtils.ts +5 -5
- package/src/internal/EntityLoadInterfaces.ts +2 -0
- package/src/internal/EntityTableDataCoordinator.ts +2 -2
- package/src/internal/__tests__/CompositeFieldHolder-test.ts +8 -2
- package/src/internal/__tests__/EntityDataManager-test.ts +71 -202
- package/src/internal/__tests__/ReadThroughEntityCache-test.ts +39 -24
- package/src/metrics/EntityMetricsUtils.ts +23 -0
- package/src/metrics/IEntityMetricsAdapter.ts +3 -0
- package/src/metrics/__tests__/EntityMetricsUtils-test.ts +120 -0
- package/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.ts +47 -0
- package/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.ts +47 -0
- package/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.ts +177 -0
- package/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.ts +46 -0
- package/src/rules/PrivacyPolicyRule.ts +2 -2
- package/src/rules/__tests__/AllowIfAllSubRulesAllowPrivacyPolicyRule-test.ts +64 -0
- package/src/rules/__tests__/AllowIfAnySubRuleAllowsPrivacyPolicyRule-test.ts +64 -0
- package/src/rules/__tests__/AllowIfInParentCascadeDeletionPrivacyPolicyRule-test.ts +268 -0
- package/src/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.ts +2 -2
- package/src/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.ts +2 -2
- package/src/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.ts +2 -2
- package/src/rules/__tests__/EvaluateIfEntityFieldPredicatePrivacyPolicyRule-test.ts +47 -0
- package/src/utils/EntityPrivacyUtils.ts +18 -29
- package/src/utils/__testfixtures__/PrivacyPolicyRuleTestUtils.ts +2 -2
- package/src/utils/__testfixtures__/StubDatabaseAdapter.ts +13 -101
- package/src/utils/__tests__/EntityCreationUtils-test.ts +6 -6
- package/src/utils/__tests__/EntityPrivacyUtils-test.ts +2 -2
- package/src/utils/collections/maps.ts +2 -2
- package/build/src/EntityLoaderUtils.js +0 -147
- package/build/src/EntityLoaderUtils.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EntityPrivacyPolicyRuleEvaluationContext } from '../EntityPrivacyPolicy';
|
|
2
2
|
import { EntityQueryContext } from '../EntityQueryContext';
|
|
3
3
|
import { ReadonlyEntity } from '../ReadonlyEntity';
|
|
4
4
|
import { ViewerContext } from '../ViewerContext';
|
|
@@ -46,7 +46,7 @@ export abstract class PrivacyPolicyRule<
|
|
|
46
46
|
abstract evaluateAsync(
|
|
47
47
|
viewerContext: TViewerContext,
|
|
48
48
|
queryContext: EntityQueryContext,
|
|
49
|
-
evaluationContext:
|
|
49
|
+
evaluationContext: EntityPrivacyPolicyRuleEvaluationContext<
|
|
50
50
|
TFields,
|
|
51
51
|
TIDField,
|
|
52
52
|
TViewerContext,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { anything, instance, mock } from 'ts-mockito';
|
|
2
|
+
|
|
3
|
+
import { EntityPrivacyPolicyRuleEvaluationContext } 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<EntityPrivacyPolicyRuleEvaluationContext<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<EntityPrivacyPolicyRuleEvaluationContext<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<EntityPrivacyPolicyRuleEvaluationContext<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 { EntityPrivacyPolicyRuleEvaluationContext } 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<EntityPrivacyPolicyRuleEvaluationContext<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<EntityPrivacyPolicyRuleEvaluationContext<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<EntityPrivacyPolicyRuleEvaluationContext<any, any, any, any, any>>()),
|
|
60
|
+
entity: anything(),
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
);
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { anything, 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
|
+
action: anything(),
|
|
100
|
+
previousValue: instance(childEntityMock),
|
|
101
|
+
cascadingDeleteCause: {
|
|
102
|
+
entity: parentEntity,
|
|
103
|
+
cascadingDeleteCause: null,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
entity: instance(childEntityMock),
|
|
107
|
+
},
|
|
108
|
+
// parent id matches parent being deleted, field null in current version but filled in previous version
|
|
109
|
+
{
|
|
110
|
+
viewerContext: instance(mock(ViewerContext)),
|
|
111
|
+
queryContext: instance(mock(EntityQueryContext)),
|
|
112
|
+
evaluationContext: {
|
|
113
|
+
action: anything(),
|
|
114
|
+
previousValue: instance(childEntityMock),
|
|
115
|
+
cascadingDeleteCause: {
|
|
116
|
+
entity: parentEntity,
|
|
117
|
+
cascadingDeleteCause: null,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
entity: instance(childEntityMockWithNullifiedField),
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
skipCases: [
|
|
124
|
+
// no cascading delete
|
|
125
|
+
{
|
|
126
|
+
viewerContext: instance(mock(ViewerContext)),
|
|
127
|
+
queryContext: instance(mock(EntityQueryContext)),
|
|
128
|
+
evaluationContext: {
|
|
129
|
+
action: anything(),
|
|
130
|
+
previousValue: null,
|
|
131
|
+
cascadingDeleteCause: null,
|
|
132
|
+
},
|
|
133
|
+
entity: instance(childEntityMock),
|
|
134
|
+
},
|
|
135
|
+
// cascading delete not from parent entity class
|
|
136
|
+
{
|
|
137
|
+
viewerContext: instance(mock(ViewerContext)),
|
|
138
|
+
queryContext: instance(mock(EntityQueryContext)),
|
|
139
|
+
evaluationContext: {
|
|
140
|
+
action: anything(),
|
|
141
|
+
previousValue: null,
|
|
142
|
+
cascadingDeleteCause: {
|
|
143
|
+
entity: instance(unrelatedOtherEntityMock),
|
|
144
|
+
cascadingDeleteCause: null,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
entity: instance(childEntityMock),
|
|
148
|
+
},
|
|
149
|
+
// cascading delete from different parent, field not nullified
|
|
150
|
+
{
|
|
151
|
+
viewerContext: instance(mock(ViewerContext)),
|
|
152
|
+
queryContext: instance(mock(EntityQueryContext)),
|
|
153
|
+
evaluationContext: {
|
|
154
|
+
action: anything(),
|
|
155
|
+
previousValue: null,
|
|
156
|
+
cascadingDeleteCause: {
|
|
157
|
+
entity: otherParentEntity,
|
|
158
|
+
cascadingDeleteCause: null,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
entity: instance(childEntityMock),
|
|
162
|
+
},
|
|
163
|
+
// entity belongs to different parent
|
|
164
|
+
{
|
|
165
|
+
viewerContext: instance(mock(ViewerContext)),
|
|
166
|
+
queryContext: instance(mock(EntityQueryContext)),
|
|
167
|
+
evaluationContext: {
|
|
168
|
+
action: anything(),
|
|
169
|
+
previousValue: null,
|
|
170
|
+
cascadingDeleteCause: {
|
|
171
|
+
entity: parentEntity,
|
|
172
|
+
cascadingDeleteCause: null,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
entity: instance(childEntityDifferentParentMock),
|
|
176
|
+
},
|
|
177
|
+
// parent id field undefined (null) and no previous value
|
|
178
|
+
{
|
|
179
|
+
viewerContext: instance(mock(ViewerContext)),
|
|
180
|
+
queryContext: instance(mock(EntityQueryContext)),
|
|
181
|
+
evaluationContext: {
|
|
182
|
+
action: anything(),
|
|
183
|
+
previousValue: null,
|
|
184
|
+
cascadingDeleteCause: {
|
|
185
|
+
entity: parentEntity,
|
|
186
|
+
cascadingDeleteCause: null,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
entity: instance(childEntityMockWithNullifiedField),
|
|
190
|
+
},
|
|
191
|
+
// parent id now null but previous value different parent
|
|
192
|
+
{
|
|
193
|
+
viewerContext: instance(mock(ViewerContext)),
|
|
194
|
+
queryContext: instance(mock(EntityQueryContext)),
|
|
195
|
+
evaluationContext: {
|
|
196
|
+
action: anything(),
|
|
197
|
+
previousValue: instance(childEntityDifferentParentMock),
|
|
198
|
+
cascadingDeleteCause: {
|
|
199
|
+
entity: parentEntity,
|
|
200
|
+
cascadingDeleteCause: null,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
entity: instance(childEntityMockWithNullifiedField),
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// Test with custom lookup field (parentEntityLookupByField)
|
|
210
|
+
const parentEntityWithNameMock = mock(TestParentEntity);
|
|
211
|
+
when(parentEntityWithNameMock.getField('name')).thenReturn('test-name');
|
|
212
|
+
const parentEntityWithName = instance(parentEntityWithNameMock);
|
|
213
|
+
Object.setPrototypeOf(parentEntityWithName, TestParentEntity.prototype);
|
|
214
|
+
|
|
215
|
+
const childEntityWithNameRefMock = mock(TestChildEntity);
|
|
216
|
+
when(childEntityWithNameRefMock.getField('parent_name')).thenReturn('test-name');
|
|
217
|
+
|
|
218
|
+
const childEntityWithNameRefMockWithNullifiedField = mock(TestChildEntity);
|
|
219
|
+
when(childEntityWithNameRefMockWithNullifiedField.getField('parent_name')).thenReturn(null);
|
|
220
|
+
|
|
221
|
+
describePrivacyPolicyRule(
|
|
222
|
+
new AllowIfInParentCascadeDeletionPrivacyPolicyRule<
|
|
223
|
+
ChildFields,
|
|
224
|
+
'id',
|
|
225
|
+
ViewerContext,
|
|
226
|
+
TestChildEntity,
|
|
227
|
+
ParentFields,
|
|
228
|
+
'id',
|
|
229
|
+
TestParentEntity,
|
|
230
|
+
TestParentPrivacyPolicy
|
|
231
|
+
>({
|
|
232
|
+
fieldIdentifyingParentEntity: 'parent_name',
|
|
233
|
+
parentEntityClass: TestParentEntity,
|
|
234
|
+
parentEntityLookupByField: 'name',
|
|
235
|
+
}),
|
|
236
|
+
{
|
|
237
|
+
allowCases: [
|
|
238
|
+
// parent name matches parent being deleted, field not yet nullified
|
|
239
|
+
{
|
|
240
|
+
viewerContext: instance(mock(ViewerContext)),
|
|
241
|
+
queryContext: instance(mock(EntityQueryContext)),
|
|
242
|
+
evaluationContext: {
|
|
243
|
+
action: anything(),
|
|
244
|
+
previousValue: instance(childEntityWithNameRefMock),
|
|
245
|
+
cascadingDeleteCause: {
|
|
246
|
+
entity: parentEntityWithName,
|
|
247
|
+
cascadingDeleteCause: null,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
entity: instance(childEntityWithNameRefMock),
|
|
251
|
+
},
|
|
252
|
+
// parent name matches parent being deleted, field null in current version but filled in previous version
|
|
253
|
+
{
|
|
254
|
+
viewerContext: instance(mock(ViewerContext)),
|
|
255
|
+
queryContext: instance(mock(EntityQueryContext)),
|
|
256
|
+
evaluationContext: {
|
|
257
|
+
action: anything(),
|
|
258
|
+
previousValue: instance(childEntityWithNameRefMock),
|
|
259
|
+
cascadingDeleteCause: {
|
|
260
|
+
entity: parentEntityWithName,
|
|
261
|
+
cascadingDeleteCause: null,
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
entity: instance(childEntityWithNameRefMockWithNullifiedField),
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { anything, instance, mock } from 'ts-mockito';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { EntityPrivacyPolicyRuleEvaluationContext } from '../../EntityPrivacyPolicy';
|
|
4
4
|
import { EntityQueryContext } from '../../EntityQueryContext';
|
|
5
5
|
import { ViewerContext } from '../../ViewerContext';
|
|
6
6
|
import { describePrivacyPolicyRule } from '../../utils/__testfixtures__/PrivacyPolicyRuleTestUtils';
|
|
@@ -12,7 +12,7 @@ describePrivacyPolicyRule(new AlwaysAllowPrivacyPolicyRule(), {
|
|
|
12
12
|
viewerContext: instance(mock(ViewerContext)),
|
|
13
13
|
queryContext: instance(mock(EntityQueryContext)),
|
|
14
14
|
evaluationContext:
|
|
15
|
-
instance(mock<
|
|
15
|
+
instance(mock<EntityPrivacyPolicyRuleEvaluationContext<any, any, any, any, any>>()),
|
|
16
16
|
entity: anything(),
|
|
17
17
|
},
|
|
18
18
|
],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { anything, instance, mock } from 'ts-mockito';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { EntityPrivacyPolicyRuleEvaluationContext } from '../../EntityPrivacyPolicy';
|
|
4
4
|
import { EntityQueryContext } from '../../EntityQueryContext';
|
|
5
5
|
import { ViewerContext } from '../../ViewerContext';
|
|
6
6
|
import { describePrivacyPolicyRule } from '../../utils/__testfixtures__/PrivacyPolicyRuleTestUtils';
|
|
@@ -12,7 +12,7 @@ describePrivacyPolicyRule(new AlwaysDenyPrivacyPolicyRule(), {
|
|
|
12
12
|
viewerContext: instance(mock(ViewerContext)),
|
|
13
13
|
queryContext: instance(mock(EntityQueryContext)),
|
|
14
14
|
evaluationContext:
|
|
15
|
-
instance(mock<
|
|
15
|
+
instance(mock<EntityPrivacyPolicyRuleEvaluationContext<any, any, any, any, any>>()),
|
|
16
16
|
entity: anything(),
|
|
17
17
|
},
|
|
18
18
|
],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { anything, instance, mock } from 'ts-mockito';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { EntityPrivacyPolicyRuleEvaluationContext } from '../../EntityPrivacyPolicy';
|
|
4
4
|
import { EntityQueryContext } from '../../EntityQueryContext';
|
|
5
5
|
import { ViewerContext } from '../../ViewerContext';
|
|
6
6
|
import { describePrivacyPolicyRule } from '../../utils/__testfixtures__/PrivacyPolicyRuleTestUtils';
|
|
@@ -12,7 +12,7 @@ describePrivacyPolicyRule(new AlwaysSkipPrivacyPolicyRule(), {
|
|
|
12
12
|
viewerContext: instance(mock(ViewerContext)),
|
|
13
13
|
queryContext: instance(mock(EntityQueryContext)),
|
|
14
14
|
evaluationContext:
|
|
15
|
-
instance(mock<
|
|
15
|
+
instance(mock<EntityPrivacyPolicyRuleEvaluationContext<any, any, any, any, any>>()),
|
|
16
16
|
entity: anything(),
|
|
17
17
|
},
|
|
18
18
|
],
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { mock, instance, when } from 'ts-mockito';
|
|
2
|
+
|
|
3
|
+
import { EntityPrivacyPolicyRuleEvaluationContext } 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<EntityPrivacyPolicyRuleEvaluationContext<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<EntityPrivacyPolicyRuleEvaluationContext<any, any, any, any, any>>()),
|
|
43
|
+
entity: entityFoo,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
);
|
|
@@ -354,13 +354,16 @@ async function canViewerDeleteInternalAsync<
|
|
|
354
354
|
entityCompanionProvider.getCompanionForEntity(inboundEdge).entityCompanionDefinition
|
|
355
355
|
.entityConfiguration;
|
|
356
356
|
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
357
|
+
const entityCompanion = viewerContext.getViewerScopedEntityCompanionForClass(inboundEdge);
|
|
358
|
+
const loaderFactory = entityCompanion.getLoaderFactory();
|
|
359
|
+
const loader = loaderFactory.forLoad(queryContext, {
|
|
360
|
+
previousValue: null,
|
|
361
|
+
cascadingDeleteCause: newCascadingDeleteCause,
|
|
362
|
+
});
|
|
363
|
+
const constructionUtils = loaderFactory.constructionUtils(queryContext, {
|
|
364
|
+
previousValue: null,
|
|
365
|
+
cascadingDeleteCause: newCascadingDeleteCause,
|
|
366
|
+
});
|
|
364
367
|
|
|
365
368
|
for (const [fieldName, fieldDefinition] of configurationForInboundEdge.schema) {
|
|
366
369
|
const association = fieldDefinition.association;
|
|
@@ -384,18 +387,12 @@ async function canViewerDeleteInternalAsync<
|
|
|
384
387
|
edgeDeletionPermissionInferenceBehavior ===
|
|
385
388
|
EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL
|
|
386
389
|
) {
|
|
387
|
-
const singleEntityResultToTestForInboundEdge =
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
? sourceEntity.getField(association.associatedEntityLookupByField as any)
|
|
394
|
-
: sourceEntity.getID(),
|
|
395
|
-
},
|
|
396
|
-
],
|
|
397
|
-
{ orderBy: [] },
|
|
398
|
-
);
|
|
390
|
+
const singleEntityResultToTestForInboundEdge = await loader['loadOneByFieldEqualingAsync'](
|
|
391
|
+
fieldName,
|
|
392
|
+
association.associatedEntityLookupByField
|
|
393
|
+
? sourceEntity.getField(association.associatedEntityLookupByField)
|
|
394
|
+
: sourceEntity.getID(),
|
|
395
|
+
);
|
|
399
396
|
entityResultsToCheckForInboundEdge = singleEntityResultToTestForInboundEdge
|
|
400
397
|
? [singleEntityResultToTestForInboundEdge]
|
|
401
398
|
: [];
|
|
@@ -403,7 +400,7 @@ async function canViewerDeleteInternalAsync<
|
|
|
403
400
|
const entityResultsForInboundEdge = await loader.loadManyByFieldEqualingAsync(
|
|
404
401
|
fieldName,
|
|
405
402
|
association.associatedEntityLookupByField
|
|
406
|
-
? sourceEntity.getField(association.associatedEntityLookupByField
|
|
403
|
+
? sourceEntity.getField(association.associatedEntityLookupByField)
|
|
407
404
|
: sourceEntity.getID(),
|
|
408
405
|
);
|
|
409
406
|
entityResultsToCheckForInboundEdge = entityResultsForInboundEdge;
|
|
@@ -451,14 +448,6 @@ async function canViewerDeleteInternalAsync<
|
|
|
451
448
|
// privacy policy as it would be after the cascading SET NULL operation
|
|
452
449
|
const previousAndSyntheticEntitiesForInboundEdge = entitiesForInboundEdge.map(
|
|
453
450
|
(entity) => {
|
|
454
|
-
const entityLoader = viewerContext
|
|
455
|
-
.getViewerScopedEntityCompanionForClass(inboundEdge)
|
|
456
|
-
.getLoaderFactory()
|
|
457
|
-
.forLoad(queryContext, {
|
|
458
|
-
previousValue: entity,
|
|
459
|
-
cascadingDeleteCause: newCascadingDeleteCause,
|
|
460
|
-
});
|
|
461
|
-
|
|
462
451
|
const allFields = entity.getAllDatabaseFields();
|
|
463
452
|
const syntheticFields = {
|
|
464
453
|
...allFields,
|
|
@@ -467,7 +456,7 @@ async function canViewerDeleteInternalAsync<
|
|
|
467
456
|
|
|
468
457
|
return {
|
|
469
458
|
previousValue: entity,
|
|
470
|
-
syntheticallyUpdatedValue:
|
|
459
|
+
syntheticallyUpdatedValue: constructionUtils.constructEntity(syntheticFields),
|
|
471
460
|
};
|
|
472
461
|
},
|
|
473
462
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from '@jest/globals';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { EntityPrivacyPolicyRuleEvaluationContext } from '../../EntityPrivacyPolicy';
|
|
4
4
|
import { EntityQueryContext } from '../../EntityQueryContext';
|
|
5
5
|
import { ReadonlyEntity } from '../../ReadonlyEntity';
|
|
6
6
|
import { ViewerContext } from '../../ViewerContext';
|
|
@@ -15,7 +15,7 @@ export interface Case<
|
|
|
15
15
|
> {
|
|
16
16
|
viewerContext: TViewerContext;
|
|
17
17
|
queryContext: EntityQueryContext;
|
|
18
|
-
evaluationContext:
|
|
18
|
+
evaluationContext: EntityPrivacyPolicyRuleEvaluationContext<
|
|
19
19
|
TFields,
|
|
20
20
|
TIDField,
|
|
21
21
|
TViewerContext,
|