Skip to content

Commit

Permalink
Add database deletion e2e tests
Browse files Browse the repository at this point in the history
  • Loading branch information
JoelCourtney committed Jan 31, 2025
1 parent f13b669 commit 1e50c77
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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;

/**
* Deletes all Bite Bananas with extreme prejudice. Used to test that updated
* anchors are saved in the database properly.
*/
@SchedulingProcedure
public record DeleteBiteBananasGoal() implements Goal {
@Override
public void run(@NotNull final EditablePlan plan) {
plan.directives("BiteBanana").forEach($ -> plan.delete($, DeletedAnchorStrategy.PreserveTree));
plan.commit();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
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 javax.json.JsonValue;
import java.io.IOException;
import java.util.Objects;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class DatabaseDeletionTests extends ProceduralSchedulingSetup {
private GoalInvocationId procedureId;

@BeforeEach
void localBeforeEach() throws IOException {
try (final var gateway = new GatewayRequests(playwright)) {
int procedureJarId = gateway.uploadJarFile("build/libs/DeleteBiteBananaGoal.jar");
// Add Scheduling Procedure
procedureId = hasura.createSchedulingSpecProcedure(
"Test Scheduling Procedure",
procedureJarId,
specId,
0
);
}
}

@AfterEach
void localAfterEach() throws IOException {
hasura.deleteSchedulingGoal(procedureId.goalId());
}

@Test
void deletesDirectiveAlreadyInDatabase() throws IOException {
hasura.insertActivityDirective(
planId,
"BiteBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT
);

var plan = hasura.getPlan(planId);
assertEquals(1, plan.activityDirectives().size());

hasura.awaitScheduling(specId);

plan = hasura.getPlan(planId);
assertEquals(0, plan.activityDirectives().size());
}

@Test
void deletesDirectiveInDatabaseWithAnchor() throws IOException {
final var bite = hasura.insertActivityDirective(
planId,
"BiteBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT
);

final var grow = hasura.insertActivityDirective(
planId,
"GrowBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT,
Json.createObjectBuilder().add("anchor_id", bite)
);

var plan = hasura.getPlan(planId);
var activities = plan.activityDirectives();
assertEquals(2, activities.size());
assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "GrowBanana")
&& Objects.equals(it.anchorId(), bite)
&& Objects.equals(it.startOffset(), "01:00:00")
));

hasura.awaitScheduling(specId);

plan = hasura.getPlan(planId);

activities = plan.activityDirectives();
assertEquals(1, activities.size());

assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "GrowBanana")
&& Objects.equals(it.anchorId(), null)
&& Objects.equals(it.startOffset(), "02:00:00")
));
}

@Test
void deletesDirectiveInDatabaseInMiddleOfChain() throws IOException {

// Creates 5 activities, deletes "Bite".
// grow1 <- bite
// bite <- grow (id lost)
// bite <- grow2
// grow2 <- grow3

// Bite has two children, a grandchild, and a parent.

final var grow1 = hasura.insertActivityDirective(
planId,
"GrowBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT
);

final var bite = hasura.insertActivityDirective(
planId,
"BiteBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT,
Json.createObjectBuilder().add("anchor_id", grow1)
);

int grow2 = -1;
for (int i = 0; i < 2; i++) {
grow2 = hasura.insertActivityDirective(
planId,
"GrowBanana",
i + "h",
JsonValue.EMPTY_JSON_OBJECT,
Json.createObjectBuilder().add("anchor_id", bite)
);
}

final var grow3 = hasura.insertActivityDirective(
planId,
"GrowBanana",
"0h",
JsonValue.EMPTY_JSON_OBJECT,
Json.createObjectBuilder().add("anchor_id", grow2)
);

var plan = hasura.getPlan(planId);
var activities = plan.activityDirectives();
assertEquals(5, activities.size());

hasura.awaitScheduling(specId);

plan = hasura.getPlan(planId);

activities = plan.activityDirectives();
assertEquals(4, activities.size());

assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "GrowBanana")
&& Objects.equals(it.id(), grow1)
&& Objects.equals(it.anchorId(), null)
));
final int finalGrow2 = grow2;
assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "GrowBanana")
&& Objects.equals(it.id(), finalGrow2)
&& Objects.equals(it.anchorId(), grow1)
&& Objects.equals(it.startOffset(), "02:00:00")
));
assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "GrowBanana")
&& Objects.equals(it.anchorId(), grow1)
&& Objects.equals(it.startOffset(), "01:00:00")
));
assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "GrowBanana")
&& Objects.equals(it.id(), grow3)
&& Objects.equals(it.anchorId(), finalGrow2)
&& Objects.equals(it.startOffset(), "00:00:00")
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.junit.jupiter.api.Test;

import javax.json.Json;
import javax.json.JsonValue;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.json.JsonValue;
import javax.json.JsonObject;
import java.io.IOException;
Expand Down Expand Up @@ -225,12 +226,15 @@ public void deletePlan(int planId) throws IOException {
makeRequest(GQL.DELETE_PLAN, variables);
}

public int insertActivityDirective(int planId, String type, String startOffset, JsonObject arguments) throws IOException {
public int insertActivityDirective(int planId, String type, String startOffset, JsonObject arguments, JsonObjectBuilder ...extraArgs) throws IOException {
final var insertActivityBuilder = Json.createObjectBuilder()
.add("plan_id", planId)
.add("type", type)
.add("start_offset", startOffset)
.add("arguments", arguments);
for (final var extraArg : extraArgs) {
insertActivityBuilder.addAll(extraArg);
}
final var variables = Json.createObjectBuilder().add("activityDirectiveInsertInput", insertActivityBuilder).build();
return makeRequest(GQL.CREATE_ACTIVITY_DIRECTIVE, variables).getJsonObject("createActivityDirective").getInt("id");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ class DefaultEditablePlanDriver(
for (d in directives) {
// the when block is used to smart-cast d.start to an Anchor. This is basically just an if statement.
// Basically we're just iterating through looking for activities anchored to the deleted one.

// Kotlin doesn't smart cast objects whose origins it can't statically check for race conditions,
// which is why I have to bind `d.start` to a local variable. Then `childStart` can be smart cast.
when (val childStart = d.start) {
is DirectiveStart.Anchor -> {
if (childStart.parentId == directive.id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,16 +572,16 @@ mutation createAllPlanActivityDirectives($activities: [activity_directive_insert
.add("anchored_to_start", act.anchoredToStart());

if (act.anchorId() != null) {
insertionObject.add("anchor_id", act.anchorId().toString());
insertionObject.add("anchor_id", act.anchorId().id());
}

if (act.name() != null) insertionObject = insertionObject.add("name", act.name());

//add duration to parameters if controllable
final var insertionObjectArguments = Json.createObjectBuilder();
if(act.getType().getDurationType() instanceof DurationType.Controllable durationType){
if(!act.arguments().containsKey(durationType.parameterName())){
insertionObjectArguments.add(durationType.parameterName(), serializedValueP.unparse(schedulerModel.serializeDuration(act.duration())));
if(act.getType().getDurationType() instanceof DurationType.Controllable(String parameterName)){
if(!act.arguments().containsKey(parameterName)){
insertionObjectArguments.add(parameterName, serializedValueP.unparse(schedulerModel.serializeDuration(act.duration())));
}
}

Expand Down Expand Up @@ -679,7 +679,7 @@ private void deleteActivityDirectives(
{
if (ids.isEmpty()) return;
ensurePlanExists(planId);
final var idString = ids.stream().map(String::valueOf).collect(Collectors.joining(","));
final var idString = ids.stream().map($ -> String.valueOf($.id())).collect(Collectors.joining(","));
final var request = """
mutation deletePlanActivityDirectives($planId: Int! = %d, $directiveIds: [Int!]! = [%s]) {
delete_activity_directive(where: {_and: {plan_id: {_eq: $planId}, id: {_in: $directiveIds}}}) {
Expand Down

0 comments on commit 1e50c77

Please sign in to comment.