@reventlessdev/reventless-local 3.0.0-alpha.249 → 3.0.0-alpha.251
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 +30 -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 +12 -16
- package/src/adapter/Auth/LocalAuth.res.mjs +2 -1
- package/src/adapter/Auth/UserStore.res +13 -40
- package/src/adapter/Auth/UserStore.res.mjs +11 -67
- 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 +6 -0
- package/src/adapter/LocalCapabilities.res.mjs +5 -2
- 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 +10 -6
- package/src/adapter/PlatformGraphQL_Server.res.mjs +2 -1
- 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
|
@@ -18,111 +18,120 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
18
18
|
})
|
|
19
19
|
|
|
20
20
|
describe("receive", () => {
|
|
21
|
-
testPromise(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
publishedCommands
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
testPromise(
|
|
22
|
+
"valid input with completed status publishes command and returns Ok",
|
|
23
|
+
async () => {
|
|
24
|
+
let publishedCommands = ref([])
|
|
25
|
+
let mockPublish: ReventlessInfra.CommandTopic.publishJsons = async cmds => {
|
|
26
|
+
publishedCommands := cmds
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let inputJson = {
|
|
29
30
|
"paymentId": "pay-123",
|
|
30
31
|
"orderId": "ord-1",
|
|
31
32
|
"status": "completed",
|
|
32
33
|
}->Obj.magic
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
35
|
+
let result = await Callback.receive(mockPublish, inputJson)
|
|
36
|
+
|
|
37
|
+
switch result {
|
|
38
|
+
| Ok({requestId, targetIds, commandCount}) =>
|
|
39
|
+
expect(targetIds)->toEqual(["ord-1"])
|
|
40
|
+
expect(commandCount)->toBe(1)
|
|
41
|
+
// requestId keys the audit row, so the caller can correlate the response.
|
|
42
|
+
expect(Callback.auditLog->Dict.get(requestId)->Option.isSome)->toBe(true)
|
|
43
|
+
| Error(_) => expect(true)->toBe(false)
|
|
44
|
+
}
|
|
45
|
+
expect(publishedCommands.contents->Array.length)->toBe(1)
|
|
46
|
+
let cmd = publishedCommands.contents->Array.getUnsafe(0)
|
|
47
|
+
expect(cmd.id)->toBe("ord-1")
|
|
48
|
+
|
|
49
|
+
// Audit log should have one entry
|
|
50
|
+
let auditEntries = Callback.auditLog->Dict.toArray
|
|
51
|
+
expect(auditEntries->Array.length)->toBe(1)
|
|
52
|
+
let (_, auditRow) = auditEntries->Array.getUnsafe(0)
|
|
53
|
+
expect(auditRow.status)->toBe(ReventlessCore.InboundTranslationSlice_Callback.Success)
|
|
54
|
+
expect(auditRow.commandCount)->toBe(Some(1))
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
testPromise(
|
|
59
|
+
"translate returns Error — no command published, audit logged",
|
|
60
|
+
async () => {
|
|
61
|
+
let publishedCommands = ref([])
|
|
62
|
+
let mockPublish: ReventlessInfra.CommandTopic.publishJsons = async cmds => {
|
|
63
|
+
publishedCommands := cmds
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let inputJson = {
|
|
64
67
|
"paymentId": "pay-123",
|
|
65
68
|
"orderId": "ord-1",
|
|
66
69
|
"status": "pending",
|
|
67
70
|
}->Obj.magic
|
|
68
71
|
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
72
|
+
let result = await Callback.receive(mockPublish, inputJson)
|
|
73
|
+
|
|
74
|
+
switch result {
|
|
75
|
+
| Error({error}) => expect(error)->toBe("Unknown payment status: pending")
|
|
76
|
+
| Ok(_) => expect(true)->toBe(false)
|
|
77
|
+
}
|
|
78
|
+
expect(publishedCommands.contents->Array.length)->toBe(0)
|
|
79
|
+
|
|
80
|
+
let auditEntries = Callback.auditLog->Dict.toArray
|
|
81
|
+
expect(auditEntries->Array.length)->toBe(1)
|
|
82
|
+
let (_, auditRow) = auditEntries->Array.getUnsafe(0)
|
|
83
|
+
expect(auditRow.status)->toBe(ReventlessCore.InboundTranslationSlice_Callback.Failure)
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
testPromise(
|
|
88
|
+
"invalid JSON input returns Error and audit logged",
|
|
89
|
+
async () => {
|
|
90
|
+
let mockPublish: ReventlessInfra.CommandTopic.publishJsons = async _cmds => ()
|
|
91
|
+
|
|
92
|
+
// Missing required fields
|
|
93
|
+
let inputJson = {"unexpected": "data"}->Obj.magic
|
|
94
|
+
|
|
95
|
+
let result = await Callback.receive(mockPublish, inputJson)
|
|
96
|
+
|
|
97
|
+
switch result {
|
|
98
|
+
| Error(_) => expect(true)->toBe(true)
|
|
99
|
+
| Ok(_) => expect(true)->toBe(false)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let auditEntries = Callback.auditLog->Dict.toArray
|
|
103
|
+
expect(auditEntries->Array.length)->toBe(1)
|
|
104
|
+
let (_, auditRow) = auditEntries->Array.getUnsafe(0)
|
|
105
|
+
expect(auditRow.status)->toBe(ReventlessCore.InboundTranslationSlice_Callback.Failure)
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
testPromise(
|
|
110
|
+
"publishJsons failure returns Error and audit logged",
|
|
111
|
+
async () => {
|
|
112
|
+
let failingPublish: ReventlessInfra.CommandTopic.publishJsons = async _cmds => {
|
|
113
|
+
JsError.throwWithMessage("publish failed")
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let inputJson = {
|
|
109
117
|
"paymentId": "pay-123",
|
|
110
118
|
"orderId": "ord-1",
|
|
111
119
|
"status": "completed",
|
|
112
120
|
}->Obj.magic
|
|
113
121
|
|
|
114
|
-
|
|
122
|
+
let result = await Callback.receive(failingPublish, inputJson)
|
|
115
123
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
switch result {
|
|
125
|
+
| Error({error}) => expect(error)->toBe("publish failed")
|
|
126
|
+
| Ok(_) => expect(true)->toBe(false)
|
|
127
|
+
}
|
|
120
128
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
let auditEntries = Callback.auditLog->Dict.toArray
|
|
130
|
+
expect(auditEntries->Array.length)->toBe(1)
|
|
131
|
+
let (_, auditRow) = auditEntries->Array.getUnsafe(0)
|
|
132
|
+
expect(auditRow.status)->toBe(ReventlessCore.InboundTranslationSlice_Callback.Failure)
|
|
133
|
+
},
|
|
134
|
+
)
|
|
126
135
|
})
|
|
127
136
|
|
|
128
137
|
// `auditLog` hands a row to whoever persists it and must not keep it: the dict
|
|
@@ -131,175 +140,188 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
131
140
|
// every request — and, since the write overwrites by row id, would resurrect
|
|
132
141
|
// rows deleted from the table in between. Persisting is per-request and takes.
|
|
133
142
|
describe("draining the audit log", () => {
|
|
134
|
-
testPromise(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
Callback.auditLog
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
143
|
+
testPromise(
|
|
144
|
+
"taking a row leaves the log empty for the next request",
|
|
145
|
+
async () => {
|
|
146
|
+
let mockPublish: ReventlessInfra.CommandTopic.publishJsons = async _cmds => ()
|
|
147
|
+
let input = status =>
|
|
148
|
+
{"paymentId": "pay-1", "orderId": "ord-1", "status": status}->Obj.magic
|
|
149
|
+
|
|
150
|
+
let first = await Callback.receive(mockPublish, input("completed"))
|
|
151
|
+
let firstId = first->ReventlessCore.InboundTranslationSlice_Callback.requestIdOf
|
|
152
|
+
expect(
|
|
153
|
+
Callback.auditLog
|
|
154
|
+
->ReventlessCore.InboundTranslationSlice_Callback.takeAuditRow(firstId)
|
|
155
|
+
->Option.isSome,
|
|
156
|
+
)->toBe(true)
|
|
157
|
+
expect(Callback.auditLog->Dict.toArray->Array.length)->toBe(0)
|
|
158
|
+
|
|
159
|
+
// A second request drains only its own row — the first one is gone, so it
|
|
160
|
+
// cannot be written a second time.
|
|
161
|
+
let second = await Callback.receive(mockPublish, input("completed"))
|
|
162
|
+
let secondId = second->ReventlessCore.InboundTranslationSlice_Callback.requestIdOf
|
|
163
|
+
expect(secondId == firstId)->toBe(false)
|
|
164
|
+
expect(Callback.auditLog->Dict.toArray->Array.length)->toBe(1)
|
|
165
|
+
expect(
|
|
166
|
+
Callback.auditLog
|
|
167
|
+
->ReventlessCore.InboundTranslationSlice_Callback.takeAuditRow(firstId)
|
|
168
|
+
->Option.isSome,
|
|
169
|
+
)->toBe(false)
|
|
170
|
+
expect(
|
|
171
|
+
Callback.auditLog
|
|
172
|
+
->ReventlessCore.InboundTranslationSlice_Callback.takeAuditRow(secondId)
|
|
173
|
+
->Option.isSome,
|
|
174
|
+
)->toBe(true)
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
testPromise(
|
|
179
|
+
"a rejected request's row is drained the same way",
|
|
180
|
+
async () => {
|
|
181
|
+
let mockPublish: ReventlessInfra.CommandTopic.publishJsons = async _cmds => ()
|
|
182
|
+
let result = await Callback.receive(
|
|
183
|
+
mockPublish,
|
|
184
|
+
{"paymentId": "pay-1", "orderId": "ord-1", "status": "pending"}->Obj.magic,
|
|
185
|
+
)
|
|
186
|
+
let id = result->ReventlessCore.InboundTranslationSlice_Callback.requestIdOf
|
|
187
|
+
switch Callback.auditLog->ReventlessCore.InboundTranslationSlice_Callback.takeAuditRow(id) {
|
|
188
|
+
| Some(row) =>
|
|
189
|
+
expect(row.status)->toBe(ReventlessCore.InboundTranslationSlice_Callback.Failure)
|
|
190
|
+
| None => expect(true)->toBe(false)
|
|
191
|
+
}
|
|
192
|
+
expect(Callback.auditLog->Dict.toArray->Array.length)->toBe(0)
|
|
193
|
+
},
|
|
194
|
+
)
|
|
180
195
|
})
|
|
181
196
|
|
|
182
197
|
describe("multi-command", () => {
|
|
183
|
-
testPromise(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
198
|
+
testPromise(
|
|
199
|
+
"translate returning multiple pairs publishes all commands",
|
|
200
|
+
async () => {
|
|
201
|
+
// Use a spec that returns multiple commands
|
|
202
|
+
module MultiSpec = {
|
|
203
|
+
let name = "BatchWebhook"
|
|
204
|
+
let moduleUrl: string = %raw(`import.meta.url`)
|
|
205
|
+
|
|
206
|
+
@schema
|
|
207
|
+
type externalInput = {orderId: string, items: array<string>}
|
|
208
|
+
|
|
209
|
+
@schema
|
|
210
|
+
type command =
|
|
211
|
+
| ConfirmPayment({
|
|
212
|
+
orderId: @s.matches(Reventless.DcbTag.string) string,
|
|
213
|
+
paymentId: string,
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
let targetName = "ConfirmPayment"
|
|
217
|
+
let externalSystem = None
|
|
218
|
+
|
|
219
|
+
let translate = (input: externalInput) => Ok(
|
|
220
|
+
input.items->Array.map(
|
|
221
|
+
item => (input.orderId, ConfirmPayment({orderId: input.orderId, paymentId: item})),
|
|
222
|
+
),
|
|
207
223
|
)
|
|
208
224
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
{
|
|
225
|
+
let commandAuthorization = (_: command): Reventless.Authorization.permission =>
|
|
226
|
+
AllowAuthenticated
|
|
227
|
+
type lifecycleState = unit
|
|
228
|
+
let commandTransition = (_: command): Reventless.Transition.t<lifecycleState> =>
|
|
229
|
+
Unrestricted
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module MultiTranslation = {
|
|
233
|
+
let translate = MultiSpec.translate
|
|
234
|
+
let moduleUrl = MultiSpec.moduleUrl
|
|
235
|
+
}
|
|
236
|
+
module MultiCallback = ReventlessCore.InboundTranslationSlice_Callback.Make(
|
|
237
|
+
MultiSpec,
|
|
238
|
+
MultiTranslation,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
let publishedCommands = ref([])
|
|
242
|
+
let mockPublish: ReventlessInfra.CommandTopic.publishJsons = async cmds => {
|
|
243
|
+
publishedCommands := cmds
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let inputJson = {
|
|
231
247
|
"orderId": "ord-1",
|
|
232
248
|
"items": ["pay-1", "pay-2", "pay-3"],
|
|
233
249
|
}->Obj.magic
|
|
234
250
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
251
|
+
let result = await MultiCallback.receive(mockPublish, inputJson)
|
|
252
|
+
|
|
253
|
+
switch result {
|
|
254
|
+
| Ok({targetIds, commandCount}) =>
|
|
255
|
+
expect(targetIds)->toEqual(["ord-1", "ord-1", "ord-1"])
|
|
256
|
+
expect(commandCount)->toBe(3)
|
|
257
|
+
| Error(_) => expect(true)->toBe(false)
|
|
258
|
+
}
|
|
259
|
+
// All 3 commands published in one batch
|
|
260
|
+
expect(publishedCommands.contents->Array.length)->toBe(3)
|
|
261
|
+
|
|
262
|
+
let auditEntries = MultiCallback.auditLog->Dict.toArray
|
|
263
|
+
expect(auditEntries->Array.length)->toBe(1)
|
|
264
|
+
let (_, auditRow) = auditEntries->Array.getUnsafe(0)
|
|
265
|
+
expect(auditRow.status)->toBe(ReventlessCore.InboundTranslationSlice_Callback.Success)
|
|
266
|
+
expect(auditRow.commandCount)->toBe(Some(3))
|
|
267
|
+
},
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
testPromise(
|
|
271
|
+
"translate returning empty array publishes nothing",
|
|
272
|
+
async () => {
|
|
273
|
+
module EmptySpec = {
|
|
274
|
+
let name = "EmptyWebhook"
|
|
275
|
+
let moduleUrl: string = %raw(`import.meta.url`)
|
|
276
|
+
|
|
277
|
+
@schema
|
|
278
|
+
type externalInput = {orderId: string}
|
|
279
|
+
|
|
280
|
+
@schema
|
|
281
|
+
type command =
|
|
282
|
+
| ConfirmPayment({
|
|
283
|
+
orderId: @s.matches(Reventless.DcbTag.string) string,
|
|
284
|
+
paymentId: string,
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
let targetName = "ConfirmPayment"
|
|
288
|
+
let externalSystem = None
|
|
289
|
+
|
|
290
|
+
let translate = (_input: externalInput) => Ok([])
|
|
291
|
+
|
|
292
|
+
let commandAuthorization = (_: command): Reventless.Authorization.permission =>
|
|
293
|
+
AllowAuthenticated
|
|
294
|
+
type lifecycleState = unit
|
|
295
|
+
let commandTransition = (_: command): Reventless.Transition.t<lifecycleState> =>
|
|
296
|
+
Unrestricted
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
module EmptyTranslation = {
|
|
300
|
+
let translate = EmptySpec.translate
|
|
301
|
+
let moduleUrl = EmptySpec.moduleUrl
|
|
302
|
+
}
|
|
303
|
+
module EmptyCallback = ReventlessCore.InboundTranslationSlice_Callback.Make(
|
|
304
|
+
EmptySpec,
|
|
305
|
+
EmptyTranslation,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
let publishedCommands = ref([])
|
|
309
|
+
let mockPublish: ReventlessInfra.CommandTopic.publishJsons = async cmds => {
|
|
310
|
+
publishedCommands := cmds
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let inputJson = {"orderId": "ord-1"}->Obj.magic
|
|
314
|
+
|
|
315
|
+
let result = await EmptyCallback.receive(mockPublish, inputJson)
|
|
316
|
+
|
|
317
|
+
switch result {
|
|
318
|
+
| Ok({targetIds, commandCount}) =>
|
|
319
|
+
expect(targetIds)->toEqual([])
|
|
320
|
+
expect(commandCount)->toBe(0)
|
|
321
|
+
| Error(_) => expect(true)->toBe(false)
|
|
322
|
+
}
|
|
323
|
+
expect(publishedCommands.contents->Array.length)->toBe(0)
|
|
324
|
+
},
|
|
325
|
+
)
|
|
304
326
|
})
|
|
305
327
|
})
|
|
@@ -8,7 +8,7 @@ module OrderEventLog = {
|
|
|
8
8
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
9
9
|
@schema
|
|
10
10
|
type event =
|
|
11
|
-
|
|
11
|
+
PaymentConfirmed({orderId: @s.matches(Reventless.DcbTag.string) string, paymentId: string})
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -23,7 +23,8 @@ module PaymentWebhookSpec = {
|
|
|
23
23
|
type externalInput = {paymentId: string, orderId: string, status: string}
|
|
24
24
|
|
|
25
25
|
@schema
|
|
26
|
-
type command =
|
|
26
|
+
type command =
|
|
27
|
+
ConfirmPayment({orderId: @s.matches(Reventless.DcbTag.string) string, paymentId: string})
|
|
27
28
|
|
|
28
29
|
let targetName = "ConfirmPayment"
|
|
29
30
|
let externalSystem = None
|