|
1 | 1 | package org.opentripplanner.updater.trip.siri.moduletests.extracall; |
2 | 2 |
|
| 3 | +import static com.google.common.truth.Truth.assertThat; |
3 | 4 | import static org.junit.jupiter.api.Assertions.assertEquals; |
4 | 5 | import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertFailure; |
5 | 6 | import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; |
@@ -50,6 +51,10 @@ void testExtraCall() { |
50 | 51 | ); |
51 | 52 | } |
52 | 53 |
|
| 54 | + /** |
| 55 | + * Apply the same extra call update twice (identical message). Verifies idempotency: the trip |
| 56 | + * times and the MODIFIED pattern are unchanged after the second application. |
| 57 | + */ |
53 | 58 | @Test |
54 | 59 | void testExtraCallMultipleTimes() { |
55 | 60 | var env = ENV_BUILDER.addTrip(TRIP_1_INPUT).build(); |
@@ -91,6 +96,101 @@ void testExtraCallAndCancellation() { |
91 | 96 | ); |
92 | 97 | } |
93 | 98 |
|
| 99 | + /** |
| 100 | + * Add an extra call (A → D(extra) → B), then send a second update with the same extra call |
| 101 | + * but different times. Unlike {@link #testExtraCallMultipleTimes()} which replays an identical |
| 102 | + * message, this test verifies that updated times are actually applied while preserving the |
| 103 | + * extra call and the MODIFIED pattern. |
| 104 | + */ |
| 105 | + @Test |
| 106 | + void testExtraCallThenUpdateTimesKeepsExtraCall() { |
| 107 | + var env = ENV_BUILDER.addTrip(TRIP_1_INPUT).build(); |
| 108 | + var siri = SiriTestHelper.of(env); |
| 109 | + |
| 110 | + // Step 1: Add extra call D between A and B |
| 111 | + var extraCallUpdate = updateWithExtraCall(siri); |
| 112 | + assertSuccess(siri.applyEstimatedTimetable(extraCallUpdate)); |
| 113 | + |
| 114 | + assertEquals( |
| 115 | + "MODIFIED | A [R] 0:00:15 0:00:15 | D [EC] 0:00:20 0:00:25 | B 0:00:33 0:00:33", |
| 116 | + env.tripData(TRIP_1_ID).showTimetable() |
| 117 | + ); |
| 118 | + assertThat(env.raptorData().summarizePatterns()).containsExactly( |
| 119 | + "F:route-id::001:RT[MODIFIED]" |
| 120 | + ); |
| 121 | + |
| 122 | + // Step 2: Send update with same extra call but different times |
| 123 | + var updatedTimes = siri |
| 124 | + .etBuilder() |
| 125 | + .withDatedVehicleJourneyRef(TRIP_1_ID) |
| 126 | + .withLineRef(ROUTE_ID) |
| 127 | + .withRecordedCalls(builder -> builder.call(STOP_A).departAimedActual("00:00:11", "00:00:16")) |
| 128 | + .withEstimatedCalls(builder -> |
| 129 | + builder |
| 130 | + .call(STOP_D) |
| 131 | + .withIsExtraCall(true) |
| 132 | + .arriveAimedExpected("00:00:18", "00:00:22") |
| 133 | + .departAimedExpected("00:00:19", "00:00:27") |
| 134 | + .call(STOP_B) |
| 135 | + .arriveAimedExpected("00:00:20", "00:00:35") |
| 136 | + ) |
| 137 | + .buildEstimatedTimetableDeliveries(); |
| 138 | + |
| 139 | + var result = siri.applyEstimatedTimetable(updatedTimes); |
| 140 | + assertSuccess(result); |
| 141 | + |
| 142 | + // Extra call D should still be present with updated times |
| 143 | + assertEquals( |
| 144 | + "MODIFIED | A [R] 0:00:16 0:00:16 | D [EC] 0:00:22 0:00:27 | B 0:00:35 0:00:35", |
| 145 | + env.tripData(TRIP_1_ID).showTimetable() |
| 146 | + ); |
| 147 | + var patterns = env.raptorData().summarizePatterns(); |
| 148 | + assertThat(patterns).hasSize(1); |
| 149 | + assertThat(patterns.stream().findFirst().get()).endsWith("[MODIFIED]"); |
| 150 | + } |
| 151 | + |
| 152 | + /** |
| 153 | + * Add an extra call (A → D(extra) → B), then send a regular update without the extra call |
| 154 | + * (A → B with updated times). The trip should revert to the scheduled pattern. |
| 155 | + */ |
| 156 | + @Test |
| 157 | + void testExtraCallThenRevertToOriginalStops() { |
| 158 | + var env = ENV_BUILDER.addTrip(TRIP_1_INPUT).build(); |
| 159 | + var siri = SiriTestHelper.of(env); |
| 160 | + |
| 161 | + // Step 1: Add extra call D between A and B |
| 162 | + var extraCallUpdate = updateWithExtraCall(siri); |
| 163 | + assertSuccess(siri.applyEstimatedTimetable(extraCallUpdate)); |
| 164 | + |
| 165 | + assertEquals( |
| 166 | + "MODIFIED | A [R] 0:00:15 0:00:15 | D [EC] 0:00:20 0:00:25 | B 0:00:33 0:00:33", |
| 167 | + env.tripData(TRIP_1_ID).showTimetable() |
| 168 | + ); |
| 169 | + assertThat(env.raptorData().summarizePatterns()).containsExactly( |
| 170 | + "F:route-id::001:RT[MODIFIED]" |
| 171 | + ); |
| 172 | + |
| 173 | + // Step 2: Send regular update without extra call — just A → B with updated times |
| 174 | + var revert = siri |
| 175 | + .etBuilder() |
| 176 | + .withDatedVehicleJourneyRef(TRIP_1_ID) |
| 177 | + .withRecordedCalls(builder -> builder.call(STOP_A).departAimedActual("00:00:11", "00:00:16")) |
| 178 | + .withEstimatedCalls(builder -> |
| 179 | + builder.call(STOP_B).arriveAimedExpected("00:00:20", "00:00:30") |
| 180 | + ) |
| 181 | + .buildEstimatedTimetableDeliveries(); |
| 182 | + |
| 183 | + var result = siri.applyEstimatedTimetable(revert); |
| 184 | + assertSuccess(result); |
| 185 | + |
| 186 | + // Trip should revert to the scheduled pattern with UPDATED state |
| 187 | + assertEquals( |
| 188 | + "UPDATED | A [R] 0:00:16 0:00:16 | B 0:00:30 0:00:30", |
| 189 | + env.tripData(TRIP_1_ID).showTimetable() |
| 190 | + ); |
| 191 | + assertThat(env.raptorData().summarizePatterns()).containsExactly("F:Pattern1[UPDATED]"); |
| 192 | + } |
| 193 | + |
94 | 194 | @Test |
95 | 195 | void testExtraUnknownStop() { |
96 | 196 | var env = ENV_BUILDER.addTrip(TRIP_1_INPUT).build(); |
|
0 commit comments