@reventlessdev/reventless-local 3.0.0-alpha.249 → 3.0.0-alpha.250
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 +8 -0
- package/package.json +13 -13
- package/src/BakedManifest.res +5 -6
- package/src/EmitCapabilities.res +3 -1
- package/src/LocalPlatformRegistry.res +3 -1
- package/src/LocalPlatformStart.res +7 -6
- package/src/LocalSeedTarget.res +3 -1
- package/src/Platform.res +291 -215
- package/src/ShellConfig.res +10 -17
- package/src/UiHints.res +8 -14
- package/src/UiSlots.res +10 -17
- package/src/adapter/Api/LocalEvents_Server.res +5 -3
- package/src/adapter/Api/LocalGraphQL_Adapter.res +4 -4
- package/src/adapter/Auth/LocalAuth.res +11 -15
- package/src/adapter/Auth/UserStore.res +5 -7
- package/src/adapter/Backend.res +1 -2
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +42 -26
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +13 -3
- package/src/adapter/CommandTopic/LocalCommandTopicChannel.res +15 -17
- package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res +18 -22
- package/src/adapter/Counter/LocalCounterHandler.res +1 -1
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +33 -22
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +57 -57
- package/src/adapter/DomainGraphQL_Server.res +35 -34
- package/src/adapter/DomainMCP_Server.res +45 -41
- package/src/adapter/EventCollector/LocalEventCollectorChannel.res +47 -36
- package/src/adapter/EventHistory/EventHistoryResolvers_GraphQL.res +15 -12
- package/src/adapter/EventLog/EventLogStorage_InMemory.res +9 -4
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res +36 -24
- package/src/adapter/EventTopic/LocalEventTopicPublisher.res +9 -3
- package/src/adapter/Heartbeat/LocalHeartbeatRunner.res +9 -14
- package/src/adapter/LocalBus.res +12 -12
- package/src/adapter/LocalCapabilities.res.mjs +1 -1
- package/src/adapter/LocalGeocodeResolvers.res +1 -4
- package/src/adapter/LocalSideEffectHandler.res +1 -1
- package/src/adapter/LocalStateChangeDescriptor.res +2 -1
- package/src/adapter/MCP_ServerInstance.res +51 -39
- package/src/adapter/ObjectStore/LocalObjectStore.res +5 -2
- package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +1 -2
- package/src/adapter/PlatformGraphQL_Server.res +9 -5
- package/src/adapter/ProjectionCheckpoint.res +8 -1
- package/src/adapter/ProjectionPending.res +1 -3
- package/src/adapter/QueryDb/LocalQueryDbStorage.res +2 -1
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +367 -269
- package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +5 -3
- package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +63 -40
- package/src/adapter/QueryDb/SortKey_Filter.res +14 -6
- package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res +12 -10
- package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res +8 -2
- package/src/adapter/Runtime/LocalPluginRuntime_Builder.res +1 -4
- package/src/adapter/Scheduler/LocalScheduledPublisher.res +12 -12
- package/src/adapter/SqliteDriver.res +16 -4
- package/src/adapter/Task/TaskBucket_Sqlite.res +5 -6
- package/src/components/Aggregate_Builder.res +6 -1
- package/src/components/Counter_Builder.res +1 -5
- package/src/components/DcbEventLog_Builder.res +5 -3
- package/src/components/ExtensionPoint_Builder.res +13 -15
- package/src/components/InboundTranslationSlice_Builder.res +3 -2
- package/src/components/OutboundTranslationSlice_Builder.res +3 -2
- package/src/components/ReadModel_Builder.res +18 -20
- package/src/components/StateViewSlice_Builder.res +3 -2
- package/src/components/Task_Builder.res +12 -13
- package/src/reset/LocalSeedReset.res +2 -5
- package/src/test/Mocks/MockDcbEventLogStorage.res +5 -1
- package/src/test/Mocks/MockEventLogStorage.res +8 -3
- package/src/test/Mocks/MockQueryDbStorage.res +3 -2
- package/src/test/TestRunner.res +1 -1
- package/tests/BakedManifestFilesTest.res +7 -10
- package/tests/LocalEventTapTest.res +26 -21
- package/tests/LocalPlatformStartTest.res +33 -28
- package/tests/PluginEventDecodeTest.res +3 -3
- package/tests/ShellConfigTest.res +30 -21
- package/tests/SplitApiFixtures.res +34 -10
- package/tests/SplitApiTest.res +92 -62
- package/tests/adapter/BackendParityTest.res +180 -130
- package/tests/adapter/CollectNEventsTest.res +6 -4
- package/tests/adapter/CommandAuthorizationTest.res +47 -41
- package/tests/adapter/CommandTopicChannelTest.res +163 -139
- package/tests/adapter/CounterHandlerTest.res +95 -77
- package/tests/adapter/DcbEventLogStorageSqliteTest.res +171 -168
- package/tests/adapter/DcbEventLogStorageTest.res +264 -207
- package/tests/adapter/DomainGraphQL_ServerTest.res +53 -43
- package/tests/adapter/EventCollectorChannelTest.res +153 -124
- package/tests/adapter/EventHistoryResolverTest.res +46 -18
- package/tests/adapter/EventLogSnapshotParityTest.res +57 -45
- package/tests/adapter/EventLogStorageSqliteTest.res +9 -10
- package/tests/adapter/EventTapTest.res +37 -20
- package/tests/adapter/EventTopicPublisherTest.res +74 -50
- package/tests/adapter/GraphQLSubscriptionBridgeTest.res +4 -3
- package/tests/adapter/GraphQL_SchemaInspectorTest.res +666 -568
- package/tests/adapter/GraphQL_SubscriptionResolversTest.res +105 -96
- package/tests/adapter/HeartbeatRunnerTest.res +99 -81
- package/tests/adapter/InboundTranslationMutationTest.res +4 -1
- package/tests/adapter/LocalAuthHttpTest.res +79 -85
- package/tests/adapter/LocalAuthLoginTest.res +42 -50
- package/tests/adapter/LocalAuthTest.res +8 -16
- package/tests/adapter/LocalAuthUserStoreTest.res +2 -7
- package/tests/adapter/LocalBusBoundedTest.res +187 -117
- package/tests/adapter/LocalBusPubSubTest.res +118 -76
- package/tests/adapter/LocalDispatchContextTest.res +1 -2
- package/tests/adapter/LocalEvents_ServerTest.res +242 -187
- package/tests/adapter/PgProjectionCatchupTest.res +44 -10
- package/tests/adapter/ProjectionCheckpointTest.res +261 -206
- package/tests/adapter/QueryDbGsiTtlTest.res +123 -83
- package/tests/adapter/QueryDbListPushdownParityTest.res +107 -67
- package/tests/adapter/QueryDbListResolverTest.res +225 -208
- package/tests/adapter/QueryDbResolvedFieldsTest.res +17 -14
- package/tests/adapter/QueryDbStorageSqliteTest.res +33 -49
- package/tests/adapter/QueryDbStorageTest.res +163 -117
- package/tests/adapter/QueryDbUnionRoundTripTest.res +8 -10
- package/tests/adapter/QueryEngineTest.res +179 -126
- package/tests/adapter/ScheduledPublisherTest.res +171 -135
- package/tests/adapter/ServedBucketHttpTest.res +65 -59
- package/tests/adapter/SortKey_FilterTest.res +19 -3
- package/tests/adapter/SqliteDriverTest.res +13 -10
- package/tests/adapter/TaskBucketTest.res +94 -80
- package/tests/components/aggregate/AggregateTest.res +31 -18
- package/tests/components/automationslice/AutomationSliceCallbackTest.res +259 -222
- package/tests/components/automationslice/AutomationSliceFixtures.res +4 -2
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +38 -38
- package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +12 -14
- package/tests/components/automationslice/MixedSourceAutomationSliceTest.res +8 -32
- package/tests/components/commandgenerator/CommandGeneratorFixtures.res +7 -5
- package/tests/components/commandgenerator/CommandGeneratorTest.res +40 -31
- package/tests/components/commandtopic/CommandTopicFixtures.res +3 -4
- package/tests/components/commandtopic/CommandTopicStreamFixtures.res +7 -5
- package/tests/components/commandtopic/CommandTopicTest.res +24 -15
- package/tests/components/counter/CounterFixtures.res +6 -8
- package/tests/components/dcb/DcbCrossPartitionFixtures.res +8 -2
- package/tests/components/dcb/DcbCrossPartitionReadTest.res +20 -13
- package/tests/components/dcb/DcbEventLogAppendStreamTest.res +12 -8
- package/tests/components/dcb/DcbEventLogStreamTest.res +88 -61
- package/tests/components/dcb/DcbFixtures.res +4 -1
- package/tests/components/dcb/DcbTest.res +9 -6
- package/tests/components/eventcollector/EventCollectorFixtures.res +0 -1
- package/tests/components/eventcollector/EventCollectorTest.res +6 -3
- package/tests/components/eventlog/EventLogAppendStreamTest.res +88 -68
- package/tests/components/eventlog/EventLogConflictTest.res +8 -4
- package/tests/components/eventlog/EventLogFixtures.res +3 -3
- package/tests/components/eventlog/EventLogProvisioningSeamTest.res +26 -13
- package/tests/components/eventlog/EventLogStreamTest.res +119 -56
- package/tests/components/eventlog/EventLogTest.res +20 -4
- package/tests/components/eventtopic/EventTopicFixtures.res +3 -3
- package/tests/components/eventtopic/EventTopicStreamFixtures.res +1 -2
- package/tests/components/eventtopic/EventTopicStreamTest.res +7 -1
- package/tests/components/extensionpoint/ExtensionPointFixtures.res +14 -9
- package/tests/components/extensionpoint/ExtensionPointTest.res +3 -2
- package/tests/components/heartbeat/HeartbeatFixtures.res +5 -6
- package/tests/components/heartbeat/HeartbeatTest.res +56 -47
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +271 -249
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +3 -2
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +361 -276
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +8 -10
- package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res +37 -43
- package/tests/components/querydb/QueryDbFixtures.res +1 -5
- package/tests/components/querydb/QueryDbTest.res +2 -4
- package/tests/components/readmodel/DcbReadModelE2EFixtures.res +15 -18
- package/tests/components/readmodel/MixedSourceReadModelFixtures.res +5 -7
- package/tests/components/readmodel/ReadModelFixtures.res +8 -8
- package/tests/components/scheduler/SchedulerTest.res +76 -58
- package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res +4 -4
- package/tests/components/stateviewslice/StateViewSliceDisplayNameFixtures.res +5 -7
- package/tests/components/stateviewslice/StateViewSliceFixtures.res +8 -6
- package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res +17 -11
- package/tests/components/stateviewslice/StateViewSliceSubIdTest.res +12 -2
- package/tests/components/task/TaskFixtures.res +5 -5
- package/tests/components/task/TaskTest.res +95 -80
- package/tests/plugin/PluginsProjection_GWT.res +3 -1
- package/tests/plugin/UiFragments_GWT.res +6 -1
- package/tests/reset/LocalSeedResetTest.res +1 -0
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
|
|
11
11
|
open JestGlobals
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
let _ = TestRunner.setup()
|
|
15
14
|
let opts: Pulumi.CustomResourceOptions.t = {}
|
|
16
15
|
|
|
@@ -80,21 +79,22 @@ describe("Backend parity (Memory vs Sqlite)", () => {
|
|
|
80
79
|
let scenario = async () => {
|
|
81
80
|
module TestBus = LocalBus.Make()
|
|
82
81
|
module Storage = LocalQueryDbStorage.Make(TestBus)
|
|
83
|
-
let s = Storage.make(
|
|
82
|
+
let s = Storage.make(
|
|
83
|
+
~name="parity-qdb",
|
|
84
|
+
~indexes=[],
|
|
85
|
+
~api=(),
|
|
86
|
+
~apiRole=(),
|
|
87
|
+
~owner=None,
|
|
88
|
+
~opts,
|
|
89
|
+
)
|
|
84
90
|
let ops = await s.operations->TestRunner.resolve
|
|
85
91
|
|
|
86
|
-
let _ = await ops.save(
|
|
87
|
-
"k",
|
|
88
|
-
JSON.Encode.string("val"),
|
|
89
|
-
ReventlessCore.QueryDb.Any,
|
|
90
|
-
None,
|
|
91
|
-
)
|
|
92
|
+
let _ = await ops.save("k", JSON.Encode.string("val"), ReventlessCore.QueryDb.Any, None)
|
|
92
93
|
|
|
93
|
-
let items =
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
->Effect.runPromise
|
|
94
|
+
let items = await ops.loadStream("k")
|
|
95
|
+
->Stream.runCollect
|
|
96
|
+
->Effect.catchAll(_ => Effect.succeed([]))
|
|
97
|
+
->Effect.runPromise
|
|
98
98
|
expect(items->Array.length)->toBe(1)
|
|
99
99
|
expect(items->Array.getUnsafe(0))->toEqual(JSON.Encode.string("val"))
|
|
100
100
|
}
|
|
@@ -103,46 +103,62 @@ describe("Backend parity (Memory vs Sqlite)", () => {
|
|
|
103
103
|
await runUnderSqlite(scenario)
|
|
104
104
|
})
|
|
105
105
|
|
|
106
|
-
testPromise(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
testPromise(
|
|
107
|
+
"QueryDb: count returns a running total and loadStream reflects it under both",
|
|
108
|
+
async () => {
|
|
109
|
+
let scenario = async () => {
|
|
110
|
+
module TestBus = LocalBus.Make()
|
|
111
|
+
module Storage = LocalQueryDbStorage.Make(TestBus)
|
|
112
|
+
let s = Storage.make(
|
|
113
|
+
~name="parity-count",
|
|
114
|
+
~indexes=[],
|
|
115
|
+
~api=(),
|
|
116
|
+
~apiRole=(),
|
|
117
|
+
~owner=None,
|
|
118
|
+
~opts,
|
|
119
|
+
)
|
|
120
|
+
let ops = await s.operations->TestRunner.resolve
|
|
112
121
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
// First increment creates the counter item and returns the new total.
|
|
123
|
+
let r1 = await ops.count("prod-1", "orderCount", 3)
|
|
124
|
+
expect(r1)->toEqual(Ok(3))
|
|
125
|
+
// Second increment accumulates (not just echoes the increment).
|
|
126
|
+
let r2 = await ops.count("prod-1", "orderCount", 2)
|
|
127
|
+
expect(r2)->toEqual(Ok(5))
|
|
119
128
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
await ops.loadStream("prod-1")
|
|
129
|
+
// loadStream must see the persisted counter — count is not a side channel.
|
|
130
|
+
let items = await ops.loadStream("prod-1")
|
|
123
131
|
->Stream.runCollect
|
|
124
132
|
->Effect.catchAll(_ => Effect.succeed([]))
|
|
125
133
|
->Effect.runPromise
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
expect(items->Array.length)->toBe(1)
|
|
135
|
+
let field =
|
|
136
|
+
items
|
|
137
|
+
->Array.getUnsafe(0)
|
|
138
|
+
->JSON.Decode.object
|
|
139
|
+
->Option.flatMap(o => o->Dict.get("orderCount"))
|
|
140
|
+
->Option.flatMap(JSON.Decode.float)
|
|
141
|
+
->Option.mapOr(0, Float.toInt)
|
|
142
|
+
expect(field)->toBe(5)
|
|
143
|
+
}
|
|
136
144
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
await runUnderMemory(scenario)
|
|
146
|
+
await runUnderSqlite(scenario)
|
|
147
|
+
},
|
|
148
|
+
)
|
|
140
149
|
|
|
141
150
|
testPromise("QueryDb: an expired-TTL item is filtered from loadStream under both", async () => {
|
|
142
151
|
let scenario = async () => {
|
|
143
152
|
module TestBus = LocalBus.Make()
|
|
144
153
|
module Storage = LocalQueryDbStorage.Make(TestBus)
|
|
145
|
-
let s = Storage.make(
|
|
154
|
+
let s = Storage.make(
|
|
155
|
+
~name="parity-ttl",
|
|
156
|
+
~indexes=[],
|
|
157
|
+
~api=(),
|
|
158
|
+
~apiRole=(),
|
|
159
|
+
~owner=None,
|
|
160
|
+
~opts,
|
|
161
|
+
)
|
|
146
162
|
let ops = await s.operations->TestRunner.resolve
|
|
147
163
|
|
|
148
164
|
let item = k => JSON.Encode.object(Dict.fromArray([("id", JSON.Encode.string(k))]))
|
|
@@ -171,16 +187,25 @@ describe("Backend parity (Memory vs Sqlite)", () => {
|
|
|
171
187
|
let scenario = async () => {
|
|
172
188
|
module TestBus = LocalBus.Make()
|
|
173
189
|
module Storage = LocalQueryDbStorage.Make(TestBus)
|
|
174
|
-
let s = Storage.make(
|
|
190
|
+
let s = Storage.make(
|
|
191
|
+
~name="parity-kind",
|
|
192
|
+
~indexes=[],
|
|
193
|
+
~api=(),
|
|
194
|
+
~apiRole=(),
|
|
195
|
+
~owner=None,
|
|
196
|
+
~opts,
|
|
197
|
+
)
|
|
175
198
|
let ops = await s.operations->TestRunner.resolve
|
|
176
199
|
|
|
177
200
|
let kinds = []
|
|
178
|
-
TestBus.subscribeToStateChanges(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
201
|
+
TestBus.subscribeToStateChanges(
|
|
202
|
+
"parity-kind",
|
|
203
|
+
descriptor =>
|
|
204
|
+
descriptor
|
|
205
|
+
->JSON.Decode.object
|
|
206
|
+
->Option.flatMap(o => o->Dict.get("changeKind"))
|
|
207
|
+
->Option.flatMap(JSON.Decode.string)
|
|
208
|
+
->Option.forEach(k => kinds->Array.push(k)),
|
|
184
209
|
)
|
|
185
210
|
|
|
186
211
|
let item = (k, v) =>
|
|
@@ -205,101 +230,126 @@ describe("Backend parity (Memory vs Sqlite)", () => {
|
|
|
205
230
|
await runUnderSqlite(scenario)
|
|
206
231
|
})
|
|
207
232
|
|
|
208
|
-
testPromise(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
let descriptors = []
|
|
223
|
-
TestBus.subscribeToStateChanges("parity-payload", d => descriptors->Array.push(d))
|
|
224
|
-
|
|
225
|
-
let field = (d, key) => d->JSON.Decode.object->Option.flatMap(o => o->Dict.get(key))
|
|
226
|
-
let item = (k, v) =>
|
|
227
|
-
JSON.Encode.object(
|
|
228
|
-
Dict.fromArray([("id", JSON.Encode.string(k)), ("v", JSON.Encode.string(v))]),
|
|
233
|
+
testPromise(
|
|
234
|
+
"QueryDb: a descriptor carries the saved row and a rising seq under both",
|
|
235
|
+
async () => {
|
|
236
|
+
let scenario = async () => {
|
|
237
|
+
module TestBus = LocalBus.Make()
|
|
238
|
+
module Storage = LocalQueryDbStorage.Make(TestBus)
|
|
239
|
+
let s = Storage.make(
|
|
240
|
+
~name="parity-payload",
|
|
241
|
+
~indexes=[],
|
|
242
|
+
~api=(),
|
|
243
|
+
~apiRole=(),
|
|
244
|
+
~owner=None,
|
|
245
|
+
~opts,
|
|
229
246
|
)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
247
|
+
let ops = await s.operations->TestRunner.resolve
|
|
248
|
+
|
|
249
|
+
let descriptors = []
|
|
250
|
+
TestBus.subscribeToStateChanges("parity-payload", d => descriptors->Array.push(d))
|
|
251
|
+
|
|
252
|
+
let field = (d, key) => d->JSON.Decode.object->Option.flatMap(o => o->Dict.get(key))
|
|
253
|
+
let item = (k, v) =>
|
|
254
|
+
JSON.Encode.object(
|
|
255
|
+
Dict.fromArray([("id", JSON.Encode.string(k)), ("v", JSON.Encode.string(v))]),
|
|
256
|
+
)
|
|
257
|
+
let stateAt = i => descriptors->Array.get(i)->Option.flatMap(d => field(d, "state"))
|
|
258
|
+
|
|
259
|
+
let _ = await ops.save("p1", item("p1", "one"), ReventlessCore.QueryDb.Any, None)
|
|
260
|
+
let _ = await ops.save("p1", item("p1", "two"), ReventlessCore.QueryDb.Any, None)
|
|
261
|
+
let _ = await ops.delete("p1", None)
|
|
262
|
+
|
|
263
|
+
// The point of the payload: a subscriber can apply the row it was handed
|
|
264
|
+
// instead of spending a round-trip to fetch what the platform already had.
|
|
265
|
+
expect(stateAt(0))->toEqual(Some(item("p1", "one")))
|
|
266
|
+
expect(stateAt(1))->toEqual(Some(item("p1", "two")))
|
|
267
|
+
// A delete has no new row to carry.
|
|
268
|
+
expect(stateAt(2))->toEqual(None)
|
|
269
|
+
|
|
270
|
+
// seq only has to rise — it is monotonic, not consecutive, so a client can
|
|
271
|
+
// reject a stale payload but cannot count gaps.
|
|
272
|
+
let seqs =
|
|
273
|
+
descriptors->Array.filterMap(
|
|
274
|
+
d =>
|
|
275
|
+
field(d, "seq")->Option.flatMap(JSON.Decode.string)->Option.flatMap(Float.fromString),
|
|
276
|
+
)
|
|
277
|
+
expect(seqs->Array.length)->toBe(3)
|
|
278
|
+
let rising =
|
|
279
|
+
seqs->Array.everyWithIndex((v, i) => i == 0 || v > seqs->Array.getUnsafe(i - 1))
|
|
280
|
+
expect(rising)->toBe(true)
|
|
281
|
+
}
|
|
235
282
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
283
|
+
await runUnderMemory(scenario)
|
|
284
|
+
await runUnderSqlite(scenario)
|
|
285
|
+
},
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
testPromise(
|
|
289
|
+
"QueryDb: saveBatch reports Added then Updated for a repeated key under both",
|
|
290
|
+
async () => {
|
|
291
|
+
let scenario = async () => {
|
|
292
|
+
module TestBus = LocalBus.Make()
|
|
293
|
+
module Storage = LocalQueryDbStorage.Make(TestBus)
|
|
294
|
+
let s = Storage.make(
|
|
295
|
+
~name="parity-batch",
|
|
296
|
+
~indexes=[],
|
|
297
|
+
~api=(),
|
|
298
|
+
~apiRole=(),
|
|
299
|
+
~owner=None,
|
|
300
|
+
~opts,
|
|
301
|
+
)
|
|
302
|
+
let ops = await s.operations->TestRunner.resolve
|
|
303
|
+
|
|
304
|
+
let kinds = []
|
|
305
|
+
TestBus.subscribeToStateChanges(
|
|
306
|
+
"parity-batch",
|
|
307
|
+
descriptor =>
|
|
308
|
+
descriptor
|
|
309
|
+
->JSON.Decode.object
|
|
310
|
+
->Option.flatMap(o => o->Dict.get("changeKind"))
|
|
311
|
+
->Option.flatMap(JSON.Decode.string)
|
|
312
|
+
->Option.forEach(k => kinds->Array.push(k)),
|
|
248
313
|
)
|
|
249
|
-
expect(seqs->Array.length)->toBe(3)
|
|
250
|
-
let rising =
|
|
251
|
-
seqs->Array.everyWithIndex((v, i) => i == 0 || v > seqs->Array.getUnsafe(i - 1))
|
|
252
|
-
expect(rising)->toBe(true)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
await runUnderMemory(scenario)
|
|
256
|
-
await runUnderSqlite(scenario)
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
testPromise("QueryDb: saveBatch reports Added then Updated for a repeated key under both", async () => {
|
|
260
|
-
let scenario = async () => {
|
|
261
|
-
module TestBus = LocalBus.Make()
|
|
262
|
-
module Storage = LocalQueryDbStorage.Make(TestBus)
|
|
263
|
-
let s = Storage.make(~name="parity-batch", ~indexes=[], ~api=(), ~apiRole=(), ~owner=None, ~opts)
|
|
264
|
-
let ops = await s.operations->TestRunner.resolve
|
|
265
314
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
->Option.forEach(k => kinds->Array.push(k))
|
|
273
|
-
)
|
|
315
|
+
let item = k => JSON.Encode.object(Dict.fromArray([("id", JSON.Encode.string(k))]))
|
|
316
|
+
let _ = await ops.saveBatch([
|
|
317
|
+
("a", item("a"), None),
|
|
318
|
+
("b", item("b"), None),
|
|
319
|
+
("a", item("a"), None),
|
|
320
|
+
])
|
|
274
321
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
("a", item("a"), None),
|
|
278
|
-
("b", item("b"), None),
|
|
279
|
-
("a", item("a"), None),
|
|
280
|
-
])
|
|
322
|
+
expect(kinds)->toEqual(["Added", "Added", "Updated"])
|
|
323
|
+
}
|
|
281
324
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
await runUnderSqlite(scenario)
|
|
287
|
-
})
|
|
325
|
+
await runUnderMemory(scenario)
|
|
326
|
+
await runUnderSqlite(scenario)
|
|
327
|
+
},
|
|
328
|
+
)
|
|
288
329
|
|
|
289
330
|
testPromise("QueryDb: a counter's first increment is Added under both", async () => {
|
|
290
331
|
let scenario = async () => {
|
|
291
332
|
module TestBus = LocalBus.Make()
|
|
292
333
|
module Storage = LocalQueryDbStorage.Make(TestBus)
|
|
293
|
-
let s = Storage.make(
|
|
334
|
+
let s = Storage.make(
|
|
335
|
+
~name="parity-ckind",
|
|
336
|
+
~indexes=[],
|
|
337
|
+
~api=(),
|
|
338
|
+
~apiRole=(),
|
|
339
|
+
~owner=None,
|
|
340
|
+
~opts,
|
|
341
|
+
)
|
|
294
342
|
let ops = await s.operations->TestRunner.resolve
|
|
295
343
|
|
|
296
344
|
let kinds = []
|
|
297
|
-
TestBus.subscribeToStateChanges(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
345
|
+
TestBus.subscribeToStateChanges(
|
|
346
|
+
"parity-ckind",
|
|
347
|
+
descriptor =>
|
|
348
|
+
descriptor
|
|
349
|
+
->JSON.Decode.object
|
|
350
|
+
->Option.flatMap(o => o->Dict.get("changeKind"))
|
|
351
|
+
->Option.flatMap(JSON.Decode.string)
|
|
352
|
+
->Option.forEach(k => kinds->Array.push(k)),
|
|
303
353
|
)
|
|
304
354
|
|
|
305
355
|
let _ = await ops.count("prod-1", "orderCount", 1)
|
|
@@ -50,10 +50,12 @@ describe("TestRunner.collectNEvents", () => {
|
|
|
50
50
|
let collecting = TestRunner.collectNEvents(Bus.subscribeToEvents, "T", 3)
|
|
51
51
|
let json = JSON.parseOrThrow("{}")
|
|
52
52
|
let resolved = ref(false)
|
|
53
|
-
let _ = collecting->Promise.then(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
let _ = collecting->Promise.then(
|
|
54
|
+
events => {
|
|
55
|
+
resolved := true
|
|
56
|
+
Promise.resolve(events)
|
|
57
|
+
},
|
|
58
|
+
)
|
|
57
59
|
await Bus.publishEvent("T", "svc", meta, json)
|
|
58
60
|
await Bus.publishEvent("T", "svc", meta, json)
|
|
59
61
|
// Only 2 events so far — promise should not yet be resolved.
|
|
@@ -49,14 +49,11 @@ let userIdentity: Reventless.Identity.t = {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
let ctxFor = (identity: Reventless.Identity.t): JSON.t =>
|
|
52
|
-
JSON.Encode.object(
|
|
53
|
-
Dict.fromArray([("identity", (identity: Reventless.Identity.t)->Obj.magic)]),
|
|
54
|
-
)
|
|
52
|
+
JSON.Encode.object(Dict.fromArray([("identity", (identity: Reventless.Identity.t)->Obj.magic)]))
|
|
55
53
|
|
|
56
|
-
let anonymousCtx: JSON.t =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
)
|
|
54
|
+
let anonymousCtx: JSON.t = JSON.Encode.object(
|
|
55
|
+
Dict.fromArray([("identity", (Reventless.Identity.anonymous: Reventless.Identity.t)->Obj.magic)]),
|
|
56
|
+
)
|
|
60
57
|
|
|
61
58
|
let getTypename = (response: JSON.t): string =>
|
|
62
59
|
response
|
|
@@ -125,16 +122,22 @@ describe("CommandGeneratorResolvers_GraphQL — per-constructor authorization",
|
|
|
125
122
|
|
|
126
123
|
testPromise("admin invoking payload-less Archive succeeds", async () => {
|
|
127
124
|
let (_, _, archiveResolver, calls) = buildFixture(~namespace="Cat1")
|
|
128
|
-
let response =
|
|
129
|
-
|
|
125
|
+
let response = await archiveResolver(
|
|
126
|
+
JSON.Encode.null,
|
|
127
|
+
JSON.Encode.object(Dict.make()),
|
|
128
|
+
ctxFor(adminIdentity),
|
|
129
|
+
)
|
|
130
130
|
expect(getTypename(response))->toEqual("CommandAccepted")
|
|
131
131
|
expect(calls.contents)->toEqual(["Archive"])
|
|
132
132
|
})
|
|
133
133
|
|
|
134
134
|
testPromise("regular user invoking Archive is rejected with Forbidden", async () => {
|
|
135
135
|
let (_, _, archiveResolver, calls) = buildFixture(~namespace="Cat2")
|
|
136
|
-
let response =
|
|
137
|
-
|
|
136
|
+
let response = await archiveResolver(
|
|
137
|
+
JSON.Encode.null,
|
|
138
|
+
JSON.Encode.object(Dict.make()),
|
|
139
|
+
ctxFor(userIdentity),
|
|
140
|
+
)
|
|
138
141
|
expect(getTypename(response))->toEqual("CommandRejected")
|
|
139
142
|
expect(getErrorCode(response))->toEqual("Forbidden")
|
|
140
143
|
expect(calls.contents)->toEqual([])
|
|
@@ -142,8 +145,11 @@ describe("CommandGeneratorResolvers_GraphQL — per-constructor authorization",
|
|
|
142
145
|
|
|
143
146
|
testPromise("anonymous invoking Archive is rejected with Forbidden", async () => {
|
|
144
147
|
let (_, _, archiveResolver, calls) = buildFixture(~namespace="Cat3")
|
|
145
|
-
let response =
|
|
146
|
-
|
|
148
|
+
let response = await archiveResolver(
|
|
149
|
+
JSON.Encode.null,
|
|
150
|
+
JSON.Encode.object(Dict.make()),
|
|
151
|
+
anonymousCtx,
|
|
152
|
+
)
|
|
147
153
|
expect(getTypename(response))->toEqual("CommandRejected")
|
|
148
154
|
expect(getErrorCode(response))->toEqual("Forbidden")
|
|
149
155
|
expect(calls.contents)->toEqual([])
|
|
@@ -151,24 +157,22 @@ describe("CommandGeneratorResolvers_GraphQL — per-constructor authorization",
|
|
|
151
157
|
|
|
152
158
|
testPromise("regular user invoking Add (default AllowAuthenticated) succeeds", async () => {
|
|
153
159
|
let (addResolver, _, _, calls) = buildFixture(~namespace="Cat4")
|
|
154
|
-
let response =
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
)
|
|
160
|
+
let response = await addResolver(
|
|
161
|
+
JSON.Encode.null,
|
|
162
|
+
JSON.Encode.object(Dict.fromArray([("name", JSON.Encode.string("Books"))])),
|
|
163
|
+
ctxFor(userIdentity),
|
|
164
|
+
)
|
|
160
165
|
expect(getTypename(response))->toEqual("CommandAccepted")
|
|
161
166
|
expect(calls.contents)->toEqual(["Add"])
|
|
162
167
|
})
|
|
163
168
|
|
|
164
169
|
testPromise("anonymous invoking Add is rejected (AllowAuthenticated default)", async () => {
|
|
165
170
|
let (addResolver, _, _, calls) = buildFixture(~namespace="Cat5")
|
|
166
|
-
let response =
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
)
|
|
171
|
+
let response = await addResolver(
|
|
172
|
+
JSON.Encode.null,
|
|
173
|
+
JSON.Encode.object(Dict.fromArray([("name", JSON.Encode.string("Books"))])),
|
|
174
|
+
anonymousCtx,
|
|
175
|
+
)
|
|
172
176
|
expect(getTypename(response))->toEqual("CommandRejected")
|
|
173
177
|
expect(getErrorCode(response))->toEqual("Forbidden")
|
|
174
178
|
expect(calls.contents)->toEqual([])
|
|
@@ -176,12 +180,11 @@ describe("CommandGeneratorResolvers_GraphQL — per-constructor authorization",
|
|
|
176
180
|
|
|
177
181
|
testPromise("admin invoking Add also succeeds (admin has User group too)", async () => {
|
|
178
182
|
let (addResolver, _, _, calls) = buildFixture(~namespace="Cat6")
|
|
179
|
-
let response =
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
)
|
|
183
|
+
let response = await addResolver(
|
|
184
|
+
JSON.Encode.null,
|
|
185
|
+
JSON.Encode.object(Dict.fromArray([("name", JSON.Encode.string("Books"))])),
|
|
186
|
+
ctxFor(adminIdentity),
|
|
187
|
+
)
|
|
185
188
|
expect(getTypename(response))->toEqual("CommandAccepted")
|
|
186
189
|
expect(calls.contents)->toEqual(["Add"])
|
|
187
190
|
})
|
|
@@ -246,10 +249,10 @@ describe("CommandGeneratorResolvers_GraphQL — a failure the caller may read",
|
|
|
246
249
|
})
|
|
247
250
|
|
|
248
251
|
testPromise("a caller-fault failure is rethrown as an unmasked GraphQL error", async () => {
|
|
249
|
-
let resolver = failingFixture(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
252
|
+
let resolver = failingFixture(
|
|
253
|
+
~namespace="Fault1",
|
|
254
|
+
~failWith=() =>
|
|
255
|
+
ReventlessCore.Plugin_ResolverError.throwCallerFault(`Error: Couldn't decode: Expected string | undefined, received null`),
|
|
253
256
|
)
|
|
254
257
|
let caught = await invoke(resolver)
|
|
255
258
|
expect((
|
|
@@ -261,10 +264,12 @@ describe("CommandGeneratorResolvers_GraphQL — a failure the caller may read",
|
|
|
261
264
|
// The reason is the point: masked, the caller learns only that something
|
|
262
265
|
// went wrong on a request that is theirs to fix.
|
|
263
266
|
testPromise("and carries the reason it was given", async () => {
|
|
264
|
-
let resolver = failingFixture(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
267
|
+
let resolver = failingFixture(
|
|
268
|
+
~namespace="Fault2",
|
|
269
|
+
~failWith=() =>
|
|
270
|
+
ReventlessCore.Plugin_ResolverError.throwCallerFault(
|
|
271
|
+
"Expected string | undefined, received null",
|
|
272
|
+
),
|
|
268
273
|
)
|
|
269
274
|
let caught = await invoke(resolver)
|
|
270
275
|
expect(
|
|
@@ -277,8 +282,9 @@ describe("CommandGeneratorResolvers_GraphQL — a failure the caller may read",
|
|
|
277
282
|
// The control, and the reason the mark exists: an internal failure is not the
|
|
278
283
|
// caller's business and must keep being masked.
|
|
279
284
|
testPromise("an unmarked failure is left to be masked", async () => {
|
|
280
|
-
let resolver = failingFixture(
|
|
281
|
-
|
|
285
|
+
let resolver = failingFixture(
|
|
286
|
+
~namespace="Fault3",
|
|
287
|
+
~failWith=() => JsError.throwWithMessage("connection to the event store was reset"),
|
|
282
288
|
)
|
|
283
289
|
let caught = await invoke(resolver)
|
|
284
290
|
expect(caught->Option.flatMap(JsExn.name))->not_->toEqual(Some("GraphQLError"))
|