1
1
using System . Collections . Immutable ;
2
2
using EventStore . Client ;
3
+ using Kurrent . Client . Streams . DecisionMaking ;
3
4
using Kurrent . Client . Streams . GettingState ;
4
5
5
6
namespace Kurrent . Client . Tests . Streams . DecisionMaking . UnionTypes ;
6
7
7
8
using static ShoppingCart ;
8
9
using static ShoppingCart . Event ;
10
+ using static ShoppingCart . Command ;
9
11
10
12
[ Trait ( "Category" , "Target:Streams" ) ]
11
- [ Trait ( "Category" , "Operation:GetState " ) ]
13
+ [ Trait ( "Category" , "Operation:Decide " ) ]
12
14
public class GettingStateTests ( ITestOutputHelper output , KurrentPermanentFixture fixture )
13
15
: KurrentPermanentTests < KurrentPermanentFixture > ( output , fixture ) {
14
16
[ RetryFact ]
15
- public async Task gets_state_for_state_builder_with_evolve_function_and_typed_events ( ) {
17
+ public async Task runs_business_logic_with_decider_and_typed_events ( ) {
16
18
// Given
17
19
var shoppingCartId = Guid . NewGuid ( ) ;
18
20
var clientId = Guid . NewGuid ( ) ;
@@ -23,16 +25,55 @@ public async Task gets_state_for_state_builder_with_evolve_function_and_typed_ev
23
25
var tShirt = new PricedProductItem ( tShirtId , 1 , 50 ) ;
24
26
25
27
var events = new Event [ ] {
26
- new Opened ( shoppingCartId , clientId , DateTime . UtcNow ) ,
27
- new ProductItemAdded ( shoppingCartId , twoPairsOfShoes , DateTime . UtcNow ) ,
28
- new ProductItemAdded ( shoppingCartId , tShirt , DateTime . UtcNow ) ,
29
- new ProductItemRemoved ( shoppingCartId , pairOfShoes , DateTime . UtcNow ) ,
30
- new Confirmed ( shoppingCartId , DateTime . UtcNow ) ,
31
- new Canceled ( shoppingCartId , DateTime . UtcNow )
28
+ new Opened ( clientId , DateTime . UtcNow ) ,
29
+ new ProductItemAdded ( twoPairsOfShoes , DateTime . UtcNow ) ,
30
+ new ProductItemAdded ( tShirt , DateTime . UtcNow ) ,
31
+ new ProductItemRemoved ( pairOfShoes , DateTime . UtcNow ) ,
32
+ new Confirmed ( DateTime . UtcNow ) ,
33
+ new Canceled ( DateTime . UtcNow )
32
34
} ;
33
35
34
36
var streamName = $ "shopping_cart-{ shoppingCartId } ";
35
37
38
+ await Fixture . Streams . DecideAsync (
39
+ streamName ,
40
+ new Open ( clientId , DateTime . UtcNow ) ,
41
+ Decider
42
+ ) ;
43
+
44
+ await Fixture . Streams . DecideAsync (
45
+ streamName ,
46
+ new AddProductItem ( twoPairsOfShoes , DateTime . UtcNow ) ,
47
+ Decider
48
+ ) ;
49
+
50
+ await Fixture . Streams . DecideAsync (
51
+ streamName ,
52
+ new AddProductItem ( tShirt , DateTime . UtcNow ) ,
53
+ Decider
54
+ ) ;
55
+
56
+ await Fixture . Streams . DecideAsync (
57
+ streamName ,
58
+ new RemoveProductItem ( pairOfShoes , DateTime . UtcNow ) ,
59
+ Decider
60
+ ) ;
61
+
62
+ await Fixture . Streams . DecideAsync (
63
+ streamName ,
64
+ new Confirm ( DateTime . UtcNow ) ,
65
+ Decider
66
+ ) ;
67
+
68
+ await Assert . ThrowsAsync < InvalidOperationException > (
69
+ ( ) =>
70
+ Fixture . Streams . DecideAsync (
71
+ streamName ,
72
+ new Cancel ( DateTime . UtcNow ) ,
73
+ Decider
74
+ )
75
+ ) ;
76
+
36
77
await Fixture . Streams . AppendToStreamAsync ( streamName , events ) ;
37
78
38
79
var stateBuilder = StateBuilder . For < ShoppingCart , Event > ( Evolve , ( ) => new Initial ( ) ) ;
@@ -44,16 +85,6 @@ public async Task gets_state_for_state_builder_with_evolve_function_and_typed_ev
44
85
45
86
// Then
46
87
Assert . IsType < Closed > ( shoppingCart ) ;
47
- // TODO: Add some time travelling
48
- // Assert.Equal(2, shoppingCart.);
49
- //
50
- // Assert.Equal(shoesId, shoppingCart.ProductItems[0].ProductId);
51
- // Assert.Equal(pairOfShoes.Quantity, shoppingCart.ProductItems[0].Quantity);
52
- // Assert.Equal(pairOfShoes.UnitPrice, shoppingCart.ProductItems[0].UnitPrice);
53
- //
54
- // Assert.Equal(tShirtId, shoppingCart.ProductItems[1].ProductId);
55
- // Assert.Equal(tShirt.Quantity, shoppingCart.ProductItems[1].Quantity);
56
- // Assert.Equal(tShirt.UnitPrice, shoppingCart.ProductItems[1].UnitPrice);
57
88
}
58
89
}
59
90
@@ -68,30 +99,25 @@ decimal UnitPrice
68
99
public abstract record ShoppingCart {
69
100
public abstract record Event {
70
101
public record Opened (
71
- Guid ShoppingCartId ,
72
102
Guid ClientId ,
73
103
DateTimeOffset OpenedAt
74
104
) : Event ;
75
105
76
106
public record ProductItemAdded (
77
- Guid ShoppingCartId ,
78
107
PricedProductItem ProductItem ,
79
108
DateTimeOffset AddedAt
80
109
) : Event ;
81
110
82
111
public record ProductItemRemoved (
83
- Guid ShoppingCartId ,
84
112
PricedProductItem ProductItem ,
85
113
DateTimeOffset RemovedAt
86
114
) : Event ;
87
115
88
116
public record Confirmed (
89
- Guid ShoppingCartId ,
90
117
DateTimeOffset ConfirmedAt
91
118
) : Event ;
92
119
93
120
public record Canceled (
94
- Guid ShoppingCartId ,
95
121
DateTimeOffset CanceledAt
96
122
) : Event ;
97
123
@@ -110,10 +136,10 @@ public static ShoppingCart Evolve(ShoppingCart state, Event @event) =>
110
136
( Initial , Opened ) =>
111
137
new Pending ( ProductItems . Empty ) ,
112
138
113
- ( Pending ( var productItems ) , ProductItemAdded ( _ , var productItem , _ ) ) =>
139
+ ( Pending ( var productItems ) , ProductItemAdded ( var productItem , _ ) ) =>
114
140
new Pending ( productItems . Add ( productItem ) ) ,
115
141
116
- ( Pending ( var productItems ) , ProductItemRemoved ( _ , var productItem , _ ) ) =>
142
+ ( Pending ( var productItems ) , ProductItemRemoved ( var productItem , _ ) ) =>
117
143
new Pending ( productItems . Remove ( productItem ) ) ,
118
144
119
145
( Pending , Confirmed ) =>
@@ -124,6 +150,61 @@ public static ShoppingCart Evolve(ShoppingCart state, Event @event) =>
124
150
125
151
_ => state
126
152
} ;
153
+
154
+ public abstract record Command {
155
+ public record Open (
156
+ Guid ClientId ,
157
+ DateTimeOffset Now
158
+ ) : Command ;
159
+
160
+ public record AddProductItem (
161
+ PricedProductItem ProductItem ,
162
+ DateTimeOffset Now
163
+ ) : Command ;
164
+
165
+ public record RemoveProductItem (
166
+ PricedProductItem ProductItem ,
167
+ DateTimeOffset Now
168
+ ) : Command ;
169
+
170
+ public record Confirm (
171
+ DateTimeOffset Now
172
+ ) : Command ;
173
+
174
+ public record Cancel (
175
+ DateTimeOffset Now
176
+ ) : Command ;
177
+
178
+ Command ( ) { }
179
+ }
180
+
181
+ public static Event [ ] Decide ( Command command , ShoppingCart state ) =>
182
+ ( state , command ) switch {
183
+ ( Pending , Open ) => [ ] ,
184
+
185
+ ( Initial , Open ( var clientId , var now ) ) => [ new Opened ( clientId , now ) ] ,
186
+
187
+ ( Pending , AddProductItem ( var productItem , var now ) ) => [ new ProductItemAdded ( productItem , now ) ] ,
188
+
189
+ ( Pending ( var productItems ) , RemoveProductItem ( var productItem , var now ) ) =>
190
+ productItems . HasEnough ( productItem )
191
+ ? [ new ProductItemRemoved ( productItem , now ) ]
192
+ : throw new InvalidOperationException ( "Not enough product items to remove" ) ,
193
+
194
+ ( Pending , Confirm ( var now ) ) => [ new Confirmed ( now ) ] ,
195
+
196
+ ( Pending , Cancel ( var now ) ) => [ new Canceled ( now ) ] ,
197
+
198
+ _ => throw new InvalidOperationException (
199
+ $ "Cannot { command . GetType ( ) . Name } for { state . GetType ( ) . Name } shopping cart"
200
+ )
201
+ } ;
202
+
203
+ public static readonly Decider < ShoppingCart , Command , Event > Decider = new Decider < ShoppingCart , Command , Event > (
204
+ Decide ,
205
+ Evolve ,
206
+ ( ) => new Initial ( )
207
+ ) ;
127
208
}
128
209
129
210
public record ProductItems ( ImmutableDictionary < string , int > Items ) {
@@ -144,19 +225,3 @@ static string Key(PricedProductItem pricedProductItem) =>
144
225
ProductItems IncrementQuantity ( string key , int quantity ) =>
145
226
new ( Items . SetItem ( key , Items . TryGetValue ( key , out var current ) ? current + quantity : quantity ) ) ;
146
227
}
147
-
148
- public static class DictionaryExtensions {
149
- public static ImmutableDictionary < TKey , TValue > Set < TKey , TValue > (
150
- this ImmutableDictionary < TKey , TValue > dictionary ,
151
- TKey key ,
152
- Func < TValue ? , TValue > set
153
- ) where TKey : notnull =>
154
- dictionary . SetItem ( key , set ( dictionary . TryGetValue ( key , out var current ) ? current : default ) ) ;
155
-
156
- public static void Set < TKey , TValue > (
157
- this Dictionary < TKey , TValue > dictionary ,
158
- TKey key ,
159
- Func < TValue ? , TValue > set
160
- ) where TKey : notnull =>
161
- dictionary [ key ] = set ( dictionary . TryGetValue ( key , out var current ) ? current : default ) ;
162
- }
0 commit comments