diff --git a/src/main/java/ch/naviqore/app/controller/RoutingController.java b/src/main/java/ch/naviqore/app/controller/RoutingController.java index af2866f7..8451fbe0 100644 --- a/src/main/java/ch/naviqore/app/controller/RoutingController.java +++ b/src/main/java/ch/naviqore/app/controller/RoutingController.java @@ -2,7 +2,7 @@ import ch.naviqore.app.dto.Connection; import ch.naviqore.app.dto.DtoMapper; -import ch.naviqore.app.dto.EarliestArrival; +import ch.naviqore.app.dto.StopConnection; import ch.naviqore.app.dto.TimeType; import ch.naviqore.service.PublicTransitService; import ch.naviqore.service.Stop; @@ -129,15 +129,16 @@ public List getConnections(@RequestParam(required = false) String so } @GetMapping("/isolines") - public List getIsolines(@RequestParam(required = false) String sourceStopId, - @RequestParam(required = false, defaultValue = "-91") double sourceLatitude, - @RequestParam(required = false, defaultValue = "-181") double sourceLongitude, - @RequestParam(required = false) LocalDateTime dateTime, - @RequestParam(required = false, defaultValue = "DEPARTURE") TimeType timeType, - @RequestParam(required = false, defaultValue = "2147483647") int maxWalkingDuration, - @RequestParam(required = false, defaultValue = "2147483647") int maxTransferNumber, - @RequestParam(required = false, defaultValue = "2147483647") int maxTravelTime, - @RequestParam(required = false, defaultValue = "0") int minTransferTime) { + public List getIsolines(@RequestParam(required = false) String sourceStopId, + @RequestParam(required = false, defaultValue = "-91") double sourceLatitude, + @RequestParam(required = false, defaultValue = "-181") double sourceLongitude, + @RequestParam(required = false) LocalDateTime dateTime, + @RequestParam(required = false, defaultValue = "DEPARTURE") TimeType timeType, + @RequestParam(required = false, defaultValue = "2147483647") int maxWalkingDuration, + @RequestParam(required = false, defaultValue = "2147483647") int maxTransferNumber, + @RequestParam(required = false, defaultValue = "2147483647") int maxTravelTime, + @RequestParam(required = false, defaultValue = "0") int minTransferTime, + @RequestParam(required = false, defaultValue = "false") boolean returnConnections) { GeoCoordinate sourceCoordinate = null; if (sourceStopId == null) { @@ -163,12 +164,12 @@ public List getIsolines(@RequestParam(required = false) String connections = service.getIsoLines(sourceCoordinate, dateTime, map(timeType), config); } - List arrivals = new ArrayList<>(); + List arrivals = new ArrayList<>(); for (Map.Entry entry : connections.entrySet()) { Stop stop = entry.getKey(); ch.naviqore.service.Connection connection = entry.getValue(); - arrivals.add(map(stop, connection)); + arrivals.add(new StopConnection(stop, connection, timeType, returnConnections)); } return arrivals; diff --git a/src/main/java/ch/naviqore/app/dto/DtoMapper.java b/src/main/java/ch/naviqore/app/dto/DtoMapper.java index 47ee787d..c4a27683 100644 --- a/src/main/java/ch/naviqore/app/dto/DtoMapper.java +++ b/src/main/java/ch/naviqore/app/dto/DtoMapper.java @@ -60,10 +60,6 @@ public static Connection map(ch.naviqore.service.Connection connection) { return new Connection(legs); } - public static EarliestArrival map(ch.naviqore.service.Stop stop, ch.naviqore.service.Connection connection) { - return new EarliestArrival(map(stop), connection.getArrivalTime(), map(connection)); - } - private static class LegVisitorImpl implements LegVisitor { @Override public Leg visit(PublicTransitLeg publicTransitLeg) { diff --git a/src/main/java/ch/naviqore/app/dto/EarliestArrival.java b/src/main/java/ch/naviqore/app/dto/EarliestArrival.java deleted file mode 100644 index 52c48cd2..00000000 --- a/src/main/java/ch/naviqore/app/dto/EarliestArrival.java +++ /dev/null @@ -1,20 +0,0 @@ -package ch.naviqore.app.dto; - -import lombok.*; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -@RequiredArgsConstructor(access = AccessLevel.PACKAGE) -@EqualsAndHashCode -@ToString -@Getter -public class EarliestArrival { - - private final Stop stop; - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) - private final LocalDateTime arrivalTime; - private final Connection connection; - -} - diff --git a/src/main/java/ch/naviqore/app/dto/StopConnection.java b/src/main/java/ch/naviqore/app/dto/StopConnection.java new file mode 100644 index 00000000..6ee81b59 --- /dev/null +++ b/src/main/java/ch/naviqore/app/dto/StopConnection.java @@ -0,0 +1,119 @@ +package ch.naviqore.app.dto; + +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * This class represents a connection between a stop and a spawn source (iso-line) in a transportation network. It + * contains information about the stop, the leg closest to the target stop, and the connection itself. + */ +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) +@EqualsAndHashCode +@ToString +@Getter +public class StopConnection { + + private final Stop stop; + private Leg connectingLeg; + private Connection connection; + + /** + * Constructs a new StopConnection object. + * + * @param serviceStop The stop from the service. + * @param serviceConnection The connection from the service. + * @param timeType The type of time (DEPARTURE or ARRIVAL), needed to construct the connecting leg. + * @param returnConnections A boolean indicating whether to return connections and trip stop times. + */ + public StopConnection(ch.naviqore.service.Stop serviceStop, ch.naviqore.service.Connection serviceConnection, + TimeType timeType, boolean returnConnections) { + + this.stop = DtoMapper.map(serviceStop); + this.connection = DtoMapper.map(serviceConnection); + if (timeType == TimeType.DEPARTURE) { + prepareDepartureConnectingLeg(); + } else { + prepareArrivalConnectingLeg(); + } + if (!returnConnections) { + reduceData(); + } + } + + /** + * Finds the index of a stop time in a trip for a given stop and time. + * + * @param trip The trip to search in. + * @param stop The stop to find. + * @param time The time to match. + * @param timeType The type of time to match (DEPARTURE or ARRIVAL). + * @return The index of the stop time in the trip. + */ + private static int findStopTimeIndexInTrip(Trip trip, Stop stop, LocalDateTime time, TimeType timeType) { + List stopTimes = trip.getStopTimes(); + for (int i = 0; i < stopTimes.size(); i++) { + StopTime stopTime = stopTimes.get(i); + if (stopTime.getStop().equals(stop)) { + if (timeType == TimeType.DEPARTURE && stopTime.getDepartureTime().equals(time)) { + return i; + } else if (timeType == TimeType.ARRIVAL && stopTime.getArrivalTime().equals(time)) { + return i; + } + } + } + throw new IllegalStateException("Stop time not found in trip."); + } + + /** + * Prepares the connecting leg for a departure connection (i.e. builds a leg from the second last to the last stop + * in the connection). + */ + private void prepareDepartureConnectingLeg() { + connectingLeg = this.connection.getLegs().getLast(); + if (connectingLeg.getTrip() == null) { + return; + } + int stopTimeIndex = findStopTimeIndexInTrip(connectingLeg.getTrip(), connectingLeg.getToStop(), + connectingLeg.getArrivalTime(), TimeType.ARRIVAL); + StopTime sourceStopTime = connectingLeg.getTrip().getStopTimes().get(stopTimeIndex - 1); + connectingLeg = new Leg(connectingLeg.getType(), sourceStopTime.getStop().getCoordinates(), + connectingLeg.getTo(), sourceStopTime.getStop(), connectingLeg.getToStop(), + sourceStopTime.getDepartureTime(), connectingLeg.getArrivalTime(), connectingLeg.getTrip()); + } + + /** + * Prepares the connecting leg for an arrival connection (i.e. builds a leg from the first to the second stop in the + * connection). + */ + private void prepareArrivalConnectingLeg() { + connectingLeg = this.connection.getLegs().getFirst(); + if (connectingLeg.getTrip() == null) { + return; + } + int stopTimeIndex = findStopTimeIndexInTrip(connectingLeg.getTrip(), connectingLeg.getFromStop(), + connectingLeg.getDepartureTime(), TimeType.DEPARTURE); + StopTime targetStopTime = connectingLeg.getTrip().getStopTimes().get(stopTimeIndex + 1); + connectingLeg = new Leg(connectingLeg.getType(), connectingLeg.getFrom(), + targetStopTime.getStop().getCoordinates(), connectingLeg.getFromStop(), targetStopTime.getStop(), + connectingLeg.getDepartureTime(), targetStopTime.getArrivalTime(), connectingLeg.getTrip()); + } + + /** + * Reduces the data of the StopConnection object by setting the connection to null and nullifying the stop times in + * the trip of the connecting leg. + */ + private void reduceData() { + connection = null; + if (connectingLeg.getTrip() == null) { + return; + } + Trip reducedTrip = new Trip(connectingLeg.getTrip().getHeadSign(), connectingLeg.getTrip().getRoute(), null); + connectingLeg = new Leg(connectingLeg.getType(), connectingLeg.getFrom(), connectingLeg.getTo(), + connectingLeg.getFromStop(), connectingLeg.getToStop(), connectingLeg.getDepartureTime(), + connectingLeg.getArrivalTime(), reducedTrip); + } + +} + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 27fb8f06..2b57e796 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -10,6 +10,7 @@ logging.level.root=${LOG_LEVEL:INFO} # value is a file path, the GTFS is loaded from the file and the update interval is ignored. Examples: # - gtfs.static.uri=benchmark/input/switzerland.zip # - gtfs.static.uri=https://opentransportdata.swiss/en/dataset/timetable-2024-gtfs2020/permalink +# - gtfs.static.uri=https://connolly.ch/zuerich-trams.zip gtfs.static.uri=${GTFS_STATIC_URI:src/test/resources/ch/naviqore/gtfs/schedule/sample-feed-1.zip} # Cron expression for updating the static GTFS feed from the provided URL. Public transit agencies update their static # GTFS data regularly. Set this interval to match the agency's publish schedule. Default is to update the schedule diff --git a/src/main/resources/ch.naviqore.app/openapi.yaml b/src/main/resources/ch.naviqore.app/openapi.yaml index d8f13d54..6d2d5adf 100644 --- a/src/main/resources/ch.naviqore.app/openapi.yaml +++ b/src/main/resources/ch.naviqore.app/openapi.yaml @@ -272,6 +272,11 @@ paths: schema: type: integer description: The minimum transfer time between trips in seconds. Defaults to `0`. + - name: returnConnections + in: query + schema: + type: boolean + description: Whether to return the connections for each reachable stop, else the connection field will be null. Defaults to `false`. responses: '200': description: A list of stop and fastest connection pairs for each reachable stop. @@ -280,7 +285,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/EarliestArrival' + $ref: '#/components/schemas/StopConnection' '400': description: Invalid input parameters '404': @@ -346,14 +351,13 @@ components: format: date-time trip: $ref: '#/components/schemas/Trip' - EarliestArrival: + StopConnection: type: object properties: stop: $ref: '#/components/schemas/Stop' - arrivalTime: - type: string - format: date-time + connectingLeg: + $ref: '#/components/schemas/Leg' connection: $ref: '#/components/schemas/Connection' Coordinate: @@ -400,7 +404,6 @@ components: - STARTS_WITH - CONTAINS - ENDS_WITH - - FUZZY TIME_TYPE: type: string enum: diff --git a/src/test/java/ch/naviqore/app/controller/DummyService.java b/src/test/java/ch/naviqore/app/controller/DummyService.java new file mode 100644 index 00000000..927841d7 --- /dev/null +++ b/src/test/java/ch/naviqore/app/controller/DummyService.java @@ -0,0 +1,380 @@ +package ch.naviqore.app.controller; + +import ch.naviqore.service.*; +import ch.naviqore.service.config.ConnectionQueryConfig; +import ch.naviqore.service.exception.RouteNotFoundException; +import ch.naviqore.service.exception.StopNotFoundException; +import ch.naviqore.service.exception.TripNotActiveException; +import ch.naviqore.service.exception.TripNotFoundException; +import ch.naviqore.utils.spatial.GeoCoordinate; +import lombok.NoArgsConstructor; +import org.jetbrains.annotations.Nullable; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; + +@NoArgsConstructor +class DummyService implements PublicTransitService { + + static final DummyServiceModels.Stop STOP_A = new DummyServiceModels.Stop("A", "Stop A", new GeoCoordinate(0, 0)); + static final DummyServiceModels.Stop STOP_B = new DummyServiceModels.Stop("B", "Stop B", new GeoCoordinate(1, 1)); + static final DummyServiceModels.Stop STOP_C = new DummyServiceModels.Stop("C", "Stop C", new GeoCoordinate(2, 2)); + static final DummyServiceModels.Stop STOP_D = new DummyServiceModels.Stop("D", "Stop D", new GeoCoordinate(3, 3)); + static final DummyServiceModels.Stop STOP_E = new DummyServiceModels.Stop("E", "Stop E", new GeoCoordinate(4, 4)); + static final DummyServiceModels.Stop STOP_F = new DummyServiceModels.Stop("F", "Stop F", new GeoCoordinate(5, 5)); + static final DummyServiceModels.Stop STOP_G = new DummyServiceModels.Stop("G", "Stop G", new GeoCoordinate(6, 6)); + static final DummyServiceModels.Stop STOP_H = new DummyServiceModels.Stop("H", "Stop H", new GeoCoordinate(7, 7)); + + static final List STOPS = List.of(STOP_A, STOP_B, STOP_C, STOP_D, STOP_E, STOP_F, STOP_G, + STOP_H); + + private record RouteData(DummyServiceModels.Route route, List stops) { + } + + private static final RouteData ROUTE_1 = new RouteData( + new DummyServiceModels.Route("1", "Route 1", "R1", "BUS", "Agency 1"), + List.of(STOP_A, STOP_B, STOP_C, STOP_D, STOP_E, STOP_F, STOP_G)); + private static final RouteData ROUTE_2 = new RouteData( + new DummyServiceModels.Route("2", "Route 2", "R2", "BUS", "Agency 2"), + List.of(STOP_A, STOP_B, STOP_C, STOP_D)); + private static final RouteData ROUTE_3 = new RouteData( + new DummyServiceModels.Route("3", "Route 3", "R3", "BUS", "Agency 3"), + List.of(STOP_D, STOP_E, STOP_F, STOP_G, STOP_H)); + + static final List ROUTES = List.of(ROUTE_1, ROUTE_2, ROUTE_3); + + @Override + public void updateStaticSchedule() { + + } + + @Override + public List getConnections(GeoCoordinate source, GeoCoordinate target, LocalDateTime time, + TimeType timeType, ConnectionQueryConfig config) { + return List.of(DummyConnectionGenerators.getSimpleConnection(source, target, time, timeType)); + } + + @Override + public List getConnections(Stop source, Stop target, LocalDateTime time, TimeType timeType, + ConnectionQueryConfig config) { + List connections = new ArrayList<>(); + connections.add(DummyConnectionGenerators.getSimpleConnection(source, target, time, timeType)); + try { + connections.add( + DummyConnectionGenerators.getConnectionWithSameStopTransfer(source, target, time, timeType)); + } catch (IllegalArgumentException e) { + // ignore + } + return connections; + } + + @Override + public List getConnections(GeoCoordinate source, Stop target, LocalDateTime time, TimeType timeType, + ConnectionQueryConfig config) { + return List.of(DummyConnectionGenerators.getSimpleConnection(source, target, time, timeType)); + } + + @Override + public List getConnections(Stop source, GeoCoordinate target, LocalDateTime time, TimeType timeType, + ConnectionQueryConfig config) { + return List.of(DummyConnectionGenerators.getSimpleConnection(source, target, time, timeType)); + } + + @Override + public Map getIsoLines(GeoCoordinate source, LocalDateTime time, TimeType timeType, + ConnectionQueryConfig config) { + Map connections = new HashMap<>(); + for (Stop stop : STOPS) { + try { + if (timeType == TimeType.DEPARTURE) { + connections.put(stop, + DummyConnectionGenerators.getSimpleConnection(source, stop, time, timeType)); + } else { + connections.put(stop, + DummyConnectionGenerators.getSimpleConnection(stop, source, time, timeType)); + } + } catch (IllegalArgumentException e) { + // ignore + } + } + return connections; + } + + @Override + public Map getIsoLines(Stop source, LocalDateTime time, TimeType timeType, + ConnectionQueryConfig config) { + Map connections = new HashMap<>(); + for (Stop stop : STOPS) { + if (source == stop) { + continue; + } + try { + if (timeType == TimeType.DEPARTURE) { + connections.put(stop, + DummyConnectionGenerators.getSimpleConnection(source, stop, time, timeType)); + } else { + connections.put(stop, + DummyConnectionGenerators.getSimpleConnection(stop, source, time, timeType)); + } + } catch (IllegalArgumentException e) { + // ignore + } + } + return connections; + } + + @Override + public List getStops(String like, SearchType searchType) { + return STOPS.stream().map(x -> (Stop) x).toList(); + } + + @Override + public Optional getNearestStop(GeoCoordinate location) { + return Optional.of(STOP_A); + } + + @Override + public List getNearestStops(GeoCoordinate location, int radius, int limit) { + if (radius > 100) { + return List.of(STOP_A, STOP_B, STOP_C); + } else { + return List.of(); + } + } + + @Override + public List getNextDepartures(Stop stop, LocalDateTime from, @Nullable LocalDateTime until, int limit) { + return List.of(); + } + + @Override + public Stop getStopById(String stopId) throws StopNotFoundException { + return STOPS.stream() + .filter(stop -> stop.getId().equals(stopId)) + .findFirst() + .orElseThrow(() -> new StopNotFoundException(stopId)); + } + + @Override + public Trip getTripById(String tripId, LocalDate date) throws TripNotFoundException, TripNotActiveException { + if (tripId.equals("not_existing_trip")) { + throw new TripNotFoundException(tripId); + } else if (date.isEqual(LocalDate.of(2021, 1, 1))) { + throw new TripNotActiveException(tripId, date); + } else { + PublicTransitLeg leg = DummyConnectionGenerators.getPublicTransitLeg(ROUTE_1, STOP_A, STOP_G, + date.atTime(8, 0), TimeType.DEPARTURE); + return leg.getTrip(); + } + } + + @Override + public Route getRouteById(String routeId) throws RouteNotFoundException { + return ROUTES.stream() + .filter(routeData -> routeData.route.getId().equals(routeId)) + .map(routeData -> routeData.route) + .findFirst() + .orElseThrow(() -> new RouteNotFoundException(routeId)); + } + + static class DummyConnectionGenerators { + private static final int SECONDS_BETWEEN_STOPS = 300; + private static final int DISTANCE_BETWEEN_STOPS = 100; + + static DummyServiceModels.Connection getSimpleConnection(Stop startStop, Stop endStop, LocalDateTime date, + TimeType timeType) { + if (startStop == endStop) { + throw new IllegalArgumentException("Start and end stop must be different."); + } else if (endStop == STOP_H) { + // downcast start stop to DummyServiceModels.Stop + return getConnectionWithFinalWalkTransfer((DummyServiceModels.Stop) startStop, + (DummyServiceModels.Stop) endStop, date, timeType); + } + DummyServiceModels.PublicTransitLeg leg = getPublicTransitLeg(ROUTE_1, startStop, endStop, date, timeType); + return new DummyServiceModels.Connection(List.of(leg)); + } + + static DummyServiceModels.Connection getConnectionWithSameStopTransfer(Stop startStop, Stop endStop, + LocalDateTime date, TimeType timeType) { + if (startStop == endStop) { + throw new IllegalArgumentException("Start and end stop must be different."); + } else if (startStop == STOP_D || endStop == STOP_D) { + throw new IllegalArgumentException("Stop D cannot be used for same stop transfer."); + } else if (!ROUTE_3.stops().contains((DummyServiceModels.Stop) endStop)) { + throw new IllegalArgumentException("End stop must be part of Route 3."); + } else if (!ROUTE_2.stops().contains((DummyServiceModels.Stop) startStop)) { + throw new IllegalArgumentException("Start stop must be part of Route 2."); + } + DummyServiceModels.PublicTransitLeg firstLeg; + DummyServiceModels.PublicTransitLeg secondLeg; + if (timeType == TimeType.DEPARTURE) { + firstLeg = getPublicTransitLeg(ROUTE_2, startStop, STOP_D, date, timeType); + LocalDateTime departureSecondLeg = firstLeg.getArrival().getArrivalTime().plusMinutes(5); + secondLeg = getPublicTransitLeg(ROUTE_3, STOP_D, endStop, departureSecondLeg, timeType); + } else { + secondLeg = getPublicTransitLeg(ROUTE_3, STOP_D, endStop, date, timeType); + LocalDateTime departureFirstLeg = secondLeg.getDeparture().getDepartureTime().minusMinutes(5); + firstLeg = getPublicTransitLeg(ROUTE_2, startStop, STOP_D, departureFirstLeg, timeType); + } + + return new DummyServiceModels.Connection(List.of(firstLeg, secondLeg)); + } + + static DummyServiceModels.Connection getConnectionWithFinalWalkTransfer(DummyServiceModels.Stop startStop, + DummyServiceModels.Stop endStop, + LocalDateTime date, TimeType timeType) { + if (startStop == endStop) { + throw new IllegalArgumentException("Start and end stop must be different."); + } + int endStopIndex = STOPS.indexOf(endStop); + if (endStopIndex == -1) { + throw new IllegalArgumentException("End stop not found in stops."); + } + int startStopIndex = STOPS.indexOf(startStop); + if (startStopIndex == -1) { + throw new IllegalArgumentException("Start stop not found in stops."); + } + if (endStopIndex < startStopIndex + 2) { + throw new IllegalArgumentException("End stop must be at least two stops after start stop."); + } + DummyServiceModels.Stop routeEndStop = STOPS.get(endStopIndex - 1); + DummyServiceModels.PublicTransitLeg leg; + DummyServiceModels.Transfer transfer; + if (timeType == TimeType.DEPARTURE) { + leg = getPublicTransitLeg(ROUTE_1, startStop, routeEndStop, date, timeType); + LocalDateTime departureWalk = leg.getArrival().getArrivalTime(); + int duration = 2 * SECONDS_BETWEEN_STOPS; + transfer = new DummyServiceModels.Transfer(DISTANCE_BETWEEN_STOPS, duration, departureWalk, + departureWalk.plusSeconds(duration), routeEndStop, endStop); + } else { + int duration = 2 * SECONDS_BETWEEN_STOPS; + transfer = new DummyServiceModels.Transfer(DISTANCE_BETWEEN_STOPS, duration, + date.minusSeconds(duration), date, startStop, routeEndStop); + leg = getPublicTransitLeg(ROUTE_1, routeEndStop, endStop, date.minusSeconds(duration), timeType); + + } + return new DummyServiceModels.Connection(List.of(leg, transfer)); + } + + static DummyServiceModels.Connection getSimpleConnection(GeoCoordinate startCoordinate, Stop endStop, + LocalDateTime date, TimeType timeType) { + if (!ROUTE_1.stops().contains((DummyServiceModels.Stop) endStop)) { + throw new IllegalArgumentException("End stop must be part of Route 1."); + } else if (endStop == STOP_A) { + throw new IllegalArgumentException("Stop A cannot be used as end stop."); + } + DummyServiceModels.Stop routeStartStop = STOP_A; + DummyServiceModels.Walk walk; + DummyServiceModels.PublicTransitLeg leg; + int walkDuration = 2 * SECONDS_BETWEEN_STOPS; + if (timeType == TimeType.DEPARTURE) { + walk = new DummyServiceModels.Walk(DISTANCE_BETWEEN_STOPS, walkDuration, WalkType.FIRST_MILE, date, + date.plusSeconds(walkDuration), startCoordinate, routeStartStop.getLocation(), routeStartStop); + leg = getPublicTransitLeg(ROUTE_1, routeStartStop, endStop, date.plusSeconds(walkDuration), timeType); + } else { + leg = getPublicTransitLeg(ROUTE_1, routeStartStop, endStop, date, timeType); + LocalDateTime legArrival = leg.getArrival().getArrivalTime(); + walk = new DummyServiceModels.Walk(DISTANCE_BETWEEN_STOPS, walkDuration, WalkType.FIRST_MILE, + legArrival.minusSeconds(walkDuration), legArrival, routeStartStop.getLocation(), + endStop.getLocation(), routeStartStop); + } + return new DummyServiceModels.Connection(List.of(walk, leg)); + + } + + static DummyServiceModels.Connection getSimpleConnection(Stop startStop, GeoCoordinate endCoordinate, + LocalDateTime date, TimeType timeType) { + if (!ROUTE_1.stops().contains((DummyServiceModels.Stop) startStop)) { + throw new IllegalArgumentException("End stop must be part of Route 1."); + } else if (startStop == STOP_G) { + throw new IllegalArgumentException("Stop G cannot be used as start stop."); + } + DummyServiceModels.Stop routeEndStop = STOP_G; + DummyServiceModels.Walk walk; + DummyServiceModels.PublicTransitLeg leg; + int walkDuration = 2 * SECONDS_BETWEEN_STOPS; + if (timeType == TimeType.DEPARTURE) { + leg = getPublicTransitLeg(ROUTE_1, startStop, routeEndStop, date, timeType); + LocalDateTime legArrival = leg.getArrival().getArrivalTime(); + walk = new DummyServiceModels.Walk(DISTANCE_BETWEEN_STOPS, walkDuration, WalkType.LAST_MILE, legArrival, + legArrival.plusSeconds(walkDuration), routeEndStop.getLocation(), endCoordinate, routeEndStop); + } else { + LocalDateTime walkDeparture = date.minusSeconds(walkDuration); + walk = new DummyServiceModels.Walk(DISTANCE_BETWEEN_STOPS, walkDuration, WalkType.LAST_MILE, + walkDeparture, date, routeEndStop.getLocation(), endCoordinate, routeEndStop); + leg = getPublicTransitLeg(ROUTE_1, startStop, routeEndStop, walkDeparture, timeType); + } + return new DummyServiceModels.Connection(List.of(leg, walk)); + } + + static DummyServiceModels.Connection getSimpleConnection(GeoCoordinate startCoordinate, + GeoCoordinate endCoordinate, LocalDateTime date, + TimeType timeType) { + DummyServiceModels.Stop routeStartStop = STOP_A; + DummyServiceModels.Stop routeEndStop = STOP_G; + DummyServiceModels.Walk firstWalk; + DummyServiceModels.Walk lastWalk; + DummyServiceModels.PublicTransitLeg leg; + int walkDuration = 2 * SECONDS_BETWEEN_STOPS; + if (timeType == TimeType.DEPARTURE) { + firstWalk = new DummyServiceModels.Walk(DISTANCE_BETWEEN_STOPS, walkDuration, WalkType.FIRST_MILE, date, + date.plusSeconds(walkDuration), startCoordinate, routeStartStop.getLocation(), routeStartStop); + leg = getPublicTransitLeg(ROUTE_1, routeStartStop, routeEndStop, date.plusSeconds(walkDuration), + timeType); + LocalDateTime legArrival = leg.getArrival().getArrivalTime(); + lastWalk = new DummyServiceModels.Walk(DISTANCE_BETWEEN_STOPS, walkDuration, WalkType.LAST_MILE, + legArrival, legArrival.plusSeconds(walkDuration), routeEndStop.getLocation(), endCoordinate, + routeEndStop); + } else { + LocalDateTime walkDeparture = date.minusSeconds(walkDuration); + lastWalk = new DummyServiceModels.Walk(DISTANCE_BETWEEN_STOPS, walkDuration, WalkType.LAST_MILE, + walkDeparture, date, routeEndStop.getLocation(), endCoordinate, routeEndStop); + leg = getPublicTransitLeg(ROUTE_1, routeStartStop, routeEndStop, walkDeparture, timeType); + LocalDateTime legDeparture = leg.getDeparture().getDepartureTime(); + firstWalk = new DummyServiceModels.Walk(DISTANCE_BETWEEN_STOPS, walkDuration, WalkType.FIRST_MILE, + legDeparture.minusSeconds(walkDuration), legDeparture, startCoordinate, + routeStartStop.getLocation(), routeStartStop); + } + return new DummyServiceModels.Connection(List.of(firstWalk, leg, lastWalk)); + } + + private static DummyServiceModels.PublicTransitLeg getPublicTransitLeg(RouteData route, Stop startStop, + Stop endStop, LocalDateTime startTime, + TimeType timeType) { + // get index of reference stop in route.stops + int startStopIndex = route.stops().indexOf((DummyServiceModels.Stop) startStop); + if (startStopIndex == -1) { + throw new IllegalArgumentException("Start stop not found in route."); + } + int endStopIndex = route.stops().indexOf((DummyServiceModels.Stop) endStop); + if (endStopIndex == -1) { + throw new IllegalArgumentException("End stop not found in route."); + } else if (endStopIndex < startStopIndex) { + throw new IllegalArgumentException("End stop must be after start stop."); + } + + int refIndex = timeType == TimeType.DEPARTURE ? startStopIndex : endStopIndex; + + DummyServiceModels.Trip trip = new DummyServiceModels.Trip(route.route().getId() + "_" + startStop.getId(), + "Head Sign", route.route()); + List stopTimes = new ArrayList<>(); + for (int i = 0; i < route.stops().size(); i++) { + DummyServiceModels.Stop stop = route.stops().get(i); + LocalDateTime arrivalTime = startTime.plusSeconds((long) SECONDS_BETWEEN_STOPS * (i - refIndex)); + stopTimes.add(new DummyServiceModels.StopTime(stop, arrivalTime, arrivalTime, trip)); + } + trip.setStopTimes(stopTimes); + + DummyServiceModels.StopTime departure = (DummyServiceModels.StopTime) stopTimes.get(startStopIndex); + DummyServiceModels.StopTime arrival = (DummyServiceModels.StopTime) stopTimes.get(endStopIndex); + + int duration = SECONDS_BETWEEN_STOPS * (endStopIndex - startStopIndex); + int distance = DISTANCE_BETWEEN_STOPS * (endStopIndex - startStopIndex); + + return new DummyServiceModels.PublicTransitLeg(distance, duration, trip, departure, arrival); + } + + } + +} diff --git a/src/test/java/ch/naviqore/app/controller/DummyServiceModels.java b/src/test/java/ch/naviqore/app/controller/DummyServiceModels.java new file mode 100644 index 00000000..818f5a32 --- /dev/null +++ b/src/test/java/ch/naviqore/app/controller/DummyServiceModels.java @@ -0,0 +1,192 @@ +package ch.naviqore.app.controller; + +import ch.naviqore.service.LegType; +import ch.naviqore.service.LegVisitor; +import ch.naviqore.service.WalkType; +import ch.naviqore.utils.spatial.GeoCoordinate; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.jetbrains.annotations.Nullable; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +class DummyServiceModels { + + @RequiredArgsConstructor + @Getter + static abstract class Leg implements ch.naviqore.service.Leg { + private final LegType legType; + private final int distance; + private final int duration; + + @Override + public abstract T accept(LegVisitor visitor); + } + + @Getter + static class PublicTransitLeg extends Leg implements ch.naviqore.service.PublicTransitLeg { + + private final Trip trip; + private final StopTime departure; + private final StopTime arrival; + + PublicTransitLeg(int distance, int duration, Trip trip, StopTime departure, StopTime arrival) { + super(LegType.PUBLIC_TRANSIT, distance, duration); + this.trip = trip; + this.departure = departure; + this.arrival = arrival; + } + + @Override + public T accept(LegVisitor visitor) { + return visitor.visit(this); + } + } + + @Getter + static class Transfer extends Leg implements ch.naviqore.service.Transfer { + private final LocalDateTime departureTime; + private final LocalDateTime arrivalTime; + private final Stop sourceStop; + private final Stop targetStop; + + Transfer(int distance, int duration, LocalDateTime departureTime, LocalDateTime arrivalTime, Stop sourceStop, + Stop targetStop) { + super(LegType.WALK, distance, duration); + this.departureTime = departureTime; + this.arrivalTime = arrivalTime; + this.sourceStop = sourceStop; + this.targetStop = targetStop; + } + + @Override + public T accept(LegVisitor visitor) { + return visitor.visit(this); + } + } + + @Getter + static class Walk extends Leg implements ch.naviqore.service.Walk { + private final WalkType walkType; + private final LocalDateTime departureTime; + private final LocalDateTime arrivalTime; + private final GeoCoordinate sourceLocation; + private final GeoCoordinate targetLocation; + private final Stop stop; + + Walk(int distance, int duration, WalkType walkType, LocalDateTime departureTime, LocalDateTime arrivalTime, + GeoCoordinate sourceLocation, GeoCoordinate targetLocation, @Nullable Stop stop) { + super(LegType.WALK, distance, duration); + this.walkType = walkType; + this.departureTime = departureTime; + this.arrivalTime = arrivalTime; + this.sourceLocation = sourceLocation; + this.targetLocation = targetLocation; + this.stop = stop; + } + + @Override + public T accept(LegVisitor visitor) { + return visitor.visit(this); + } + + @Override + public Optional getStop() { + return Optional.ofNullable(stop); + } + } + + @RequiredArgsConstructor + @Getter + static class Route implements ch.naviqore.service.Route { + private final String id; + private final String name; + private final String shortName; + private final String routeType; + private final String Agency; + + } + + @RequiredArgsConstructor + @Getter + static class Trip implements ch.naviqore.service.Trip { + private final String id; + private final String headSign; + private final Route route; + @Setter + private List stopTimes; + + } + + @RequiredArgsConstructor + @Getter + static class Stop implements ch.naviqore.service.Stop { + private final String id; + private final String name; + private final GeoCoordinate location; + + } + + @RequiredArgsConstructor + @Getter + static class StopTime implements ch.naviqore.service.StopTime { + private final Stop stop; + private final LocalDateTime arrivalTime; + private final LocalDateTime departureTime; + private final transient Trip trip; + } + + @RequiredArgsConstructor + static class Connection implements ch.naviqore.service.Connection { + + private final List legs; + + @Override + public List getLegs() { + return legs.stream().map(leg -> (ch.naviqore.service.Leg) leg).toList(); + } + + @Override + public LocalDateTime getDepartureTime() { + return legs.getFirst().accept(new LegVisitor<>() { + @Override + public LocalDateTime visit(ch.naviqore.service.PublicTransitLeg publicTransitLeg) { + return publicTransitLeg.getDeparture().getDepartureTime(); + } + + @Override + public LocalDateTime visit(ch.naviqore.service.Transfer transfer) { + return transfer.getDepartureTime(); + } + + @Override + public LocalDateTime visit(ch.naviqore.service.Walk walk) { + return walk.getDepartureTime(); + } + }); + } + + @Override + public LocalDateTime getArrivalTime() { + return legs.getLast().accept(new LegVisitor<>() { + @Override + public LocalDateTime visit(ch.naviqore.service.PublicTransitLeg publicTransitLeg) { + return publicTransitLeg.getArrival().getArrivalTime(); + } + + @Override + public LocalDateTime visit(ch.naviqore.service.Transfer transfer) { + return transfer.getArrivalTime(); + } + + @Override + public LocalDateTime visit(ch.naviqore.service.Walk walk) { + return walk.getArrivalTime(); + } + }); + } + } +} diff --git a/src/test/java/ch/naviqore/app/controller/RoutingControllerTest.java b/src/test/java/ch/naviqore/app/controller/RoutingControllerTest.java index 1744bd8f..e83098c6 100644 --- a/src/test/java/ch/naviqore/app/controller/RoutingControllerTest.java +++ b/src/test/java/ch/naviqore/app/controller/RoutingControllerTest.java @@ -1,54 +1,29 @@ package ch.naviqore.app.controller; -import ch.naviqore.app.dto.Connection; -import ch.naviqore.app.dto.EarliestArrival; -import ch.naviqore.app.dto.TimeType; -import ch.naviqore.service.PublicTransitService; -import ch.naviqore.service.Stop; -import ch.naviqore.service.exception.StopNotFoundException; +import ch.naviqore.app.dto.*; import ch.naviqore.utils.spatial.GeoCoordinate; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatusCode; import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; -import java.util.Collections; import java.util.List; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -@ExtendWith(MockitoExtension.class) public class RoutingControllerTest { - @Mock - private PublicTransitService publicTransitService; + private final DummyService dummyService = new DummyService(); - @InjectMocks - private RoutingController routingController; + private final RoutingController routingController = new RoutingController(dummyService); @Test - void testGetConnections_WithValidSourceAndTargetStopIds() throws StopNotFoundException { + void testGetConnections_WithValidSourceAndTargetStopIds() { // Arrange - String sourceStopId = "sourceStopId"; - String targetStopId = "targetStopId"; + String sourceStopId = "A"; + String targetStopId = "G"; LocalDateTime departureDateTime = LocalDateTime.now(); - Stop sourceStop = mock(Stop.class); - Stop targetStop = mock(Stop.class); - - when(publicTransitService.getStopById(sourceStopId)).thenReturn(sourceStop); - when(publicTransitService.getStopById(targetStopId)).thenReturn(targetStop); - when(publicTransitService.getConnections(eq(sourceStop), eq(targetStop), any(), any(), any())).thenReturn( - Collections.emptyList()); - // Act List connections = routingController.getConnections(sourceStopId, -1.0, -1.0, targetStopId, -1.0, -1.0, departureDateTime, TimeType.DEPARTURE, 30, 2, 120, 5); @@ -58,19 +33,13 @@ void testGetConnections_WithValidSourceAndTargetStopIds() throws StopNotFoundExc } @Test - void testGetConnections_WithoutSourceStopIdButWithCoordinates() throws StopNotFoundException { + void testGetConnections_WithoutSourceStopIdButWithCoordinates() { // Arrange double sourceLatitude = 46.2044; double sourceLongitude = 6.1432; - String targetStopId = "targetStopId"; + String targetStopId = "G"; LocalDateTime departureDateTime = LocalDateTime.now(); - Stop targetStop = mock(Stop.class); - - when(publicTransitService.getStopById(targetStopId)).thenReturn(targetStop); - when(publicTransitService.getConnections(any(GeoCoordinate.class), eq(targetStop), any(), any(), - any())).thenReturn(Collections.emptyList()); - // Act List connections = routingController.getConnections(null, sourceLatitude, sourceLongitude, targetStopId, -1.0, -1.0, departureDateTime, TimeType.DEPARTURE, 30, 2, 120, 5); @@ -80,12 +49,10 @@ void testGetConnections_WithoutSourceStopIdButWithCoordinates() throws StopNotFo } @Test - void testGetConnections_InvalidStopId() throws StopNotFoundException { + void testGetConnections_InvalidStopId() { // Arrange String invalidStopId = "invalidStopId"; - String targetStopId = "targetStopId"; - - when(publicTransitService.getStopById(invalidStopId)).thenThrow(new StopNotFoundException(invalidStopId)); + String targetStopId = "G"; // Act & Assert ResponseStatusException exception = assertThrows(ResponseStatusException.class, @@ -169,35 +136,242 @@ void testGetConnections_InvalidMinTransferTime() { } @Test - void testGetIsoLines() throws StopNotFoundException { + void testGetIsoLines_fromStopReturnConnectionsFalse() { // Arrange - String sourceStopId = "sourceStopId"; + String sourceStopId = "A"; LocalDateTime time = LocalDateTime.now(); - Stop sourceStop = mock(Stop.class); + // Act + List stopConnections = routingController.getIsolines(sourceStopId, -1.0, -1.0, time, + TimeType.DEPARTURE, 30, 2, 120, 5, false); + + assertNotNull(stopConnections); + + for (StopConnection stopConnection : stopConnections) { + assertEquals(stopConnection.getStop(), stopConnection.getConnectingLeg().getToStop()); + // because returnConnections == false + assertNull(stopConnection.getConnection()); + Trip trip = stopConnection.getConnectingLeg().getTrip(); + if (trip != null) { + assertNull(trip.getStopTimes()); + } + } + } - when(publicTransitService.getStopById(sourceStopId)).thenReturn(sourceStop); - when(publicTransitService.getIsoLines(eq(sourceStop), eq(time), any(), any())).thenReturn( - Collections.emptyMap()); + @Test + void testGetIsoLines_fromStopReturnConnectionsTrue() { + // Arrange + String sourceStopId = "A"; + // This tests if the time is set to now if null + LocalDateTime expectedStartTime = LocalDateTime.now(); + + List stopConnections = routingController.getIsolines(sourceStopId, -1.0, -1.0, null, + TimeType.DEPARTURE, 30, 2, 120, 5, true); + + assertNotNull(stopConnections); + + for (StopConnection stopConnection : stopConnections) { + assertEquals(stopConnection.getStop(), stopConnection.getConnectingLeg().getToStop()); + // because returnConnections == true + assertNotNull(stopConnection.getConnection()); + assertEquals(stopConnection.getStop(), stopConnection.getConnection().getLegs().getLast().getToStop()); + Connection connection = stopConnection.getConnection(); + // make sure each connection has a departure time after/equal the expected start time + assertFalse(connection.getLegs().getFirst().getDepartureTime().isBefore(expectedStartTime)); + assertEquals(connection.getLegs().getFirst().getFromStop().getId(), sourceStopId); + + Trip trip = stopConnection.getConnectingLeg().getTrip(); + if (trip != null) { + List stopTimes = trip.getStopTimes(); + assertNotNull(stopTimes); + // find index of the stopConnection.getStop() in the stopTimes + int index = -1; + for (int i = 0; i < stopTimes.size(); i++) { + if (stopTimes.get(i).getStop().equals(stopConnection.getStop())) { + index = i; + break; + } + } + if (index == -1) { + fail("Stop not found in trip stop times"); + } + // check if the previous stop in the connecting leg is the same as the previous stop in the trip + assertEquals(stopTimes.get(index - 1).getStop(), stopConnection.getConnectingLeg().getFromStop()); + } + } + } + + @Test + void testGetIsoLines_fromCoordinatesReturnConnectionsFalse() { + // Arrange + double sourceLatitude = 46.2044; + double sourceLongitude = 6.1432; + LocalDateTime time = LocalDateTime.now(); // Act - List connections = routingController.getIsolines(sourceStopId, -1.0, -1.0, time, - TimeType.DEPARTURE, 30, 2, 120, 5); + List stopConnections = routingController.getIsolines(null, sourceLatitude, sourceLongitude, + time, TimeType.DEPARTURE, 30, 2, 120, 5, false); + + assertNotNull(stopConnections); + + for (StopConnection stopConnection : stopConnections) { + assertEquals(stopConnection.getStop(), stopConnection.getConnectingLeg().getToStop()); + // because returnConnections == false + assertNull(stopConnection.getConnection()); + Trip trip = stopConnection.getConnectingLeg().getTrip(); + if (trip != null) { + assertNull(trip.getStopTimes()); + } + } + } - // Assert - assertNotNull(connections); + @Test + void testGetIsoLines_fromCoordinateReturnConnectionsTrue() { + // Arrange + GeoCoordinate sourceCoordinate = new GeoCoordinate(46.2044, 6.1432); + // This tests if the time is set to now if null + LocalDateTime expectedStartTime = LocalDateTime.now(); + + List stopConnections = routingController.getIsolines(null, sourceCoordinate.latitude(), + sourceCoordinate.longitude(), null, TimeType.DEPARTURE, 30, 2, 120, 5, true); + + assertNotNull(stopConnections); + + for (StopConnection stopConnection : stopConnections) { + assertEquals(stopConnection.getStop(), stopConnection.getConnectingLeg().getToStop()); + // because returnConnections == true + assertNotNull(stopConnection.getConnection()); + assertEquals(stopConnection.getStop(), stopConnection.getConnection().getLegs().getLast().getToStop()); + Connection connection = stopConnection.getConnection(); + // make sure each connection has a departure time after/equal the expected start time + assertFalse(connection.getLegs().getFirst().getDepartureTime().isBefore(expectedStartTime)); + assertNull(connection.getLegs().getFirst().getFromStop()); + assertEquals(connection.getLegs().getFirst().getFrom(), sourceCoordinate); + + Trip trip = stopConnection.getConnectingLeg().getTrip(); + if (trip != null) { + List stopTimes = trip.getStopTimes(); + assertNotNull(stopTimes); + // find index of the stopConnection.getStop() in the stopTimes + int index = -1; + for (int i = 0; i < stopTimes.size(); i++) { + if (stopTimes.get(i).getStop().equals(stopConnection.getStop())) { + index = i; + break; + } + } + if (index == -1) { + fail("Stop not found in trip stop times"); + } + // check if the previous stop in the connecting leg is the same as the previous stop in the trip + assertEquals(stopTimes.get(index - 1).getStop(), stopConnection.getConnectingLeg().getFromStop()); + } + } } @Test - void testGetIsoLines_InvalidSourceStopId() throws StopNotFoundException { + void testGetIsoLines_fromStopReturnConnectionsTrueTimeTypeArrival() { + // Arrange + String sourceStopId = "G"; + + List stopConnections = routingController.getIsolines(sourceStopId, -1.0, -1.0, null, + TimeType.ARRIVAL, 30, 2, 120, 5, true); + + // This tests if the time is set to now if null + LocalDateTime expectedArrivalTime = LocalDateTime.now(); + assertNotNull(stopConnections); + + for (StopConnection stopConnection : stopConnections) { + Leg connectingLeg = stopConnection.getConnectingLeg(); + Connection connection = stopConnection.getConnection(); + + assertEquals(stopConnection.getStop(), connectingLeg.getFromStop()); + // because returnConnections == true + assertNotNull(connection); + assertEquals(stopConnection.getStop(), connection.getLegs().getFirst().getFromStop()); + + // make sure each connection has an arrival time after/equal the expected start time + assertFalse(connection.getLegs().getLast().getArrivalTime().isAfter(expectedArrivalTime)); + assertEquals(connection.getLegs().getLast().getToStop().getId(), sourceStopId); + + Trip trip = connectingLeg.getTrip(); + if (trip != null) { + List stopTimes = trip.getStopTimes(); + assertNotNull(stopTimes); + // find index of the stopConnection.getStop() in the stopTimes + int index = -1; + for (int i = 0; i < stopTimes.size(); i++) { + if (stopTimes.get(i).getStop().equals(stopConnection.getStop())) { + index = i; + break; + } + } + if (index == -1) { + fail("Stop not found in trip stop times"); + } + // check if the target stop in the connecting leg is the same as the next stop in the trip + assertEquals(stopTimes.get(index + 1).getStop(), connectingLeg.getToStop()); + } + } + } + + @Test + void testGetIsoLines_fromCoordinateReturnConnectionsTrueTimeTypeArrival() { + // Arrange + GeoCoordinate sourceCoordinate = new GeoCoordinate(46.2044, 6.1432); + + List stopConnections = routingController.getIsolines(null, sourceCoordinate.latitude(), + sourceCoordinate.longitude(), null, TimeType.ARRIVAL, 30, 2, 120, 5, true); + + // This tests if the time is set to now if null + LocalDateTime expectedArrivalTime = LocalDateTime.now(); + assertNotNull(stopConnections); + + for (StopConnection stopConnection : stopConnections) { + Leg connectingLeg = stopConnection.getConnectingLeg(); + Connection connection = stopConnection.getConnection(); + + assertEquals(stopConnection.getStop(), connectingLeg.getFromStop()); + // because returnConnections == true + assertNotNull(connection); + assertEquals(stopConnection.getStop(), connection.getLegs().getFirst().getFromStop()); + assertEquals(sourceCoordinate, connection.getLegs().getLast().getTo()); + // should be walk transfer from location without stop object + assertNull(connection.getLegs().getLast().getToStop()); + + // make sure each connection has an arrival time before/equal the expected start time + assertFalse(connection.getLegs().getLast().getArrivalTime().isAfter(expectedArrivalTime)); + + Trip trip = connectingLeg.getTrip(); + if (trip != null) { + List stopTimes = trip.getStopTimes(); + assertNotNull(stopTimes); + // find index of the stopConnection.getStop() in the stopTimes + int index = -1; + for (int i = 0; i < stopTimes.size(); i++) { + if (stopTimes.get(i).getStop().equals(stopConnection.getStop())) { + index = i; + break; + } + } + if (index == -1) { + fail("Stop not found in trip stop times"); + } + // check if the target stop in the connecting leg is the same as the next stop in the trip + assertEquals(stopTimes.get(index + 1).getStop(), connectingLeg.getToStop()); + } + } + } + + @Test + void testGetIsoLines_InvalidSourceStopId() { // Arrange String invalidStopId = "invalidStopId"; - when(publicTransitService.getStopById(invalidStopId)).thenThrow(new StopNotFoundException(invalidStopId)); // Act & Assert ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> routingController.getIsolines(invalidStopId, -91.0, -181.0, LocalDateTime.now(), - TimeType.DEPARTURE, 30, 2, 120, 5)); + TimeType.DEPARTURE, 30, 2, 120, 5, false)); assertEquals("Stop not found", exception.getReason()); assertEquals(HttpStatusCode.valueOf(404), exception.getStatusCode()); } @@ -207,7 +381,7 @@ void testGetIsoLines_MissingSourceAndCoordinates() { // Act & Assert ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> routingController.getIsolines(null, -91.0, -181.0, LocalDateTime.now(), TimeType.DEPARTURE, 30, 2, - 120, 5)); + 120, 5, false)); assertEquals("Either sourceStopId or sourceLatitude and sourceLongitude must be provided.", exception.getReason()); assertEquals(HttpStatusCode.valueOf(400), exception.getStatusCode()); @@ -218,7 +392,7 @@ void testGetIsoLines_InvalidCoordinates() { // Act & Assert ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> routingController.getIsolines(null, 91, 181, LocalDateTime.now(), TimeType.DEPARTURE, 30, 2, 120, - 5)); + 5, false)); assertEquals("Coordinates must be valid, Latitude between -90 and 90 and Longitude between -180 and 180.", exception.getReason()); assertEquals(HttpStatusCode.valueOf(400), exception.getStatusCode()); @@ -228,8 +402,8 @@ void testGetIsoLines_InvalidCoordinates() { void testGetIsoLines_InvalidMaxTransferNumber() { // Act & Assert ResponseStatusException exception = assertThrows(ResponseStatusException.class, - () -> routingController.getIsolines(null, 0, 0, LocalDateTime.now(), TimeType.DEPARTURE, 30, -2, 120, - 5)); + () -> routingController.getIsolines(null, 0, 0, LocalDateTime.now(), TimeType.DEPARTURE, 30, -2, 120, 5, + false)); assertEquals("Max transfer number must be greater than or equal to 0.", exception.getReason()); assertEquals(HttpStatusCode.valueOf(400), exception.getStatusCode()); } @@ -238,8 +412,8 @@ void testGetIsoLines_InvalidMaxTransferNumber() { void testGetIsoLines_InvalidMaxWalkingDuration() { // Act & Assert ResponseStatusException exception = assertThrows(ResponseStatusException.class, - () -> routingController.getIsolines(null, 0, 0, LocalDateTime.now(), TimeType.DEPARTURE, -30, 2, 120, - 5)); + () -> routingController.getIsolines(null, 0, 0, LocalDateTime.now(), TimeType.DEPARTURE, -30, 2, 120, 5, + false)); assertEquals("Max walking duration must be greater than or equal to 0.", exception.getReason()); assertEquals(HttpStatusCode.valueOf(400), exception.getStatusCode()); } @@ -248,8 +422,8 @@ void testGetIsoLines_InvalidMaxWalkingDuration() { void testGetIsoLines_InvalidMaxTravelTime() { // Act & Assert ResponseStatusException exception = assertThrows(ResponseStatusException.class, - () -> routingController.getIsolines(null, 0, 0, LocalDateTime.now(), TimeType.DEPARTURE, 30, 2, -120, - 5)); + () -> routingController.getIsolines(null, 0, 0, LocalDateTime.now(), TimeType.DEPARTURE, 30, 2, -120, 5, + false)); assertEquals("Max travel time must be greater than 0.", exception.getReason()); assertEquals(HttpStatusCode.valueOf(400), exception.getStatusCode()); } @@ -258,8 +432,8 @@ void testGetIsoLines_InvalidMaxTravelTime() { void testGetIsoLines_InvalidMinTransferTime() { // Act & Assert ResponseStatusException exception = assertThrows(ResponseStatusException.class, - () -> routingController.getIsolines(null, 0, 0, LocalDateTime.now(), TimeType.DEPARTURE, 30, 2, 120, - -5)); + () -> routingController.getIsolines(null, 0, 0, LocalDateTime.now(), TimeType.DEPARTURE, 30, 2, 120, -5, + false)); assertEquals("Min transfer time must be greater than or equal to 0.", exception.getReason()); assertEquals(HttpStatusCode.valueOf(400), exception.getStatusCode()); }