Explorar el Código

Expanded voyage rescheduling scenario test to include the case where a voyage is rescheduled so that the old itinerary is no longer maintainable, and modified the domain model to handle that case.

peter_backlund hace 17 años
padre
commit
6f06185593

+ 42
- 4
dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Itinerary.java Ver fichero

@@ -131,7 +131,7 @@ public class Itinerary implements ValueObject<Itinerary> {
131 131
   /**
132 132
    * @return The first leg on the itinerary.
133 133
    */
134
-  Leg firstLeg() {
134
+  public Leg firstLeg() {
135 135
     if (isEmpty()) return null;
136 136
     return legs.get(0);
137 137
   }
@@ -139,7 +139,7 @@ public class Itinerary implements ValueObject<Itinerary> {
139 139
   /**
140 140
    * @return The last leg on the itinerary.
141 141
    */
142
-  Leg lastLeg() {
142
+  public Leg lastLeg() {
143 143
     if (isEmpty()) return null;
144 144
     return legs.get(legs.size() - 1);
145 145
   }
@@ -151,19 +151,30 @@ public class Itinerary implements ValueObject<Itinerary> {
151 151
   public Itinerary withRescheduledVoyage(final Voyage rescheduledVoyage) {
152 152
     final List<Leg> newLegsList = new ArrayList<Leg>(this.legs.size());
153 153
 
154
+    Leg lastAdded = null;
154 155
     for (Leg leg : this.legs) {
155 156
       if (leg.voyage().sameIdentityAs(rescheduledVoyage)) {
156
-        newLegsList.add(leg.withRescheduledVoyage(rescheduledVoyage));
157
+        Leg modifiedLeg = leg.withRescheduledVoyage(rescheduledVoyage);
158
+        // This truncates the itinerary if the voyage rescheduling makes
159
+        // it impossible to maintain the old unload-load chain.
160
+        if (lastAdded != null && modifiedLeg.loadTime().before(lastAdded.unloadTime())) {
161
+          break;
162
+        }
163
+        newLegsList.add(modifiedLeg);
157 164
       } else {
158 165
         newLegsList.add(leg);
159 166
       }
167
+      lastAdded = leg;
160 168
     }
161 169
 
162 170
     return new Itinerary(newLegsList);
163 171
   }
164 172
 
173
+  /**
174
+   * @return A list of all locations on this itinerary.
175
+   */
165 176
   public List<Location> locations() {
166
-    List<Location> result = new ArrayList<Location>();
177
+    final List<Location> result = new ArrayList<Location>();
167 178
     result.add(firstLeg().loadLocation());
168 179
     for (Leg leg : legs) {
169 180
       result.add(leg.unloadLocation());
@@ -172,6 +183,33 @@ public class Itinerary implements ValueObject<Itinerary> {
172 183
   }
173 184
 
174 185
   /**
186
+   * @param location a location
187
+   * @return Load time at this location, or null if the location isn't on this itinerary.
188
+   */
189
+  public Date loadTimeAt(final Location location) {
190
+    for (Leg leg : legs) {
191
+      if (leg.loadLocation().sameIdentityAs(location)) {
192
+        return leg.loadTime();
193
+      }
194
+    }
195
+    return null;
196
+  }
197
+
198
+  /**
199
+   * @param location a location
200
+   * @return Unload time at this location, or null if the location isn't on this itinerary.
201
+   */
202
+  public Date unloadTimeAt(final Location location) {
203
+    for (Leg leg : legs) {
204
+      if (leg.unloadLocation().sameIdentityAs(location)) {
205
+        return leg.unloadTime();
206
+      }
207
+    }
208
+    return null;
209
+  }
210
+
211
+
212
+  /**
175 213
    * @param other itinerary to compare
176 214
    * @return <code>true</code> if the legs in this and the other itinerary are all equal.
177 215
    */

+ 7
- 6
dddsample/src/main/java/se/citerus/dddsample/domain/model/cargo/Leg.java Ver fichero

@@ -20,9 +20,14 @@ public class Leg implements ValueObject<Leg> {
20 20
   private Date loadTime;
21 21
   private Date unloadTime;
22 22
 
23
-  // TODO remove this ctor
23
+  // TODO hide this, use factory
24 24
   public Leg(Voyage voyage, Location loadLocation, Location unloadLocation, Date loadTime, Date unloadTime) {
25
-    Validate.noNullElements(new Object[]{voyage, loadLocation, unloadLocation, loadTime, unloadTime});
25
+    Validate.notNull(voyage, "Voyage is required");
26
+    Validate.notNull(loadLocation, "Load location is required");
27
+    Validate.notNull(unloadLocation, "Unload location is required");
28
+    Validate.isTrue(!loadLocation.sameIdentityAs(unloadLocation));
29
+    // TODO use a minimum time between unloading and loading?
30
+    Validate.isTrue(unloadTime.after(loadTime));
26 31
 
27 32
     this.voyage = voyage;
28 33
     this.loadLocation = loadLocation;
@@ -47,10 +52,6 @@ public class Leg implements ValueObject<Leg> {
47 52
    * @return A leg on this voyage between the given locations.
48 53
    */
49 54
   public static Leg deriveLeg(Voyage voyage, Location loadLocation, Location unloadLocation) {
50
-    Validate.notNull(voyage, "Voyage is required");
51
-    Validate.notNull(loadLocation, "Load location is required");
52
-    Validate.notNull(unloadLocation, "Unload location is required");
53
-    Validate.isTrue(!loadLocation.sameIdentityAs(unloadLocation));
54 55
     return new Leg(voyage, loadLocation, unloadLocation, voyage.schedule().departureTimeAt(loadLocation), voyage.schedule().arrivalTimeAt(unloadLocation));
55 56
   }
56 57
 

+ 2
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/model/voyage/Voyage.java Ver fichero

@@ -48,6 +48,8 @@ public class Voyage implements Entity<Voyage> {
48 48
    * @param newDepartureTime new departure time
49 49
    */
50 50
   public void departureRescheduled(final Location location, final Date newDepartureTime) {
51
+    // TODO Change departure/arrival by diffs instead of absolute times
52
+
51 53
     final int size = schedule.carrierMovements().size();
52 54
     final List<CarrierMovement> carrierMovements = new ArrayList<CarrierMovement>(size);
53 55
 

+ 65
- 12
dddsample/src/test/java/se/citerus/dddsample/scenario/VoyageRescheduledScenarioTest.java Ver fichero

@@ -6,43 +6,96 @@
6 6
  */
7 7
 package se.citerus.dddsample.scenario;
8 8
 
9
-import junit.framework.TestCase;
9
+import static org.hamcrest.MatcherAssert.assertThat;
10
+import static org.hamcrest.Matchers.is;
11
+import org.junit.Before;
12
+import org.junit.Test;
10 13
 import static se.citerus.dddsample.application.util.DateTestUtil.toDate;
14
+import se.citerus.dddsample.domain.model.cargo.Cargo;
15
+import se.citerus.dddsample.domain.model.cargo.CargoFactory;
11 16
 import se.citerus.dddsample.domain.model.cargo.Itinerary;
12 17
 import se.citerus.dddsample.domain.model.cargo.Leg;
18
+import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.MISROUTED;
19
+import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.ROUTED;
13 20
 import static se.citerus.dddsample.domain.model.location.SampleLocations.*;
14 21
 import static se.citerus.dddsample.domain.model.voyage.SampleVoyages.*;
15 22
 import se.citerus.dddsample.domain.model.voyage.Voyage;
16 23
 import se.citerus.dddsample.domain.model.voyage.VoyageNumber;
24
+import se.citerus.dddsample.infrastructure.persistence.inmemory.CargoRepositoryInMem;
25
+import se.citerus.dddsample.infrastructure.persistence.inmemory.LocationRepositoryInMem;
17 26
 
18 27
 import java.util.Date;
19 28
 
20
-public class VoyageRescheduledScenarioTest extends TestCase {
29
+public class VoyageRescheduledScenarioTest {
21 30
 
22
-  public void testVoyageRescheduled() throws Exception {
23
-    // Creating new voyages to avoid rescheduling shared ones, breaking other tests
24
-    Voyage voyage1 = new Voyage(new VoyageNumber("V1"), HONGKONG_TO_NEW_YORK.schedule());
25
-    Voyage voyage2 = new Voyage(new VoyageNumber("V2"), NEW_YORK_TO_DALLAS.schedule());
26
-    Voyage voyage3 = new Voyage(new VoyageNumber("V3"), DALLAS_TO_HELSINKI.schedule());
31
+  Cargo cargo;
32
+  Voyage voyage1;
33
+  Voyage voyage2;
34
+  Voyage voyage3;
27 35
 
36
+  @Before
37
+  public void setupCargo() {
38
+    // Creating new voyages to avoid rescheduling shared ones, breaking other tests
39
+    voyage1 = new Voyage(new VoyageNumber("V1"), HONGKONG_TO_NEW_YORK.schedule());
40
+    voyage2 = new Voyage(new VoyageNumber("V2"), NEW_YORK_TO_DALLAS.schedule());
41
+    voyage3 = new Voyage(new VoyageNumber("V3"), DALLAS_TO_HELSINKI.schedule());
42
+    CargoFactory cargoFactory = new CargoFactory(new CargoRepositoryInMem(), new LocationRepositoryInMem());
43
+    cargo = cargoFactory.newCargo(HANGZOU.unLocode(), STOCKHOLM.unLocode(), toDate("2008-12-23"));
28 44
     Itinerary itinerary = new Itinerary(
29 45
       Leg.deriveLeg(voyage1, HANGZOU, NEWYORK),
30 46
       Leg.deriveLeg(voyage2, NEWYORK, DALLAS),
31 47
       Leg.deriveLeg(voyage3, DALLAS, STOCKHOLM)
32 48
     );
49
+    cargo.assignToRoute(itinerary);
50
+  }
51
+
52
+  @Test
53
+  public void voyageIsRescheduledWithMaintainableRoute() {
54
+    assertThat(cargo.delivery().routingStatus(), is(ROUTED));
33 55
 
34 56
     Date oldDepartureTime = toDate("2008-10-24", "07:00");
35 57
 
36
-    assertEquals(itinerary.legs().get(1).loadTime(), oldDepartureTime);
37
-    assertEquals(voyage2.schedule().carrierMovements().get(0).departureTime(), oldDepartureTime);
58
+    assertThat(voyage2.schedule().departureTimeAt(NEWYORK), is(oldDepartureTime));
59
+    assertThat(cargo.itinerary().loadTimeAt(NEWYORK), is(oldDepartureTime));
38 60
 
61
+    // Now voyage2 is rescheduled, the departure from NYC is delayed a few hours.
39 62
     Date newDepartureTime = toDate("2008-10-24", "18:00");
63
+    voyage2.departureRescheduled(NEWYORK, newDepartureTime);
64
+
65
+    // The schedule of voyage2 is updated
66
+    assertThat(voyage2.schedule().departureTimeAt(NEWYORK), is(newDepartureTime));
67
+    // ...but the cargo itinerary still has the old departure time
68
+    assertThat(cargo.itinerary().loadTimeAt(NEWYORK), is(oldDepartureTime));
40 69
 
70
+    // Generate a new itinerary from the old one and assign the cargo to this route
71
+    Itinerary newItinerary = cargo.itinerary().withRescheduledVoyage(voyage2);
72
+    cargo.assignToRoute(newItinerary);
73
+
74
+    // Now the cargo aggregate is updated to reflect the scheduling change!
75
+    assertThat(cargo.itinerary().loadTimeAt(NEWYORK), is(newDepartureTime));
76
+    assertThat(cargo.delivery().routingStatus(), is(ROUTED));
77
+  }
78
+
79
+  @Test
80
+  public void voyageIsRescheduledWithUnmaintainableRoute() {
81
+    assertThat(cargo.delivery().routingStatus(), is(ROUTED));
82
+
83
+    // Voyage1 arrives in NYC at 2008-10-23 23:10
84
+    // Now rescheduling the departure of voyage2 to BEFORE
85
+    // voyage1 arrives in NYC. This makes it impossible to
86
+    // keep the latter part of the old itinerary, and the new itinerary
87
+    // is therefore truncated after unload in NYC.
88
+
89
+    Date newDepartureTime = toDate("2008-10-23", "18:30");
41 90
     voyage2.departureRescheduled(NEWYORK, newDepartureTime);
42
-    Itinerary newItinerary = itinerary.withRescheduledVoyage(voyage2);
43 91
 
44
-    assertEquals(voyage2.schedule().carrierMovements().get(0).departureTime(), newDepartureTime);
45
-    assertEquals(newItinerary.legs().get(1).loadTime(), newDepartureTime);
92
+    // Only the part of the itinerary up to and including NYC is maintainable, the rest is truncated
93
+    Itinerary truncatedItinerary = cargo.itinerary().withRescheduledVoyage(voyage2);
94
+    assertThat(truncatedItinerary.lastLeg().unloadLocation(), is(NEWYORK));
95
+
96
+    // The cargo enters MISROUTED state
97
+    cargo.assignToRoute(truncatedItinerary);
98
+    assertThat(cargo.delivery().routingStatus(), is(MISROUTED));
46 99
   }
47 100
 
48 101
 }