Преглед изворни кода

Cleaner and clearer lifecycle scenario test. Now used only the domain layer.

peter_backlund пре 17 година
родитељ
комит
7b67f9694d

+ 227
- 194
dddsample/src/test/java/se/citerus/dddsample/scenario/CargoLifecycleScenarioTest.java Прегледај датотеку

@@ -1,31 +1,22 @@
1 1
 package se.citerus.dddsample.scenario;
2 2
 
3
-import junit.framework.TestCase;
4
-import se.citerus.dddsample.application.ApplicationEvents;
5
-import se.citerus.dddsample.application.BookingService;
6
-import se.citerus.dddsample.application.CargoInspectionService;
7
-import se.citerus.dddsample.application.HandlingEventService;
8
-import se.citerus.dddsample.application.impl.BookingServiceImpl;
9
-import se.citerus.dddsample.application.impl.CargoInspectionServiceImpl;
10
-import se.citerus.dddsample.application.impl.HandlingEventServiceImpl;
3
+import static org.junit.Assert.*;
4
+import org.junit.Test;
11 5
 import static se.citerus.dddsample.application.util.DateTestUtil.toDate;
12 6
 import se.citerus.dddsample.domain.model.cargo.*;
13 7
 import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.*;
14 8
 import static se.citerus.dddsample.domain.model.cargo.TransportStatus.*;
15
-import se.citerus.dddsample.domain.model.handling.CannotCreateHandlingEventException;
9
+import se.citerus.dddsample.domain.model.handling.*;
16 10
 import static se.citerus.dddsample.domain.model.handling.HandlingEvent.Type.*;
17
-import se.citerus.dddsample.domain.model.handling.HandlingEventFactory;
18
-import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
19 11
 import se.citerus.dddsample.domain.model.location.Location;
20 12
 import se.citerus.dddsample.domain.model.location.LocationRepository;
21 13
 import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
22
-import se.citerus.dddsample.domain.model.location.UnLocode;
23 14
 import static se.citerus.dddsample.domain.model.voyage.SampleVoyages.*;
15
+import se.citerus.dddsample.domain.model.voyage.Voyage;
24 16
 import static se.citerus.dddsample.domain.model.voyage.Voyage.NONE;
25 17
 import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
26 18
 import se.citerus.dddsample.domain.model.voyage.VoyageRepository;
27 19
 import se.citerus.dddsample.domain.service.RoutingService;
28
-import se.citerus.dddsample.infrastructure.messaging.stub.SynchronousApplicationEventsStub;
29 20
 import se.citerus.dddsample.infrastructure.persistence.inmemory.CargoRepositoryInMem;
30 21
 import se.citerus.dddsample.infrastructure.persistence.inmemory.HandlingEventRepositoryInMem;
31 22
 import se.citerus.dddsample.infrastructure.persistence.inmemory.LocationRepositoryInMem;
@@ -35,44 +26,19 @@ import java.util.Arrays;
35 26
 import java.util.Date;
36 27
 import java.util.List;
37 28
 
38
-public class CargoLifecycleScenarioTest extends TestCase {
29
+public class CargoLifecycleScenarioTest {
39 30
 
40 31
   /**
41 32
    * Repository implementations are part of the infrastructure layer,
42 33
    * which in this test is stubbed out by in-memory replacements.
43 34
    */
44
-  HandlingEventRepository handlingEventRepository;
45
-  CargoRepository cargoRepository;
46
-  LocationRepository locationRepository;
47
-  VoyageRepository voyageRepository;
35
+  HandlingEventRepository handlingEventRepository = new HandlingEventRepositoryInMem();
36
+  CargoRepository cargoRepository = new CargoRepositoryInMem();
37
+  LocationRepository locationRepository = new LocationRepositoryInMem();
38
+  VoyageRepository voyageRepository = new VoyageRepositoryInMem();
48 39
 
49
-  /**
50
-   * This interface is part of the application layer,
51
-   * and defines a number of events that occur during
52
-   * aplication execution. It is used for message-driving
53
-   * and is implemented using JMS.
54
-   *
55
-   * In this test it is stubbed with synchronous calls.
56
-   */
57
-  ApplicationEvents applicationEvents;
58
-
59
-  /**
60
-   * These three components all belong to the application layer,
61
-   * and map against use cases of the application. The "real"
62
-   * implementations are used in this lifecycle test,
63
-   * but wired with stubbed infrastructure.
64
-   */
65
-  BookingService bookingService;
66
-  HandlingEventService handlingEventService;
67
-  CargoInspectionService cargoInspectionService;
68
-
69
-  /**
70
-   * This factory is part of the handling aggregate and belongs to
71
-   * the domain layer. Similar to the application layer components,
72
-   * the "real" implementation is used here too,
73
-   * wired with stubbed infrastructure.
74
-   */
75
-  HandlingEventFactory handlingEventFactory;
40
+  HandlingEventFactory handlingEventFactory = new HandlingEventFactory(cargoRepository, voyageRepository, locationRepository);
41
+  CargoFactory cargoFactory = new CargoFactory(cargoRepository, locationRepository);
76 42
 
77 43
   /**
78 44
    * This is a domain service interface, whose implementation
@@ -80,136 +46,212 @@ public class CargoLifecycleScenarioTest extends TestCase {
80 46
    *
81 47
    * It is stubbed in this test.
82 48
    */
83
-  RoutingService routingService;
49
+  RoutingService routingService = new ScenarioStubRoutingService();
50
+
51
+  TrackingId trackingId;
52
+
53
+  /* The tracking id can be used to lookup the cargo in the repository.
54
+
55
+     Important: The cargo, and thus the domain model, is responsible for determining
56
+     the status of the cargo, whether it is on the right track or not and so on.
57
+     This is core domain logic.
58
+
59
+     Tracking the cargo basically amounts to presenting information extracted from
60
+     the cargo aggregate in a suitable way. */
61
+  /* Test setup: A cargo should be shipped from Hongkong to Stockholm,
62
+     and it should arrive in no more than two weeks. */
63
+  /* Use case 2: routing
64
+
65
+     A number of possible routes for this cargo is requested and may be
66
+     presented to the customer in some way for him/her to choose from.
67
+     Selection could be affected by things like price and time of delivery,
68
+     but this test simply uses an arbitrary selection to mimic that process.
69
+
70
+     The cargo is then assigned to the selected route, described by an itinerary. */
71
+
72
+  /*
73
+    Use case 3: handling
74
+
75
+    A handling event registration attempt will be formed from parsing
76
+    the data coming in as a handling report either via
77
+    the web service interface or as an uploaded CSV file.
78
+
79
+    The handling event factory tries to create a HandlingEvent from the attempt,
80
+    and if the factory decides that this is a plausible handling event, it is stored.
81
+    If the attempt is invalid, for example if no cargo exists for the specfied tracking id,
82
+    the attempt is rejected.
83
+
84
+    Handling begins: cargo is received in Hongkong.
85
+    */
86
+
87
+  @Test
88
+  public void cargoIsMisdirectedAndRerouted() throws Exception {
89
+    bookCargoFromHongkongToStockholm();
90
+    checkDeliveryAfterBooking();
91
+
92
+
93
+    // Initial route: Hongkong - New York - Chicago - Stockholm
94
+    routeCargoFromHongkongToStockholm();
95
+    checkDeliveryAfterRouting();
96
+
97
+
98
+    receiveInHongkong();
99
+    checkDeliveryAfterReceiveInHongkong();
100
+
101
+    loadInHongkong();
102
+    checkDeliveryAfterLoadInHongKong();
103
+
104
+    unloadInTokyo();
105
+    checkDeliveryAfterUnloadInTokyo();
84 106
 
85
-  public void testCargoFromHongkongToStockholm() throws Exception {
86
-    /* Test setup: A cargo should be shipped from Hongkong to Stockholm,
87
-       and it should arrive in no more than two weeks. */
88
-    Location origin = HONGKONG;
89
-    Location destination = STOCKHOLM;
90
-    Date arrivalDeadline = toDate("2009-03-18");
91 107
 
92
-    /* Use case 1: booking
108
+    // Reroute: Tokyo - Hamburg - Stockholm
109
+    specifyNewRouteFromTokyoToStockholm();
110
+    checkDeliveryAfterNewRouteSpecified();
93 111
 
94
-       A new cargo is booked, and the unique tracking id is assigned to the cargo. */
95
-    TrackingId trackingId = bookingService.bookNewCargo(
96
-      origin.unLocode(), destination.unLocode(), arrivalDeadline
97
-    );
112
+    assignToNewRouteFromTokyoToStockholm();
113
+    checkDeliveryAfterAssignedToNewRoute();
98 114
 
99
-    /* The tracking id can be used to lookup the cargo in the repository.
100 115
 
101
-       Important: The cargo, and thus the domain model, is responsible for determining
102
-       the status of the cargo, whether it is on the right track or not and so on.
103
-       This is core domain logic.
116
+    loadInTokyo();
117
+    checkDeliveryAfterLoadInTokyo();
104 118
 
105
-       Tracking the cargo basically amounts to presenting information extracted from
106
-       the cargo aggregate in a suitable way. */
119
+    unloadInHamburg();
120
+    checkDeliveryAfterUnloadInHamburg();
121
+
122
+    loadInHamburg();
123
+    checkDeliveryAfterLoadInHamburg();
124
+
125
+    unloadInStockholm();
126
+    checkDeliveryAfterUnloadInStockholm();
127
+
128
+    claimInStockholm();
129
+    checkDeliveryAfterClaimInStockholm();
130
+  }
131
+
132
+  private void bookCargoFromHongkongToStockholm() {
133
+    Date arrivalDeadline = toDate("2009-03-18");
134
+    Cargo cargo = cargoFactory.newCargo(HONGKONG.unLocode(), STOCKHOLM.unLocode(), arrivalDeadline);
135
+    cargoRepository.store(cargo);
136
+
137
+    trackingId = cargo.trackingId();
138
+  }
139
+
140
+  private void routeCargoFromHongkongToStockholm() {
107 141
     Cargo cargo = cargoRepository.find(trackingId);
142
+    List<Itinerary> itineraries = routingService.fetchRoutesForSpecification(cargo.routeSpecification());
143
+    Itinerary itinerary = itineraries.get(0);
144
+    cargo.assignToRoute(itinerary);
145
+    cargoRepository.store(cargo);
146
+  }
147
+
148
+  public void checkDeliveryAfterBooking() throws Exception {
149
+    Cargo cargo = cargoRepository.find(trackingId);
150
+
108 151
     assertNotNull(cargo);
109 152
     assertEquals(NOT_RECEIVED, cargo.delivery().transportStatus());
110 153
     assertEquals(NOT_ROUTED, cargo.delivery().routingStatus());
111 154
     assertFalse(cargo.delivery().isMisdirected());
112 155
     assertNull(cargo.delivery().estimatedTimeOfArrival());
113 156
     assertNull(cargo.delivery().nextExpectedActivity());
157
+  }
114 158
 
115
-    /* Use case 2: routing
116
-
117
-       A number of possible routes for this cargo is requested and may be
118
-       presented to the customer in some way for him/her to choose from.
119
-       Selection could be affected by things like price and time of delivery,
120
-       but this test simply uses an arbitrary selection to mimic that process.
121
-
122
-       The cargo is then assigned to the selected route, described by an itinerary. */
123
-    List<Itinerary> itineraries = bookingService.requestPossibleRoutesForCargo(trackingId);
124
-    Itinerary itinerary = selectPreferedItinerary(itineraries);
125
-    cargo.assignToRoute(itinerary);
159
+  public void checkDeliveryAfterRouting() throws Exception {
160
+    Cargo cargo = cargoRepository.find(trackingId);
126 161
 
127 162
     assertEquals(NOT_RECEIVED, cargo.delivery().transportStatus());
128 163
     assertEquals(ROUTED, cargo.delivery().routingStatus());
129 164
     assertNotNull(cargo.delivery().estimatedTimeOfArrival());
130 165
     assertEquals(new HandlingActivity(RECEIVE, HONGKONG), cargo.delivery().nextExpectedActivity());
166
+  }
131 167
 
132
-    /*
133
-      Use case 3: handling
134
-
135
-      A handling event registration attempt will be formed from parsing
136
-      the data coming in as a handling report either via
137
-      the web service interface or as an uploaded CSV file.
138
-
139
-      The handling event factory tries to create a HandlingEvent from the attempt,
140
-      and if the factory decides that this is a plausible handling event, it is stored.
141
-      If the attempt is invalid, for example if no cargo exists for the specfied tracking id,
142
-      the attempt is rejected.
143
-
144
-      Handling begins: cargo is received in Hongkong.
145
-      */
146
-    handlingEventService.registerHandlingEvent(
147
-      toDate("2009-03-01"), trackingId, null, HONGKONG.unLocode(), RECEIVE
148
-    );
168
+  public void receiveInHongkong() throws CannotCreateHandlingEventException {
169
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-01"), null, HONGKONG, RECEIVE);
170
+  }
149 171
 
172
+  private void checkDeliveryAfterReceiveInHongkong() {
173
+    Cargo cargo = cargoRepository.find(trackingId);
150 174
     assertEquals(IN_PORT, cargo.delivery().transportStatus());
151 175
     assertEquals(HONGKONG, cargo.delivery().lastKnownLocation());
152
-    
153
-    // Next event: Load onto voyage CM003 in Hongkong
154
-    handlingEventService.registerHandlingEvent(
155
-      toDate("2009-03-03"), trackingId, v100.voyageNumber(), HONGKONG.unLocode(), LOAD
156
-    );
176
+  }
177
+
178
+  public void loadInHongkong() throws CannotCreateHandlingEventException {
179
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-03"), v100, HONGKONG, LOAD);
180
+  }
157 181
 
182
+  private void checkDeliveryAfterLoadInHongKong() {
158 183
     // Check current state - should be ok
184
+    Cargo cargo = cargoRepository.find(trackingId);
159 185
     assertEquals(v100, cargo.delivery().currentVoyage());
160 186
     assertEquals(HONGKONG, cargo.delivery().lastKnownLocation());
161 187
     assertEquals(ONBOARD_CARRIER, cargo.delivery().transportStatus());
162 188
     assertFalse(cargo.delivery().isMisdirected());
163 189
     assertEquals(new HandlingActivity(UNLOAD, NEWYORK, v100), cargo.delivery().nextExpectedActivity());
190
+  }
164 191
 
192
+  /*
193
+   Here's an attempt to register a handling event that's not valid
194
+   because there is no voyage with the specified voyage number,
195
+   and there's no location with the specified UN Locode either.
196
+
197
+   This attempt will be rejected and will not affect the cargo delivery in any way.
198
+ final VoyageNumber noSuchVoyageNumber = new VoyageNumber("XX000");
199
+ final UnLocode noSuchUnLocode = new UnLocode("ZZZZZ");
200
+ try {
201
+   handlingEventService.registerHandlingEvent(
202
+   toDate("2009-03-05"), trackingId, noSuchVoyageNumber, noSuchUnLocode, LOAD
203
+   );
204
+   fail("Should not be able to register a handling event with invalid location and voyage");
205
+ } catch (CannotCreateHandlingEventException expected) {
206
+ }
207
+  */
165 208
 
166
-    /*
167
-      Here's an attempt to register a handling event that's not valid
168
-      because there is no voyage with the specified voyage number,
169
-      and there's no location with the specified UN Locode either.
170
-
171
-      This attempt will be rejected and will not affect the cargo delivery in any way.
172
-     */
173
-    final VoyageNumber noSuchVoyageNumber = new VoyageNumber("XX000");
174
-    final UnLocode noSuchUnLocode = new UnLocode("ZZZZZ");
175
-    try {
176
-      handlingEventService.registerHandlingEvent(
177
-      toDate("2009-03-05"), trackingId, noSuchVoyageNumber, noSuchUnLocode, LOAD
178
-      );
179
-      fail("Should not be able to register a handling event with invalid location and voyage");
180
-    } catch (CannotCreateHandlingEventException expected) {
181
-    }
182
-
183
-
209
+  public void unloadInTokyo() throws CannotCreateHandlingEventException {
184 210
     // Cargo is now (incorrectly) unloaded in Tokyo
185
-    handlingEventService.registerHandlingEvent(
186
-      toDate("2009-03-05"), trackingId, v100.voyageNumber(), TOKYO.unLocode(), UNLOAD
187
-    );
211
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-05"), v100, TOKYO, UNLOAD);
212
+  }
188 213
 
214
+  private void checkDeliveryAfterUnloadInTokyo() {
215
+    Cargo cargo = cargoRepository.find(trackingId);
189 216
     // Check current state - cargo is misdirected!
190 217
     assertEquals(NONE, cargo.delivery().currentVoyage());
191 218
     assertEquals(TOKYO, cargo.delivery().lastKnownLocation());
192 219
     assertEquals(IN_PORT, cargo.delivery().transportStatus());
193 220
     assertTrue(cargo.delivery().isMisdirected());
194 221
     assertNull(cargo.delivery().nextExpectedActivity());
222
+  }
195 223
 
196
-
197
-    // -- Cargo needs to be rerouted --
198
-
224
+  public void specifyNewRouteFromTokyoToStockholm() {
199 225
     // TODO cleaner reroute from "earliest location from where the new route originates"
226
+    Cargo cargo = cargoRepository.find(trackingId);
200 227
 
201 228
     // Specify a new route, this time from Tokyo (where it was incorrectly unloaded) to Stockholm
202
-    RouteSpecification fromTokyo = new RouteSpecification(TOKYO, STOCKHOLM, arrivalDeadline);
229
+    RouteSpecification fromTokyo = cargo.routeSpecification().withOrigin(TOKYO);
203 230
     cargo.specifyNewRoute(fromTokyo);
231
+    cargoRepository.store(cargo);
232
+  }
233
+
234
+  public void checkDeliveryAfterNewRouteSpecified() {
235
+    Cargo cargo = cargoRepository.find(trackingId);
204 236
 
205 237
     // The old itinerary does not satisfy the new specification
206 238
     assertEquals(MISROUTED, cargo.delivery().routingStatus());
207 239
     assertNull(cargo.delivery().nextExpectedActivity());
240
+  }
241
+
242
+  public void assignToNewRouteFromTokyoToStockholm() {
243
+    Cargo cargo = cargoRepository.find(trackingId);
208 244
 
209 245
     // Repeat procedure of selecting one out of a number of possible routes satisfying the route spec
210
-    List<Itinerary> newItineraries = bookingService.requestPossibleRoutesForCargo(cargo.trackingId());
211
-    Itinerary newItinerary = selectPreferedItinerary(newItineraries);
246
+    List<Itinerary> newItineraries = routingService.fetchRoutesForSpecification(cargo.routeSpecification());
247
+    Itinerary newItinerary = newItineraries.get(0);
248
+
212 249
     cargo.assignToRoute(newItinerary);
250
+    cargoRepository.store(cargo);
251
+  }
252
+
253
+  public void checkDeliveryAfterAssignedToNewRoute() {
254
+    Cargo cargo = cargoRepository.find(trackingId);
213 255
 
214 256
     // New itinerary should satisfy new route
215 257
     assertEquals(ROUTED, cargo.delivery().routingStatus());
@@ -217,66 +259,71 @@ public class CargoLifecycleScenarioTest extends TestCase {
217 259
     // TODO we can't handle the face that after a reroute, the cargo isn't misdirected anymore
218 260
     //assertFalse(cargo.isMisdirected());
219 261
     //assertEquals(new HandlingActivity(LOAD, TOKYO), cargo.nextExpectedActivity());
262
+  }
220 263
 
264
+  public void loadInTokyo() throws CannotCreateHandlingEventException {
265
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-08"), v300, TOKYO, LOAD);
266
+  }
221 267
 
222
-    // -- Cargo has been rerouted, shipping continues --
223
-
224
-
225
-    // Load in Tokyo
226
-    handlingEventService.registerHandlingEvent(
227
-      toDate("2009-03-08"), trackingId, v300.voyageNumber(), TOKYO.unLocode(), LOAD
228
-    );
229
-
268
+  private void checkDeliveryAfterLoadInTokyo() {
269
+    Cargo cargo = cargoRepository.find(trackingId);
230 270
     // Check current state - should be ok
231 271
     assertEquals(v300, cargo.delivery().currentVoyage());
232 272
     assertEquals(TOKYO, cargo.delivery().lastKnownLocation());
233 273
     assertEquals(ONBOARD_CARRIER, cargo.delivery().transportStatus());
234 274
     assertFalse(cargo.delivery().isMisdirected());
235 275
     assertEquals(new HandlingActivity(UNLOAD, HAMBURG, v300), cargo.delivery().nextExpectedActivity());
276
+  }
236 277
 
237
-    // Unload in Hamburg
238
-    handlingEventService.registerHandlingEvent(
239
-      toDate("2009-03-12"), trackingId, v300.voyageNumber(), HAMBURG.unLocode(), UNLOAD
240
-    );
278
+  public void unloadInHamburg() throws CannotCreateHandlingEventException {
279
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-12"), v300, HAMBURG, UNLOAD);
280
+  }
241 281
 
282
+  private void checkDeliveryAfterUnloadInHamburg() {
283
+    Cargo cargo = cargoRepository.find(trackingId);
242 284
     // Check current state - should be ok
243 285
     assertEquals(NONE, cargo.delivery().currentVoyage());
244 286
     assertEquals(HAMBURG, cargo.delivery().lastKnownLocation());
245 287
     assertEquals(IN_PORT, cargo.delivery().transportStatus());
246 288
     assertFalse(cargo.delivery().isMisdirected());
247 289
     assertEquals(new HandlingActivity(LOAD, HAMBURG, v400), cargo.delivery().nextExpectedActivity());
290
+  }
248 291
 
292
+  public void loadInHamburg() throws CannotCreateHandlingEventException {
293
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-14"), v400, HAMBURG, LOAD);
294
+  }
249 295
 
250
-    // Load in Hamburg
251
-    handlingEventService.registerHandlingEvent(
252
-      toDate("2009-03-14"), trackingId, v400.voyageNumber(), HAMBURG.unLocode(), LOAD
253
-    );
254
-
296
+  private void checkDeliveryAfterLoadInHamburg() {
297
+    Cargo cargo = cargoRepository.find(trackingId);
255 298
     // Check current state - should be ok
256 299
     assertEquals(v400, cargo.delivery().currentVoyage());
257 300
     assertEquals(HAMBURG, cargo.delivery().lastKnownLocation());
258 301
     assertEquals(ONBOARD_CARRIER, cargo.delivery().transportStatus());
259 302
     assertFalse(cargo.delivery().isMisdirected());
260 303
     assertEquals(new HandlingActivity(UNLOAD, STOCKHOLM, v400), cargo.delivery().nextExpectedActivity());
304
+  }
261 305
 
306
+  public void unloadInStockholm() throws CannotCreateHandlingEventException {
307
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-15"), v400, STOCKHOLM, UNLOAD);
308
+  }
262 309
 
263
-    // Unload in Stockholm
264
-    handlingEventService.registerHandlingEvent(
265
-      toDate("2009-03-15"), trackingId, v400.voyageNumber(), STOCKHOLM.unLocode(), UNLOAD
266
-    );
267
-
310
+  private void checkDeliveryAfterUnloadInStockholm() {
311
+    Cargo cargo = cargoRepository.find(trackingId);
268 312
     // Check current state - should be ok
269 313
     assertEquals(NONE, cargo.delivery().currentVoyage());
270 314
     assertEquals(STOCKHOLM, cargo.delivery().lastKnownLocation());
271 315
     assertEquals(IN_PORT, cargo.delivery().transportStatus());
272 316
     assertFalse(cargo.delivery().isMisdirected());
273 317
     assertEquals(new HandlingActivity(CLAIM, STOCKHOLM), cargo.delivery().nextExpectedActivity());
318
+  }
274 319
 
320
+  private void claimInStockholm() throws CannotCreateHandlingEventException {
275 321
     // Finally, cargo is claimed in Stockholm. This ends the cargo lifecycle from our perspective.
276
-    handlingEventService.registerHandlingEvent(
277
-      toDate("2009-03-16"), trackingId, null, STOCKHOLM.unLocode(), CLAIM
278
-    );
322
+    createHandlingEventAndUpdateAggregates(toDate("2009-03-16"), null, STOCKHOLM, CLAIM);
323
+  }
279 324
 
325
+  private void checkDeliveryAfterClaimInStockholm() {
326
+    Cargo cargo = cargoRepository.find(trackingId);
280 327
     // Check current state - should be ok
281 328
     assertEquals(NONE, cargo.delivery().currentVoyage());
282 329
     assertEquals(STOCKHOLM, cargo.delivery().lastKnownLocation());
@@ -285,57 +332,43 @@ public class CargoLifecycleScenarioTest extends TestCase {
285 332
     assertNull(cargo.delivery().nextExpectedActivity());
286 333
   }
287 334
 
335
+  private void createHandlingEventAndUpdateAggregates(Date completionTime, Voyage voyage, Location location, HandlingEvent.Type type) throws CannotCreateHandlingEventException {
336
+    VoyageNumber voyageNumber = voyage != null ? voyage.voyageNumber() : null;
337
+    HandlingEvent handlingEvent = handlingEventFactory.createHandlingEvent(new Date(), completionTime, trackingId, voyageNumber, location.unLocode(), type);
338
+    handlingEventRepository.store(handlingEvent);
339
+    updateCargoAggregate();
340
+  }
288 341
 
289
-  /*
290
-  * Utility stubs below.
291
-  */
292
-
293
-  private Itinerary selectPreferedItinerary(List<Itinerary> itineraries) {
294
-    return itineraries.get(0);
342
+  private void updateCargoAggregate() {
343
+    Cargo cargo = cargoRepository.find(trackingId);
344
+    HandlingHistory handlingHistory = handlingEventRepository.lookupHandlingHistoryOfCargo(cargo);
345
+    cargo.deriveDeliveryProgress(handlingHistory);
346
+    cargoRepository.store(cargo);
295 347
   }
296 348
 
297
-  protected void setUp() throws Exception {
298
-    routingService = new RoutingService() {
299
-      public List<Itinerary> fetchRoutesForSpecification(RouteSpecification routeSpecification) {
300
-        if (routeSpecification.origin().equals(HONGKONG)) {
301
-          // Hongkong - NYC - Chicago - Stockholm, initial routing
302
-          return Arrays.asList(
303
-            new Itinerary(Arrays.asList(
304
-              new Leg(v100, HONGKONG, NEWYORK, toDate("2009-03-03"), toDate("2009-03-09")),
305
-              new Leg(v200, NEWYORK, CHICAGO, toDate("2009-03-10"), toDate("2009-03-14")),
306
-              new Leg(v200, CHICAGO, STOCKHOLM, toDate("2009-03-07"), toDate("2009-03-11"))
307
-            ))
308
-          );
309
-        } else {
310
-          // Tokyo - Hamburg - Stockholm, rerouting misdirected cargo from Tokyo 
311
-          return Arrays.asList(
312
-            new Itinerary(Arrays.asList(
313
-              new Leg(v300, TOKYO, HAMBURG, toDate("2009-03-08"), toDate("2009-03-12")),
314
-              new Leg(v400, HAMBURG, STOCKHOLM, toDate("2009-03-14"), toDate("2009-03-15"))
315
-            ))
316
-          );
317
-        }
349
+  private static class ScenarioStubRoutingService implements RoutingService {
350
+
351
+    public List<Itinerary> fetchRoutesForSpecification(RouteSpecification routeSpecification) {
352
+      if (routeSpecification.origin().equals(HONGKONG)) {
353
+        // Hongkong - NYC - Chicago - Stockholm, initial routing
354
+        return Arrays.asList(
355
+          new Itinerary(Arrays.asList(
356
+            new Leg(v100, HONGKONG, NEWYORK, toDate("2009-03-03"), toDate("2009-03-09")),
357
+            new Leg(v200, NEWYORK, CHICAGO, toDate("2009-03-10"), toDate("2009-03-14")),
358
+            new Leg(v200, CHICAGO, STOCKHOLM, toDate("2009-03-07"), toDate("2009-03-11"))
359
+          ))
360
+        );
361
+      } else {
362
+        // Tokyo - Hamburg - Stockholm, rerouting misdirected cargo from Tokyo
363
+        return Arrays.asList(
364
+          new Itinerary(Arrays.asList(
365
+            new Leg(v300, TOKYO, HAMBURG, toDate("2009-03-08"), toDate("2009-03-12")),
366
+            new Leg(v400, HAMBURG, STOCKHOLM, toDate("2009-03-14"), toDate("2009-03-15"))
367
+          ))
368
+        );
318 369
       }
319
-    };
320
-
321
-
322
-    applicationEvents = new SynchronousApplicationEventsStub();
323
-
324
-    // In-memory implementations of the repositories
325
-    handlingEventRepository = new HandlingEventRepositoryInMem();
326
-    cargoRepository = new CargoRepositoryInMem();
327
-    locationRepository = new LocationRepositoryInMem();
328
-    voyageRepository = new VoyageRepositoryInMem();
329
-
330
-    // Actual factories and application services, wired with stubbed or in-memory infrastructure
331
-    handlingEventFactory = new HandlingEventFactory(cargoRepository, voyageRepository, locationRepository);
332
-
333
-    cargoInspectionService = new CargoInspectionServiceImpl(applicationEvents, cargoRepository, handlingEventRepository);
334
-    handlingEventService = new HandlingEventServiceImpl(handlingEventRepository, applicationEvents, handlingEventFactory);
335
-    bookingService = new BookingServiceImpl(cargoRepository, locationRepository, routingService);
370
+    }
336 371
 
337
-    // Circular dependency when doing synchrounous calls
338
-    ((SynchronousApplicationEventsStub) applicationEvents).setCargoInspectionService(cargoInspectionService);
339 372
   }
340 373
 
341 374
 }