diff --git a/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ActivityDeletionGoal.java b/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ActivityDeletionGoal.java index f9242bf14e..9c4f12a315 100644 --- a/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ActivityDeletionGoal.java +++ b/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ActivityDeletionGoal.java @@ -15,9 +15,10 @@ /** * Creates three activities in a chain of anchors, then deletes one. * If `whichToDelete` is negative, this leaves all three activities. + * If `rollback` is true, this will roll the edit back before finishing. */ @SchedulingProcedure -public record ActivityDeletionGoal(int whichToDelete, DeletedAnchorStrategy anchorStrategy) implements Goal { +public record ActivityDeletionGoal(int whichToDelete, DeletedAnchorStrategy anchorStrategy, boolean rollback) implements Goal { @Override public void run(@NotNull final EditablePlan plan) { final var ids = new ActivityDirectiveId[3]; @@ -42,6 +43,7 @@ public void run(@NotNull final EditablePlan plan) { plan.delete(ids[whichToDelete], anchorStrategy); } - plan.commit(); + if (rollback) plan.rollback(); + else plan.commit(); } } diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/DeletionTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/DeletionTests.java index 5e38b57b19..9cd51f98f5 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/DeletionTests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/DeletionTests.java @@ -42,6 +42,7 @@ void createsThreeActivities() throws IOException { .createObjectBuilder() .add("whichToDelete", -1) .add("anchorStrategy", "Error") + .add("rollback", false) .build(); hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); @@ -82,6 +83,7 @@ void deletesLast() throws IOException { .createObjectBuilder() .add("whichToDelete", 2) .add("anchorStrategy", "Error") + .add("rollback", false) .build(); hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); @@ -113,6 +115,7 @@ void deletesMiddleCascade() throws IOException { .createObjectBuilder() .add("whichToDelete", 1) .add("anchorStrategy", "Cascade") + .add("rollback", false) .build(); hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); @@ -134,7 +137,8 @@ void deletesMiddleAnchorToParent() throws IOException { final var args = Json .createObjectBuilder() .add("whichToDelete", 1) - .add("anchorStrategy", "ReAnchor") + .add("anchorStrategy", "PreserveTree") + .add("rollback", false) .build(); hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); @@ -168,6 +172,7 @@ void deletesFirstCascade() throws IOException { .createObjectBuilder() .add("whichToDelete", 0) .add("anchorStrategy", "Cascade") + .add("rollback", false) .build(); hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); @@ -185,7 +190,8 @@ void deletesFirstReAnchorToPlan() throws IOException { final var args = Json .createObjectBuilder() .add("whichToDelete", 0) - .add("anchorStrategy", "ReAnchor") + .add("anchorStrategy", "PreserveTree") + .add("rollback", false) .build(); hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); @@ -212,4 +218,45 @@ void deletesFirstReAnchorToPlan() throws IOException { it -> Objects.equals(it.type(), "BiteBanana") && Objects.equals(it.anchorId(), id2.get()) )); } + + @Test + void anchorResetOnRollback() throws IOException { + final var args = Json + .createObjectBuilder() + .add("whichToDelete", 1) + .add("anchorStrategy", "PreserveTree") + .add("rollback", true) + .build(); + + hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); + + hasura.awaitScheduling(specId); + + final var plan = hasura.getPlan(planId); + final var activities = plan.activityDirectives(); + + assertEquals(3, activities.size()); + + final AtomicReference id1 = new AtomicReference<>(); + final AtomicReference id2 = new AtomicReference<>(); + assertTrue(activities.stream().anyMatch( + it -> { + final var result = Objects.equals(it.type(), "BiteBanana") && Objects.equals(it.anchorId(), null); + if (result) id1.set(it.id()); + return result; + } + )); + + assertTrue(activities.stream().anyMatch( + it -> { + final var result = Objects.equals(it.type(), "BiteBanana") && Objects.equals(it.anchorId(), id1.get()); + if (result) id2.set(it.id()); + return result; + } + )); + + assertTrue(activities.stream().anyMatch( + it -> Objects.equals(it.type(), "BiteBanana") && Objects.equals(it.anchorId(), id2.get()) + )); + } } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java index 5c31c7eee9..43c1a8c9fb 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java @@ -442,6 +442,20 @@ public Map updatePlanActivityDirective activity.anchoredToStart() ); if (!activityDirectiveFromSchedulingDirective.equals(actFromInitialPlan.get())) { + final var newState = activityDirectiveFromSchedulingDirective.serializedActivity(); + final var oldState = actFromInitialPlan.get().serializedActivity(); + if (!Objects.equals(newState.getTypeName(), oldState.getTypeName())) { + throw new IllegalStateException( + "Modified activities cannot change type. Was " + oldState.getTypeName() + + ", now " + newState.getTypeName() + ); + } + if (!Objects.equals(newState.getArguments(), oldState.getArguments())) { + throw new IllegalStateException( + "Modified activities cannot change arguments. Was " + oldState.getArguments() + + ", now " + newState.getArguments() + ); + } toModify.add(activity); } ids.put(activity.id(), activity.id());