From 62049ea985f29f2a3243eb7afd26576a1160928e Mon Sep 17 00:00:00 2001 From: JoelCourtney Date: Thu, 21 Nov 2024 18:04:25 -0800 Subject: [PATCH] Activity deletion e2e tests --- .../procedures/ActivityDeletionGoal.java | 47 ++++ .../e2e/procedural/scheduling/BasicTests.java | 8 + .../procedural/scheduling/DeletionTests.java | 215 ++++++++++++++++++ .../scheduling/ProceduralSchedulingSetup.java | 8 +- .../gov/nasa/jpl/aerie/e2e/types/Plan.java | 11 +- .../gov/nasa/jpl/aerie/e2e/utils/GQL.java | 2 + 6 files changed, 283 insertions(+), 8 deletions(-) create mode 100644 e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ActivityDeletionGoal.java create mode 100644 e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/DeletionTests.java 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 new file mode 100644 index 0000000000..874e6a5bc1 --- /dev/null +++ b/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ActivityDeletionGoal.java @@ -0,0 +1,47 @@ +package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures; + +import gov.nasa.ammos.aerie.procedural.scheduling.Goal; +import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure; +import gov.nasa.ammos.aerie.procedural.scheduling.plan.DeletedAnchorStrategy; +import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart; +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Optional; + +/** + * Creates three activities in a chain of anchors, then deletes one. + */ +@SchedulingProcedure +public record ActivityDeletionGoal(int whichToDelete, DeletedAnchorStrategy anchorStrategy) implements Goal { + @Override + public void run(@NotNull final EditablePlan plan) { + final var ids = new ActivityDirectiveId[3]; + + ids[0] = plan.create( + "BiteBanana", + new DirectiveStart.Absolute(Duration.HOUR), + Map.of("biteSize", SerializedValue.of(0)) + ); + ids[1] = plan.create( + "BiteBanana", + new DirectiveStart.Anchor(ids[0], Duration.HOUR, DirectiveStart.Anchor.AnchorPoint.End), + Map.of("biteSize", SerializedValue.of(1)) + ); + ids[2] = plan.create( + "BiteBanana", + new DirectiveStart.Anchor(ids[1], Duration.HOUR, DirectiveStart.Anchor.AnchorPoint.Start), + Map.of("biteSize", SerializedValue.of(2)) + ); + + if (whichToDelete >= 0) { + plan.delete(ids[whichToDelete], anchorStrategy); + } + + plan.commit(); + } +} diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/BasicTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/BasicTests.java index 71fb6aae76..6a8b8d7f04 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/BasicTests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/BasicTests.java @@ -134,6 +134,14 @@ void executeEDSLAndProcedure() throws IOException { final var args = Json.createObjectBuilder().add("quantity", 4).build(); hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); + final String recurrenceGoalDefinition = + """ + export default function myGoal() { + return Goal.ActivityRecurrenceGoal({ + activityTemplate: ActivityTemplates.PeelBanana({peelDirection: 'fromStem'}), + interval: Temporal.Duration.from({hours:1}) + })}"""; + hasura.createSchedulingSpecGoal( "Recurrence Scheduling Test Goal", recurrenceGoalDefinition, 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 new file mode 100644 index 0000000000..5e38b57b19 --- /dev/null +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/DeletionTests.java @@ -0,0 +1,215 @@ +package gov.nasa.jpl.aerie.e2e.procedural.scheduling; + +import gov.nasa.jpl.aerie.e2e.types.GoalInvocationId; +import gov.nasa.jpl.aerie.e2e.utils.GatewayRequests; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.json.Json; +import java.io.IOException; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DeletionTests extends ProceduralSchedulingSetup { + private GoalInvocationId procedureId; + + @BeforeEach + void localBeforeEach() throws IOException { + try (final var gateway = new GatewayRequests(playwright)) { + int procedureJarId = gateway.uploadJarFile("build/libs/ActivityDeletionGoal.jar"); + // Add Scheduling Procedure + procedureId = hasura.createSchedulingSpecProcedure( + "Test Scheduling Procedure", + procedureJarId, + specId, + 0 + ); + } + } + + @AfterEach + void localAfterEach() throws IOException { + hasura.deleteSchedulingGoal(procedureId.goalId()); + } + + @Test + void createsThreeActivities() throws IOException { + final var args = Json + .createObjectBuilder() + .add("whichToDelete", -1) + .add("anchorStrategy", "Error") + .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()) + )); + } + + @Test + void deletesLast() throws IOException { + final var args = Json + .createObjectBuilder() + .add("whichToDelete", 2) + .add("anchorStrategy", "Error") + .build(); + + hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); + + hasura.awaitScheduling(specId); + + final var plan = hasura.getPlan(planId); + final var activities = plan.activityDirectives(); + + assertEquals(2, activities.size()); + + final AtomicReference id1 = 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 -> Objects.equals(it.type(), "BiteBanana") && Objects.equals(it.anchorId(), id1.get()) + )); + } + + @Test + void deletesMiddleCascade() throws IOException { + final var args = Json + .createObjectBuilder() + .add("whichToDelete", 1) + .add("anchorStrategy", "Cascade") + .build(); + + hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); + + hasura.awaitScheduling(specId); + + final var plan = hasura.getPlan(planId); + final var activities = plan.activityDirectives(); + + assertEquals(1, activities.size()); + + assertTrue(activities.stream().anyMatch( + it -> Objects.equals(it.type(), "BiteBanana") && Objects.equals(it.anchorId(), null) + )); + } + + @Test + void deletesMiddleAnchorToParent() throws IOException { + final var args = Json + .createObjectBuilder() + .add("whichToDelete", 1) + .add("anchorStrategy", "ReAnchor") + .build(); + + hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); + + hasura.awaitScheduling(specId); + + final var plan = hasura.getPlan(planId); + final var activities = plan.activityDirectives(); + + assertEquals(2, activities.size()); + + final AtomicReference id1 = 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 -> Objects.equals(it.type(), "BiteBanana") + && Objects.equals(it.anchorId(), id1.get()) + && Objects.equals(it.startOffset(), "02:00:00") + )); + } + + @Test + void deletesFirstCascade() throws IOException { + final var args = Json + .createObjectBuilder() + .add("whichToDelete", 0) + .add("anchorStrategy", "Cascade") + .build(); + + hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); + + hasura.awaitScheduling(specId); + + final var plan = hasura.getPlan(planId); + final var activities = plan.activityDirectives(); + + assertEquals(0, activities.size()); + } + + @Test + void deletesFirstReAnchorToPlan() throws IOException { + final var args = Json + .createObjectBuilder() + .add("whichToDelete", 0) + .add("anchorStrategy", "ReAnchor") + .build(); + + hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); + + hasura.awaitScheduling(specId); + + final var plan = hasura.getPlan(planId); + final var activities = plan.activityDirectives(); + + assertEquals(2, activities.size()); + + final AtomicReference id2 = new AtomicReference<>(); + assertTrue(activities.stream().anyMatch( + it -> { + final var result = Objects.equals(it.type(), "BiteBanana") + && Objects.equals(it.anchorId(), null) + && Objects.equals(it.startOffset(), "02:00:00"); + 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/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/ProceduralSchedulingSetup.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/ProceduralSchedulingSetup.java index c1ef5853b1..5ab2db69d8 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/ProceduralSchedulingSetup.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/ProceduralSchedulingSetup.java @@ -26,13 +26,7 @@ public abstract class ProceduralSchedulingSetup { // Cross-Test Constants protected final String planStartTimestamp = "2023-01-01T00:00:00+00:00"; - protected final String recurrenceGoalDefinition = - """ - export default function myGoal() { - return Goal.ActivityRecurrenceGoal({ - activityTemplate: ActivityTemplates.PeelBanana({peelDirection: 'fromStem'}), - interval: Temporal.Duration.from({hours:1}) - })}"""; + @BeforeAll void beforeAll() { diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/Plan.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/Plan.java index d050a14496..5fb5d74501 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/Plan.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/Plan.java @@ -12,7 +12,16 @@ public record Plan( int revision, List activityDirectives ) { - public record ActivityDirective(int id, int planId, String type, String startOffset, JsonObject arguments, String name) { + public record ActivityDirective( + int id, + int planId, + String type, + String startOffset, + JsonObject arguments, + String name, + Integer anchorId, + boolean anchoredToStart + ) { public static ActivityDirective fromJSON(JsonObject json){ return new ActivityDirective( json.getInt("id"), diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java index 8d07e3fa95..7c8c1ec9ce 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java @@ -398,6 +398,8 @@ query GetPlan($id: Int!) { startOffset: start_offset type name + anchorId: anchor_id + anchoredToStart: anchored_to_start } constraint_specification { constraint_id