diff --git a/src/main/java/ch/naviqore/app/controller/RoutingController.java b/src/main/java/ch/naviqore/app/controller/RoutingController.java index 0b4d9a27..7ce2d268 100644 --- a/src/main/java/ch/naviqore/app/controller/RoutingController.java +++ b/src/main/java/ch/naviqore/app/controller/RoutingController.java @@ -16,16 +16,15 @@ import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; import java.util.EnumSet; import java.util.List; +import java.util.Map; import static ch.naviqore.app.dto.DtoMapper.map; @@ -42,9 +41,15 @@ public RoutingController(PublicTransitService service) { this.service = service; } - private static void handleConnectionRoutingException(ConnectionRoutingException e) { + private static ConnectionResponse handleConnectionRoutingException(ConnectionRoutingException e) { log.error("Connection routing exception", e); - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + return map(List.of(), e.getMessage(), MessageType.ERROR); + } + + private static IsoLineResponse handleConnectionRoutingException(ConnectionRoutingException e, TimeType timeType, + boolean returnConnections) { + log.error("Connection routing exception", e); + return map(Map.of(), timeType, returnConnections, e.getMessage(), MessageType.ERROR); } @Operation(summary = "Get information about the routing", description = "Get all relevant information about the routing features supported by the service.") @@ -62,21 +67,21 @@ public RoutingInfo getRoutingInfo() { @ApiResponse(responseCode = "400", description = "Invalid input parameters", content = @Content(schema = @Schema())) @ApiResponse(responseCode = "404", description = "StopID does not exist", content = @Content(schema = @Schema())) @GetMapping("/connections") - public List getConnections(@RequestParam(required = false) String sourceStopId, - @RequestParam(required = false) Double sourceLatitude, - @RequestParam(required = false) Double sourceLongitude, - @RequestParam(required = false) String targetStopId, - @RequestParam(required = false) Double targetLatitude, - @RequestParam(required = false) Double targetLongitude, - @RequestParam(required = false) LocalDateTime dateTime, - @RequestParam(required = false, defaultValue = "DEPARTURE") TimeType timeType, - @RequestParam(required = false) Integer maxWalkingDuration, - @RequestParam(required = false) Integer maxTransferNumber, - @RequestParam(required = false) Integer maxTravelTime, - @RequestParam(required = false, defaultValue = "0") int minTransferTime, - @RequestParam(required = false, defaultValue = "false") boolean wheelchairAccessible, - @RequestParam(required = false, defaultValue = "false") boolean bikeAllowed, - @RequestParam(required = false) EnumSet travelModes) { + public ConnectionResponse getConnections(@RequestParam(required = false) String sourceStopId, + @RequestParam(required = false) Double sourceLatitude, + @RequestParam(required = false) Double sourceLongitude, + @RequestParam(required = false) String targetStopId, + @RequestParam(required = false) Double targetLatitude, + @RequestParam(required = false) Double targetLongitude, + @RequestParam(required = false) LocalDateTime dateTime, + @RequestParam(required = false, defaultValue = "DEPARTURE") TimeType timeType, + @RequestParam(required = false) Integer maxWalkingDuration, + @RequestParam(required = false) Integer maxTransferNumber, + @RequestParam(required = false) Integer maxTravelTime, + @RequestParam(required = false, defaultValue = "0") int minTransferTime, + @RequestParam(required = false, defaultValue = "false") boolean wheelchairAccessible, + @RequestParam(required = false, defaultValue = "false") boolean bikeAllowed, + @RequestParam(required = false) EnumSet travelModes) { // get coordinates if available GeoCoordinate sourceCoordinate = Utils.getCoordinateIfAvailable(sourceStopId, sourceLatitude, sourceLongitude, GlobalValidator.StopType.SOURCE); @@ -106,8 +111,7 @@ public List getConnections(@RequestParam(required = false) String so return map(service.getConnections(sourceCoordinate, targetCoordinate, dateTime, map(timeType), config)); } } catch (ConnectionRoutingException e) { - handleConnectionRoutingException(e); - return null; + return handleConnectionRoutingException(e); } } @@ -116,19 +120,19 @@ public List getConnections(@RequestParam(required = false) String so @ApiResponse(responseCode = "400", description = "Invalid input parameters", content = @Content(schema = @Schema())) @ApiResponse(responseCode = "404", description = "StopID does not exist", content = @Content(schema = @Schema())) @GetMapping("/isolines") - public List getIsolines(@RequestParam(required = false) String sourceStopId, - @RequestParam(required = false) Double sourceLatitude, - @RequestParam(required = false) Double sourceLongitude, - @RequestParam(required = false) LocalDateTime dateTime, - @RequestParam(required = false, defaultValue = "DEPARTURE") TimeType timeType, - @RequestParam(required = false) Integer maxWalkingDuration, - @RequestParam(required = false) Integer maxTransferNumber, - @RequestParam(required = false) Integer maxTravelTime, - @RequestParam(required = false, defaultValue = "0") int minTransferTime, - @RequestParam(required = false, defaultValue = "false") boolean wheelchairAccessible, - @RequestParam(required = false, defaultValue = "false") boolean bikeAllowed, - @RequestParam(required = false) EnumSet travelModes, - @RequestParam(required = false, defaultValue = "false") boolean returnConnections) { + public IsoLineResponse getIsolines(@RequestParam(required = false) String sourceStopId, + @RequestParam(required = false) Double sourceLatitude, + @RequestParam(required = false) Double sourceLongitude, + @RequestParam(required = false) LocalDateTime dateTime, + @RequestParam(required = false, defaultValue = "DEPARTURE") TimeType timeType, + @RequestParam(required = false) Integer maxWalkingDuration, + @RequestParam(required = false) Integer maxTransferNumber, + @RequestParam(required = false) Integer maxTravelTime, + @RequestParam(required = false, defaultValue = "0") int minTransferTime, + @RequestParam(required = false, defaultValue = "false") boolean wheelchairAccessible, + @RequestParam(required = false, defaultValue = "false") boolean bikeAllowed, + @RequestParam(required = false) EnumSet travelModes, + @RequestParam(required = false, defaultValue = "false") boolean returnConnections) { // get stops or coordinates if available GeoCoordinate sourceCoordinate = Utils.getCoordinateIfAvailable(sourceStopId, sourceLatitude, sourceLongitude, @@ -150,8 +154,7 @@ public List getIsolines(@RequestParam(required = false) String s returnConnections); } } catch (ConnectionRoutingException e) { - handleConnectionRoutingException(e); - return null; + return handleConnectionRoutingException(e, timeType, returnConnections); } } diff --git a/src/main/java/ch/naviqore/app/dto/ConnectionResponse.java b/src/main/java/ch/naviqore/app/dto/ConnectionResponse.java new file mode 100644 index 00000000..28ffc95a --- /dev/null +++ b/src/main/java/ch/naviqore/app/dto/ConnectionResponse.java @@ -0,0 +1,15 @@ +package ch.naviqore.app.dto; + +import lombok.*; + +import java.util.List; + +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) +@EqualsAndHashCode +@ToString +@Getter +public class ConnectionResponse { + private final List connections; + private final String message; + private final MessageType messageType; +} diff --git a/src/main/java/ch/naviqore/app/dto/DtoMapper.java b/src/main/java/ch/naviqore/app/dto/DtoMapper.java index 3558af8d..2d448bd5 100644 --- a/src/main/java/ch/naviqore/app/dto/DtoMapper.java +++ b/src/main/java/ch/naviqore/app/dto/DtoMapper.java @@ -76,17 +76,26 @@ public static Connection map(ch.naviqore.service.Connection connection) { return new Connection(legs); } - public static List map(List connections) { - return connections.stream().map(DtoMapper::map).toList(); + public static ConnectionResponse map(List connections, String message, MessageType messageType) { + return new ConnectionResponse( connections.stream().map(DtoMapper::map).toList(), message, messageType); } - public static List map(Map connections, - TimeType timeType, boolean returnConnections) { + public static ConnectionResponse map(List connections) { + return map( connections, "", MessageType.SUCCESS); + } + + public static IsoLineResponse map(Map connections, + TimeType timeType, boolean returnConnections, String message, MessageType messageType) { List arrivals = new ArrayList<>(); for (Map.Entry entry : connections.entrySet()) { arrivals.add(new StopConnection(entry.getKey(), entry.getValue(), map(timeType), returnConnections)); } - return arrivals; + return new IsoLineResponse( arrivals, message, messageType); + } + + public static IsoLineResponse map(Map connections, + TimeType timeType, boolean returnConnections) { + return map(connections, timeType, returnConnections, "", MessageType.SUCCESS); } public static ScheduleValidity map(ch.naviqore.service.Validity validity) { diff --git a/src/main/java/ch/naviqore/app/dto/IsoLineResponse.java b/src/main/java/ch/naviqore/app/dto/IsoLineResponse.java new file mode 100644 index 00000000..f5ac1807 --- /dev/null +++ b/src/main/java/ch/naviqore/app/dto/IsoLineResponse.java @@ -0,0 +1,15 @@ +package ch.naviqore.app.dto; + +import lombok.*; + +import java.util.List; + +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) +@EqualsAndHashCode +@ToString +@Getter +public class IsoLineResponse { + private final List stopConnections; + private final String message; + private final MessageType messageType; +} diff --git a/src/main/java/ch/naviqore/app/dto/MessageType.java b/src/main/java/ch/naviqore/app/dto/MessageType.java new file mode 100644 index 00000000..2d5735a7 --- /dev/null +++ b/src/main/java/ch/naviqore/app/dto/MessageType.java @@ -0,0 +1,7 @@ +package ch.naviqore.app.dto; + +public enum MessageType { + SUCCESS, + WARNING, + ERROR, +} diff --git a/src/main/java/ch/naviqore/service/gtfs/raptor/convert/GtfsToRaptorConverter.java b/src/main/java/ch/naviqore/service/gtfs/raptor/convert/GtfsToRaptorConverter.java index 2ba75df7..cd514a7d 100644 --- a/src/main/java/ch/naviqore/service/gtfs/raptor/convert/GtfsToRaptorConverter.java +++ b/src/main/java/ch/naviqore/service/gtfs/raptor/convert/GtfsToRaptorConverter.java @@ -27,6 +27,7 @@ public class GtfsToRaptorConverter { private final Set addedStops = new HashSet<>(); + private final Set stopsForTransfers = new HashSet<>(); private final RaptorRouterBuilder builder; private final GtfsRoutePartitioner partitioner; private final List additionalTransfers; @@ -65,8 +66,7 @@ private void addRoute(GtfsRoutePartitioner.SubRoute subRoute) { List stopIds = subRoute.getStopsSequence().stream().map(Stop::getId).toList(); for (String stopId : stopIds) { if (!addedStops.contains(stopId)) { - builder.addStop(stopId); - addedStops.add(stopId); + this.addStop(stopId); } } @@ -99,8 +99,14 @@ private void addRoute(GtfsRoutePartitioner.SubRoute subRoute) { */ private void processAllTransfers() { addAdditionalTransfers(); - processStopAndParentChildTransfers(); - addGtfsTransfersWithPrecedence(); + // It is possible that stops are added without departures in this loop, in that case this block will be + // re-run to ensure that transfers from those newly added stops are also included. + while (!stopsForTransfers.isEmpty()) { + Set stopIterator = new HashSet<>(stopsForTransfers); + stopsForTransfers.clear(); + processStopAndParentChildTransfers(stopIterator); + addGtfsTransfersWithPrecedence(stopIterator); + } } /** @@ -108,15 +114,31 @@ private void processAllTransfers() { */ private void addAdditionalTransfers() { for (TransferGenerator.Transfer transfer : additionalTransfers) { - builder.addTransfer(transfer.from().getId(), transfer.to().getId(), transfer.duration()); + this.addTransfer(transfer.from().getId(), transfer.to().getId(), transfer.duration()); } } + private void addStop(String stopId) { + builder.addStop(stopId); + addedStops.add(stopId); + stopsForTransfers.add(stopId); + } + + private void addTransfer(String fromId, String toId, int duration) { + if (!addedStops.contains(fromId)) { + this.addStop(fromId); + } + if (!addedStops.contains(toId)) { + this.addStop(toId); + } + builder.addTransfer(fromId, toId, duration); + } + /** * Processes transfers for each stop and handles parent-child relationships. */ - private void processStopAndParentChildTransfers() { - for (String stopId : addedStops) { + private void processStopAndParentChildTransfers(Set stopIterator) { + for (String stopId : stopIterator) { Stop stop = schedule.getStops().get(stopId); processParentAndChildTransfersForStop(stop); } @@ -162,7 +184,7 @@ private void processParentAndChildTransfersForStop(Stop stop) { private void applyTransfersFromOtherStop(Stop consumerStop, Stop providerStop) { Collection transfers = expandTransfersFromStop(providerStop); for (TransferGenerator.Transfer transfer : transfers) { - builder.addTransfer(consumerStop.getId(), transfer.to().getId(), transfer.duration()); + this.addTransfer(consumerStop.getId(), transfer.to().getId(), transfer.duration()); } } @@ -177,11 +199,7 @@ private void addToStopChildrenTransfers(Stop stop) { if (stopTransfer.getTransferType() == TransferType.MINIMUM_TIME && stopTransfer.getMinTransferTime() .isPresent()) { for (Stop toChildStop : stopTransfer.getToStop().getChildren()) { - // only add new transfers if the to stop also has departures, else the raptor router does not care - // about this stop and the builder will throw an exception. - if (addedStops.contains(toChildStop.getId())) { - builder.addTransfer(stop.getId(), toChildStop.getId(), stopTransfer.getMinTransferTime().get()); - } + this.addTransfer(stop.getId(), toChildStop.getId(), stopTransfer.getMinTransferTime().get()); } } } @@ -190,16 +208,13 @@ private void addToStopChildrenTransfers(Stop stop) { /** * Adds transfers explicitly defined in the GTFS schedule, ensuring precedence over additional transfers. */ - private void addGtfsTransfersWithPrecedence() { - for (String stopId : addedStops) { + private void addGtfsTransfersWithPrecedence(Set stopIterator) { + for (String stopId : stopIterator) { Stop stop = schedule.getStops().get(stopId); for (Transfer transfer : stop.getTransfers()) { - // only add new transfers if the to stop also has departures, else the raptor router does not care about - // this stop and the builder will throw an exception. if (transfer.getTransferType() == TransferType.MINIMUM_TIME && transfer.getMinTransferTime() - .isPresent() && addedStops.contains(transfer.getToStop().getId())) { - builder.addTransfer(stop.getId(), transfer.getToStop().getId(), - transfer.getMinTransferTime().get()); + .isPresent()) { + this.addTransfer(stop.getId(), transfer.getToStop().getId(), transfer.getMinTransferTime().get()); } } } @@ -229,14 +244,10 @@ private Collection expandTransfersFromStop(Stop stop Stop toStop = transfer.getToStop(); // only add new transfers if the to stop also has departures, else the raptor router does not care about // this stop and the builder will throw an exception. - if (addedStops.contains(toStop.getId())) { - otherTransfers.add(new TransferGenerator.Transfer(stop, toStop, transfer.getMinTransferTime().get())); - } + otherTransfers.add(new TransferGenerator.Transfer(stop, toStop, transfer.getMinTransferTime().get())); for (Stop childToStop : toStop.getChildren()) { - if (addedStops.contains(childToStop.getId())) { - parentTransfers.put(childToStop, - new TransferGenerator.Transfer(stop, childToStop, transfer.getMinTransferTime().get())); - } + parentTransfers.put(childToStop, + new TransferGenerator.Transfer(stop, childToStop, transfer.getMinTransferTime().get())); } } diff --git a/src/test/java/ch/naviqore/app/controller/RoutingControllerTest.java b/src/test/java/ch/naviqore/app/controller/RoutingControllerTest.java index f3f6caa0..67fe1c33 100644 --- a/src/test/java/ch/naviqore/app/controller/RoutingControllerTest.java +++ b/src/test/java/ch/naviqore/app/controller/RoutingControllerTest.java @@ -109,7 +109,7 @@ static Stream provideQueryConfigTestCombinations() { hasAccessibilityInformation, hasBikeInformation, hasTravelModeInformation, null)); } - List getConnections(String sourceStopId, Double sourceLatitude, Double sourceLongitude, + ConnectionResponse getConnections(String sourceStopId, Double sourceLatitude, Double sourceLongitude, String targetStopId, Double targetLatitude, Double targetLongitude, LocalDateTime departureDateTime) { return routingController.getConnections(sourceStopId, sourceLatitude, sourceLongitude, targetStopId, @@ -117,7 +117,7 @@ List getConnections(String sourceStopId, Double sourceLatitude, Doub false, null); } - List getIsolines(String sourceStopId, Double sourceLatitude, Double sourceLongitude, + IsoLineResponse getIsolines(String sourceStopId, Double sourceLatitude, Double sourceLongitude, LocalDateTime departureDateTime, TimeType timeType, boolean returnConnections) { return routingController.getIsolines(sourceStopId, sourceLatitude, sourceLongitude, departureDateTime, timeType, null, null, null, 0, false, false, null, returnConnections); @@ -138,11 +138,11 @@ void testWithValidSourceAndTargetStopIds() { LocalDateTime departureDateTime = LocalDateTime.now(); // Act - List connections = getConnections(sourceStopId, null, null, targetStopId, null, null, + ConnectionResponse response = getConnections(sourceStopId, null, null, targetStopId, null, null, departureDateTime); // Assert - assertNotNull(connections); + assertNotNull(response.getConnections()); } @Test @@ -154,11 +154,11 @@ void testWithoutSourceStopIdButWithCoordinates() { LocalDateTime departureDateTime = LocalDateTime.now(); // Act - List connections = getConnections(null, sourceLatitude, sourceLongitude, targetStopId, null, + ConnectionResponse response = getConnections(null, sourceLatitude, sourceLongitude, targetStopId, null, null, departureDateTime); // Assert - assertNotNull(connections); + assertNotNull(response); } @Test @@ -256,7 +256,7 @@ void testInvalidCoordinates() { @ParameterizedTest(name = "connectionQueryConfig_{0}") @MethodSource("provideQueryConfigTestCombinations") - void testQueryConfigValues(String name, Integer maxWalkingDuration, Integer maxTransferDuration, + void testQueryConfigValues(String ignoredName, Integer maxWalkingDuration, Integer maxTransferDuration, Integer maxTravelTime, int minTransferTime, boolean wheelChairAccessible, boolean bikeAllowed, EnumSet travelModes, boolean hasAccessibilityInformation, boolean hasBikeInformation, @@ -296,7 +296,7 @@ void testFromStopReturnConnectionsFalse() { // Act List stopConnections = routingController.getIsolines(sourceStopId, null, null, time, - TimeType.DEPARTURE, 30, 2, 120, 5, false, false, null, false); + TimeType.DEPARTURE, 30, 2, 120, 5, false, false, null, false).getStopConnections(); assertNotNull(stopConnections); @@ -319,7 +319,7 @@ void testFromStopReturnConnectionsTrue() { LocalDateTime expectedStartTime = LocalDateTime.now(); List stopConnections = routingController.getIsolines(sourceStopId, null, null, null, - TimeType.DEPARTURE, 30, 2, 120, 5, false, false, null, true); + TimeType.DEPARTURE, 30, 2, 120, 5, false, false, null, true).getStopConnections(); assertNotNull(stopConnections); @@ -363,7 +363,7 @@ void testFromCoordinatesReturnConnectionsFalse() { // Act List stopConnections = getIsolines(null, sourceLatitude, sourceLongitude, time, - TimeType.DEPARTURE, false); + TimeType.DEPARTURE, false).getStopConnections(); assertNotNull(stopConnections); @@ -386,7 +386,7 @@ void testFromCoordinateReturnConnectionsTrue() { LocalDateTime expectedStartTime = LocalDateTime.now(); List stopConnections = getIsolines(null, sourceCoordinate.latitude(), - sourceCoordinate.longitude(), null, TimeType.DEPARTURE, true); + sourceCoordinate.longitude(), null, TimeType.DEPARTURE, true).getStopConnections(); assertNotNull(stopConnections); @@ -427,7 +427,7 @@ void testFromStopReturnConnectionsTrueTimeTypeArrival() { // Arrange String sourceStopId = "G"; - List stopConnections = getIsolines(sourceStopId, null, null, null, TimeType.ARRIVAL, true); + List stopConnections = getIsolines(sourceStopId, null, null, null, TimeType.ARRIVAL, true).getStopConnections(); // This tests if the time is set to now if null LocalDateTime expectedArrivalTime = LocalDateTime.now(); @@ -473,7 +473,7 @@ void testFromCoordinateReturnConnectionsTrueTimeTypeArrival() { GeoCoordinate sourceCoordinate = new GeoCoordinate(46.2044, 6.1432); List stopConnections = getIsolines(null, sourceCoordinate.latitude(), - sourceCoordinate.longitude(), null, TimeType.ARRIVAL, true); + sourceCoordinate.longitude(), null, TimeType.ARRIVAL, true).getStopConnections(); // This tests if the time is set to now if null LocalDateTime expectedArrivalTime = LocalDateTime.now(); @@ -559,7 +559,7 @@ void testInvalidCoordinates() { @ParameterizedTest(name = "isolineQueryConfig_{0}") @MethodSource("provideQueryConfigTestCombinations") - void testQueryConfigValues(String name, Integer maxWalkingDuration, Integer maxTransferDuration, + void testQueryConfigValues(String ignoredName, Integer maxWalkingDuration, Integer maxTransferDuration, Integer maxTravelTime, int minTransferTime, boolean wheelChairAccessible, boolean bikeAllowed, EnumSet travelModes, boolean hasAccessibilityInformation, boolean hasBikeInformation, diff --git a/src/test/java/ch/naviqore/service/gtfs/raptor/convert/GtfsToRaptorConverterIT.java b/src/test/java/ch/naviqore/service/gtfs/raptor/convert/GtfsToRaptorConverterIT.java index d41a33f0..3f52135b 100644 --- a/src/test/java/ch/naviqore/service/gtfs/raptor/convert/GtfsToRaptorConverterIT.java +++ b/src/test/java/ch/naviqore/service/gtfs/raptor/convert/GtfsToRaptorConverterIT.java @@ -144,15 +144,16 @@ void noTransfers() throws NoSuchFieldException, IllegalAccessException { @Test void sameStopTransfersOnAllActiveStops() throws NoSuchFieldException, IllegalAccessException { - // since C is also a parent stop, additional transfer C1 -> C and C -> C1 will also be generated + // since C is also a parent stop, additional transfer C1 -> C and C -> C1 will also be generated and + // C2 -> C, C -> C2, C1 -> C2, C2 -> C1 are also created even though C2 does not have departures. RaptorBuilderData data = convertRaptor( List.of(new Transfer("A", "A", 120), new Transfer("B1", "B1", 120), new Transfer("B2", "B2", 120), new Transfer("C", "C", 120), new Transfer("C1", "C1", 120)), List.of()); - data.assertNumStops(5); - data.assertNumSameStopTransfers(5); + data.assertNumStops(6); + data.assertNumSameStopTransfers(6); // no way to test further as the Raptor.Transfer is not public - data.assertNumNonSameStopTransfers(2); - List existingStops = List.of("A", "B1", "B2", "C", "C1"); + data.assertNumNonSameStopTransfers(6); + List existingStops = List.of("A", "B1", "B2", "C", "C1", "C2"); for (String existingStop : existingStops) { data.assertStopExists(existingStop); data.assertSameStopTransferDuration(existingStop, 120); @@ -161,18 +162,18 @@ void sameStopTransfersOnAllActiveStops() throws NoSuchFieldException, IllegalAcc @Test void sameStopTransfersOnParentStops() throws NoSuchFieldException, IllegalAccessException { - // since B is not active, but B1 and B2 are active, it should create B1-B1, B1-B2, B2-B2, B2-B1 + // though B is not active, B1 and B2 are active, it should create B1-B1, B1-B2, B2-B2, B2-B1 // and C and C1 are active thus will have C-C1, C-C, C1-C1, C1-C - // even though D is specified, it should not be included and the stop should not be created because it does - // not have any departures + // also C2 and B will be included even though they do not have departures, with following extra transfers: + // B-B, C2-C2, B-B1, B-B2, B1-B, B2-B, C2-C, C2-C1, C-C2, C1-C2 RaptorBuilderData data = convertRaptor( List.of(new Transfer("A", "A", 120), new Transfer("B", "B", 120), new Transfer("C", "C", 120), new Transfer("D", "D", 120)), List.of()); - data.assertNumStops(5); - data.assertNumSameStopTransfers(5); + data.assertNumStops(7); + data.assertNumSameStopTransfers(7); // no way to test further as the Raptor.Transfer is not public - data.assertNumNonSameStopTransfers(4); - List existingStops = List.of("A", "B1", "B2", "C", "C1"); + data.assertNumNonSameStopTransfers(12); + List existingStops = List.of("A", "B", "B1", "B2", "C", "C1", "C2"); for (String existingStop : existingStops) { data.assertStopExists(existingStop); data.assertSameStopTransferDuration(existingStop, 120); @@ -186,25 +187,28 @@ void sameStopTransfersOnParentAndChildStops() throws NoSuchFieldException, Illeg RaptorBuilderData data = convertRaptor( List.of(new Transfer("B", "B", 120), new Transfer("B1", "B1", 60), new Transfer("B2", "B2", 60)), List.of()); - data.assertNumStops(5); - data.assertNumSameStopTransfers(2); + data.assertNumStops(6); + data.assertNumSameStopTransfers(3); // no way to test further as the Raptor.Transfer is not public and make sure that transfer time is 120 - data.assertNumNonSameStopTransfers(2); - List stops = List.of("B1", "B2"); + data.assertNumNonSameStopTransfers(6); + List stops = List.of("B", "B1", "B2"); for (String stop : stops) { data.assertStopExists(stop); - data.assertSameStopTransferDuration(stop, 60); + int expectedDuration = stop.equals("B") ? 120 : 60; + data.assertSameStopTransferDuration(stop, expectedDuration); } } @Test void betweenStopTransfersOnParentStops() throws NoSuchFieldException, IllegalAccessException { - // since B1, B2, C, and C1 are active following transfers should be derived from B-C: + // since B1, B2, C, and C1 are active following transfers should be derived from B-C and C-B: // B1-C, B1-C1, B2-C, B2-C1, C-B1, C-B2, C1-B1, C1-B2 + // also transfers between stops from B and C with no departures (B and C2) should be generated: + // B-C, B-C1, B-C2, B1-C2, B2-C2 C-B, C1-B, C2-B, C2-B1, C2-B2 RaptorBuilderData data = convertRaptor(List.of(new Transfer("B", "C", 120), new Transfer("C", "B", 120)), List.of()); data.assertNumSameStopTransfers(0); - data.assertNumNonSameStopTransfers(8); + data.assertNumNonSameStopTransfers(18); } @Test @@ -274,11 +278,11 @@ void assertNumStops(int numStops) { } void assertStopExists(String stopId) { - assertThat(stops.containsKey(stopId)).isTrue(); + assertThat(stops.containsKey(stopId)).as("stop does not exist: " + stopId).isTrue(); } void assertStopNotExists(String stopId) { - assertThat(stops.containsKey(stopId)).isFalse(); + assertThat(stops.containsKey(stopId)).as("stop does exist: " + stopId).isFalse(); } void assertStopHasNumRoutes(String stopId, int numRoutes) {