@reventlessdev/reventless-core 3.0.0-alpha.260 → 3.0.0-alpha.262
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/CHANGELOG.md +24 -0
- package/package.json +10 -10
- package/src/Behavior.res +1 -4
- package/src/Message.res +17 -14
- package/src/Projection.res +51 -25
- package/src/RequestContext.res +8 -9
- package/src/ResourceAttribution.res +28 -14
- package/src/adapter/Adapter.res +48 -4
- package/src/adapter/EventLogProvisioning/EventLogProvisioning.res +5 -5
- package/src/adapter/Messaging/Messaging_Log_Backend.res +10 -7
- package/src/adapter/Messaging/Messaging_Log_Backend.res.mjs +2 -4
- package/src/adapter/Monitoring/Monitoring.res +16 -17
- package/src/adapter/QueryInterception/QueryInterception.res +0 -1
- package/src/adapter/Runtime/AggregateRuntime_Builder_Common.res +12 -5
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +11 -7
- package/src/adapter/Runtime/PluginRuntime_Builder_Micro.res +1 -7
- package/src/adapter/Runtime/PluginRuntime_Builder_Single.res +1 -7
- package/src/adapter/RuntimeExtension/RuntimeExtension.res +0 -1
- package/src/adapter/Secrets/Secrets_Node_Backend.res +60 -0
- package/src/adapter/Secrets/Secrets_Node_Backend.res.mjs +69 -0
- package/src/admin/Platform_Admin.res +17 -6
- package/src/admin/Platform_AdminApi.res +14 -9
- package/src/admin/Platform_Admin_Callback.res +1 -1
- package/src/admin/Platform_BakedManifest.res +34 -34
- package/src/admin/Platform_ComponentDefinitionsApi.res +9 -21
- package/src/admin/Platform_PluginStructuresApi.res +15 -12
- package/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res +1 -1
- package/src/components/Aggregate/Aggregate.res +13 -12
- package/src/components/Aggregate/Aggregate_Builder.res +22 -21
- package/src/components/Aggregate/Aggregate_Callback.res +91 -86
- package/src/components/Api/ApiNoApiHelpers.res +1 -3
- package/src/components/Api/Api_Ids.res +0 -1
- package/src/components/Api/Api_Naming.res +41 -18
- package/src/components/Api/GraphQL_FragmentGenerator.res +38 -51
- package/src/components/Api/GraphQL_SchemaInspector.res +5 -3
- package/src/components/Api/GraphQL_Stitcher.res +9 -10
- package/src/components/Api/MCP_SchemaGenerator.res +8 -12
- package/src/components/Api/OwnerScopeDiagnostics.res +1 -2
- package/src/components/Api/QueryDbListQuery.res +13 -6
- package/src/components/Api/SchemaType.res +28 -23
- package/src/components/Api/SuryToJsonSchema.res +9 -10
- package/src/components/AutomationSlice/AutomationSlice.res +4 -4
- package/src/components/AutomationSlice/AutomationSlice_Builder.res +15 -14
- package/src/components/AutomationSlice/AutomationSlice_Callback.res +3 -3
- package/src/components/Builder_Helpers.res +12 -23
- package/src/components/CommandGenerator/CommandGenerator.res +6 -1
- package/src/components/CommandGenerator/CommandGenerator_Builder.res +29 -21
- package/src/components/CommandGenerator/CommandGenerator_Callback.res +17 -21
- package/src/components/CommandTopic/CommandTopic.res +3 -3
- package/src/components/CommandTopic/CommandTopic_Builder.res +10 -10
- package/src/components/CommandTopic/CommandTopic_Callback.res +1 -4
- package/src/components/CommandTopic/CommandTopic_Helpers.res +1 -2
- package/src/components/Counter/Counter_Builder.res +1 -6
- package/src/components/Counter/Counter_Callback.res +3 -3
- package/src/components/Dcb/Dcb_Builder.res +970 -956
- package/src/components/DcbEventLog/DcbEventLog.res +4 -4
- package/src/components/DcbEventLog/DcbEventLog_Builder.res +0 -1
- package/src/components/DcbEventLog/DcbEventLog_Operations.res +17 -18
- package/src/components/EventCollector/EventCollector_Builder.res +2 -1
- package/src/components/EventLog/EventLog.res +5 -1
- package/src/components/EventLog/EventLog_Operations.res +12 -8
- package/src/components/EventMapper/EventMapper.res +8 -10
- package/src/components/EventMapper/EventMapper_Builder.res +4 -1
- package/src/components/EventMapper/EventMapper_Callback.res +46 -31
- package/src/components/EventTopic/EventTopic.res +3 -3
- package/src/components/EventTopic/EventTopic_Builder.res +1 -1
- package/src/components/Extension/Extension.res +4 -1
- package/src/components/Extension/Extension_Operations.res +11 -3
- package/src/components/ExtensionPoint/ExtensionPoint.res +3 -3
- package/src/components/ExtensionPoint/ExtensionPoint_Builder.res +9 -10
- package/src/components/ExtensionPoint/ExtensionPoint_Callback.res +2 -6
- package/src/components/ExtensionPoint/ExtensionPoint_Operations.res +13 -5
- package/src/components/Heartbeat/Heartbeat_Callback.res +3 -3
- package/src/components/InboundTranslationSlice/InboundTranslationSlice.res +4 -4
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res +7 -10
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice.res +4 -4
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +15 -14
- package/src/components/QueryDb/QueryDb_Adapter.res +1 -3
- package/src/components/QueryDb/QueryDb_Operations.res +10 -10
- package/src/components/ReadModel/ReadModel.res +3 -3
- package/src/components/ReadModel/ReadModel_Builder.res +1 -2
- package/src/components/ReadModel/ReadModel_Callback.res +2 -1
- package/src/components/SideEffectHandler/SideEffectHandler_Callback.res +5 -2
- package/src/components/StateChangeSlice/StateChangeSlice.res +3 -3
- package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +23 -4
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +22 -6
- package/src/components/StateTopic.res +1 -3
- package/src/components/StateViewSlice/StateViewSlice.res +4 -4
- package/src/components/StateViewSlice/StateViewSlice_Builder.res +79 -68
- package/src/components/StateViewSlice/StateViewSlice_Callback.res +3 -1
- package/src/components/Task/Task.res +3 -3
- package/src/components/Task/Task_Adapter.res +4 -1
- package/src/components/Task/Task_Builder.res +24 -21
- package/src/plugin/api/PluginBaseFragment.res +9 -3
- package/src/plugin/component/Capability_Inference.res +8 -10
- package/src/plugin/component/Plugin.res +6 -6
- package/src/plugin/component/Plugin_Builder.res +54 -50
- package/src/plugin/component/Plugin_Callback.res +10 -6
- package/src/plugin/component/Plugin_Helpers.res +264 -230
- package/src/plugin/component/Plugin_Structure.res +370 -324
- package/src/plugin/component/Plugin_SubscriptionSchema.res +8 -6
- package/src/plugin/component/SchemaWalker.res +11 -12
- package/src/plugin/connect/PluginConnectExtension_Mapping.res +45 -50
- package/src/plugin/lifecycle/PluginBehavior.res +3 -4
- package/src/plugin/lifecycle/PluginSpec.res +1 -2
- package/src/plugin/lifecycle/PluginsProjection.res +16 -5
- package/src/plugin/lifecycle/PluginsReadModelSpec.res +0 -2
- package/src/util/CommandPublisher.res +9 -2
- package/src/util/CsvStream.res +3 -3
- package/src/util/Interstack.res +7 -15
- package/src/util/Interstack.resi +3 -3
- package/src/util/LogFormat.res +5 -9
- package/src/util/Logger.res +1 -2
- package/src/util/Lru.res +2 -1
- package/src/util/Lru.resi +2 -3
- package/src/util/ScheduleOps.res +24 -27
- package/src/util/StorePrefixCollision.res +6 -9
- package/src/util/Util_Promise.res +4 -1
- package/src/util/Util_Promise.res.mjs +1 -1
- package/tests/IdentityTest.res +166 -113
- package/tests/ResourceAttributionTest.res +28 -25
- package/tests/Secrets_Node_BackendTest.res +141 -0
- package/tests/Secrets_Node_BackendTest.res.mjs +152 -0
- package/tests/StorePrefixCollisionTest.res +2 -1
- package/tests/TaskBucketNamingTest.res +3 -2
- package/tests/adapter/EventLogProvisioningTest.res +51 -48
- package/tests/adapter/MonitoringTest.res +81 -38
- package/tests/adapter/QueryInterceptionTest.res +2 -2
- package/tests/adapter/RefusalVocabularyTest.res +31 -23
- package/tests/adapter/RuntimeExtensionHookReachTest.res +9 -8
- package/tests/adapter/RuntimeExtensionTest.res +5 -3
- package/tests/admin/AdminApiSchemaDriftTest.res +22 -16
- package/tests/admin/Platform_Admin_StructureTest.res +22 -14
- package/tests/admin/Platform_Admin_StructureTest.res.mjs +4 -3
- package/tests/admin/Platform_BakedManifestTest.res +31 -35
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +18 -15
- package/tests/admin/Platform_PluginStructuresApiTest.res +5 -3
- package/tests/admin/Platform_UIFragmentsApiTest.res +6 -3
- package/tests/aggregate/AggregateCacheTest.res +10 -12
- package/tests/aggregate/AggregateCallbackTest.res +75 -46
- package/tests/aggregate/AggregateCausationTest.res +19 -20
- package/tests/aggregate/AggregateConflictTest.res +41 -19
- package/tests/aggregate/AggregateFixtures.res +15 -5
- package/tests/aggregate/AggregateRejectionTest.res +97 -80
- package/tests/aggregate/AggregateSnapshotTest.res +44 -16
- package/tests/api/Api_IdsTest.res +10 -4
- package/tests/api/BrandedMarkerCompositionTest.res +48 -36
- package/tests/api/GeolocationSemanticTest.res +3 -3
- package/tests/api/GraphQL_FragmentGeneratorTest.res +66 -54
- package/tests/api/OwnerScopeDiagnosticsTest.res +31 -27
- package/tests/api/StorageRefFieldsTest.res +30 -18
- package/tests/api/SuryToJsonSchemaTest.res +1315 -1014
- package/tests/api/TaggedUnionSchemaTest.res +17 -24
- package/tests/commandgenerator/CommandGeneratorCallbackTest.res +235 -179
- package/tests/commandgenerator/CommandGeneratorFixtures.res +8 -13
- package/tests/commandgenerator/OwnerStampingTest.res +91 -76
- package/tests/commandtopic/CommandOutcomeHookTest.res +198 -155
- package/tests/commandtopic/CommandTopicCallbackFixtures.res +5 -4
- package/tests/commandtopic/CommandTopicCallbackTest.res +61 -43
- package/tests/commandtopic/CommandTopicHelpersRejectionTest.res +70 -55
- package/tests/counter/CounterCallbackTest.res +40 -34
- package/tests/counter/CounterFixtures.res +4 -4
- package/tests/dcb/DcbDecodeTest.res +115 -84
- package/tests/dcb/DcbEventLogOperationsTest.res +194 -143
- package/tests/dcb/DcbEventLogSchemaTest.res +13 -13
- package/tests/dcb/DcbFixtures.res +4 -5
- package/tests/dcb/DcbScopeInferenceTest.res +173 -114
- package/tests/dcb/DcbStateChangeSliceTest.res +391 -262
- package/tests/dcb/DcbTagTest.res +22 -11
- package/tests/dcb/DcbValidationTest.res +288 -218
- package/tests/eventlog/EventLogFixtures.res +4 -4
- package/tests/eventlog/EventLogOperationsTest.res +102 -67
- package/tests/eventlog/EventLogRetryTest.res +104 -65
- package/tests/eventmapper/EventMapperCallbackTest.res +118 -83
- package/tests/eventmapper/EventMapperFixtures.res +15 -23
- package/tests/eventmapper/EventMapperTest.res +54 -38
- package/tests/eventtopic/EventTopicFixtures.res +3 -4
- package/tests/eventtopic/EventTopicOperationsTest.res +41 -29
- package/tests/extensionpoint/ExtensionPointCallbackTest.res +58 -45
- package/tests/extensionpoint/ExtensionPointFixtures.res +16 -20
- package/tests/extensionpoint/ExtensionPointOperationsTest.res +70 -49
- package/tests/extensionpoint/PluginDisconnectScheduleTest.res +2 -1
- package/tests/logger/LogFormatTest.res +104 -82
- package/tests/logger/LoggerLazyTest.res +21 -12
- package/tests/message/MessageTest.res +96 -93
- package/tests/message/MetaEnvelopeTest.res +26 -30
- package/tests/plugin/HeartbeatDisconnectGraceTest.res +31 -25
- package/tests/plugin/ManifestVisibilityTest.res +20 -12
- package/tests/plugin/PluginDefinitionScalars.res +1 -2
- package/tests/plugin/PluginLifecycleCorpusTest.res +15 -12
- package/tests/plugin/PluginSpecExperiment.res +0 -1
- package/tests/plugin/PluginStructureTest.res +1529 -1045
- package/tests/plugin/Plugin_EventQuerySchemaTest.res +21 -10
- package/tests/plugin/SchemaWalkerTest.res +116 -70
- package/tests/plugin/StateChange/PsReserveStock.res +4 -4
- package/tests/plugin/StateView/PsAnnotatedView.res +4 -4
- package/tests/plugin/StateView/PsCustomersView.res +4 -4
- package/tests/plugin/StateView/PsShipmentsView.res +3 -2
- package/tests/projection/LifecycleTrailTest.res +1 -4
- package/tests/projection/ProjectionOptimizeTest.res +13 -9
- package/tests/querydb/QueryDbFixtures.res +0 -1
- package/tests/querydb/QueryDbOperationsTest.res +99 -64
- package/tests/querydb/TaggedUnionStampTest.res +10 -8
- package/tests/sideeffecthandler/SideEffectHandlerCallbackTest.res +50 -35
- package/tests/sideeffecthandler/SideEffectHandlerFixtures.res +4 -10
- package/tests/util/CommandPublisherTest.res +0 -1
- package/tests/util/CsvStreamTest.res +3 -3
- package/tests/util/LruTest.res +84 -56
package/tests/IdentityTest.res
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
open JestGlobals
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
describe("Identity", () => {
|
|
5
4
|
describe("anonymous", () => {
|
|
6
|
-
testSync(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
testSync(
|
|
6
|
+
"has userId 'anonymous'",
|
|
7
|
+
() => {
|
|
8
|
+
expect(Reventless.Identity.anonymous.userId)->toBe("anonymous")
|
|
9
|
+
},
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
testSync(
|
|
13
|
+
"has empty groups",
|
|
14
|
+
() => {
|
|
15
|
+
expect(Reventless.Identity.anonymous.groups)->toEqual([])
|
|
16
|
+
},
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
testSync(
|
|
20
|
+
"has InMemory provider",
|
|
21
|
+
() => {
|
|
22
|
+
expect(Reventless.Identity.anonymous.provider)->toEqual(Reventless.Identity.InMemory)
|
|
23
|
+
},
|
|
24
|
+
)
|
|
17
25
|
})
|
|
18
26
|
|
|
19
27
|
describe("hasGroup", () => {
|
|
@@ -24,13 +32,19 @@ describe("Identity", () => {
|
|
|
24
32
|
provider: Cognito,
|
|
25
33
|
}
|
|
26
34
|
|
|
27
|
-
testSync(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
testSync(
|
|
36
|
+
"returns true when group is present",
|
|
37
|
+
() => {
|
|
38
|
+
expect(Reventless.Identity.hasGroup(identity, "admin"))->toBe(true)
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
testSync(
|
|
43
|
+
"returns false when group is absent",
|
|
44
|
+
() => {
|
|
45
|
+
expect(Reventless.Identity.hasGroup(identity, "viewers"))->toBe(false)
|
|
46
|
+
},
|
|
47
|
+
)
|
|
34
48
|
})
|
|
35
49
|
|
|
36
50
|
describe("getClaim", () => {
|
|
@@ -42,113 +56,152 @@ describe("Identity", () => {
|
|
|
42
56
|
provider: InMemory,
|
|
43
57
|
}
|
|
44
58
|
|
|
45
|
-
testSync(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
testSync(
|
|
60
|
+
"returns Some for existing claim",
|
|
61
|
+
() => {
|
|
62
|
+
expect(Reventless.Identity.getClaim(identity, "tenant"))->toEqual(Some("acme"))
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
testSync(
|
|
67
|
+
"returns None for missing claim",
|
|
68
|
+
() => {
|
|
69
|
+
expect(Reventless.Identity.getClaim(identity, "missing"))->toEqual(None)
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
testSync(
|
|
74
|
+
"returns None when claims are absent",
|
|
75
|
+
() => {
|
|
76
|
+
let noClaims: Reventless.Identity.t = {
|
|
77
|
+
userId: "user-2",
|
|
78
|
+
username: "bob",
|
|
79
|
+
groups: [],
|
|
80
|
+
provider: InMemory,
|
|
81
|
+
}
|
|
82
|
+
expect(Reventless.Identity.getClaim(noClaims, "tenant"))->toEqual(None)
|
|
83
|
+
},
|
|
84
|
+
)
|
|
62
85
|
})
|
|
63
86
|
|
|
64
87
|
describe("schema roundtrip", () => {
|
|
65
|
-
testSync(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
88
|
+
testSync(
|
|
89
|
+
"encode then decode preserves identity",
|
|
90
|
+
() => {
|
|
91
|
+
let identity: Reventless.Identity.t = {
|
|
92
|
+
userId: "user-1",
|
|
93
|
+
username: "alice",
|
|
94
|
+
groups: ["admin"],
|
|
95
|
+
claims: Dict.fromArray([("tenant", "acme")]),
|
|
96
|
+
provider: Cognito,
|
|
97
|
+
}
|
|
98
|
+
let json = identity->Reventless.Util_Sury.toJson(Reventless.Identity.schema)
|
|
99
|
+
let decoded = json->Reventless.Util_Sury.fromJson(Reventless.Identity.schema)
|
|
100
|
+
expect(decoded)->toEqual(identity)
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
testSync(
|
|
105
|
+
"roundtrip with Custom provider",
|
|
106
|
+
() => {
|
|
107
|
+
let identity: Reventless.Identity.t = {
|
|
108
|
+
userId: "ext-1",
|
|
109
|
+
username: "external",
|
|
110
|
+
groups: [],
|
|
111
|
+
provider: Custom("oauth2"),
|
|
112
|
+
}
|
|
113
|
+
let json = identity->Reventless.Util_Sury.toJson(Reventless.Identity.schema)
|
|
114
|
+
let decoded = json->Reventless.Util_Sury.fromJson(Reventless.Identity.schema)
|
|
115
|
+
expect(decoded)->toEqual(identity)
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
testSync(
|
|
120
|
+
"roundtrip without optional claims",
|
|
121
|
+
() => {
|
|
122
|
+
let identity: Reventless.Identity.t = {
|
|
123
|
+
userId: "user-3",
|
|
124
|
+
username: "charlie",
|
|
125
|
+
groups: [],
|
|
126
|
+
provider: InMemory,
|
|
127
|
+
}
|
|
128
|
+
let json = identity->Reventless.Util_Sury.toJson(Reventless.Identity.schema)
|
|
129
|
+
let decoded = json->Reventless.Util_Sury.fromJson(Reventless.Identity.schema)
|
|
130
|
+
expect(decoded.userId)->toBe("user-3")->ignore
|
|
131
|
+
expect(decoded.provider)->toEqual(Reventless.Identity.InMemory)
|
|
132
|
+
},
|
|
133
|
+
)
|
|
102
134
|
})
|
|
103
135
|
})
|
|
104
136
|
|
|
105
137
|
describe("RequestContext", () => {
|
|
106
138
|
describe("test() constructor", () => {
|
|
107
|
-
testSync(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
testSync(
|
|
140
|
+
"defaults to anonymous identity",
|
|
141
|
+
() => {
|
|
142
|
+
let ctx = RequestContext.test()
|
|
143
|
+
expect(ctx.identity.userId)->toBe("anonymous")
|
|
144
|
+
},
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
testSync(
|
|
148
|
+
"accepts custom identity",
|
|
149
|
+
() => {
|
|
150
|
+
let identity: Reventless.Identity.t = {
|
|
151
|
+
userId: "user-1",
|
|
152
|
+
username: "alice",
|
|
153
|
+
groups: ["admin"],
|
|
154
|
+
provider: Cognito,
|
|
155
|
+
}
|
|
156
|
+
let ctx = RequestContext.test(~identity)
|
|
157
|
+
expect(ctx.identity.userId)->toBe("user-1")
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
testSync(
|
|
162
|
+
"defaults to empty claims",
|
|
163
|
+
() => {
|
|
164
|
+
let ctx = RequestContext.test()
|
|
165
|
+
expect(ctx.claims->Dict.keysToArray)->toEqual([])
|
|
166
|
+
},
|
|
167
|
+
)
|
|
127
168
|
})
|
|
128
169
|
|
|
129
170
|
describe("getClaim", () => {
|
|
130
|
-
testSync(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
171
|
+
testSync(
|
|
172
|
+
"returns claim value when present",
|
|
173
|
+
() => {
|
|
174
|
+
let ctx = RequestContext.test(~claims=Dict.fromArray([("tenant", "acme")]))
|
|
175
|
+
expect(RequestContext.getClaim(ctx, "tenant"))->toEqual(Some("acme"))
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
testSync(
|
|
180
|
+
"returns None when absent",
|
|
181
|
+
() => {
|
|
182
|
+
let ctx = RequestContext.test()
|
|
183
|
+
expect(RequestContext.getClaim(ctx, "missing"))->toEqual(None)
|
|
184
|
+
},
|
|
185
|
+
)
|
|
139
186
|
})
|
|
140
187
|
|
|
141
188
|
describe("withClaim", () => {
|
|
142
|
-
testSync(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
189
|
+
testSync(
|
|
190
|
+
"adds a claim to the context",
|
|
191
|
+
() => {
|
|
192
|
+
let ctx = RequestContext.test()
|
|
193
|
+
let updated = RequestContext.withClaim(ctx, "tenant", "acme")
|
|
194
|
+
expect(RequestContext.getClaim(updated, "tenant"))->toEqual(Some("acme"))
|
|
195
|
+
},
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
testSync(
|
|
199
|
+
"does not mutate the original context",
|
|
200
|
+
() => {
|
|
201
|
+
let ctx = RequestContext.test()
|
|
202
|
+
let _updated = RequestContext.withClaim(ctx, "tenant", "acme")
|
|
203
|
+
expect(RequestContext.getClaim(ctx, "tenant"))->toEqual(None)
|
|
204
|
+
},
|
|
205
|
+
)
|
|
153
206
|
})
|
|
154
207
|
})
|
|
@@ -104,15 +104,15 @@ describe("ResourceAttribution — carrying the context into deferred work", () =
|
|
|
104
104
|
testSync("two plugins deferred from one apply are each attributed to themselves", () => {
|
|
105
105
|
let outer = Attribution.enter(~platform="online-shop", ~plugin="Catalog")
|
|
106
106
|
let catalogSaw = ref(None)
|
|
107
|
-
let catalogFinish = Attribution.deferred(
|
|
108
|
-
catalogSaw := Attribution.current.contents.plugin
|
|
107
|
+
let catalogFinish = Attribution.deferred(
|
|
108
|
+
() => catalogSaw := Attribution.current.contents.plugin,
|
|
109
109
|
)
|
|
110
110
|
Attribution.restore(outer)
|
|
111
111
|
|
|
112
112
|
let previous = Attribution.enter(~platform="online-shop", ~plugin="Ordering")
|
|
113
113
|
let orderingSaw = ref(None)
|
|
114
|
-
let orderingFinish = Attribution.deferred(
|
|
115
|
-
orderingSaw := Attribution.current.contents.plugin
|
|
114
|
+
let orderingFinish = Attribution.deferred(
|
|
115
|
+
() => orderingSaw := Attribution.current.contents.plugin,
|
|
116
116
|
)
|
|
117
117
|
Attribution.restore(previous)
|
|
118
118
|
|
|
@@ -126,8 +126,8 @@ describe("ResourceAttribution — carrying the context into deferred work", () =
|
|
|
126
126
|
|
|
127
127
|
testSync("a throwing callback still puts the context back", () => {
|
|
128
128
|
let previous = Attribution.enter(~platform="online-shop", ~plugin="Catalog")
|
|
129
|
-
let finish: unit => unit = Attribution.deferred(
|
|
130
|
-
JsError.throwWithMessage("finish blew up")
|
|
129
|
+
let finish: unit => unit = Attribution.deferred(
|
|
130
|
+
() => JsError.throwWithMessage("finish blew up"),
|
|
131
131
|
)
|
|
132
132
|
Attribution.restore(previous)
|
|
133
133
|
|
|
@@ -164,22 +164,25 @@ describe("ResourceAttribution — carrying the context into deferred work", () =
|
|
|
164
164
|
// exercises the production choke point: a task side-effect handler registered
|
|
165
165
|
// during construct, then run the way `finishTasks` runs it — from an apply,
|
|
166
166
|
// with nothing attributed.
|
|
167
|
-
describe(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
167
|
+
describe(
|
|
168
|
+
"Builder_Helpers.registerTaskSideEffectHandler — attribution survives the deferral",
|
|
169
|
+
() => {
|
|
170
|
+
testSync("the registered finish sees the plugin that registered it", () => {
|
|
171
|
+
let before = Builder_Helpers.taskSideEffectFinishFns->Array.length
|
|
172
|
+
let observed = ref(None)
|
|
173
|
+
|
|
174
|
+
let previous = Attribution.enter(~platform="online-shop", ~plugin="Catalog")
|
|
175
|
+
Builder_Helpers.registerTaskSideEffectHandler(
|
|
176
|
+
~gate=Pulumi.Output.make(),
|
|
177
|
+
~finish=() => observed := Attribution.current.contents.plugin,
|
|
178
|
+
)
|
|
179
|
+
Attribution.restore(previous)
|
|
180
|
+
|
|
181
|
+
switch Builder_Helpers.taskSideEffectFinishFns->Array.get(before) {
|
|
182
|
+
| Some(finish) => finish()
|
|
183
|
+
| None => JsError.throwWithMessage("handler was not registered")
|
|
184
|
+
}
|
|
185
|
+
expect(observed.contents)->toEqual(Some("Catalog"))
|
|
186
|
+
})
|
|
187
|
+
},
|
|
188
|
+
)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
|
|
3
|
+
// The secret source. What matters here is not that it produces characters, but
|
|
4
|
+
// that it produces them uniformly — a token drawn from a skewed distribution is
|
|
5
|
+
// guessable in a way that no other test would notice.
|
|
6
|
+
|
|
7
|
+
module Secrets = Reventless.Secrets
|
|
8
|
+
module Backend = Secrets_Node_Backend
|
|
9
|
+
|
|
10
|
+
let token = (~length, ~alphabet) =>
|
|
11
|
+
Backend.provider.randomToken(~length, ~alphabet)->Result.getOr("")
|
|
12
|
+
|
|
13
|
+
describe("randomToken", () => {
|
|
14
|
+
testSync("draws the requested length", () =>
|
|
15
|
+
expect(
|
|
16
|
+
[1, 6, 32, 64]->Array.map(length => token(~length, ~alphabet=Digits)->String.length),
|
|
17
|
+
)->toEqual([1, 6, 32, 64])
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
testSync("draws only from the alphabet it was given", () => {
|
|
21
|
+
let allowed = Secrets.characters(UrlSafe)
|
|
22
|
+
expect(
|
|
23
|
+
token(~length=512, ~alphabet=UrlSafe)
|
|
24
|
+
->String.split("")
|
|
25
|
+
->Array.every(c => allowed->String.includes(c)),
|
|
26
|
+
)->toBe(true)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
testSync("digits are digits", () =>
|
|
30
|
+
expect(
|
|
31
|
+
token(~length=512, ~alphabet=Digits)
|
|
32
|
+
->String.split("")
|
|
33
|
+
->Array.every(c => "0123456789"->String.includes(c)),
|
|
34
|
+
)->toBe(true)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
// Not proof of randomness — it is proof the source is not a constant, which is
|
|
38
|
+
// the failure a fixed or broken binding would produce.
|
|
39
|
+
testSync("two draws differ", () =>
|
|
40
|
+
expect(token(~length=32, ~alphabet=UrlSafe) == token(~length=32, ~alphabet=UrlSafe))->toBe(
|
|
41
|
+
false,
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
// A zero-length token would compare equal to nothing and settle every
|
|
46
|
+
// challenge presented against it, so it is refused rather than clamped.
|
|
47
|
+
testSync("a non-positive length is refused, not clamped", () =>
|
|
48
|
+
expect(
|
|
49
|
+
[0, -1]->Array.map(
|
|
50
|
+
length => Backend.provider.randomToken(~length, ~alphabet=Digits)->Result.isError,
|
|
51
|
+
),
|
|
52
|
+
)->toEqual([true, true])
|
|
53
|
+
)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// 🚨 Checked over every byte value rather than by sampling the output, and the
|
|
57
|
+
// first attempt here got that wrong. `byte % 10` skews the low six digits by
|
|
58
|
+
// about four percent each — roughly 0.609 of draws landing in the low half
|
|
59
|
+
// against 0.600 for a fair source. At any sample size a test can afford that
|
|
60
|
+
// gap is a couple of standard errors, so a sampled assertion either flakes or,
|
|
61
|
+
// as the first version did, passes happily with the defence deleted.
|
|
62
|
+
describe("accepts — the rejection rule", () => {
|
|
63
|
+
let rejected = size =>
|
|
64
|
+
Array.fromInitializer(~length=256, i => i)->Array.filter(byte => !Backend.accepts(~byte, ~size))
|
|
65
|
+
|
|
66
|
+
testSync("rejects exactly the bytes that would skew a 10-character alphabet", () =>
|
|
67
|
+
expect(rejected(10))->toEqual([250, 251, 252, 253, 254, 255])
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
// 64 divides 256 exactly, so nothing needs discarding and a url-safe token
|
|
71
|
+
// costs no extra draws.
|
|
72
|
+
testSync("rejects nothing when the alphabet divides evenly", () =>
|
|
73
|
+
expect(rejected(64))->toEqual([])
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
// What survives always folds onto a whole number of cycles, which is the
|
|
77
|
+
// property that makes every character equally likely.
|
|
78
|
+
testSync("what it accepts is a whole multiple of the alphabet", () =>
|
|
79
|
+
expect(
|
|
80
|
+
[2, 3, 7, 10, 16, 26, 62, 64]->Array.map(
|
|
81
|
+
size => mod(256 - rejected(size)->Array.length, size),
|
|
82
|
+
),
|
|
83
|
+
)->toEqual([0, 0, 0, 0, 0, 0, 0, 0])
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
testSync("never discards more than one cycle's worth", () =>
|
|
87
|
+
expect([3, 7, 10, 26, 62]->Array.map(size => rejected(size)->Array.length < size))->toEqual([
|
|
88
|
+
true,
|
|
89
|
+
true,
|
|
90
|
+
true,
|
|
91
|
+
true,
|
|
92
|
+
true,
|
|
93
|
+
])
|
|
94
|
+
)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe("hash", () => {
|
|
98
|
+
testSync("is deterministic, which is what lets a proof be compared", () =>
|
|
99
|
+
expect(Backend.provider.hash("a-secret") == Backend.provider.hash("a-secret"))->toBe(true)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
testSync("separates different inputs", () =>
|
|
103
|
+
expect(Backend.provider.hash("a-secret") == Backend.provider.hash("a-secret "))->toBe(false)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
// One-way: the digest must not carry the secret it was made from.
|
|
107
|
+
testSync("does not contain its input", () =>
|
|
108
|
+
expect(
|
|
109
|
+
Backend.provider.hash("a-secret")
|
|
110
|
+
->Result.getOr("")
|
|
111
|
+
->String.includes("a-secret"),
|
|
112
|
+
)->toBe(false)
|
|
113
|
+
)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe("Capabilities.none.secrets", () => {
|
|
117
|
+
// Refusing rather than degrading: there is no weaker token that would be safe
|
|
118
|
+
// to hand back, so an unwired platform must fail rather than produce one.
|
|
119
|
+
testSync("refuses both operations", () =>
|
|
120
|
+
expect((
|
|
121
|
+
Reventless.Capabilities.none.secrets.randomToken(
|
|
122
|
+
~length=32,
|
|
123
|
+
~alphabet=UrlSafe,
|
|
124
|
+
)->Result.isError,
|
|
125
|
+
Reventless.Capabilities.none.secrets.hash("x")->Result.isError,
|
|
126
|
+
))->toEqual((true, true))
|
|
127
|
+
)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
describe("Secrets.fixed", () => {
|
|
131
|
+
// What a consumer's suite uses to know the secret before presenting it.
|
|
132
|
+
let provider = Secrets.fixed(~token="known", ~hash=input => `hashed:${input}`)
|
|
133
|
+
|
|
134
|
+
testSync("hands back the token it was built with", () =>
|
|
135
|
+
expect(provider.randomToken(~length=32, ~alphabet=UrlSafe))->toEqual(Ok("known"))
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
testSync("hashes through the function it was given", () =>
|
|
139
|
+
expect(provider.hash("known"))->toEqual(Ok("hashed:known"))
|
|
140
|
+
)
|
|
141
|
+
})
|