1010
1111import javax .json .Json ;
1212import java .io .IOException ;
13+ import java .util .Comparator ;
1314import java .util .Objects ;
1415
1516import static org .junit .jupiter .api .Assertions .assertEquals ;
@@ -34,7 +35,8 @@ void localBeforeEach() throws IOException {
3435 "Test Scheduling Procedure 1" ,
3536 dumbRecurrenceGoalJarId ,
3637 specId ,
37- 0
38+ 0 ,
39+ true
3840 );
3941 }
4042
@@ -184,7 +186,7 @@ void saveActivityName() throws IOException {
184186 * Run a spec that includes unfinished activities
185187 */
186188 @ Test
187- void IncludesUnfinishedActivities () throws IOException {
189+ void includesUnfinishedActivities () throws IOException {
188190 // Disable the recurrence goal to simplify scheduling results
189191 hasura .updateSchedulingSpecEnabled (dumbRecurrenceGoalId .invocationId (), false );
190192
@@ -226,4 +228,47 @@ void IncludesUnfinishedActivities() throws IOException {
226228 assertNull (act .duration ());
227229 }
228230 }
231+
232+ /**
233+ * Scheduling can handle two activities with the same parameters located that start at the same time without
234+ * throwing an IllegalStateException.
235+ *
236+ * Uses DumbRecurrenceGoal for testing, as it will always place duplicate directives when the spec is rescheduled.
237+ */
238+ @ Test
239+ void duplicateActivitiesSupportedOnRerun () throws IOException {
240+ final var args = Json .createObjectBuilder ().add ("quantity" , 2 ).add ("biteSize" , 1 ).build ();
241+ hasura .updateSchedulingSpecGoalArguments (dumbRecurrenceGoalId .invocationId (), args );
242+
243+ hasura .awaitScheduling (planId );
244+
245+ final var initialActivities = hasura .getPlan (planId ).activityDirectives ();
246+
247+ // Rerun scheduling
248+ hasura .updatePlanRevisionSchedulingSpec (planId );
249+ hasura .awaitScheduling (planId );
250+
251+ final var updatedActivities = hasura .getPlan (planId ).activityDirectives ();
252+
253+ // Two new directives should have been placed on the plan
254+ assertEquals (2 , initialActivities .size ());
255+ assertEquals (4 , updatedActivities .size ());
256+
257+ // Sort the activities by start time
258+ initialActivities .sort (Comparator .comparing (Plan .ActivityDirective ::startOffset ));
259+ updatedActivities .sort (Comparator .comparing (Plan .ActivityDirective ::startOffset ));
260+
261+ // Check that the four activities are grouped into two sets of two.
262+ final var firstStartTime = initialActivities .getFirst ().startOffset ();
263+ assertEquals (2 , updatedActivities .stream ()
264+ .filter (dir -> dir .startOffset ().equals (firstStartTime ))
265+ .toList ()
266+ .size ());
267+
268+ final var secondStartTime = initialActivities .getLast ().startOffset ();
269+ assertEquals (2 , updatedActivities .stream ()
270+ .filter (dir -> dir .startOffset ().equals (secondStartTime ))
271+ .toList ()
272+ .size ());
273+ }
229274}
0 commit comments