diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index 823e6bae..38baa635 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -756,7 +756,7 @@ public boolean shouldSkipMonitoredTripCheck(boolean persist) throws Exception { // Attempt to advance to the next monitored day, except for one-time trips // or if tracking is ongoing or if the matching itinerary is still valid. - if (!trip.isOneTime() && !isTrackingOngoing() && !isMatchingItineraryStartTimeInTheFuture()) { + if (!trip.isOneTime() && !isTrackingOngoing()) { advanceToNextMonitoredDay(); } @@ -874,6 +874,13 @@ private void advanceToNextMonitoredDay() { } } + initializeTargetZonedDateTime(); + + // advance the trip to the next active date + advanceToNextActiveTripDate(); + } + + private void initializeTargetZonedDateTime() { // Check if the CheckMonitoredTrip is being ran for the first time for this trip and if the trip's saved // itinerary has already ended. Additionally, make sure that the saved itinerary occurred on the same // service day. If both of these conditions are true, then there is no need to @@ -885,9 +892,6 @@ private void advanceToNextMonitoredDay() { ) { targetZonedDateTime = targetZonedDateTime.plusDays(1); } - - // advance the trip to the next active date - advanceToNextActiveTripDate(); } /** Check if the previous matching itinerary was null or if it has already concluded */ @@ -948,6 +952,14 @@ private void advanceToNextActiveTripDate() { LOG.info("Next matching itinerary starts at {}", matchingItinerary.startTime); + resetJourneyState(); + + // reset the snoozed parameter to false + trip.snoozed = false; + updateTripStatus(); + } + + private void resetJourneyState() { // update journey state with baseline departure and arrival times which are the last known departure/arrival journeyState.baselineDepartureTimeEpochMillis = matchingItinerary.startTime.getTime(); journeyState.baselineArrivalTimeEpochMillis = matchingItinerary.endTime.getTime(); @@ -960,10 +972,6 @@ private void advanceToNextActiveTripDate() { // resent journey state's realtime data to be false as it has just been manually advanced without having checked // the trip planner for realtime data journeyState.hasRealtimeData = false; - - // reset the snoozed parameter to false - trip.snoozed = false; - updateTripStatus(); } /** diff --git a/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java b/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java index 8f56d137..ec1bc9e0 100644 --- a/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java +++ b/src/main/java/org/opentripplanner/middleware/triptracker/ManageTripTracking.java @@ -27,7 +27,10 @@ import org.opentripplanner.middleware.utils.NotificationUtils; import spark.Request; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.function.Supplier; import static org.opentripplanner.middleware.i18n.Message.TRIP_REROUTED_NOTIFICATION; @@ -354,12 +357,39 @@ private static EndTrackingResponse completeJourney(TripTrackingData tripData, bo trackedJourney.longestConsecutiveDeviatedPoints ); + resetMatchingItineraryIfNeeded(trackedJourney); + return new EndTrackingResponse( NO_INSTRUCTION, TripStatus.ENDED.name() ); } + /** + * If rerouting occurred, then the matching itinerary changed with a starting point different from the + * starting location in the original itinerary. + * In that case, reset the matching itinerary, so that trip monitoring/live tracking uses the original routing. + */ + private static void resetMatchingItineraryIfNeeded(TrackedJourney trackedJourney) { + if (trackedJourney.getLastReroutingLocation() != null) { + try { + MonitoredTrip trip = trackedJourney.trip; + + trip.journeyState.matchingItinerary = trip.itinerary.clone(); + + // TODO refactor same formula as in CheckMonitoredTrip + ZonedDateTime targetZonedDateTime = trip.tripZonedDateTime(LocalDate.parse(trip.journeyState.targetDate, DateTimeFormatter.ISO_LOCAL_DATE)); + long offsetMillis = targetZonedDateTime.toInstant().toEpochMilli() - trip.journeyState.matchingItinerary.getScheduledStartTimeEpochMillis(); + // update overall itinerary and leg start/end times by adding offset + trip.journeyState.matchingItinerary.offsetTimes(offsetMillis); + + Persistence.monitoredTrips.replace(trip.id, trip); + } catch (CloneNotSupportedException e) { + // Do nothing if clone was not created. + } + } + } + /** * Cancel bus notifications which are no longer needed/relevant. */ diff --git a/src/test/java/org/opentripplanner/middleware/controllers/api/TrackedTripControllerTest.java b/src/test/java/org/opentripplanner/middleware/controllers/api/TrackedTripControllerTest.java index 9f77197a..4dc8fd34 100644 --- a/src/test/java/org/opentripplanner/middleware/controllers/api/TrackedTripControllerTest.java +++ b/src/test/java/org/opentripplanner/middleware/controllers/api/TrackedTripControllerTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -45,12 +46,13 @@ import org.opentripplanner.middleware.triptracker.response.TrackingResponse; import org.opentripplanner.middleware.utils.Coordinates; import org.opentripplanner.middleware.utils.DateTimeUtils; -import org.opentripplanner.middleware.utils.HttpResponseValues; import org.opentripplanner.middleware.utils.JsonUtils; import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -78,9 +80,6 @@ public class TrackedTripControllerTest extends OtpMiddlewareTestEnvironment { private static OtpUser soloOtpUser; - private static MonitoredTrip monitoredTrip; - private static MonitoredTrip multiLegMonitoredTrip; - private static MonitoredTrip rerouteMonitoredTrip; private static TrackedJourney trackedJourney; private static Itinerary itinerary; private static Itinerary multiLegItinerary; @@ -94,6 +93,8 @@ public class TrackedTripControllerTest extends OtpMiddlewareTestEnvironment { private static final String FORCIBLY_END_TRACKING_TRIP_PATH = ROUTE_PATH + "forciblyendtracking"; private static HashMap headers; + private MonitoredTrip monitoredTrip; + @BeforeAll public static void setUp() throws Exception { assumeTrue(IS_END_TO_END); @@ -124,18 +125,18 @@ public static void setUp() throws Exception { } catch (Auth0Exception e) { throw new RuntimeException(e); } - - monitoredTrip = createMonitoredTrip(itinerary); - multiLegMonitoredTrip = createMonitoredTrip(multiLegItinerary); - rerouteMonitoredTrip = createMonitoredTrip(walkToVoterRegCenterItinerary); } private static MonitoredTrip createMonitoredTrip(Itinerary itin) { MonitoredTrip trip = new MonitoredTrip(); trip.userId = soloOtpUser.id; trip.itinerary = itin; + // Original itinerary time should be populated. + trip.tripTime = DateTimeUtils.convertToLocalDateTime(itin.startTime).toLocalTime().format(DateTimeFormatter.ofPattern("HH:mm")); trip.journeyState = new JourneyState(); trip.journeyState.matchingItinerary = itin; + // Original target date should be populated but does not really matter. + trip.journeyState.targetDate = "2024-01-26"; Persistence.monitoredTrips.create(trip); return trip; } @@ -146,74 +147,55 @@ public static void tearDown() throws Exception { restoreDefaultAuthDisabled(); soloOtpUser = Persistence.otpUsers.getById(soloOtpUser.id); if (soloOtpUser != null) soloOtpUser.delete(true); - monitoredTrip = Persistence.monitoredTrips.getById(monitoredTrip.id); - if (monitoredTrip != null) monitoredTrip.delete(); - multiLegMonitoredTrip = Persistence.monitoredTrips.getById(multiLegMonitoredTrip.id); - if (multiLegMonitoredTrip != null) multiLegMonitoredTrip.delete(); - rerouteMonitoredTrip = Persistence.monitoredTrips.getById(rerouteMonitoredTrip.id); - if (rerouteMonitoredTrip != null) rerouteMonitoredTrip.delete(); + } + + @BeforeEach + public void beforeEachTest() { + assumeTrue(IS_END_TO_END); + monitoredTrip = createMonitoredTrip(itinerary); } @AfterEach public void tearDownAfterTest() { + assumeTrue(IS_END_TO_END); if (trackedJourney != null) { trackedJourney.delete(); trackedJourney = null; } + + monitoredTrip = Persistence.monitoredTrips.getById(monitoredTrip.id); + if (monitoredTrip != null) monitoredTrip.delete(); } @Test void canCompleteJourneyLifeCycle() throws Exception { assumeTrue(IS_END_TO_END); - var response = makeRequest( - START_TRACKING_TRIP_PATH, - JsonUtils.toJson(createStartTrackingPayload()), - headers, - HttpMethod.POST - ); - - var startTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); + var startTrackingResponse = startTracking(createStartTrackingPayload(), HttpStatus.OK_200); assertEquals(ManageTripTracking.TRIP_TRACKING_UPDATE_FREQUENCY_SECONDS, startTrackingResponse.frequencySeconds); assertEquals(TripStatus.DEVIATED.name(), startTrackingResponse.tripStatus); - assertEquals(HttpStatus.OK_200, response.status); - trackedJourney = Persistence.trackedJourneys.getById(startTrackingResponse.journeyId); + String journeyId = startTrackingResponse.journeyId; + trackedJourney = Persistence.trackedJourneys.getById(journeyId); // A single location is submitted when starting tracking. assertEquals(1, trackedJourney.locations.size()); assertEquals(TripStatus.DEVIATED, trackedJourney.lastLocation().tripStatus); - response = makeRequest( - UPDATE_TRACKING_TRIP_PATH, - JsonUtils.toJson(createUpdateTrackingPayload(startTrackingResponse.journeyId)), - headers, - HttpMethod.POST - ); - - var updateTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); + var updateTrackingResponse = updateTracking(createUpdateTrackingPayload(journeyId), HttpStatus.OK_200); assertEquals(TripStatus.DEVIATED.name(), updateTrackingResponse.tripStatus); assertNotEquals(0, updateTrackingResponse.frequencySeconds); assertNotNull(updateTrackingResponse.journeyId); - assertEquals(HttpStatus.OK_200, response.status); - trackedJourney = Persistence.trackedJourneys.getById(startTrackingResponse.journeyId); + trackedJourney = Persistence.trackedJourneys.getById(journeyId); // The call to updatetracking sent 3 additional locations, so there are 4 locations stored at this point. assertEquals(4, trackedJourney.locations.size()); assertEquals(trackedJourney.locations.get(3), trackedJourney.lastLocation()); assertEquals(TripStatus.DEVIATED, trackedJourney.lastLocation().tripStatus); - response = makeRequest( - END_TRACKING_TRIP_PATH, - JsonUtils.toJson(createEndTrackingPayload(startTrackingResponse.journeyId)), - headers, - HttpMethod.POST - ); - var endTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, EndTrackingResponse.class); - assertEquals(TripStatus.ENDED.name(), endTrackingResponse.tripStatus); - assertEquals(HttpStatus.OK_200, response.status); + endTracking(journeyId); // Check that the TrackedJourney Mongo record has been updated. - TrackedJourney mongoTrackedJourney = Persistence.trackedJourneys.getById(startTrackingResponse.journeyId); + TrackedJourney mongoTrackedJourney = Persistence.trackedJourneys.getById(journeyId); assertEquals(TERMINATED_BY_USER, mongoTrackedJourney.endCondition); assertNotEquals(-1, mongoTrackedJourney.longestConsecutiveDeviatedPoints); DateTimeUtils.useSystemDefaultClockAndTimezone(); @@ -222,20 +204,20 @@ void canCompleteJourneyLifeCycle() throws Exception { @Test void canNotRestartAnOngoingJourney() throws Exception { assumeTrue(IS_END_TO_END); - - String jsonPayload = JsonUtils.toJson(createStartTrackingPayload()); - + // Make two identical requests to start and update a journey. The second one should fail. for (int i = 0; i < 2; i++) { - var response = makeRequest(START_TRACKING_TRIP_PATH, jsonPayload, headers, HttpMethod.POST); - var startTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); - + var response = startTracking( + createStartTrackingPayload(), + i == 0 ? HttpStatus.OK_200 : HttpStatus.FORBIDDEN_403 + ); if (i == 0) { - assertEquals(HttpStatus.OK_200, response.status); - trackedJourney = Persistence.trackedJourneys.getById(startTrackingResponse.journeyId); + trackedJourney = Persistence.trackedJourneys.getById(response.journeyId); } else { - assertEquals("A journey of this trip has already been started. End the current journey before starting another.", startTrackingResponse.message); - assertEquals(HttpStatus.FORBIDDEN_403, response.status); + assertEquals( + "A journey of this trip has already been started. End the current journey before starting another.", + response.message + ); } } } @@ -272,7 +254,7 @@ void canStartThenUpdateOngoingJourney() throws Exception { @ParameterizedTest @MethodSource("createInstructionAndStatusCases") void canGenerateInstructionAndStatus( - MonitoredTrip trip, + Itinerary itin, Coordinates coords, String instruction, TripStatus status, @@ -280,8 +262,10 @@ void canGenerateInstructionAndStatus( ) throws Exception { assumeTrue(IS_END_TO_END); + monitoredTrip = createMonitoredTrip(itin); + String jsonPayload = JsonUtils.toJson( - createTrackPayload(trip, coords, Date.from(Instant.ofEpochMilli(trip.itinerary.startTime.getTime() / 1000))) + createTrackPayload(monitoredTrip, coords, Date.from(Instant.ofEpochMilli(monitoredTrip.itinerary.startTime.getTime() / 1000))) ); // Make a request to start a journey. @@ -338,63 +322,63 @@ private static Stream createInstructionAndStatusCases() { return Stream.of( Arguments.of( - monitoredTrip, + itinerary, createPoint(firstStepCoords, 1, NORTH_EAST_BEARING), new OnTrackInstruction(1, adairAvenueNortheastStep, locale).build(), TripStatus.ON_SCHEDULE, "Coords near first step should produce relevant instruction" ), Arguments.of( - monitoredTrip, + itinerary, createPoint(firstStepCoords, 4, NORTH_EAST_BEARING), new OnTrackInstruction(4, adairAvenueNortheastStep, locale).build(), TripStatus.DEVIATED, "Coords deviated but near first step should produce relevant instruction" ), Arguments.of( - monitoredTrip, + itinerary, createPoint(firstStepCoords, 30, NORTH_EAST_BEARING), new DeviatedInstruction(adairAvenueNortheastStep.streetName, locale).build(), TripStatus.DEVIATED, "Deviated coords near first step should produce instruction to head to first step #1" ), Arguments.of( - monitoredTrip, + itinerary, createPoint(firstStepCoords, 15, NORTH_WEST_BEARING), new DeviatedInstruction(adairAvenueNortheastStep.streetName, locale).build(), TripStatus.DEVIATED, "Deviated coords near first step should produce instruction to head to first step #2" ), Arguments.of( - monitoredTrip, + itinerary, createPoint(firstStepCoords, 20, WEST_BEARING), new ContinueInstruction(adairAvenueNortheastStep, locale).build(), TripStatus.ON_SCHEDULE, "Coords along a step should produce a continue on street instruction" ), Arguments.of( - monitoredTrip, + itinerary, thirdStepCoords, new OnTrackInstruction(0, ponceDeLeonPlaceNortheastStep, locale).build(), TripStatus.AHEAD_OF_SCHEDULE, "Coords near a not-first step should produce relevant instruction" ), Arguments.of( - monitoredTrip, + itinerary, createPoint(thirdStepCoords, 30, NORTH_WEST_BEARING), new DeviatedInstruction(ponceDeLeonPlaceNortheastStep.streetName, locale).build(), TripStatus.DEVIATED, "Deviated coords near a not-first step should produce instruction to head to step" ), Arguments.of( - monitoredTrip, + itinerary, createPoint(destinationCoords, 1, NORTH_WEST_BEARING), new OnTrackInstruction(2, monroeDrDestinationName, locale).build(), TripStatus.COMPLETED, "Instructions for destination coordinate" ), Arguments.of( - multiLegMonitoredTrip, + multiLegItinerary, createPoint(multiItinFirstLegDestCoords, 1.5, WEST_BEARING), new WaitForTransitInstruction( multiItinBusLeg, @@ -405,14 +389,14 @@ private static Stream createInstructionAndStatusCases() { "Arriving ahead of schedule to a bus stop at the end of first leg." ), Arguments.of( - multiLegMonitoredTrip, + multiLegItinerary, createPoint(multiItinLastLegDestCoords, 1, NORTH_WEST_BEARING), new OnTrackInstruction(1, ansleyMallPetShopDestinationName, locale).build(), TripStatus.COMPLETED, "Instructions for destination coordinate of multi-leg trip" ), Arguments.of( - monitoredTrip, + itinerary, createPoint(thirdStepCoords, 1000, NORTH_WEST_BEARING), NO_INSTRUCTION, TripStatus.DEVIATED, @@ -425,106 +409,66 @@ private static Stream createInstructionAndStatusCases() { void canForciblyEndJourney() throws Exception { assumeTrue(IS_END_TO_END); - var response = makeRequest( - START_TRACKING_TRIP_PATH, - JsonUtils.toJson(createStartTrackingPayload()), - headers, - HttpMethod.POST - ); + monitoredTrip = createMonitoredTrip(itinerary); - var startTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); + var startTrackingResponse = startTracking(createStartTrackingPayload(), HttpStatus.OK_200); trackedJourney = Persistence.trackedJourneys.getById(startTrackingResponse.journeyId); - assertEquals(HttpStatus.OK_200, response.status); - response = makeRequest( + endTracking( FORCIBLY_END_TRACKING_TRIP_PATH, - JsonUtils.toJson(createForceEndTrackingPayload(monitoredTrip.id)), - headers, - HttpMethod.POST + JsonUtils.toJson(createForceEndTrackingPayload(monitoredTrip.id)) ); - var endTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, EndTrackingResponse.class); - assertEquals(TripStatus.ENDED.name(), endTrackingResponse.tripStatus); - assertEquals(HttpStatus.OK_200, response.status); // Check that the TrackedJourney Mongo record has been updated. TrackedJourney mongoTrackedJourney = Persistence.trackedJourneys.getById(startTrackingResponse.journeyId); assertEquals(FORCIBLY_TERMINATED, mongoTrackedJourney.endCondition); assertNotEquals(-1, mongoTrackedJourney.longestConsecutiveDeviatedPoints); } - + @Test void canNotUseUnassociatedTrip() throws Exception { assumeTrue(IS_END_TO_END); - - HttpResponseValues response = makeRequest( - START_TRACKING_TRIP_PATH, - JsonUtils.toJson(createStartTrackingPayload("unassociated-trip-id")), - headers, - HttpMethod.POST + var response = startTracking( + createStartTrackingPayload("unassociated-trip-id"), + HttpStatus.FORBIDDEN_403 ); - - var startTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); - assertEquals("Monitored trip is not associated with this user!", startTrackingResponse.message); - assertEquals(HttpStatus.FORBIDDEN_403, response.status); + assertEquals("Monitored trip is not associated with this user!", response.message); } @Test void canNotUpdateUnknownJourney() throws Exception { assumeTrue(IS_END_TO_END); - - HttpResponseValues response = makeRequest( - UPDATE_TRACKING_TRIP_PATH, - JsonUtils.toJson(createUpdateTrackingPayload("unknown-journey-id")), - headers, - HttpMethod.POST + var updateTrackingResponse = updateTracking( + createUpdateTrackingPayload("unknown-journey-id"), + HttpStatus.BAD_REQUEST_400 ); - - var updateTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); assertEquals("Provided journey does not exist or has already been completed!", updateTrackingResponse.message); - assertEquals(HttpStatus.BAD_REQUEST_400, response.status); } @Test void canNotUpdateCompletedJourney() throws Exception { assumeTrue(IS_END_TO_END); - var response = makeRequest( - START_TRACKING_TRIP_PATH, - JsonUtils.toJson(createStartTrackingPayload()), - headers, - HttpMethod.POST - ); - - var startTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); + var startTrackingResponse = startTracking(createStartTrackingPayload(), HttpStatus.OK_200); trackedJourney = Persistence.trackedJourneys.getById(startTrackingResponse.journeyId); assertEquals(ManageTripTracking.TRIP_TRACKING_UPDATE_FREQUENCY_SECONDS, startTrackingResponse.frequencySeconds); assertEquals(TripStatus.DEVIATED.name(), startTrackingResponse.tripStatus); - assertEquals(HttpStatus.OK_200, response.status); - response = makeRequest( - END_TRACKING_TRIP_PATH, - JsonUtils.toJson(createEndTrackingPayload(startTrackingResponse.journeyId)), - headers, - HttpMethod.POST - ); - assertEquals(HttpStatus.OK_200, response.status); + endTracking(startTrackingResponse.journeyId); - response = makeRequest( - UPDATE_TRACKING_TRIP_PATH, - JsonUtils.toJson(createUpdateTrackingPayload(startTrackingResponse.journeyId)), - headers, - HttpMethod.POST + var updateTrackingResponse = updateTracking( + createUpdateTrackingPayload(startTrackingResponse.journeyId), + HttpStatus.BAD_REQUEST_400 ); - - var updateTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); assertEquals("Provided journey does not exist or has already been completed!", updateTrackingResponse.message); - assertEquals(HttpStatus.BAD_REQUEST_400, response.status); } @Test void canRerouteTrip() throws Exception { assumeTrue(IS_END_TO_END); + MonitoredTrip rerouteMonitoredTrip = monitoredTrip = createMonitoredTrip(walkToVoterRegCenterItinerary); + var startTrackingPayload = createStartTrackingPayload(rerouteMonitoredTrip.id); var mockOtpResponse = mockOtpReroutedPlanResponse(); var expectedReroutedItinerary = getShortestDuration(mockOtpResponse.get().plan.itineraries); @@ -534,30 +478,13 @@ void canRerouteTrip() throws Exception { var reroutingPointPosition = new TrackingLocation(Instant.now(), reroutingPoint.lat,reroutingPoint.lon); // Start tracking. - var response = makeRequest( - START_TRACKING_TRIP_PATH, - JsonUtils.toJson(startTrackingPayload), - headers, - HttpMethod.POST - ); - - var startTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); + var startTrackingResponse = startTracking(startTrackingPayload, HttpStatus.OK_200); trackedJourney = Persistence.trackedJourneys.getById(startTrackingResponse.journeyId); - assertEquals(HttpStatus.OK_200, response.status); // Update tracking from a 'deviated' position. - response = makeRequest( - UPDATE_TRACKING_TRIP_PATH, - JsonUtils.toJson(createUpdateTrackingPayload( - trackedJourney.id, - List.of(deviatedPosition) - )), - headers, - HttpMethod.POST - ); - + UpdatedTrackingPayload deviatedPositionPayload = createUpdateTrackingPayload(trackedJourney.id, List.of(deviatedPosition)); + var updateTrackingResponse = updateTracking(deviatedPositionPayload, HttpStatus.OK_200); // Confirm traveler is classed as 'deviated'. - var updateTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); assertEquals(TripStatus.DEVIATED.name(), updateTrackingResponse.tripStatus); // Reroute trip from 'deviated' position. @@ -574,18 +501,9 @@ void canRerouteTrip() throws Exception { assertEquals(expectedReroutedItinerary.legs.size(), trip.journeyState.matchingItinerary.legs.size()); // Update tracking from start of rerouted position. - response = makeRequest( - UPDATE_TRACKING_TRIP_PATH, - JsonUtils.toJson(createUpdateTrackingPayload( - trackedJourney.id, - List.of(deviatedPosition) - )), - headers, - HttpMethod.POST - ); + updateTrackingResponse = updateTracking(deviatedPositionPayload, HttpStatus.OK_200); // Confirm traveler is no longer 'deviated'. - updateTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); assertNotEquals(TripStatus.DEVIATED.name(), updateTrackingResponse.tripStatus); rerouteMonitoredTrip.tripTime = "12:31"; @@ -593,8 +511,6 @@ void canRerouteTrip() throws Exception { CheckMonitoredTrip checkMonitoredTrip = new CheckMonitoredTrip(rerouteMonitoredTrip); checkMonitoredTrip.run(); Itinerary afterCheck = Persistence.monitoredTrips.getById(rerouteMonitoredTrip.id).journeyState.matchingItinerary; - System.out.println(beforeCheck); - System.out.println(afterCheck); assertEquals(beforeCheck.duration, afterCheck.duration); // Reroute again from a different location. @@ -610,19 +526,38 @@ void canRerouteTrip() throws Exception { assertEquals(new Coordinates(updated.lastLocation()), reroutingPoint); // Update tracking from start of the new rerouted position. - response = makeRequest( - UPDATE_TRACKING_TRIP_PATH, - JsonUtils.toJson(createUpdateTrackingPayload( - trackedJourney.id, - List.of(reroutingPointPosition) - )), - headers, - HttpMethod.POST + updateTrackingResponse = updateTracking( + createUpdateTrackingPayload(trackedJourney.id, List.of(reroutingPointPosition)), + HttpStatus.OK_200 ); - // Confirm traveler is still not 'deviated'. - updateTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); assertNotEquals(TripStatus.DEVIATED.name(), updateTrackingResponse.tripStatus); + + // Stop tracking + endTracking(trackedJourney.id); + + // Check that matching itinerary has been reset to original departure location + MonitoredTrip resetTrip = Persistence.monitoredTrips.getById(rerouteMonitoredTrip.id); + assertEquals( + rerouteMonitoredTrip.itinerary.legs.get(0).from.toCoordinates(), + resetTrip.journeyState.matchingItinerary.legs.get(0).from.toCoordinates() + ); + + // Check that matching itinerary time corresponds to "today". + assertEquals( + DateTimeUtils.nowAsZonedDateTime(DateTimeUtils.getOtpZoneId()).toLocalDate(), + ZonedDateTime.ofInstant(resetTrip.journeyState.matchingItinerary.startTime.toInstant(), DateTimeUtils.getOtpZoneId()).toLocalDate() + ); + + // Start tracking again from the same last position above. Traveler should be deviated. + startTrackingResponse = startTracking(startTrackingPayload, HttpStatus.OK_200); + trackedJourney = Persistence.trackedJourneys.getById(startTrackingResponse.journeyId); + + updateTrackingResponse = updateTracking( + createUpdateTrackingPayload(trackedJourney.id, List.of(reroutingPointPosition)), + HttpStatus.OK_200 + ); + assertEquals(TripStatus.DEVIATED.name(), updateTrackingResponse.tripStatus); } /** Provides a mock OTP 'plan' rerouted response. */ @@ -662,6 +597,7 @@ void canGetTheLatestReroutingLocation() { @Test void canCheckForRerouting() throws CloneNotSupportedException { + monitoredTrip = createMonitoredTrip(itinerary); monitoredTrip.journeyState.tripStatus = TRIP_ACTIVE; Coordinates fromCoords = new Coordinates(33.94412, -83.98899); TrackedJourney reroutedTrackedJourney = new TrackedJourney(); @@ -695,10 +631,7 @@ private static List createTrackingLocations() { } private UpdatedTrackingPayload createUpdateTrackingPayload(String journeyId) { - var payload = new UpdatedTrackingPayload(); - payload.journeyId = journeyId; - payload.locations = createTrackingLocations(); - return payload; + return createUpdateTrackingPayload(journeyId, createTrackingLocations()); } private UpdatedTrackingPayload createUpdateTrackingPayload(String journeyId, List locations) { @@ -746,4 +679,27 @@ private ForceEndTrackingPayload createForceEndTrackingPayload(String monitorTrip private static Date getDateAndConvertToSeconds() { return new Date(new Date().getTime() / 1000); } + + private TrackingResponse startTracking(StartTrackingPayload payload, int expectedStatus) throws JsonProcessingException { + var response = makeRequest(START_TRACKING_TRIP_PATH, JsonUtils.toJson(payload), headers, HttpMethod.POST); + assertEquals(expectedStatus, response.status); + return JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); + } + + private void endTracking(String journeyId) throws JsonProcessingException { + endTracking(END_TRACKING_TRIP_PATH, JsonUtils.toJson(createEndTrackingPayload(journeyId))); + } + + private static void endTracking(String path, String payload) throws JsonProcessingException { + var response = makeRequest(path, payload, headers, HttpMethod.POST); + var endTrackingResponse = JsonUtils.getPOJOFromJSON(response.responseBody, EndTrackingResponse.class); + assertEquals(TripStatus.ENDED.name(), endTrackingResponse.tripStatus); + assertEquals(HttpStatus.OK_200, response.status); + } + + private TrackingResponse updateTracking(UpdatedTrackingPayload payload, int expectedStatus) throws JsonProcessingException { + var response = makeRequest(UPDATE_TRACKING_TRIP_PATH, JsonUtils.toJson(payload), headers, HttpMethod.POST); + assertEquals(expectedStatus, response.status); + return JsonUtils.getPOJOFromJSON(response.responseBody, TrackingResponse.class); + } } diff --git a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java index 34d72f98..57bada94 100644 --- a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java +++ b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java @@ -103,17 +103,13 @@ public OtpResponse mockOtpPlanResponse() { } } - /** - * To run this trip, change the env.yml config values for OTP_API_ROOT - * (and OTP_PLAN_ENDPOINT) to a valid OTP server. - */ @Test - void canMonitorTrip() throws Exception { + void canMonitorOngoingTrip() throws Exception { MonitoredTrip monitoredTrip = PersistenceTestUtils.createMonitoredTrip( user.id, OtpTestUtils.OTP2_DISPATCHER_PLAN_RESPONSE.clone(), false, - OtpTestUtils.createDefaultJourneyState() + null ); monitoredTrip.itineraryExistence.monday = new ItineraryExistence.ItineraryExistenceResult(); Persistence.monitoredTrips.create(monitoredTrip); @@ -146,8 +142,68 @@ void canMonitorTrip() throws Exception { // Next, run a monitor trip check from the new monitored trip using the simulated response. CheckMonitoredTrip checkMonitoredTrip = new CheckMonitoredTrip(monitoredTrip, () -> mockResponse); checkMonitoredTrip.run(); - // Assert that there is one notification generated during check. - Assertions.assertEquals(1, checkMonitoredTrip.notifications.size()); + + // Assert that there is one notification generated during check and it is an alert. + assertEquals(1, checkMonitoredTrip.notifications.size()); + assertEquals(NotificationType.ALERT_FOUND, checkMonitoredTrip.notifications.iterator().next().type); + // Clear the created trip. + PersistenceTestUtils.deleteMonitoredTrip(monitoredTrip); + } + + @Test + void canMonitorFutureTrip() throws Exception { + // TODO refactor with above test. + // Save an itinerary in the future, and run an itinerary check on it at at time before that itinerary start. + + MonitoredTrip monitoredTrip = PersistenceTestUtils.createMonitoredTrip( + user.id, + OtpTestUtils.OTP2_DISPATCHER_PLAN_RESPONSE.clone(), + false, + null + ); + monitoredTrip.tripTime = "08:35"; + monitoredTrip.itineraryExistence.monday = new ItineraryExistence.ItineraryExistenceResult(); + monitoredTrip.itineraryExistence.tuesday = new ItineraryExistence.ItineraryExistenceResult(); + + Persistence.monitoredTrips.create(monitoredTrip); + LOG.info("Created trip {}", monitoredTrip.id); + + + OtpResponse mockResponse = mockOtpPlanResponse(); + Itinerary mockTuesdayJune9Itinerary = mockResponse.plan.itineraries.get(0); + + // parse original itinerary date/time and then update mock itinerary to occur on Monday June 15 + ZonedDateTime mockItineraryDate = DateTimeUtils.makeOtpZonedDateTime(mockTuesdayJune9Itinerary.startTime) + .withDayOfMonth(9); + OtpTestUtils.updateBaseItineraryTime( + mockTuesdayJune9Itinerary, + mockItineraryDate + ); + + // Add fake alerts to simulated itinerary. + ArrayList fakeAlerts = new ArrayList<>(); + fakeAlerts.add(new LocalizedAlert()); + mockTuesdayJune9Itinerary.legs.get(1).alerts = fakeAlerts; + + // The trip is set to be monitored Monday to Friday. + // Mock time to be 7:30am on Tuesday, June 9 before the trip start. + DateTimeUtils.useFixedClockAt( + noonMonday8June2020 + .withDayOfMonth(9) + .withHour(7) + .withMinute(30) + ); + + // Next, run a monitor trip check from the new monitored trip using the simulated response. + CheckMonitoredTrip checkMonitoredTrip = new CheckMonitoredTrip(monitoredTrip, () -> mockResponse); + checkMonitoredTrip.run(); + // This process should initialize the scheduled departure time on the journey state + assertNotEquals(0, checkMonitoredTrip.trip.journeyState.scheduledDepartureTimeEpochMillis); + + // No notifications in this case because the trip is next day. + assertEquals(1, checkMonitoredTrip.notifications.size()); + assertEquals(NotificationType.ALERT_FOUND, checkMonitoredTrip.notifications.iterator().next().type); + // Clear the created trip. PersistenceTestUtils.deleteMonitoredTrip(monitoredTrip); }