@reventlessdev/reventless-gwt 1.0.0-alpha.242 → 1.0.0-alpha.243
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 +12 -0
- package/package.json +6 -6
- package/src/Automation_GWT.res +55 -16
- package/src/Automation_GWT.res.mjs +67 -14
- package/src/Flow_GWT.res +3 -2
- package/src/Flow_GWT.res.mjs +3 -2
- package/tests/AutomationDeadlineGwtTest.res +111 -0
- package/tests/AutomationDeadlineGwtTest.res.mjs +267 -0
- package/tests/AutomationFromSliceGwtTest.res +4 -1
- package/tests/AutomationFromSliceGwtTest.res.mjs +23 -14
- package/tests/AutomationGwtTest.res +1 -1
- package/tests/AutomationGwtTest.res.mjs +3 -3
- package/tests/FlowGwtTest.res +1 -1
- package/tests/FlowGwtTest.res.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 1.0.0-alpha.243 (2026-10-15)
|
|
7
|
+
|
|
8
|
+
* feat!: an automation's process takes ~now, and collect the event's recorded time ([8c8f3f5](https://github.com/ReventlessDev/reventless-core/commit/8c8f3f570474b1658b605b726077debc7d6a1dc7))
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* a process the PPX does not see — in an Automation module
|
|
13
|
+
written by hand, or a SliceSpec given to the deprecated Automation_GWT.Make
|
|
14
|
+
or Flow_GWT.AutomationStep — must take ~now.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
6
18
|
# 1.0.0-alpha.242 (2026-10-14)
|
|
7
19
|
|
|
8
20
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-gwt",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.243",
|
|
4
4
|
"description": "Given-When-Then DSLs and test harness for Reventless slice testing",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"sury": "11.0.0",
|
|
17
|
-
"@reventlessdev/rescript-effect": "
|
|
18
|
-
"@reventlessdev/
|
|
19
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.294",
|
|
20
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.191",
|
|
17
|
+
"@reventlessdev/rescript-effect": "1.0.0-alpha.0",
|
|
18
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.295",
|
|
21
19
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.12",
|
|
22
|
-
"@reventlessdev/
|
|
20
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.22",
|
|
21
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.192",
|
|
22
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.164"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"rescript": "12.3.1",
|
package/src/Automation_GWT.res
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
open ReventlessCore
|
|
2
2
|
|
|
3
|
+
// The current time when a scenario fixes none, from Effect's Clock.
|
|
4
|
+
let clockNow = () => Clock.currentTimeMillis->Effect.runSync
|
|
5
|
+
let nowOr = now =>
|
|
6
|
+
switch now {
|
|
7
|
+
| Some(now) => now
|
|
8
|
+
| None => clockNow()
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
// Minimal inline spec for AutomationSlice — mirrors the pattern used by
|
|
4
12
|
// the other GWT DSLs so sury-ppx processes the @schema attributes in this
|
|
5
13
|
// compilation unit.
|
|
@@ -17,7 +25,7 @@ module type SliceSpec = {
|
|
|
17
25
|
|
|
18
26
|
let collect: consumedEvent => array<(string, todoItem)>
|
|
19
27
|
let resolve: consumedEvent => option<string>
|
|
20
|
-
let process: (string, todoItem) => option<(string, command)>
|
|
28
|
+
let process: (string, todoItem, ~now: float) => option<(string, command)>
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
module type T = {
|
|
@@ -44,13 +52,13 @@ module type T = {
|
|
|
44
52
|
|
|
45
53
|
// Unit combinators — process
|
|
46
54
|
let givenTodo: (string, Spec.todoItem) => (string, Spec.todoItem)
|
|
47
|
-
let whenProcess: ((string, Spec.todoItem)) => option<(string, Spec.command)>
|
|
55
|
+
let whenProcess: ((string, Spec.todoItem), ~now: float=?) => option<(string, Spec.command)>
|
|
48
56
|
let thenCommand: (option<(string, Spec.command)>, string, Spec.command) => Outcome.outcome
|
|
49
57
|
let thenNoCommand: option<(string, Spec.command)> => Outcome.outcome
|
|
50
58
|
|
|
51
59
|
// Scenario combinators — full sweep
|
|
52
60
|
let givenEvents: array<Spec.consumedEvent> => array<Spec.consumedEvent>
|
|
53
|
-
let whenSweep: array<Spec.consumedEvent
|
|
61
|
+
let whenSweep: (array<Spec.consumedEvent>, ~now: float=?) => scenario
|
|
54
62
|
let thenCommands: (scenario, array<(string, Spec.command)>) => Outcome.outcome
|
|
55
63
|
let andThenEvents: (scenario, array<Spec.consumedEvent>) => scenario
|
|
56
64
|
let thenScenarioTodos: (scenario, array<(string, Spec.todoItem)>) => Outcome.outcome
|
|
@@ -111,7 +119,7 @@ module Make = (Spec: SliceSpec): (T with module Spec = Spec) => {
|
|
|
111
119
|
|
|
112
120
|
// Unit: process
|
|
113
121
|
let givenTodo = (id, todo) => (id, todo)
|
|
114
|
-
let whenProcess = ((id, todo)) => Spec.process(id, todo)
|
|
122
|
+
let whenProcess = ((id, todo), ~now=?) => Spec.process(id, todo, ~now=nowOr(now))
|
|
115
123
|
let thenCommand = (actual, expectedId, expectedCmd) =>
|
|
116
124
|
switch actual {
|
|
117
125
|
| Some((id, cmd)) if id == expectedId && cmd == expectedCmd => Outcome.pass
|
|
@@ -157,7 +165,8 @@ module Make = (Spec: SliceSpec): (T with module Spec = Spec) => {
|
|
|
157
165
|
|
|
158
166
|
// Scenario: full sweep
|
|
159
167
|
let givenEvents = es => es
|
|
160
|
-
let whenSweep = events => {
|
|
168
|
+
let whenSweep = (events, ~now=?) => {
|
|
169
|
+
let now = nowOr(now)
|
|
161
170
|
// Build the todo list by running collect, then drain any items that are
|
|
162
171
|
// completed by resolve in the same input stream.
|
|
163
172
|
let collected = events->Array.map(e => e->Spec.collect)->Array.flat
|
|
@@ -165,7 +174,7 @@ module Make = (Spec: SliceSpec): (T with module Spec = Spec) => {
|
|
|
165
174
|
let pending = collected->Array.filter(((id, _)) => !Array.includes(resolvedIds, id))
|
|
166
175
|
|
|
167
176
|
// Process each pending todo to produce commands.
|
|
168
|
-
let commands = pending->Array.filterMap(((id, todo)) => Spec.process(id, todo))
|
|
177
|
+
let commands = pending->Array.filterMap(((id, todo)) => Spec.process(id, todo, ~now))
|
|
169
178
|
|
|
170
179
|
{todos: pending, commands}
|
|
171
180
|
}
|
|
@@ -217,9 +226,13 @@ module FromSlice = (
|
|
|
217
226
|
sliceName: Spec.name,
|
|
218
227
|
}
|
|
219
228
|
|
|
220
|
-
/** A source event as the runtime receives it: encoded,
|
|
221
|
-
source's name
|
|
222
|
-
|
|
229
|
+
/** A source event as the runtime receives it: encoded, addressed by its
|
|
230
|
+
source's name, and recorded at a time when the scenario gives one. Built
|
|
231
|
+
with `Mapping(M).event`. */
|
|
232
|
+
type sourced = {sourceName: string, sourceId: string, payload: JSON.t, recordedAt: option<float>}
|
|
233
|
+
|
|
234
|
+
let withRecordedAt = (context: Reventless.AutomationSlice.context, recordedAt) =>
|
|
235
|
+
recordedAt->Option.mapOr(context, recordedAt => {...context, recordedAt})
|
|
223
236
|
|
|
224
237
|
type scenario = {
|
|
225
238
|
todos: array<(string, Spec.todoItem)>,
|
|
@@ -248,19 +261,21 @@ module FromSlice = (
|
|
|
248
261
|
}
|
|
249
262
|
|
|
250
263
|
module Mapping = (M: Automation.Mapping) => {
|
|
251
|
-
let event = (e: M.sourceEvent, ~sourceId="") => {
|
|
264
|
+
let event = (e: M.sourceEvent, ~sourceId="", ~recordedAt=?) => {
|
|
252
265
|
sourceName: M.sourceName,
|
|
253
266
|
sourceId,
|
|
254
267
|
payload: e->Message.encode(M.sourceEventSchema),
|
|
268
|
+
recordedAt,
|
|
255
269
|
}
|
|
256
270
|
let givenEvent = (e: M.sourceEvent) => e
|
|
257
|
-
let whenCollect = (e, ~sourceId="", ~context=testContext
|
|
271
|
+
let whenCollect = (e, ~sourceId="", ~context=testContext, ~recordedAt=?) =>
|
|
272
|
+
M.collect(e, ~sourceId, context->withRecordedAt(recordedAt))
|
|
258
273
|
let whenResolve = M.resolve
|
|
259
274
|
}
|
|
260
275
|
|
|
261
276
|
// Unit: process and abandonment
|
|
262
277
|
let givenTodo = (id: string, item: Spec.todoItem) => (id, item)
|
|
263
|
-
let whenProcess = ((id, item)) => Automation.process(id, item)
|
|
278
|
+
let whenProcess = ((id, item), ~now=?) => Automation.process(id, item, ~now=nowOr(now))
|
|
264
279
|
let whenExhausted = ((id, item)) => Automation.onExhausted(id, item)
|
|
265
280
|
let thenCommand = (actual, expectedId, expectedCmd) =>
|
|
266
281
|
switch actual {
|
|
@@ -282,13 +297,30 @@ module FromSlice = (
|
|
|
282
297
|
// Scenario: a sweep routes each event in turn, as phase 1 does, so to-dos keep
|
|
283
298
|
// the order the events produced them.
|
|
284
299
|
let givenEvents = (es: array<sourced>) => es
|
|
300
|
+
/** The events happened at `time`: each one the scenario gave no time of its
|
|
301
|
+
own is recorded then. A sweep given no `~now` runs at the latest of them. */
|
|
302
|
+
let atTime = (events: array<sourced>, time) =>
|
|
303
|
+
events->Array.map(e => e.recordedAt->Option.isSome ? e : {...e, recordedAt: Some(time)})
|
|
304
|
+
let latestRecorded = (events: array<sourced>) =>
|
|
305
|
+
events->Array.reduce(None, (latest, e) =>
|
|
306
|
+
switch (latest, e.recordedAt) {
|
|
307
|
+
| (Some(l), Some(r)) => Some(Math.max(l, r))
|
|
308
|
+
| (None, r) => r
|
|
309
|
+
| (l, None) => l
|
|
310
|
+
}
|
|
311
|
+
)
|
|
285
312
|
let routeAll = (events: array<sourced>, context) =>
|
|
286
313
|
events->Array.flatMap(e =>
|
|
287
|
-
Route.route(
|
|
314
|
+
Route.route(
|
|
315
|
+
e.payload,
|
|
316
|
+
~sourceName=e.sourceName,
|
|
317
|
+
~sourceId=e.sourceId,
|
|
318
|
+
context->withRecordedAt(e.recordedAt),
|
|
319
|
+
)
|
|
288
320
|
)
|
|
289
321
|
/** Applies routed events as the runtime's to-do list does: the first writer of
|
|
290
322
|
an id wins, and a resolve completes only a row that already exists. */
|
|
291
|
-
let sweep = (routed: array<AutomationSlice_Route.routed<Spec.todoItem
|
|
323
|
+
let sweep = (routed: array<AutomationSlice_Route.routed<Spec.todoItem>>, ~now) => {
|
|
292
324
|
let rows: array<(string, Spec.todoItem, ref<bool>)> = []
|
|
293
325
|
routed->Array.forEach(r => {
|
|
294
326
|
r.collected->Array.forEach(((id, item)) =>
|
|
@@ -306,9 +338,16 @@ module FromSlice = (
|
|
|
306
338
|
)
|
|
307
339
|
})
|
|
308
340
|
let todos = rows->Array.filterMap(((id, item, done)) => done.contents ? None : Some((id, item)))
|
|
309
|
-
{todos, commands: todos->Array.filterMap(((id, item)) => Automation.process(id, item))}
|
|
341
|
+
{todos, commands: todos->Array.filterMap(((id, item)) => Automation.process(id, item, ~now))}
|
|
310
342
|
}
|
|
311
|
-
let whenSweep = (events, ~context=testContext) =>
|
|
343
|
+
let whenSweep = (events, ~context=testContext, ~now=?) =>
|
|
344
|
+
sweep(
|
|
345
|
+
routeAll(events, context),
|
|
346
|
+
~now=switch (now, latestRecorded(events)) {
|
|
347
|
+
| (Some(now), _) | (None, Some(now)) => now
|
|
348
|
+
| (None, None) => clockNow()
|
|
349
|
+
},
|
|
350
|
+
)
|
|
312
351
|
let thenCommands = (scenario, expected) =>
|
|
313
352
|
scenario.commands == expected
|
|
314
353
|
? Outcome.pass
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
|
+
import * as Clock from "effect/Clock";
|
|
4
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
5
7
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
6
8
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
7
9
|
import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
|
|
@@ -9,6 +11,18 @@ import * as JestBind$ReventlessGwt from "./JestBind.res.mjs";
|
|
|
9
11
|
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
10
12
|
import * as AutomationSlice_Route$ReventlessCore from "@reventlessdev/reventless-core/src/components/AutomationSlice/AutomationSlice_Route.res.mjs";
|
|
11
13
|
|
|
14
|
+
function clockNow() {
|
|
15
|
+
return Effect.runSync(Clock.currentTimeMillis);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function nowOr(now) {
|
|
19
|
+
if (now !== undefined) {
|
|
20
|
+
return now;
|
|
21
|
+
} else {
|
|
22
|
+
return Effect.runSync(Clock.currentTimeMillis);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
function Make(Spec) {
|
|
13
27
|
let test = (name, body) => JestBind$ReventlessGwt.test(Spec.name, name, body);
|
|
14
28
|
let encTodos = arr => arr.map(param => [
|
|
@@ -53,7 +67,7 @@ function Make(Spec) {
|
|
|
53
67
|
id,
|
|
54
68
|
todo
|
|
55
69
|
];
|
|
56
|
-
let whenProcess = param => Spec.process(param[0], param[1]);
|
|
70
|
+
let whenProcess = (param, now) => Spec.process(param[0], param[1], nowOr(now));
|
|
57
71
|
let thenCommand = (actual, expectedId, expectedCmd) => {
|
|
58
72
|
if (actual !== undefined) {
|
|
59
73
|
let cmd = actual[1];
|
|
@@ -98,11 +112,12 @@ function Make(Spec) {
|
|
|
98
112
|
});
|
|
99
113
|
};
|
|
100
114
|
let givenEvents = es => es;
|
|
101
|
-
let whenSweep = events => {
|
|
115
|
+
let whenSweep = (events, now) => {
|
|
116
|
+
let now$1 = nowOr(now);
|
|
102
117
|
let collected = events.map(e => Spec.collect(e)).flat();
|
|
103
118
|
let resolvedIds = Stdlib_Array.filterMap(events, e => Spec.resolve(e));
|
|
104
119
|
let pending = collected.filter(param => !resolvedIds.includes(param[0]));
|
|
105
|
-
let commands = Stdlib_Array.filterMap(pending, param => Spec.process(param[0], param[1]));
|
|
120
|
+
let commands = Stdlib_Array.filterMap(pending, param => Spec.process(param[0], param[1], now$1));
|
|
106
121
|
return {
|
|
107
122
|
todos: pending,
|
|
108
123
|
commands: commands
|
|
@@ -165,6 +180,11 @@ function FromSlice(Spec) {
|
|
|
165
180
|
pluginName: "test",
|
|
166
181
|
sliceName: testContext_sliceName
|
|
167
182
|
};
|
|
183
|
+
let withRecordedAt = (context, recordedAt) => Stdlib_Option.mapOr(recordedAt, context, recordedAt => {
|
|
184
|
+
let newrecord = {...context};
|
|
185
|
+
newrecord.recordedAt = recordedAt;
|
|
186
|
+
return newrecord;
|
|
187
|
+
});
|
|
168
188
|
let encTodos = arr => arr.map(param => [
|
|
169
189
|
param[0],
|
|
170
190
|
Message$ReventlessCore.encode(param[1], Spec.todoItemSchema)
|
|
@@ -202,19 +222,20 @@ function FromSlice(Spec) {
|
|
|
202
222
|
}
|
|
203
223
|
};
|
|
204
224
|
let Mapping = M => {
|
|
205
|
-
let event = (e, sourceIdOpt) => {
|
|
225
|
+
let event = (e, sourceIdOpt, recordedAt) => {
|
|
206
226
|
let sourceId = sourceIdOpt !== undefined ? sourceIdOpt : "";
|
|
207
227
|
return {
|
|
208
228
|
sourceName: M.sourceName,
|
|
209
229
|
sourceId: sourceId,
|
|
210
|
-
payload: Message$ReventlessCore.encode(e, M.sourceEventSchema)
|
|
230
|
+
payload: Message$ReventlessCore.encode(e, M.sourceEventSchema),
|
|
231
|
+
recordedAt: recordedAt
|
|
211
232
|
};
|
|
212
233
|
};
|
|
213
234
|
let givenEvent = e => e;
|
|
214
|
-
let whenCollect = (e, sourceIdOpt, contextOpt) => {
|
|
235
|
+
let whenCollect = (e, sourceIdOpt, contextOpt, recordedAt) => {
|
|
215
236
|
let sourceId = sourceIdOpt !== undefined ? sourceIdOpt : "";
|
|
216
237
|
let context = contextOpt !== undefined ? contextOpt : testContext;
|
|
217
|
-
return M.collect(e, sourceId, context);
|
|
238
|
+
return M.collect(e, sourceId, withRecordedAt(context, recordedAt));
|
|
218
239
|
};
|
|
219
240
|
return {
|
|
220
241
|
event: event,
|
|
@@ -227,7 +248,7 @@ function FromSlice(Spec) {
|
|
|
227
248
|
id,
|
|
228
249
|
item
|
|
229
250
|
];
|
|
230
|
-
let whenProcess = param => Automation.process(param[0], param[1]);
|
|
251
|
+
let whenProcess = (param, now) => Automation.process(param[0], param[1], nowOr(now));
|
|
231
252
|
let whenExhausted = param => Automation.onExhausted(param[0], param[1]);
|
|
232
253
|
let thenCommand = (actual, expectedId, expectedCmd) => {
|
|
233
254
|
if (actual !== undefined && actual[0] === expectedId && Primitive_object.equal(actual[1], expectedCmd)) {
|
|
@@ -253,8 +274,32 @@ function FromSlice(Spec) {
|
|
|
253
274
|
}
|
|
254
275
|
};
|
|
255
276
|
let givenEvents = es => es;
|
|
256
|
-
let
|
|
257
|
-
|
|
277
|
+
let atTime = (events, time) => events.map(e => {
|
|
278
|
+
if (Stdlib_Option.isSome(e.recordedAt)) {
|
|
279
|
+
return e;
|
|
280
|
+
} else {
|
|
281
|
+
return {
|
|
282
|
+
sourceName: e.sourceName,
|
|
283
|
+
sourceId: e.sourceId,
|
|
284
|
+
payload: e.payload,
|
|
285
|
+
recordedAt: time
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
let latestRecorded = events => Stdlib_Array.reduce(events, undefined, (latest, e) => {
|
|
290
|
+
let match = e.recordedAt;
|
|
291
|
+
if (latest !== undefined) {
|
|
292
|
+
if (match !== undefined) {
|
|
293
|
+
return Math.max(latest, match);
|
|
294
|
+
} else {
|
|
295
|
+
return latest;
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
return match;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
let routeAll = (events, context) => events.flatMap(e => Route.route(e.payload, e.sourceName, e.sourceId, withRecordedAt(context, e.recordedAt)));
|
|
302
|
+
let sweep = (routed, now) => {
|
|
258
303
|
let rows = [];
|
|
259
304
|
routed.forEach(r => {
|
|
260
305
|
r.collected.forEach(param => {
|
|
@@ -291,12 +336,15 @@ function FromSlice(Spec) {
|
|
|
291
336
|
});
|
|
292
337
|
return {
|
|
293
338
|
todos: todos,
|
|
294
|
-
commands: Stdlib_Array.filterMap(todos, param => Automation.process(param[0], param[1]))
|
|
339
|
+
commands: Stdlib_Array.filterMap(todos, param => Automation.process(param[0], param[1], now))
|
|
295
340
|
};
|
|
296
341
|
};
|
|
297
|
-
let whenSweep = (events, contextOpt) => {
|
|
342
|
+
let whenSweep = (events, contextOpt, now) => {
|
|
298
343
|
let context = contextOpt !== undefined ? contextOpt : testContext;
|
|
299
|
-
|
|
344
|
+
let match = latestRecorded(events);
|
|
345
|
+
return sweep(routeAll(events, context), now !== undefined ? now : (
|
|
346
|
+
match !== undefined ? match : Effect.runSync(Clock.currentTimeMillis)
|
|
347
|
+
));
|
|
300
348
|
};
|
|
301
349
|
let thenCommands = (scenario, expected) => {
|
|
302
350
|
if (Primitive_object.equal(scenario.commands, expected)) {
|
|
@@ -323,6 +371,7 @@ function FromSlice(Spec) {
|
|
|
323
371
|
describe: JestBind$ReventlessGwt.describe,
|
|
324
372
|
test: test,
|
|
325
373
|
testContext: testContext,
|
|
374
|
+
withRecordedAt: withRecordedAt,
|
|
326
375
|
encTodos: encTodos,
|
|
327
376
|
encPair: encPair,
|
|
328
377
|
thenTodos: thenTodos,
|
|
@@ -334,6 +383,8 @@ function FromSlice(Spec) {
|
|
|
334
383
|
thenCommand: thenCommand,
|
|
335
384
|
thenNoCommand: thenNoCommand,
|
|
336
385
|
givenEvents: givenEvents,
|
|
386
|
+
atTime: atTime,
|
|
387
|
+
latestRecorded: latestRecorded,
|
|
337
388
|
routeAll: routeAll,
|
|
338
389
|
sweep: sweep,
|
|
339
390
|
whenSweep: whenSweep,
|
|
@@ -345,7 +396,9 @@ function FromSlice(Spec) {
|
|
|
345
396
|
}
|
|
346
397
|
|
|
347
398
|
export {
|
|
399
|
+
clockNow,
|
|
400
|
+
nowOr,
|
|
348
401
|
Make,
|
|
349
402
|
FromSlice,
|
|
350
403
|
}
|
|
351
|
-
/*
|
|
404
|
+
/* effect/Clock Not a pure module */
|
package/src/Flow_GWT.res
CHANGED
|
@@ -505,7 +505,8 @@ module AutomationStep = (Spec: Automation_GWT.SliceSpec) => {
|
|
|
505
505
|
let collected = events->Array.map(e => e->Spec.collect)->Array.flat
|
|
506
506
|
let resolvedIds = events->Array.filterMap(e => e->Spec.resolve)
|
|
507
507
|
let pending = collected->Array.filter(((id, _)) => !(resolvedIds->Array.includes(id)))
|
|
508
|
-
let
|
|
508
|
+
let now = Automation_GWT.clockNow()
|
|
509
|
+
let commands = pending->Array.filterMap(((id, todo)) => Spec.process(id, todo, ~now))
|
|
509
510
|
{
|
|
510
511
|
...s,
|
|
511
512
|
lastCommands: commands->Array.map(((_id, cmd)) => cmd->Message.encode(Spec.commandSchema)),
|
|
@@ -628,7 +629,7 @@ module AutomationSlice = (
|
|
|
628
629
|
d.handle(entry.json, ~sourceId=entry->sourceIdOf, A.testContext)
|
|
629
630
|
)
|
|
630
631
|
)
|
|
631
|
-
let {commands, _} = A.sweep(routed)
|
|
632
|
+
let {commands, _} = A.sweep(routed, ~now=Automation_GWT.clockNow())
|
|
632
633
|
{
|
|
633
634
|
...s,
|
|
634
635
|
lastCommands: commands->Array.map(((_id, cmd)) => cmd->Message.encode(Spec.commandSchema)),
|
package/src/Flow_GWT.res.mjs
CHANGED
|
@@ -406,7 +406,8 @@ function AutomationStep(Spec) {
|
|
|
406
406
|
let collected = events.map(e => Spec.collect(e)).flat();
|
|
407
407
|
let resolvedIds = Stdlib_Array.filterMap(events, e => Spec.resolve(e));
|
|
408
408
|
let pending = collected.filter(param => !resolvedIds.includes(param[0]));
|
|
409
|
-
let
|
|
409
|
+
let now = Automation_GWT$ReventlessGwt.clockNow();
|
|
410
|
+
let commands = Stdlib_Array.filterMap(pending, param => Spec.process(param[0], param[1], now));
|
|
410
411
|
return {
|
|
411
412
|
log: s.log,
|
|
412
413
|
outcome: s.outcome,
|
|
@@ -524,7 +525,7 @@ function AutomationSlice(Spec) {
|
|
|
524
525
|
let whenReacts = async flowP => {
|
|
525
526
|
let s = await flowP;
|
|
526
527
|
let routed = s.log.flatMap(entry => Stdlib_Array.filterMap(A.Route.dispatches, d => d.handle(entry.json, Stdlib_Option.getOr(entry.aggregateId, ""), A.testContext)));
|
|
527
|
-
let match = A.sweep(routed);
|
|
528
|
+
let match = A.sweep(routed, Automation_GWT$ReventlessGwt.clockNow());
|
|
528
529
|
return {
|
|
529
530
|
log: s.log,
|
|
530
531
|
outcome: s.outcome,
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// A deadline tested at a fixed time: an order not shipped within three days of
|
|
2
|
+
// being placed is cancelled. `collect` counts the due time from the event's
|
|
3
|
+
// recorded time; `process` compares it with `~now`. `atTime` and `~recordedAt`
|
|
4
|
+
// say when the events happened, `~now` when the sweep runs.
|
|
5
|
+
|
|
6
|
+
module CancelUnshipped = {
|
|
7
|
+
let name = "CancelUnshipped"
|
|
8
|
+
let moduleUrl = ""
|
|
9
|
+
|
|
10
|
+
@schema
|
|
11
|
+
type todoItem = {orderId: string, dueAt: float}
|
|
12
|
+
|
|
13
|
+
@schema
|
|
14
|
+
type command = CancelOrder({orderId: string})
|
|
15
|
+
|
|
16
|
+
let maxRetries = 3
|
|
17
|
+
let heartbeatInterval = 60
|
|
18
|
+
let targetName = "Order"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let day = 24. *. 60. *. 60. *. 1000.
|
|
22
|
+
let day0 = Date.fromString("2026-10-01T09:00:00Z")->Date.getTime
|
|
23
|
+
|
|
24
|
+
module CancelUnshipped_Automation = {
|
|
25
|
+
module M = Reventless.AutomationSlice.Mappings.Make(CancelUnshipped)
|
|
26
|
+
module type Mapping = M.Mapping
|
|
27
|
+
let moduleUrl = ""
|
|
28
|
+
|
|
29
|
+
module Orders = {
|
|
30
|
+
module Id = Reventless.Id.String
|
|
31
|
+
let name = "Orders"
|
|
32
|
+
@schema
|
|
33
|
+
type event =
|
|
34
|
+
| OrderPlaced({orderId: string})
|
|
35
|
+
| OrderShipped({orderId: string})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module FromOrders = Reventless.AutomationSlice.Mapping.Make(
|
|
39
|
+
Orders,
|
|
40
|
+
CancelUnshipped,
|
|
41
|
+
{
|
|
42
|
+
let collect = (event, ~sourceId as _, ctx: Reventless.AutomationSlice.context) =>
|
|
43
|
+
switch (event, ctx.recordedAt) {
|
|
44
|
+
| (Orders.OrderPlaced({orderId}), Some(placedAt)) => [
|
|
45
|
+
(orderId, ({orderId, dueAt: placedAt +. 3. *. day}: CancelUnshipped.todoItem)),
|
|
46
|
+
]
|
|
47
|
+
| _ => []
|
|
48
|
+
}
|
|
49
|
+
let resolve = event =>
|
|
50
|
+
switch event {
|
|
51
|
+
| Orders.OrderShipped({orderId}) => Some(orderId)
|
|
52
|
+
| OrderPlaced(_) => None
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
let mappings: array<module(Mapping)> = [module(FromOrders)]
|
|
58
|
+
|
|
59
|
+
let process = (id, item: CancelUnshipped.todoItem, ~now) =>
|
|
60
|
+
now >= item.dueAt ? Some((id, CancelUnshipped.CancelOrder({orderId: item.orderId}))) : None
|
|
61
|
+
let onExhausted = (_id, _item: CancelUnshipped.todoItem) => None
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
open CancelUnshipped
|
|
65
|
+
open CancelUnshipped_Automation
|
|
66
|
+
include Automation_GWT.FromSlice(CancelUnshipped, CancelUnshipped_Automation)
|
|
67
|
+
module O = Mapping(FromOrders)
|
|
68
|
+
|
|
69
|
+
describe("a deadline at a fixed time", () => {
|
|
70
|
+
test("collect counts the due time from the event's recorded time", () =>
|
|
71
|
+
O.givenEvent(OrderPlaced({orderId: "o1"}))
|
|
72
|
+
->O.whenCollect(~recordedAt=day0)
|
|
73
|
+
->thenTodos([("o1", {orderId: "o1", dueAt: day0 +. 3. *. day})])
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
test("not yet due on day 2: no command", () =>
|
|
77
|
+
givenEvents([O.event(OrderPlaced({orderId: "o1"}))])
|
|
78
|
+
->atTime(day0)
|
|
79
|
+
->whenSweep(~now=day0 +. 2. *. day)
|
|
80
|
+
->thenCommands([])
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
test("due on day 3: CancelOrder", () =>
|
|
84
|
+
givenEvents([O.event(OrderPlaced({orderId: "o1"}))])
|
|
85
|
+
->atTime(day0)
|
|
86
|
+
->whenSweep(~now=day0 +. 3. *. day)
|
|
87
|
+
->thenCommands([("o1", CancelOrder({orderId: "o1"}))])
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
test("shipped on day 2: nothing on day 3", () =>
|
|
91
|
+
givenEvents([
|
|
92
|
+
O.event(OrderPlaced({orderId: "o1"}), ~recordedAt=day0),
|
|
93
|
+
O.event(OrderShipped({orderId: "o1"}), ~recordedAt=day0 +. 2. *. day),
|
|
94
|
+
])
|
|
95
|
+
->whenSweep(~now=day0 +. 3. *. day)
|
|
96
|
+
->thenCommands([])
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
test("a sweep given no time runs when the events happened", () =>
|
|
100
|
+
givenEvents([O.event(OrderPlaced({orderId: "o1"}))])
|
|
101
|
+
->atTime(day0)
|
|
102
|
+
->whenSweep
|
|
103
|
+
->thenCommands([])
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
test("process is given the time a scenario fixes", () =>
|
|
107
|
+
givenTodo("o1", {orderId: "o1", dueAt: day0})
|
|
108
|
+
->whenProcess(~now=day0)
|
|
109
|
+
->thenCommand("o1", CancelOrder({orderId: "o1"}))
|
|
110
|
+
)
|
|
111
|
+
})
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Sury from "sury";
|
|
4
|
+
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
5
|
+
import * as AutomationSlice$Reventless from "@reventlessdev/reventless-spec/src/components/AutomationSlice.res.mjs";
|
|
6
|
+
import * as Automation_GWT$ReventlessGwt from "../src/Automation_GWT.res.mjs";
|
|
7
|
+
|
|
8
|
+
let todoItemSchema = Sury.$schema(s => ({
|
|
9
|
+
orderId: s.m(Sury.string),
|
|
10
|
+
dueAt: s.m(Sury.number)
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
let commandSchema = Sury.$schema(s => ({
|
|
14
|
+
TAG: "CancelOrder",
|
|
15
|
+
orderId: s.m(Sury.string)
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
function authorizationOf(param) {
|
|
19
|
+
return "AllowAuthenticated";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function commandTransition(param) {
|
|
23
|
+
return "Undeclared";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let traits = [];
|
|
27
|
+
|
|
28
|
+
let CancelUnshipped = {
|
|
29
|
+
name: "CancelUnshipped",
|
|
30
|
+
moduleUrl: "",
|
|
31
|
+
todoItemSchema: todoItemSchema,
|
|
32
|
+
commandSchema: commandSchema,
|
|
33
|
+
maxRetries: 3,
|
|
34
|
+
heartbeatInterval: 60,
|
|
35
|
+
targetName: "Order",
|
|
36
|
+
authorizationOf: authorizationOf,
|
|
37
|
+
commandTransition: commandTransition,
|
|
38
|
+
traits: traits
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
let day = 24 * 60 * 60 * 1000;
|
|
42
|
+
|
|
43
|
+
let day0 = new Date("2026-10-01T09:00:00Z").getTime();
|
|
44
|
+
|
|
45
|
+
let M = AutomationSlice$Reventless.Mappings.Make(CancelUnshipped);
|
|
46
|
+
|
|
47
|
+
let moduleUrl = "";
|
|
48
|
+
|
|
49
|
+
let name = "Orders";
|
|
50
|
+
|
|
51
|
+
let eventSchema = Sury.union([
|
|
52
|
+
Sury.$schema(s => ({
|
|
53
|
+
TAG: "OrderPlaced",
|
|
54
|
+
orderId: s.m(Sury.string)
|
|
55
|
+
})),
|
|
56
|
+
Sury.$schema(s => ({
|
|
57
|
+
TAG: "OrderShipped",
|
|
58
|
+
orderId: s.m(Sury.string)
|
|
59
|
+
}))
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
let Orders = {
|
|
63
|
+
Id: undefined,
|
|
64
|
+
name: name,
|
|
65
|
+
eventSchema: eventSchema
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function collect(event, param, ctx) {
|
|
69
|
+
let match = ctx.recordedAt;
|
|
70
|
+
if (event.TAG !== "OrderPlaced") {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
if (match === undefined) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
let orderId = event.orderId;
|
|
77
|
+
return [[
|
|
78
|
+
orderId,
|
|
79
|
+
{
|
|
80
|
+
orderId: orderId,
|
|
81
|
+
dueAt: match + 3 * day
|
|
82
|
+
}
|
|
83
|
+
]];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resolve(event) {
|
|
87
|
+
if (event.TAG === "OrderPlaced") {
|
|
88
|
+
return;
|
|
89
|
+
} else {
|
|
90
|
+
return event.orderId;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let FromOrders = AutomationSlice$Reventless.Mapping.Make({
|
|
95
|
+
Id: Id$Reventless.$$String,
|
|
96
|
+
name: name,
|
|
97
|
+
eventSchema: eventSchema
|
|
98
|
+
})(CancelUnshipped)({
|
|
99
|
+
collect: collect,
|
|
100
|
+
resolve: resolve
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
let mappings = [FromOrders];
|
|
104
|
+
|
|
105
|
+
function process(id, item, now) {
|
|
106
|
+
if (now >= item.dueAt) {
|
|
107
|
+
return [
|
|
108
|
+
id,
|
|
109
|
+
{
|
|
110
|
+
TAG: "CancelOrder",
|
|
111
|
+
orderId: item.orderId
|
|
112
|
+
}
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function onExhausted(_id, _item) {
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let CancelUnshipped_Automation = {
|
|
122
|
+
M: M,
|
|
123
|
+
moduleUrl: moduleUrl,
|
|
124
|
+
Orders: Orders,
|
|
125
|
+
FromOrders: FromOrders,
|
|
126
|
+
mappings: mappings,
|
|
127
|
+
process: process,
|
|
128
|
+
onExhausted: onExhausted
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
let include = Automation_GWT$ReventlessGwt.FromSlice(CancelUnshipped)({
|
|
132
|
+
process: process,
|
|
133
|
+
onExhausted: onExhausted,
|
|
134
|
+
moduleUrl: moduleUrl,
|
|
135
|
+
mappings: mappings
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
let describe = include.describe;
|
|
139
|
+
|
|
140
|
+
let test = include.test;
|
|
141
|
+
|
|
142
|
+
let thenTodos = include.thenTodos;
|
|
143
|
+
|
|
144
|
+
let Mapping = include.Mapping;
|
|
145
|
+
|
|
146
|
+
let givenTodo = include.givenTodo;
|
|
147
|
+
|
|
148
|
+
let whenProcess = include.whenProcess;
|
|
149
|
+
|
|
150
|
+
let thenCommand = include.thenCommand;
|
|
151
|
+
|
|
152
|
+
let givenEvents = include.givenEvents;
|
|
153
|
+
|
|
154
|
+
let atTime = include.atTime;
|
|
155
|
+
|
|
156
|
+
let whenSweep = include.whenSweep;
|
|
157
|
+
|
|
158
|
+
let thenCommands = include.thenCommands;
|
|
159
|
+
|
|
160
|
+
let O = Mapping(FromOrders);
|
|
161
|
+
|
|
162
|
+
describe("a deadline at a fixed time", () => {
|
|
163
|
+
test("collect counts the due time from the event's recorded time", () => thenTodos(O.whenCollect(O.givenEvent({
|
|
164
|
+
TAG: "OrderPlaced",
|
|
165
|
+
orderId: "o1"
|
|
166
|
+
}), undefined, undefined, day0), [[
|
|
167
|
+
"o1",
|
|
168
|
+
{
|
|
169
|
+
orderId: "o1",
|
|
170
|
+
dueAt: day0 + 3 * day
|
|
171
|
+
}
|
|
172
|
+
]]));
|
|
173
|
+
test("not yet due on day 2: no command", () => thenCommands(whenSweep(atTime(givenEvents([O.event({
|
|
174
|
+
TAG: "OrderPlaced",
|
|
175
|
+
orderId: "o1"
|
|
176
|
+
}, undefined, undefined)]), day0), undefined, day0 + 2 * day), []));
|
|
177
|
+
test("due on day 3: CancelOrder", () => thenCommands(whenSweep(atTime(givenEvents([O.event({
|
|
178
|
+
TAG: "OrderPlaced",
|
|
179
|
+
orderId: "o1"
|
|
180
|
+
}, undefined, undefined)]), day0), undefined, day0 + 3 * day), [[
|
|
181
|
+
"o1",
|
|
182
|
+
{
|
|
183
|
+
TAG: "CancelOrder",
|
|
184
|
+
orderId: "o1"
|
|
185
|
+
}
|
|
186
|
+
]]));
|
|
187
|
+
test("shipped on day 2: nothing on day 3", () => thenCommands(whenSweep(givenEvents([
|
|
188
|
+
O.event({
|
|
189
|
+
TAG: "OrderPlaced",
|
|
190
|
+
orderId: "o1"
|
|
191
|
+
}, undefined, day0),
|
|
192
|
+
O.event({
|
|
193
|
+
TAG: "OrderShipped",
|
|
194
|
+
orderId: "o1"
|
|
195
|
+
}, undefined, day0 + 2 * day)
|
|
196
|
+
]), undefined, day0 + 3 * day), []));
|
|
197
|
+
test("a sweep given no time runs when the events happened", () => thenCommands(whenSweep(atTime(givenEvents([O.event({
|
|
198
|
+
TAG: "OrderPlaced",
|
|
199
|
+
orderId: "o1"
|
|
200
|
+
}, undefined, undefined)]), day0), undefined, undefined), []));
|
|
201
|
+
test("process is given the time a scenario fixes", () => thenCommand(whenProcess(givenTodo("o1", {
|
|
202
|
+
orderId: "o1",
|
|
203
|
+
dueAt: day0
|
|
204
|
+
}), day0), "o1", {
|
|
205
|
+
TAG: "CancelOrder",
|
|
206
|
+
orderId: "o1"
|
|
207
|
+
}));
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
let Route = include.Route;
|
|
211
|
+
|
|
212
|
+
let testContext = include.testContext;
|
|
213
|
+
|
|
214
|
+
let withRecordedAt = include.withRecordedAt;
|
|
215
|
+
|
|
216
|
+
let encTodos = include.encTodos;
|
|
217
|
+
|
|
218
|
+
let encPair = include.encPair;
|
|
219
|
+
|
|
220
|
+
let thenResolved = include.thenResolved;
|
|
221
|
+
|
|
222
|
+
let whenExhausted = include.whenExhausted;
|
|
223
|
+
|
|
224
|
+
let thenNoCommand = include.thenNoCommand;
|
|
225
|
+
|
|
226
|
+
let latestRecorded = include.latestRecorded;
|
|
227
|
+
|
|
228
|
+
let routeAll = include.routeAll;
|
|
229
|
+
|
|
230
|
+
let sweep = include.sweep;
|
|
231
|
+
|
|
232
|
+
let andThenEvents = include.andThenEvents;
|
|
233
|
+
|
|
234
|
+
let thenScenarioTodos = include.thenScenarioTodos;
|
|
235
|
+
|
|
236
|
+
export {
|
|
237
|
+
CancelUnshipped,
|
|
238
|
+
day,
|
|
239
|
+
day0,
|
|
240
|
+
CancelUnshipped_Automation,
|
|
241
|
+
Route,
|
|
242
|
+
describe,
|
|
243
|
+
test,
|
|
244
|
+
testContext,
|
|
245
|
+
withRecordedAt,
|
|
246
|
+
encTodos,
|
|
247
|
+
encPair,
|
|
248
|
+
thenTodos,
|
|
249
|
+
thenResolved,
|
|
250
|
+
Mapping,
|
|
251
|
+
givenTodo,
|
|
252
|
+
whenProcess,
|
|
253
|
+
whenExhausted,
|
|
254
|
+
thenCommand,
|
|
255
|
+
thenNoCommand,
|
|
256
|
+
givenEvents,
|
|
257
|
+
atTime,
|
|
258
|
+
latestRecorded,
|
|
259
|
+
routeAll,
|
|
260
|
+
sweep,
|
|
261
|
+
whenSweep,
|
|
262
|
+
thenCommands,
|
|
263
|
+
andThenEvents,
|
|
264
|
+
thenScenarioTodos,
|
|
265
|
+
O,
|
|
266
|
+
}
|
|
267
|
+
/* todoItemSchema Not a pure module */
|
|
@@ -79,7 +79,10 @@ module ShipPaid_Automation = {
|
|
|
79
79
|
|
|
80
80
|
let mappings: array<module(Mapping)> = [module(FromOrders), module(FromPayments)]
|
|
81
81
|
|
|
82
|
-
let process = (id, _item: ShipPaid.todoItem) => Some((
|
|
82
|
+
let process = (id, _item: ShipPaid.todoItem, ~now as _) => Some((
|
|
83
|
+
id,
|
|
84
|
+
ShipPaid.Ship({orderId: id}),
|
|
85
|
+
))
|
|
83
86
|
let onExhausted = (id, _item: ShipPaid.todoItem) => Some((id, ShipPaid.GiveUp({orderId: id})))
|
|
84
87
|
}
|
|
85
88
|
|
|
@@ -146,7 +146,7 @@ let mappings = [
|
|
|
146
146
|
FromPayments
|
|
147
147
|
];
|
|
148
148
|
|
|
149
|
-
function process(id, _item) {
|
|
149
|
+
function process(id, _item, param) {
|
|
150
150
|
return [
|
|
151
151
|
id,
|
|
152
152
|
{
|
|
@@ -221,7 +221,7 @@ describe("Automation_GWT.FromSlice", () => {
|
|
|
221
221
|
test("collect through one mapping", () => thenTodos(O.whenCollect(O.givenEvent({
|
|
222
222
|
TAG: "OrderPlaced",
|
|
223
223
|
orderId: "o1"
|
|
224
|
-
}), undefined, undefined), [[
|
|
224
|
+
}), undefined, undefined, undefined), [[
|
|
225
225
|
"o1",
|
|
226
226
|
{
|
|
227
227
|
orderId: "o1"
|
|
@@ -230,7 +230,7 @@ describe("Automation_GWT.FromSlice", () => {
|
|
|
230
230
|
test("collect keys by the source id where the event does not name it", () => thenTodos(P.whenCollect(P.givenEvent({
|
|
231
231
|
TAG: "Captured",
|
|
232
232
|
amount: 5
|
|
233
|
-
}), "o2", undefined), [[
|
|
233
|
+
}), "o2", undefined, undefined), [[
|
|
234
234
|
"o2",
|
|
235
235
|
{
|
|
236
236
|
orderId: "o2"
|
|
@@ -242,7 +242,7 @@ describe("Automation_GWT.FromSlice", () => {
|
|
|
242
242
|
})), "o1"));
|
|
243
243
|
test("process", () => thenCommand(whenProcess(givenTodo("o1", {
|
|
244
244
|
orderId: "o1"
|
|
245
|
-
})), "o1", {
|
|
245
|
+
}), undefined), "o1", {
|
|
246
246
|
TAG: "Ship",
|
|
247
247
|
orderId: "o1"
|
|
248
248
|
}));
|
|
@@ -256,12 +256,12 @@ describe("Automation_GWT.FromSlice", () => {
|
|
|
256
256
|
P.event({
|
|
257
257
|
TAG: "Captured",
|
|
258
258
|
amount: 5
|
|
259
|
-
}, "o2"),
|
|
259
|
+
}, "o2", undefined),
|
|
260
260
|
O.event({
|
|
261
261
|
TAG: "OrderPlaced",
|
|
262
262
|
orderId: "o1"
|
|
263
|
-
}, undefined)
|
|
264
|
-
]), undefined), [
|
|
263
|
+
}, undefined, undefined)
|
|
264
|
+
]), undefined, undefined), [
|
|
265
265
|
[
|
|
266
266
|
"o2",
|
|
267
267
|
{
|
|
@@ -281,20 +281,20 @@ describe("Automation_GWT.FromSlice", () => {
|
|
|
281
281
|
O.event({
|
|
282
282
|
TAG: "OrderCancelled",
|
|
283
283
|
orderId: "o1"
|
|
284
|
-
}, undefined),
|
|
284
|
+
}, undefined, undefined),
|
|
285
285
|
O.event({
|
|
286
286
|
TAG: "OrderPlaced",
|
|
287
287
|
orderId: "o1"
|
|
288
|
-
}, undefined),
|
|
288
|
+
}, undefined, undefined),
|
|
289
289
|
O.event({
|
|
290
290
|
TAG: "OrderPlaced",
|
|
291
291
|
orderId: "o3"
|
|
292
|
-
}, undefined),
|
|
292
|
+
}, undefined, undefined),
|
|
293
293
|
O.event({
|
|
294
294
|
TAG: "OrderCancelled",
|
|
295
295
|
orderId: "o3"
|
|
296
|
-
}, undefined)
|
|
297
|
-
]), undefined), [[
|
|
296
|
+
}, undefined, undefined)
|
|
297
|
+
]), undefined, undefined), [[
|
|
298
298
|
"o1",
|
|
299
299
|
{
|
|
300
300
|
orderId: "o1"
|
|
@@ -303,22 +303,28 @@ describe("Automation_GWT.FromSlice", () => {
|
|
|
303
303
|
test("andThenEvents drains what later events resolve", () => thenScenarioTodos(andThenEvents(whenSweep(givenEvents([O.event({
|
|
304
304
|
TAG: "OrderPlaced",
|
|
305
305
|
orderId: "o1"
|
|
306
|
-
}, undefined)]), undefined), [O.event({
|
|
306
|
+
}, undefined, undefined)]), undefined, undefined), [O.event({
|
|
307
307
|
TAG: "OrderCancelled",
|
|
308
308
|
orderId: "o1"
|
|
309
|
-
}, undefined)], undefined), []));
|
|
309
|
+
}, undefined, undefined)], undefined), []));
|
|
310
310
|
});
|
|
311
311
|
|
|
312
312
|
let Route = include.Route;
|
|
313
313
|
|
|
314
314
|
let testContext = include.testContext;
|
|
315
315
|
|
|
316
|
+
let withRecordedAt = include.withRecordedAt;
|
|
317
|
+
|
|
316
318
|
let encTodos = include.encTodos;
|
|
317
319
|
|
|
318
320
|
let encPair = include.encPair;
|
|
319
321
|
|
|
320
322
|
let thenNoCommand = include.thenNoCommand;
|
|
321
323
|
|
|
324
|
+
let atTime = include.atTime;
|
|
325
|
+
|
|
326
|
+
let latestRecorded = include.latestRecorded;
|
|
327
|
+
|
|
322
328
|
let routeAll = include.routeAll;
|
|
323
329
|
|
|
324
330
|
let sweep = include.sweep;
|
|
@@ -330,6 +336,7 @@ export {
|
|
|
330
336
|
describe,
|
|
331
337
|
test,
|
|
332
338
|
testContext,
|
|
339
|
+
withRecordedAt,
|
|
333
340
|
encTodos,
|
|
334
341
|
encPair,
|
|
335
342
|
thenTodos,
|
|
@@ -341,6 +348,8 @@ export {
|
|
|
341
348
|
thenCommand,
|
|
342
349
|
thenNoCommand,
|
|
343
350
|
givenEvents,
|
|
351
|
+
atTime,
|
|
352
|
+
latestRecorded,
|
|
344
353
|
routeAll,
|
|
345
354
|
sweep,
|
|
346
355
|
whenSweep,
|
|
@@ -49,7 +49,7 @@ function resolve(event) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function process(_id, item) {
|
|
52
|
+
function process(_id, item, param) {
|
|
53
53
|
return [
|
|
54
54
|
item.orderId,
|
|
55
55
|
{
|
|
@@ -139,7 +139,7 @@ describe("ShipOrder AutomationSlice", () => {
|
|
|
139
139
|
test("process: pending TODO emits CreateShipment", () => thenCommand(whenProcess(givenTodo("o1", {
|
|
140
140
|
orderId: "o1",
|
|
141
141
|
shippingAddress: "1 Main St"
|
|
142
|
-
})), "o1", {
|
|
142
|
+
}), undefined), "o1", {
|
|
143
143
|
TAG: "CreateShipment",
|
|
144
144
|
orderId: "o1",
|
|
145
145
|
address: "1 Main St"
|
|
@@ -149,7 +149,7 @@ describe("ShipOrder AutomationSlice", () => {
|
|
|
149
149
|
TAG: "OrderPlaced",
|
|
150
150
|
orderId: "o1",
|
|
151
151
|
shippingAddress: "1 Main St"
|
|
152
|
-
}]));
|
|
152
|
+
}]), undefined);
|
|
153
153
|
let commandsOk = thenCommands(s, [[
|
|
154
154
|
"o1",
|
|
155
155
|
{
|
package/tests/FlowGwtTest.res
CHANGED