@reventlessdev/online-shop-hybrid-ordering 1.0.0-alpha.262 → 1.0.0-alpha.263
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 +7 -0
- package/package.json +9 -9
- package/src/Order/StateChange/CancelOrder.res +4 -1
- package/src/Order/StateChange/CancelOrder.res.mjs +4 -1
- package/tests/CatalogProduct/StateChange/SyncCatalogProduct_GWT.res.mjs +9 -0
- package/tests/Customer/Aggregate/Customer_GWT.res.mjs +9 -0
- package/tests/Customer/StateChange/EmailVerificationChallenges_GWT.res.mjs +9 -0
- package/tests/Notification/StateChange/NotificationPreferences_GWT.res.mjs +9 -0
- package/tests/Notification/StateChange/NotificationSourceClaims_GWT.res.mjs +9 -0
- package/tests/Order/StateChange/CancelOrder_GWT.res +41 -6
- package/tests/Order/StateChange/CancelOrder_GWT.res.mjs +61 -6
- package/tests/Order/StateChange/PlaceOrder_GWT.res.mjs +9 -0
- package/tests/Order/StateChange/ShipOrder_GWT.res.mjs +9 -0
- package/tests/Ordering_Examples.res +1 -0
- package/tests/Ordering_Examples.res.mjs +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
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.263 (2026-10-01)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **spec, core, gwt, ppx:** a command acts only on what its caller owns ([2636f0e](https://github.com/ReventlessDev/reventless-core/commit/2636f0ee44797ee2c0b0c40a7107cf306113173f))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 1.0.0-alpha.262 (2026-09-29)
|
|
7
14
|
|
|
8
15
|
**Note:** Version bump only for package @reventlessdev/online-shop-hybrid-ordering
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/online-shop-hybrid-ordering",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.263",
|
|
4
4
|
"description": "Online Shop Hybrid example — Ordering plugin (Customer aggregate + Order/CatalogProduct DCB)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"sury": "11.0.0",
|
|
24
|
-
"@reventlessdev/online-shop-hybrid-
|
|
25
|
-
"@reventlessdev/
|
|
24
|
+
"@reventlessdev/online-shop-hybrid-catalog-spec": "1.0.0-alpha.152",
|
|
25
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.232",
|
|
26
26
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.22",
|
|
27
|
-
"@reventlessdev/
|
|
28
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.
|
|
29
|
-
"@reventlessdev/reventless-local": "3.0.0-alpha.
|
|
30
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
31
|
-
"@reventlessdev/trait-address-geocoding": "1.0.0-alpha.
|
|
32
|
-
"@reventlessdev/trait-notification": "1.0.0-alpha.
|
|
27
|
+
"@reventlessdev/online-shop-hybrid-ordering-spec": "1.0.0-alpha.151",
|
|
28
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.183",
|
|
29
|
+
"@reventlessdev/reventless-local": "3.0.0-alpha.275",
|
|
30
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.155",
|
|
31
|
+
"@reventlessdev/trait-address-geocoding": "1.0.0-alpha.34",
|
|
32
|
+
"@reventlessdev/trait-notification": "1.0.0-alpha.34"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"rescript": "12.3.1",
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
@@reventless.spec
|
|
5
5
|
|
|
6
|
+
// `@owner` on the consumed `customerId` says whose order this is. The framework
|
|
7
|
+
// reads it before `decide` and refuses a shopper cancelling someone else's order;
|
|
8
|
+
// an operator, or an automation, still acts on anyone's.
|
|
6
9
|
@schema
|
|
7
10
|
type consumedEvent =
|
|
8
|
-
| OrderPlaced({productIds: array<CatalogSpec.ProductId.t
|
|
11
|
+
| OrderPlaced({productIds: array<CatalogSpec.ProductId.t>, @owner customerId: CustomerId.t})
|
|
9
12
|
| OrderShipped
|
|
10
13
|
| OrderCancelled
|
|
11
14
|
| OrderReopened
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Sury from "sury";
|
|
4
|
+
import * as Owner$Reventless from "@reventlessdev/reventless-spec/src/components/Owner.res.mjs";
|
|
4
5
|
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
5
6
|
import * as Api$ReventlessInfra from "@reventlessdev/reventless-infra/src/components/Api.res.mjs";
|
|
6
7
|
import * as ProductId$CatalogSpec from "@reventlessdev/online-shop-hybrid-catalog-spec/src/ProductId.res.mjs";
|
|
7
8
|
import * as OrderId$OrderingPlugin from "../OrderId.res.mjs";
|
|
9
|
+
import * as CustomerId$OrderingPlugin from "../../Customer/CustomerId.res.mjs";
|
|
8
10
|
|
|
9
11
|
let consumedEventSchema = Sury.union([
|
|
10
12
|
Sury.$schema(s => ({
|
|
11
13
|
TAG: "OrderPlaced",
|
|
12
|
-
productIds: s.m(Sury.array(DcbTag$Reventless.mark(ProductId$CatalogSpec.schema)))
|
|
14
|
+
productIds: s.m(Sury.array(DcbTag$Reventless.mark(ProductId$CatalogSpec.schema))),
|
|
15
|
+
customerId: s.m(Owner$Reventless.mark(DcbTag$Reventless.mark(CustomerId$OrderingPlugin.schema)))
|
|
13
16
|
})),
|
|
14
17
|
Sury.literal("OrderShipped"),
|
|
15
18
|
Sury.literal("OrderCancelled"),
|
|
@@ -149,6 +149,10 @@ let Spec = include.Spec;
|
|
|
149
149
|
|
|
150
150
|
let todo = include.todo;
|
|
151
151
|
|
|
152
|
+
let Caller = include.Caller;
|
|
153
|
+
|
|
154
|
+
let asCaller = include.asCaller;
|
|
155
|
+
|
|
152
156
|
let thenEvents = include.thenEvents;
|
|
153
157
|
|
|
154
158
|
let thenEventWithError = include.thenEventWithError;
|
|
@@ -157,6 +161,8 @@ let thenEventsWithError = include.thenEventsWithError;
|
|
|
157
161
|
|
|
158
162
|
let thenError = include.thenError;
|
|
159
163
|
|
|
164
|
+
let thenRefused = include.thenRefused;
|
|
165
|
+
|
|
160
166
|
let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
|
|
161
167
|
|
|
162
168
|
let thenAppendsConditionedOnExactly = include.thenAppendsConditionedOnExactly;
|
|
@@ -167,6 +173,8 @@ export {
|
|
|
167
173
|
todo,
|
|
168
174
|
test,
|
|
169
175
|
givenEvents,
|
|
176
|
+
Caller,
|
|
177
|
+
asCaller,
|
|
170
178
|
whenCmd,
|
|
171
179
|
thenEvent,
|
|
172
180
|
thenEvents,
|
|
@@ -174,6 +182,7 @@ export {
|
|
|
174
182
|
thenEventWithError,
|
|
175
183
|
thenEventsWithError,
|
|
176
184
|
thenError,
|
|
185
|
+
thenRefused,
|
|
177
186
|
thenAppendsConditionedOn,
|
|
178
187
|
thenAppendsConditionedOnExactly,
|
|
179
188
|
}
|
|
@@ -278,6 +278,10 @@ describe("Customer email verification", () => {
|
|
|
278
278
|
|
|
279
279
|
let Spec = include.Spec;
|
|
280
280
|
|
|
281
|
+
let Caller = include.Caller;
|
|
282
|
+
|
|
283
|
+
let asCaller = include.asCaller;
|
|
284
|
+
|
|
281
285
|
let thenCompareEvent = include.thenCompareEvent;
|
|
282
286
|
|
|
283
287
|
let thenEventWithError = include.thenEventWithError;
|
|
@@ -288,11 +292,15 @@ let thenCompareEvents = include.thenCompareEvents;
|
|
|
288
292
|
|
|
289
293
|
let thenEventsWithError = include.thenEventsWithError;
|
|
290
294
|
|
|
295
|
+
let thenRefused = include.thenRefused;
|
|
296
|
+
|
|
291
297
|
export {
|
|
292
298
|
Spec,
|
|
293
299
|
describe,
|
|
294
300
|
test,
|
|
295
301
|
givenEvents,
|
|
302
|
+
Caller,
|
|
303
|
+
asCaller,
|
|
296
304
|
whenCmd,
|
|
297
305
|
thenEvent,
|
|
298
306
|
thenCompareEvent,
|
|
@@ -302,5 +310,6 @@ export {
|
|
|
302
310
|
thenCompareEvents,
|
|
303
311
|
thenEventsWithError,
|
|
304
312
|
thenError,
|
|
313
|
+
thenRefused,
|
|
305
314
|
}
|
|
306
315
|
/* include Not a pure module */
|
|
@@ -161,12 +161,18 @@ let Spec = include.Spec;
|
|
|
161
161
|
|
|
162
162
|
let todo = include.todo;
|
|
163
163
|
|
|
164
|
+
let Caller = include.Caller;
|
|
165
|
+
|
|
166
|
+
let asCaller = include.asCaller;
|
|
167
|
+
|
|
164
168
|
let thenEvents = include.thenEvents;
|
|
165
169
|
|
|
166
170
|
let thenEventWithError = include.thenEventWithError;
|
|
167
171
|
|
|
168
172
|
let thenEventsWithError = include.thenEventsWithError;
|
|
169
173
|
|
|
174
|
+
let thenRefused = include.thenRefused;
|
|
175
|
+
|
|
170
176
|
let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
|
|
171
177
|
|
|
172
178
|
let thenAppendsConditionedOnExactly = include.thenAppendsConditionedOnExactly;
|
|
@@ -177,6 +183,8 @@ export {
|
|
|
177
183
|
todo,
|
|
178
184
|
test,
|
|
179
185
|
givenEvents,
|
|
186
|
+
Caller,
|
|
187
|
+
asCaller,
|
|
180
188
|
whenCmd,
|
|
181
189
|
thenEvent,
|
|
182
190
|
thenEvents,
|
|
@@ -184,6 +192,7 @@ export {
|
|
|
184
192
|
thenEventWithError,
|
|
185
193
|
thenEventsWithError,
|
|
186
194
|
thenError,
|
|
195
|
+
thenRefused,
|
|
187
196
|
thenAppendsConditionedOn,
|
|
188
197
|
thenAppendsConditionedOnExactly,
|
|
189
198
|
issuedAt,
|
|
@@ -105,6 +105,10 @@ let Spec = include.Spec;
|
|
|
105
105
|
|
|
106
106
|
let todo = include.todo;
|
|
107
107
|
|
|
108
|
+
let Caller = include.Caller;
|
|
109
|
+
|
|
110
|
+
let asCaller = include.asCaller;
|
|
111
|
+
|
|
108
112
|
let thenEvents = include.thenEvents;
|
|
109
113
|
|
|
110
114
|
let thenNoEvent = include.thenNoEvent;
|
|
@@ -115,6 +119,8 @@ let thenEventsWithError = include.thenEventsWithError;
|
|
|
115
119
|
|
|
116
120
|
let thenError = include.thenError;
|
|
117
121
|
|
|
122
|
+
let thenRefused = include.thenRefused;
|
|
123
|
+
|
|
118
124
|
let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
|
|
119
125
|
|
|
120
126
|
let thenAppendsConditionedOnExactly = include.thenAppendsConditionedOnExactly;
|
|
@@ -125,6 +131,8 @@ export {
|
|
|
125
131
|
todo,
|
|
126
132
|
test,
|
|
127
133
|
givenEvents,
|
|
134
|
+
Caller,
|
|
135
|
+
asCaller,
|
|
128
136
|
whenCmd,
|
|
129
137
|
thenEvent,
|
|
130
138
|
thenEvents,
|
|
@@ -132,6 +140,7 @@ export {
|
|
|
132
140
|
thenEventWithError,
|
|
133
141
|
thenEventsWithError,
|
|
134
142
|
thenError,
|
|
143
|
+
thenRefused,
|
|
135
144
|
thenAppendsConditionedOn,
|
|
136
145
|
thenAppendsConditionedOnExactly,
|
|
137
146
|
}
|
|
@@ -76,6 +76,10 @@ let Spec = include.Spec;
|
|
|
76
76
|
|
|
77
77
|
let todo = include.todo;
|
|
78
78
|
|
|
79
|
+
let Caller = include.Caller;
|
|
80
|
+
|
|
81
|
+
let asCaller = include.asCaller;
|
|
82
|
+
|
|
79
83
|
let thenEvents = include.thenEvents;
|
|
80
84
|
|
|
81
85
|
let thenEventWithError = include.thenEventWithError;
|
|
@@ -84,6 +88,8 @@ let thenEventsWithError = include.thenEventsWithError;
|
|
|
84
88
|
|
|
85
89
|
let thenError = include.thenError;
|
|
86
90
|
|
|
91
|
+
let thenRefused = include.thenRefused;
|
|
92
|
+
|
|
87
93
|
let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
|
|
88
94
|
|
|
89
95
|
let thenAppendsConditionedOnExactly = include.thenAppendsConditionedOnExactly;
|
|
@@ -94,6 +100,8 @@ export {
|
|
|
94
100
|
todo,
|
|
95
101
|
test,
|
|
96
102
|
givenEvents,
|
|
103
|
+
Caller,
|
|
104
|
+
asCaller,
|
|
97
105
|
whenCmd,
|
|
98
106
|
thenEvent,
|
|
99
107
|
thenEvents,
|
|
@@ -101,6 +109,7 @@ export {
|
|
|
101
109
|
thenEventWithError,
|
|
102
110
|
thenEventsWithError,
|
|
103
111
|
thenError,
|
|
112
|
+
thenRefused,
|
|
104
113
|
thenAppendsConditionedOn,
|
|
105
114
|
thenAppendsConditionedOnExactly,
|
|
106
115
|
}
|
|
@@ -12,35 +12,35 @@ describe("CancelOrder StateChangeSlice", () => {
|
|
|
12
12
|
|
|
13
13
|
// scenario-id: 44e2fd1b-d3da-4581-b102-0ccb47e004eb
|
|
14
14
|
test("placed order produces OrderCancelled with productIds carried over", () =>
|
|
15
|
-
givenEvents([OrderPlaced({productIds: [p1, p2]})])
|
|
15
|
+
givenEvents([OrderPlaced({productIds: [p1, p2], customerId: c1})])
|
|
16
16
|
->whenCmd(CancelOrder({orderId: o1}))
|
|
17
17
|
->thenEvent(OrderCancelled({orderId: o1, productIds: [p1, p2]}))
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
// scenario-id: 36e7a929-73a3-48ad-96bf-98265877dd9f
|
|
21
21
|
test("already cancelled order produces no events for CancelOrder (idempotent)", () =>
|
|
22
|
-
givenEvents([OrderPlaced({productIds: [p1]}), OrderCancelled])
|
|
22
|
+
givenEvents([OrderPlaced({productIds: [p1], customerId: c1}), OrderCancelled])
|
|
23
23
|
->whenCmd(CancelOrder({orderId: o1}))
|
|
24
24
|
->thenNoEvent
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
// scenario-id: a8adfaaf-2a97-487d-b80b-34b2e79a621d
|
|
28
28
|
test("shipped order returns OrderAlreadyShipped for CancelOrder", () =>
|
|
29
|
-
givenEvents([OrderPlaced({productIds: [p1]}), OrderShipped])
|
|
29
|
+
givenEvents([OrderPlaced({productIds: [p1], customerId: c1}), OrderShipped])
|
|
30
30
|
->whenCmd(CancelOrder({orderId: o1}))
|
|
31
31
|
->thenError(OrderAlreadyShipped)
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
// scenario-id: da5c2d1d-adaf-412c-ba71-293158b29841
|
|
35
35
|
test("ReopenOrder on cancelled order produces OrderReopened", () =>
|
|
36
|
-
givenEvents([OrderPlaced({productIds: [p1]}), OrderCancelled])
|
|
36
|
+
givenEvents([OrderPlaced({productIds: [p1], customerId: c1}), OrderCancelled])
|
|
37
37
|
->whenCmd(ReopenOrder({orderId: o1}))
|
|
38
38
|
->thenEvent(OrderReopened({orderId: o1}))
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
// scenario-id: edebec12-a5ec-4f71-a1fc-f1db65108db9
|
|
42
42
|
test("ReopenOrder on a placed order produces no events (idempotent)", () =>
|
|
43
|
-
givenEvents([OrderPlaced({productIds: [p1]})])
|
|
43
|
+
givenEvents([OrderPlaced({productIds: [p1], customerId: c1})])
|
|
44
44
|
->whenCmd(ReopenOrder({orderId: o1}))
|
|
45
45
|
->thenNoEvent
|
|
46
46
|
)
|
|
@@ -50,7 +50,7 @@ describe("CancelOrder StateChangeSlice", () => {
|
|
|
50
50
|
// shipped — the case no test covered, which is how it survived.
|
|
51
51
|
// scenario-id: 96bdee04-7518-4297-81f5-7242a0cffa9f
|
|
52
52
|
test("ReopenOrder on a shipped order returns OrderAlreadyShipped", () =>
|
|
53
|
-
givenEvents([OrderPlaced({productIds: [p1]}), OrderShipped])
|
|
53
|
+
givenEvents([OrderPlaced({productIds: [p1], customerId: c1}), OrderShipped])
|
|
54
54
|
->whenCmd(ReopenOrder({orderId: o1}))
|
|
55
55
|
->thenError(OrderAlreadyShipped)
|
|
56
56
|
)
|
|
@@ -61,4 +61,39 @@ describe("CancelOrder StateChangeSlice", () => {
|
|
|
61
61
|
->whenCmd(ReopenOrder({orderId: o1}))
|
|
62
62
|
->thenError(OrderNotFound)
|
|
63
63
|
)
|
|
64
|
+
|
|
65
|
+
// Only the customer who placed an order may cancel it. The slice says so by
|
|
66
|
+
// marking `@owner` on the `customerId` its `OrderPlaced` carries; an operator
|
|
67
|
+
// still cancels on anyone's behalf.
|
|
68
|
+
// scenario-id: 80c0ad21-a352-43d6-862c-74c5250991d1
|
|
69
|
+
test("the customer who placed the order may cancel it", () =>
|
|
70
|
+
givenEvents([OrderPlaced({productIds: [p1], customerId: c1})])
|
|
71
|
+
->asCaller(Caller.owner(c1))
|
|
72
|
+
->whenCmd(CancelOrder({orderId: o1}))
|
|
73
|
+
->thenEvent(OrderCancelled({orderId: o1, productIds: [p1]}))
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
// scenario-id: 48fc5fa8-449d-40df-b58d-e87f0acea478
|
|
77
|
+
test("another customer cannot cancel the order", () =>
|
|
78
|
+
givenEvents([OrderPlaced({productIds: [p1], customerId: c1})])
|
|
79
|
+
->asCaller(Caller.owner(c2))
|
|
80
|
+
->whenCmd(CancelOrder({orderId: o1}))
|
|
81
|
+
->thenRefused
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
// scenario-id: e7561ebf-5566-4a4e-87c0-9aa403d68310
|
|
85
|
+
test("an operator may cancel any customer's order", () =>
|
|
86
|
+
givenEvents([OrderPlaced({productIds: [p1], customerId: c1})])
|
|
87
|
+
->asCaller(Caller.operator)
|
|
88
|
+
->whenCmd(CancelOrder({orderId: o1}))
|
|
89
|
+
->thenEvent(OrderCancelled({orderId: o1, productIds: [p1]}))
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
// scenario-id: e0df85d2-f997-4833-816e-39ddce5c895e
|
|
93
|
+
test("a caller who is not signed in cannot cancel an order", () =>
|
|
94
|
+
givenEvents([OrderPlaced({productIds: [p1], customerId: c1})])
|
|
95
|
+
->asCaller(Caller.anonymous)
|
|
96
|
+
->whenCmd(CancelOrder({orderId: o1}))
|
|
97
|
+
->thenRefused
|
|
98
|
+
)
|
|
64
99
|
})
|
|
@@ -23,6 +23,10 @@ let test = include.test;
|
|
|
23
23
|
|
|
24
24
|
let givenEvents = include.givenEvents;
|
|
25
25
|
|
|
26
|
+
let Caller = include.Caller;
|
|
27
|
+
|
|
28
|
+
let asCaller = include.asCaller;
|
|
29
|
+
|
|
26
30
|
let whenCmd = include.whenCmd;
|
|
27
31
|
|
|
28
32
|
let thenEvent = include.thenEvent;
|
|
@@ -31,6 +35,8 @@ let thenNoEvent = include.thenNoEvent;
|
|
|
31
35
|
|
|
32
36
|
let thenError = include.thenError;
|
|
33
37
|
|
|
38
|
+
let thenRefused = include.thenRefused;
|
|
39
|
+
|
|
34
40
|
describe("CancelOrder StateChangeSlice", () => {
|
|
35
41
|
test("non-existent order returns OrderNotFound for CancelOrder", () => thenError(whenCmd(givenEvents([]), {
|
|
36
42
|
TAG: "CancelOrder",
|
|
@@ -41,7 +47,8 @@ describe("CancelOrder StateChangeSlice", () => {
|
|
|
41
47
|
productIds: [
|
|
42
48
|
Ordering_Examples$OrderingPlugin.p1,
|
|
43
49
|
Ordering_Examples$OrderingPlugin.p2
|
|
44
|
-
]
|
|
50
|
+
],
|
|
51
|
+
customerId: Ordering_Examples$OrderingPlugin.c1
|
|
45
52
|
}]), {
|
|
46
53
|
TAG: "CancelOrder",
|
|
47
54
|
orderId: Ordering_Examples$OrderingPlugin.o1
|
|
@@ -56,7 +63,8 @@ describe("CancelOrder StateChangeSlice", () => {
|
|
|
56
63
|
test("already cancelled order produces no events for CancelOrder (idempotent)", () => thenNoEvent(whenCmd(givenEvents([
|
|
57
64
|
{
|
|
58
65
|
TAG: "OrderPlaced",
|
|
59
|
-
productIds: [Ordering_Examples$OrderingPlugin.p1]
|
|
66
|
+
productIds: [Ordering_Examples$OrderingPlugin.p1],
|
|
67
|
+
customerId: Ordering_Examples$OrderingPlugin.c1
|
|
60
68
|
},
|
|
61
69
|
"OrderCancelled"
|
|
62
70
|
]), {
|
|
@@ -66,7 +74,8 @@ describe("CancelOrder StateChangeSlice", () => {
|
|
|
66
74
|
test("shipped order returns OrderAlreadyShipped for CancelOrder", () => thenError(whenCmd(givenEvents([
|
|
67
75
|
{
|
|
68
76
|
TAG: "OrderPlaced",
|
|
69
|
-
productIds: [Ordering_Examples$OrderingPlugin.p1]
|
|
77
|
+
productIds: [Ordering_Examples$OrderingPlugin.p1],
|
|
78
|
+
customerId: Ordering_Examples$OrderingPlugin.c1
|
|
70
79
|
},
|
|
71
80
|
"OrderShipped"
|
|
72
81
|
]), {
|
|
@@ -76,7 +85,8 @@ describe("CancelOrder StateChangeSlice", () => {
|
|
|
76
85
|
test("ReopenOrder on cancelled order produces OrderReopened", () => thenEvent(whenCmd(givenEvents([
|
|
77
86
|
{
|
|
78
87
|
TAG: "OrderPlaced",
|
|
79
|
-
productIds: [Ordering_Examples$OrderingPlugin.p1]
|
|
88
|
+
productIds: [Ordering_Examples$OrderingPlugin.p1],
|
|
89
|
+
customerId: Ordering_Examples$OrderingPlugin.c1
|
|
80
90
|
},
|
|
81
91
|
"OrderCancelled"
|
|
82
92
|
]), {
|
|
@@ -88,7 +98,8 @@ describe("CancelOrder StateChangeSlice", () => {
|
|
|
88
98
|
}));
|
|
89
99
|
test("ReopenOrder on a placed order produces no events (idempotent)", () => thenNoEvent(whenCmd(givenEvents([{
|
|
90
100
|
TAG: "OrderPlaced",
|
|
91
|
-
productIds: [Ordering_Examples$OrderingPlugin.p1]
|
|
101
|
+
productIds: [Ordering_Examples$OrderingPlugin.p1],
|
|
102
|
+
customerId: Ordering_Examples$OrderingPlugin.c1
|
|
92
103
|
}]), {
|
|
93
104
|
TAG: "ReopenOrder",
|
|
94
105
|
orderId: Ordering_Examples$OrderingPlugin.o1
|
|
@@ -96,7 +107,8 @@ describe("CancelOrder StateChangeSlice", () => {
|
|
|
96
107
|
test("ReopenOrder on a shipped order returns OrderAlreadyShipped", () => thenError(whenCmd(givenEvents([
|
|
97
108
|
{
|
|
98
109
|
TAG: "OrderPlaced",
|
|
99
|
-
productIds: [Ordering_Examples$OrderingPlugin.p1]
|
|
110
|
+
productIds: [Ordering_Examples$OrderingPlugin.p1],
|
|
111
|
+
customerId: Ordering_Examples$OrderingPlugin.c1
|
|
100
112
|
},
|
|
101
113
|
"OrderShipped"
|
|
102
114
|
]), {
|
|
@@ -107,6 +119,46 @@ describe("CancelOrder StateChangeSlice", () => {
|
|
|
107
119
|
TAG: "ReopenOrder",
|
|
108
120
|
orderId: Ordering_Examples$OrderingPlugin.o1
|
|
109
121
|
}), "OrderNotFound"));
|
|
122
|
+
test("the customer who placed the order may cancel it", () => thenEvent(whenCmd(asCaller(givenEvents([{
|
|
123
|
+
TAG: "OrderPlaced",
|
|
124
|
+
productIds: [Ordering_Examples$OrderingPlugin.p1],
|
|
125
|
+
customerId: Ordering_Examples$OrderingPlugin.c1
|
|
126
|
+
}]), Caller.owner(Ordering_Examples$OrderingPlugin.c1)), {
|
|
127
|
+
TAG: "CancelOrder",
|
|
128
|
+
orderId: Ordering_Examples$OrderingPlugin.o1
|
|
129
|
+
}), {
|
|
130
|
+
TAG: "OrderCancelled",
|
|
131
|
+
orderId: Ordering_Examples$OrderingPlugin.o1,
|
|
132
|
+
productIds: [Ordering_Examples$OrderingPlugin.p1]
|
|
133
|
+
}));
|
|
134
|
+
test("another customer cannot cancel the order", () => thenRefused(whenCmd(asCaller(givenEvents([{
|
|
135
|
+
TAG: "OrderPlaced",
|
|
136
|
+
productIds: [Ordering_Examples$OrderingPlugin.p1],
|
|
137
|
+
customerId: Ordering_Examples$OrderingPlugin.c1
|
|
138
|
+
}]), Caller.owner(Ordering_Examples$OrderingPlugin.c2)), {
|
|
139
|
+
TAG: "CancelOrder",
|
|
140
|
+
orderId: Ordering_Examples$OrderingPlugin.o1
|
|
141
|
+
})));
|
|
142
|
+
test("an operator may cancel any customer's order", () => thenEvent(whenCmd(asCaller(givenEvents([{
|
|
143
|
+
TAG: "OrderPlaced",
|
|
144
|
+
productIds: [Ordering_Examples$OrderingPlugin.p1],
|
|
145
|
+
customerId: Ordering_Examples$OrderingPlugin.c1
|
|
146
|
+
}]), Caller.operator), {
|
|
147
|
+
TAG: "CancelOrder",
|
|
148
|
+
orderId: Ordering_Examples$OrderingPlugin.o1
|
|
149
|
+
}), {
|
|
150
|
+
TAG: "OrderCancelled",
|
|
151
|
+
orderId: Ordering_Examples$OrderingPlugin.o1,
|
|
152
|
+
productIds: [Ordering_Examples$OrderingPlugin.p1]
|
|
153
|
+
}));
|
|
154
|
+
test("a caller who is not signed in cannot cancel an order", () => thenRefused(whenCmd(asCaller(givenEvents([{
|
|
155
|
+
TAG: "OrderPlaced",
|
|
156
|
+
productIds: [Ordering_Examples$OrderingPlugin.p1],
|
|
157
|
+
customerId: Ordering_Examples$OrderingPlugin.c1
|
|
158
|
+
}]), Caller.anonymous), {
|
|
159
|
+
TAG: "CancelOrder",
|
|
160
|
+
orderId: Ordering_Examples$OrderingPlugin.o1
|
|
161
|
+
})));
|
|
110
162
|
});
|
|
111
163
|
|
|
112
164
|
let Spec = include.Spec;
|
|
@@ -129,6 +181,8 @@ export {
|
|
|
129
181
|
todo,
|
|
130
182
|
test,
|
|
131
183
|
givenEvents,
|
|
184
|
+
Caller,
|
|
185
|
+
asCaller,
|
|
132
186
|
whenCmd,
|
|
133
187
|
thenEvent,
|
|
134
188
|
thenEvents,
|
|
@@ -136,6 +190,7 @@ export {
|
|
|
136
190
|
thenEventWithError,
|
|
137
191
|
thenEventsWithError,
|
|
138
192
|
thenError,
|
|
193
|
+
thenRefused,
|
|
139
194
|
thenAppendsConditionedOn,
|
|
140
195
|
thenAppendsConditionedOnExactly,
|
|
141
196
|
}
|
|
@@ -814,6 +814,10 @@ let Spec = include.Spec;
|
|
|
814
814
|
|
|
815
815
|
let todo = include.todo;
|
|
816
816
|
|
|
817
|
+
let Caller = include.Caller;
|
|
818
|
+
|
|
819
|
+
let asCaller = include.asCaller;
|
|
820
|
+
|
|
817
821
|
let thenEvents = include.thenEvents;
|
|
818
822
|
|
|
819
823
|
let thenNoEvent = include.thenNoEvent;
|
|
@@ -822,6 +826,8 @@ let thenEventWithError = include.thenEventWithError;
|
|
|
822
826
|
|
|
823
827
|
let thenEventsWithError = include.thenEventsWithError;
|
|
824
828
|
|
|
829
|
+
let thenRefused = include.thenRefused;
|
|
830
|
+
|
|
825
831
|
let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
|
|
826
832
|
|
|
827
833
|
let thenAppendsConditionedOnExactly = include.thenAppendsConditionedOnExactly;
|
|
@@ -832,6 +838,8 @@ export {
|
|
|
832
838
|
todo,
|
|
833
839
|
test,
|
|
834
840
|
givenEvents,
|
|
841
|
+
Caller,
|
|
842
|
+
asCaller,
|
|
835
843
|
whenCmd,
|
|
836
844
|
thenEvent,
|
|
837
845
|
thenEvents,
|
|
@@ -839,6 +847,7 @@ export {
|
|
|
839
847
|
thenEventWithError,
|
|
840
848
|
thenEventsWithError,
|
|
841
849
|
thenError,
|
|
850
|
+
thenRefused,
|
|
842
851
|
thenAppendsConditionedOn,
|
|
843
852
|
thenAppendsConditionedOnExactly,
|
|
844
853
|
}
|
|
@@ -92,12 +92,18 @@ let Spec = include.Spec;
|
|
|
92
92
|
|
|
93
93
|
let todo = include.todo;
|
|
94
94
|
|
|
95
|
+
let Caller = include.Caller;
|
|
96
|
+
|
|
97
|
+
let asCaller = include.asCaller;
|
|
98
|
+
|
|
95
99
|
let thenEvents = include.thenEvents;
|
|
96
100
|
|
|
97
101
|
let thenEventWithError = include.thenEventWithError;
|
|
98
102
|
|
|
99
103
|
let thenEventsWithError = include.thenEventsWithError;
|
|
100
104
|
|
|
105
|
+
let thenRefused = include.thenRefused;
|
|
106
|
+
|
|
101
107
|
let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
|
|
102
108
|
|
|
103
109
|
let thenAppendsConditionedOnExactly = include.thenAppendsConditionedOnExactly;
|
|
@@ -108,6 +114,8 @@ export {
|
|
|
108
114
|
todo,
|
|
109
115
|
test,
|
|
110
116
|
givenEvents,
|
|
117
|
+
Caller,
|
|
118
|
+
asCaller,
|
|
111
119
|
whenCmd,
|
|
112
120
|
thenEvent,
|
|
113
121
|
thenEvents,
|
|
@@ -115,6 +123,7 @@ export {
|
|
|
115
123
|
thenEventWithError,
|
|
116
124
|
thenEventsWithError,
|
|
117
125
|
thenError,
|
|
126
|
+
thenRefused,
|
|
118
127
|
thenAppendsConditionedOn,
|
|
119
128
|
thenAppendsConditionedOnExactly,
|
|
120
129
|
}
|
|
@@ -4,6 +4,7 @@ let p1: CatalogSpec.ProductId.t = CatalogSpec.ProductId.makeFromString("p1")
|
|
|
4
4
|
let p2: CatalogSpec.ProductId.t = CatalogSpec.ProductId.makeFromString("p2")
|
|
5
5
|
|
|
6
6
|
let c1: CustomerId.t = CustomerId.makeFromString("c1")
|
|
7
|
+
let c2: CustomerId.t = CustomerId.makeFromString("c2")
|
|
7
8
|
let id: CustomerId.t = CustomerId.makeFromString("id")
|
|
8
9
|
|
|
9
10
|
let o1: OrderId.t = OrderId.makeFromString("o1")
|
|
@@ -11,6 +11,8 @@ let p2 = ProductId$CatalogSpec.makeFromString("p2");
|
|
|
11
11
|
|
|
12
12
|
let c1 = CustomerId$OrderingPlugin.makeFromString("c1");
|
|
13
13
|
|
|
14
|
+
let c2 = CustomerId$OrderingPlugin.makeFromString("c2");
|
|
15
|
+
|
|
14
16
|
let id = CustomerId$OrderingPlugin.makeFromString("id");
|
|
15
17
|
|
|
16
18
|
let o1 = OrderId$OrderingPlugin.makeFromString("o1");
|
|
@@ -71,6 +73,7 @@ export {
|
|
|
71
73
|
p1,
|
|
72
74
|
p2,
|
|
73
75
|
c1,
|
|
76
|
+
c2,
|
|
74
77
|
id,
|
|
75
78
|
o1,
|
|
76
79
|
o2,
|