@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
|
@@ -5,10 +5,7 @@
|
|
|
5
5
|
open JestGlobals
|
|
6
6
|
open AutomationSliceFixtures
|
|
7
7
|
|
|
8
|
-
module Callback = ReventlessCore.AutomationSlice_Callback.Make(
|
|
9
|
-
ShipOrderSpec,
|
|
10
|
-
ShipOrderAutomation,
|
|
11
|
-
)
|
|
8
|
+
module Callback = ReventlessCore.AutomationSlice_Callback.Make(ShipOrderSpec, ShipOrderAutomation)
|
|
12
9
|
|
|
13
10
|
module SkipCallback = ReventlessCore.AutomationSlice_Callback.Make(
|
|
14
11
|
SkipProcessSpec,
|
|
@@ -28,155 +25,182 @@ describe("AutomationSlice Callback (mixed-source)", () => {
|
|
|
28
25
|
})
|
|
29
26
|
|
|
30
27
|
describe("Phase 1 — collect", () => {
|
|
31
|
-
testPromise(
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
28
|
+
testPromise(
|
|
29
|
+
"OrderPlaced JSON creates a pending TODO item",
|
|
30
|
+
async () => {
|
|
31
|
+
let json = encodeShipOrderEvent(OrderPlaced({orderId: "ord-1", address: "123 Main St"}))
|
|
32
|
+
Callback.phase1([json], testContext)
|
|
33
|
+
let items = Callback.todoItems
|
|
34
|
+
expect(items->Dict.toArray->Array.length)->toBe(1)
|
|
35
|
+
let row = items->Dict.get("ord-1")->Option.getOrThrow
|
|
36
|
+
expect(row.status)->toBe(Pending)
|
|
37
|
+
expect(row.retryCount)->toBe(0)
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
testPromise(
|
|
42
|
+
"duplicate OrderPlaced is idempotent",
|
|
43
|
+
async () => {
|
|
44
|
+
let j1 = encodeShipOrderEvent(OrderPlaced({orderId: "ord-1", address: "123 Main St"}))
|
|
45
|
+
let j2 = encodeShipOrderEvent(OrderPlaced({orderId: "ord-1", address: "456 Oak Ave"}))
|
|
46
|
+
Callback.phase1([j1], testContext)
|
|
47
|
+
Callback.phase1([j2], testContext)
|
|
48
|
+
expect(Callback.todoItems->Dict.toArray->Array.length)->toBe(1)
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
testPromise(
|
|
53
|
+
"multiple OrderPlaced events create multiple TODO items",
|
|
54
|
+
async () => {
|
|
55
|
+
let j1 = encodeShipOrderEvent(OrderPlaced({orderId: "ord-1", address: "123 Main St"}))
|
|
56
|
+
let j2 = encodeShipOrderEvent(OrderPlaced({orderId: "ord-2", address: "456 Oak Ave"}))
|
|
57
|
+
Callback.phase1([j1, j2], testContext)
|
|
58
|
+
expect(Callback.todoItems->Dict.toArray->Array.length)->toBe(2)
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
testPromise(
|
|
63
|
+
"ShipmentCreated does not create a TODO item",
|
|
64
|
+
async () => {
|
|
65
|
+
let json = encodeShipOrderEvent(ShipmentCreated({orderId: "ord-1"}))
|
|
66
|
+
Callback.phase1([json], testContext)
|
|
67
|
+
expect(Callback.todoItems->Dict.toArray->Array.length)->toBe(0)
|
|
68
|
+
},
|
|
69
|
+
)
|
|
61
70
|
})
|
|
62
71
|
|
|
63
72
|
describe("Phase 1 — resolve", () => {
|
|
64
|
-
testPromise(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
testContext
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
testPromise(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
testContext
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
73
|
+
testPromise(
|
|
74
|
+
"ShipmentCreated marks pending TODO item as completed",
|
|
75
|
+
async () => {
|
|
76
|
+
Callback.phase1(
|
|
77
|
+
[encodeShipOrderEvent(OrderPlaced({orderId: "ord-1", address: "123 Main St"}))],
|
|
78
|
+
testContext,
|
|
79
|
+
)
|
|
80
|
+
Callback.phase1([encodeShipOrderEvent(ShipmentCreated({orderId: "ord-1"}))], testContext)
|
|
81
|
+
let row = Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow
|
|
82
|
+
expect(row.status)->toBe(Completed)
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
testPromise(
|
|
87
|
+
"ShipmentCreated for unknown id is a no-op",
|
|
88
|
+
async () => {
|
|
89
|
+
Callback.phase1([encodeShipOrderEvent(ShipmentCreated({orderId: "unknown"}))], testContext)
|
|
90
|
+
expect(Callback.todoItems->Dict.toArray->Array.length)->toBe(0)
|
|
91
|
+
},
|
|
92
|
+
)
|
|
84
93
|
})
|
|
85
94
|
|
|
86
95
|
describe("Phase 2 — process", () => {
|
|
87
|
-
testPromise(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
publishedCommands
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
publishedCommands
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
96
|
+
testPromise(
|
|
97
|
+
"pending items produce commands via publishJsons",
|
|
98
|
+
async () => {
|
|
99
|
+
Callback.phase1(
|
|
100
|
+
[encodeShipOrderEvent(OrderPlaced({orderId: "ord-1", address: "123 Main St"}))],
|
|
101
|
+
testContext,
|
|
102
|
+
)
|
|
103
|
+
let publishedCommands = ref([])
|
|
104
|
+
let mockPublish: ReventlessInfra.CommandTopic.publishJsons = async cmds => {
|
|
105
|
+
publishedCommands := cmds
|
|
106
|
+
}
|
|
107
|
+
await Callback.phase2(mockPublish)
|
|
108
|
+
|
|
109
|
+
expect(publishedCommands.contents->Array.length)->toBe(1)
|
|
110
|
+
let cmd = publishedCommands.contents->Array.getUnsafe(0)
|
|
111
|
+
expect(cmd.id)->toBe("ord-1")
|
|
112
|
+
|
|
113
|
+
let row = Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow
|
|
114
|
+
expect(row.status)->toBe(Processing)
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
testPromise(
|
|
119
|
+
"completed items are not processed",
|
|
120
|
+
async () => {
|
|
121
|
+
Callback.phase1(
|
|
122
|
+
[encodeShipOrderEvent(OrderPlaced({orderId: "ord-1", address: "123 Main St"}))],
|
|
123
|
+
testContext,
|
|
124
|
+
)
|
|
125
|
+
Callback.phase1([encodeShipOrderEvent(ShipmentCreated({orderId: "ord-1"}))], testContext)
|
|
126
|
+
let publishedCommands = ref([])
|
|
127
|
+
let mockPublish: ReventlessInfra.CommandTopic.publishJsons = async cmds => {
|
|
128
|
+
publishedCommands := cmds
|
|
129
|
+
}
|
|
130
|
+
await Callback.phase2(mockPublish)
|
|
131
|
+
expect(publishedCommands.contents->Array.length)->toBe(0)
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
testPromise(
|
|
136
|
+
"items where process returns None are skipped",
|
|
137
|
+
async () => {
|
|
138
|
+
let json = encodeSkipProcessEvent(OrderPlaced({orderId: "ord-1"}))
|
|
139
|
+
SkipCallback.phase1([json], testContext)
|
|
140
|
+
let publishedCommands = ref([])
|
|
141
|
+
let mockPublish: ReventlessInfra.CommandTopic.publishJsons = async cmds => {
|
|
142
|
+
publishedCommands := cmds
|
|
143
|
+
}
|
|
144
|
+
await SkipCallback.phase2(mockPublish)
|
|
145
|
+
expect(publishedCommands.contents->Array.length)->toBe(0)
|
|
146
|
+
let row = SkipCallback.todoItems->Dict.get("ord-1")->Option.getOrThrow
|
|
147
|
+
expect(row.status)->toBe(Pending)
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
testPromise(
|
|
152
|
+
"empty TODO list produces no commands",
|
|
153
|
+
async () => {
|
|
154
|
+
let publishedCommands = ref([])
|
|
155
|
+
let mockPublish: ReventlessInfra.CommandTopic.publishJsons = async cmds => {
|
|
156
|
+
publishedCommands := cmds
|
|
157
|
+
}
|
|
158
|
+
await Callback.phase2(mockPublish)
|
|
159
|
+
expect(publishedCommands.contents->Array.length)->toBe(0)
|
|
160
|
+
},
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
testPromise(
|
|
164
|
+
"publishJsons failure marks items as Failed",
|
|
165
|
+
async () => {
|
|
166
|
+
Callback.phase1(
|
|
167
|
+
[encodeShipOrderEvent(OrderPlaced({orderId: "ord-1", address: "123 Main St"}))],
|
|
168
|
+
testContext,
|
|
169
|
+
)
|
|
170
|
+
let failingPublish: ReventlessInfra.CommandTopic.publishJsons = async _cmds => {
|
|
171
|
+
JsError.throwWithMessage("publish failed")
|
|
172
|
+
}
|
|
173
|
+
await Callback.phase2(failingPublish)
|
|
174
|
+
let row = Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow
|
|
175
|
+
expect(row.status)->toBe(Failed)
|
|
176
|
+
expect(row.retryCount)->toBe(1)
|
|
177
|
+
},
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
testPromise(
|
|
181
|
+
"failed items with retryCount < maxRetries are retried",
|
|
182
|
+
async () => {
|
|
183
|
+
Callback.phase1(
|
|
184
|
+
[encodeShipOrderEvent(OrderPlaced({orderId: "ord-1", address: "123 Main St"}))],
|
|
185
|
+
testContext,
|
|
186
|
+
)
|
|
187
|
+
let failingPublish: ReventlessInfra.CommandTopic.publishJsons = async _cmds => {
|
|
188
|
+
JsError.throwWithMessage("publish failed")
|
|
189
|
+
}
|
|
190
|
+
await Callback.phase2(failingPublish)
|
|
191
|
+
let row1 = Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow
|
|
192
|
+
expect(row1.retryCount)->toBe(1)
|
|
193
|
+
|
|
194
|
+
let publishedCommands = ref([])
|
|
195
|
+
let successPublish: ReventlessInfra.CommandTopic.publishJsons = async cmds => {
|
|
196
|
+
publishedCommands := cmds
|
|
197
|
+
}
|
|
198
|
+
await Callback.phase2(successPublish)
|
|
199
|
+
expect(publishedCommands.contents->Array.length)->toBe(1)
|
|
200
|
+
let row2 = Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow
|
|
201
|
+
expect(row2.status)->toBe(Processing)
|
|
202
|
+
},
|
|
203
|
+
)
|
|
180
204
|
})
|
|
181
205
|
|
|
182
206
|
// A row used to leave the retry budget by falling out of phase2's filter, so
|
|
@@ -194,83 +218,96 @@ describe("AutomationSlice Callback (mixed-source)", () => {
|
|
|
194
218
|
|
|
195
219
|
let statusOf = id => (Callback.todoItems->Dict.get(id)->Option.getOrThrow).status
|
|
196
220
|
|
|
197
|
-
testPromise(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
221
|
+
testPromise(
|
|
222
|
+
"the attempt that spends the budget writes Abandoned, not Failed",
|
|
223
|
+
async () => {
|
|
224
|
+
collectOne()
|
|
225
|
+
// ShipOrderSpec.maxRetries is 3: attempts at retryCount 0, 1 and 2 leave the
|
|
226
|
+
// row retriable, and the third failure is the one that ends it.
|
|
227
|
+
await Callback.phase2(failingPublish)
|
|
228
|
+
expect(statusOf("ord-1"))->toBe(Failed)
|
|
229
|
+
await Callback.phase2(failingPublish)
|
|
230
|
+
expect(statusOf("ord-1"))->toBe(Failed)
|
|
231
|
+
await Callback.phase2(failingPublish)
|
|
232
|
+
expect(statusOf("ord-1"))->toBe(Abandoned)
|
|
233
|
+
expect((Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow).retryCount)->toBe(3)
|
|
234
|
+
},
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
testPromise(
|
|
238
|
+
"an abandoned row is not picked up again",
|
|
239
|
+
async () => {
|
|
240
|
+
collectOne()
|
|
241
|
+
await Callback.phase2(failingPublish)
|
|
242
|
+
await Callback.phase2(failingPublish)
|
|
243
|
+
await Callback.phase2(failingPublish)
|
|
244
|
+
expect(statusOf("ord-1"))->toBe(Abandoned)
|
|
245
|
+
|
|
246
|
+
// A pass that would have succeeded: the row must still not be attempted,
|
|
247
|
+
// which is what makes the status a decision rather than a label.
|
|
248
|
+
let published = ref([])
|
|
249
|
+
await Callback.phase2(async cmds => published := cmds)
|
|
250
|
+
expect(published.contents->Array.length)->toBe(0)
|
|
251
|
+
expect(statusOf("ord-1"))->toBe(Abandoned)
|
|
252
|
+
expect((Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow).retryCount)->toBe(3)
|
|
253
|
+
},
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
testPromise(
|
|
257
|
+
"a row left stranded at the ceiling is normalised on the next pass",
|
|
258
|
+
async () => {
|
|
259
|
+
collectOne()
|
|
260
|
+
// What an older build wrote, and what a Spec that lowered maxRetries leaves
|
|
261
|
+
// behind: Failed at the ceiling, which no pass would ever pick up or mark.
|
|
262
|
+
let row = Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow
|
|
263
|
+
Callback.todoItems->Dict.set("ord-1", {...row, status: Failed, retryCount: 3})
|
|
264
|
+
|
|
265
|
+
let published = ref([])
|
|
266
|
+
await Callback.phase2(async cmds => published := cmds)
|
|
267
|
+
expect(published.contents->Array.length)->toBe(0)
|
|
268
|
+
expect(statusOf("ord-1"))->toBe(Abandoned)
|
|
269
|
+
},
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
testPromise(
|
|
273
|
+
"the row carries the ceiling its count is racing",
|
|
274
|
+
async () => {
|
|
275
|
+
collectOne()
|
|
276
|
+
expect((Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow).maxRetries)->toEqual(
|
|
277
|
+
Some(ShipOrderSpec.maxRetries),
|
|
278
|
+
)
|
|
279
|
+
},
|
|
280
|
+
)
|
|
244
281
|
})
|
|
245
282
|
|
|
246
283
|
describe("full lifecycle", () => {
|
|
247
|
-
testPromise(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
publishedCommands
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
testContext
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
284
|
+
testPromise(
|
|
285
|
+
"collect → process → resolve completes the TODO item",
|
|
286
|
+
async () => {
|
|
287
|
+
Callback.phase1(
|
|
288
|
+
[encodeShipOrderEvent(OrderPlaced({orderId: "ord-1", address: "123 Main St"}))],
|
|
289
|
+
testContext,
|
|
290
|
+
)
|
|
291
|
+
expect((Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow).status)->toBe(Pending)
|
|
292
|
+
|
|
293
|
+
let publishedCommands = ref([])
|
|
294
|
+
let mockPublish: ReventlessInfra.CommandTopic.publishJsons = async cmds => {
|
|
295
|
+
publishedCommands := cmds
|
|
296
|
+
}
|
|
297
|
+
await Callback.phase2(mockPublish)
|
|
298
|
+
expect(publishedCommands.contents->Array.length)->toBe(1)
|
|
299
|
+
expect((Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow).status)->toBe(Processing)
|
|
300
|
+
|
|
301
|
+
Callback.phase1([encodeShipOrderEvent(ShipmentCreated({orderId: "ord-1"}))], testContext)
|
|
302
|
+
expect((Callback.todoItems->Dict.get("ord-1")->Option.getOrThrow).status)->toBe(Completed)
|
|
303
|
+
|
|
304
|
+
let publishedCommands2 = ref([])
|
|
305
|
+
let mockPublish2: ReventlessInfra.CommandTopic.publishJsons = async cmds => {
|
|
306
|
+
publishedCommands2 := cmds
|
|
307
|
+
}
|
|
308
|
+
await Callback.phase2(mockPublish2)
|
|
309
|
+
expect(publishedCommands2.contents->Array.length)->toBe(0)
|
|
310
|
+
},
|
|
311
|
+
)
|
|
275
312
|
})
|
|
276
313
|
})
|
|
@@ -106,8 +106,10 @@ module SkipProcessMapping = Reventless.AutomationSlice.Mapping.Make(
|
|
|
106
106
|
|
|
107
107
|
module ShipOrderAutomation: Reventless.AutomationSlice.Automation
|
|
108
108
|
with module Spec := ShipOrderSpec = {
|
|
109
|
-
let process = (id, _item: ShipOrderSpec.todoItem) =>
|
|
110
|
-
|
|
109
|
+
let process = (id, _item: ShipOrderSpec.todoItem) => Some((
|
|
110
|
+
id,
|
|
111
|
+
ShipOrderSpec.CreateShipment({orderId: id}),
|
|
112
|
+
))
|
|
111
113
|
let onExhausted = (_id, _item: ShipOrderSpec.todoItem) => None
|
|
112
114
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
113
115
|
module M = Reventless.AutomationSlice.Mappings.Make(ShipOrderSpec)
|
|
@@ -156,8 +156,7 @@ module FromDcb = AutomationSlice.Mapping.Make(
|
|
|
156
156
|
)
|
|
157
157
|
|
|
158
158
|
module AutoShipAutomation: AutomationSlice.Automation with module Spec := AutoShipSpec = {
|
|
159
|
-
let process = (id, _item: AutoShipSpec.todoItem) =>
|
|
160
|
-
Some((id, AutoShipSpec.Ship({orderId: id})))
|
|
159
|
+
let process = (id, _item: AutoShipSpec.todoItem) => Some((id, AutoShipSpec.Ship({orderId: id})))
|
|
161
160
|
let onExhausted = (_id, _item: AutoShipSpec.todoItem) => None
|
|
162
161
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
163
162
|
module M = AutomationSlice.Mappings.Make(AutoShipSpec)
|
|
@@ -189,42 +188,40 @@ let dcbEventLog = DcbLogMaker.make(
|
|
|
189
188
|
// ─────────────────────────────────────────────────────────────
|
|
190
189
|
|
|
191
190
|
let publishJsons: ReventlessInfra.CommandTopic.publishJsons = async cmdJsons => {
|
|
192
|
-
let _ =
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
)
|
|
205
|
-
->Option.getOr("")
|
|
206
|
-
| _ => ""
|
|
207
|
-
}
|
|
208
|
-
let fullBody = JSON.Encode.object(
|
|
209
|
-
Dict.fromArray([
|
|
210
|
-
("id", JSON.Encode.string(cmdJson.id)),
|
|
211
|
-
("meta", cmdJson.meta->Reventless.Util_Sury.toJson(Reventless.Message.metaSchema)),
|
|
212
|
-
("command", cmdJson.commandJson),
|
|
213
|
-
]),
|
|
191
|
+
let _ = await cmdJsons
|
|
192
|
+
->Array.map(async cmdJson => {
|
|
193
|
+
let typeName = switch cmdJson.commandJson {
|
|
194
|
+
| JSON.Object(dict) =>
|
|
195
|
+
dict
|
|
196
|
+
->Dict.get("TAG")
|
|
197
|
+
->Option.flatMap(j =>
|
|
198
|
+
switch j {
|
|
199
|
+
| JSON.String(s) => Some(s)
|
|
200
|
+
| _ => None
|
|
201
|
+
}
|
|
214
202
|
)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
203
|
+
->Option.getOr("")
|
|
204
|
+
| _ => ""
|
|
205
|
+
}
|
|
206
|
+
let fullBody = JSON.Encode.object(
|
|
207
|
+
Dict.fromArray([
|
|
208
|
+
("id", JSON.Encode.string(cmdJson.id)),
|
|
209
|
+
("meta", cmdJson.meta->Reventless.Util_Sury.toJson(Reventless.Message.metaSchema)),
|
|
210
|
+
("command", cmdJson.commandJson),
|
|
211
|
+
]),
|
|
212
|
+
)
|
|
213
|
+
let handlers = ReventlessCore.CommandTopic.getHandlers(typeName)
|
|
214
|
+
let _ = await handlers
|
|
215
|
+
->Array.map(async entry => {
|
|
216
|
+
let item: ReventlessInfra.CommandTopic.topicItem<JSON.t> = {
|
|
217
|
+
reference: cmdJson.id,
|
|
218
|
+
command: fullBody,
|
|
219
|
+
}
|
|
220
|
+
let _ = await entry.handler(Stream.fromIterable([item]))->Effect.runPromise
|
|
226
221
|
})
|
|
227
222
|
->Promise.all
|
|
223
|
+
})
|
|
224
|
+
->Promise.all
|
|
228
225
|
}
|
|
229
226
|
|
|
230
227
|
let publishJsonsOutput = publishJsons->Pulumi.Output.make
|
|
@@ -239,7 +236,9 @@ let _placeSlice = PlaceMaker.make(~dcbEventLog, ~publishJsons=publishJsonsOutput
|
|
|
239
236
|
module ShipMaker = StateChangeSlice_Builder.Make(ShipSpec, ShipBehavior)
|
|
240
237
|
let _shipSlice = ShipMaker.make(~dcbEventLog, ~publishJsons=publishJsonsOutput)
|
|
241
238
|
|
|
242
|
-
let dcbTopicOutputs: ReventlessInfra.EventTopic.outputs = (
|
|
239
|
+
let dcbTopicOutputs: ReventlessInfra.EventTopic.outputs = (
|
|
240
|
+
dcbEventLog->ReventlessInfra.Component.outputs
|
|
241
|
+
).eventTopic
|
|
243
242
|
let allEventTopics: ReventlessInfra.EventTopic.allOutputs = Dict.fromArray([
|
|
244
243
|
(DcbSource.name, dcbTopicOutputs),
|
|
245
244
|
])
|
|
@@ -266,8 +265,9 @@ let autoShipSlice = AutoShip.make(
|
|
|
266
265
|
let placeCmdJson = (orderId: string): Reventless.Message.commandJson => {
|
|
267
266
|
id: orderId,
|
|
268
267
|
meta: testMeta,
|
|
269
|
-
commandJson: PlaceSpec.Place({orderId: orderId})
|
|
270
|
-
|
|
268
|
+
commandJson: PlaceSpec.Place({orderId: orderId})->Reventless.Util_Sury.toJson(
|
|
269
|
+
PlaceSpec.commandSchema,
|
|
270
|
+
),
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
// Promise-based timeout used to fail fast if the chain self-deadlocks.
|