From e2b653b36d060af8f6c1a8e30791632ae3ee5167 Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Wed, 31 Jan 2024 14:50:24 -0800 Subject: [PATCH] Add E2E Tests - Update queries used in tests - Collapse "insert goal" and "add goal to scheduling spec" into a single function --- .../nasa/jpl/aerie/e2e/SchedulingTests.java | 179 +++++++++++++----- .../gov/nasa/jpl/aerie/e2e/utils/GQL.java | 67 ++++--- .../jpl/aerie/e2e/utils/HasuraRequests.java | 98 ++++++---- 3 files changed, 232 insertions(+), 112 deletions(-) diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/SchedulingTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/SchedulingTests.java index 38c4ea588a..2955842836 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/SchedulingTests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/SchedulingTests.java @@ -11,11 +11,13 @@ import gov.nasa.jpl.aerie.e2e.utils.HasuraRequests; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.opentest4j.AssertionFailedError; import javax.json.Json; import javax.json.JsonObject; @@ -143,11 +145,11 @@ private void insertActivities() throws IOException { @Test void twoInARow() throws IOException { // Setup: Add Goal - final int bakeBananaBreadGoalId = hasura.insertSchedulingGoal( + final int bakeBananaBreadGoalId = hasura.createSchedulingSpecGoal( "BakeBanana Scheduling Test Goal", - modelId, - bakeBananaGoalDefinition); - hasura.createSchedulingSpecGoal(bakeBananaBreadGoalId, schedulingSpecId, 0); + bakeBananaGoalDefinition, + schedulingSpecId, + 0); try { // Schedule and get Plan hasura.awaitScheduling(schedulingSpecId); @@ -174,14 +176,14 @@ void getSchedulingDSLTypeScript() throws IOException { @Test void schedulingRecurrenceGoal() throws IOException { // Setup: Add Goal - final int recurrenceGoalId = hasura.insertSchedulingGoal( + final int recurrenceGoalId = hasura.createSchedulingSpecGoal( "Recurrence Scheduling Test Goal", - modelId, - recurrenceGoalDefinition); - hasura.createSchedulingSpecGoal(recurrenceGoalId, schedulingSpecId, 0); + recurrenceGoalDefinition, + schedulingSpecId, + 0); try { // Schedule and get Plan - hasura.awaitScheduling(schedulingSpecId); + hasura.awaitScheduling(schedulingSpecId, 100000000); final var plan = hasura.getPlan(planId); final var activities = plan.activityDirectives(); @@ -198,11 +200,11 @@ void schedulingRecurrenceGoal() throws IOException { void schedulingCoexistenceGoal() throws IOException { // Setup: Add Goal and Activities insertActivities(); - final int coexistenceGoalId = hasura.insertSchedulingGoal( + final int coexistenceGoalId = hasura.createSchedulingSpecGoal( "Coexistence Scheduling Test Goal", - modelId, - coexistenceGoalDefinition); - hasura.createSchedulingSpecGoal(coexistenceGoalId, schedulingSpecId, 0); + coexistenceGoalDefinition, + schedulingSpecId, + 0); try { // Schedule and get Plan @@ -235,16 +237,16 @@ void schedulingCoexistenceGoal() throws IOException { void schedulingMultipleGoals() throws IOException { // Setup: Add Goals insertActivities(); - final int recurrenceGoalId = hasura.insertSchedulingGoal( + final int recurrenceGoalId = hasura.createSchedulingSpecGoal( "Recurrence Scheduling Test Goal", - modelId, - recurrenceGoalDefinition); - hasura.createSchedulingSpecGoal(recurrenceGoalId, schedulingSpecId, 0); - final int coexistenceGoalId = hasura.insertSchedulingGoal( + recurrenceGoalDefinition, + schedulingSpecId, + 0); + final int coexistenceGoalId = hasura.createSchedulingSpecGoal( "Coexistence Scheduling Test Goal", - modelId, - coexistenceGoalDefinition); - hasura.createSchedulingSpecGoal(coexistenceGoalId, schedulingSpecId, 1); + coexistenceGoalDefinition, + schedulingSpecId, + 1); try { // Schedule and get Plan hasura.awaitScheduling(schedulingSpecId); @@ -360,11 +362,11 @@ void outdatedPlanRevision() throws IOException { hasura.insertActivity(planId, "GrowBanana", "5h", JsonObject.EMPTY_JSON_OBJECT); // Setup: Add Goal - final int coexistenceGoalId = hasura.insertSchedulingGoal( + final int coexistenceGoalId = hasura.createSchedulingSpecGoal( "Coexistence Scheduling Test Goal", - modelId, - coexistenceGoalDefinition); - hasura.createSchedulingSpecGoal(coexistenceGoalId, schedulingSpecId, 0); + coexistenceGoalDefinition, + schedulingSpecId, + 0); try { hasura.updatePlanRevisionSchedulingSpec(planId); @@ -411,11 +413,11 @@ void outdatedSimConfig() throws IOException { hasura.awaitSimulation(planId); hasura.deleteSimTemplate(templateId); // Return to blank sim config args - final int plantGoal = hasura.insertSchedulingGoal( + final int plantGoal = hasura.createSchedulingSpecGoal( "Scheduling Test: When Plant < 300", - modelId, - plantCountGoalDefinition); - hasura.createSchedulingSpecGoal(plantGoal, schedulingSpecId, 0); + plantCountGoalDefinition, + schedulingSpecId, + 0); try { hasura.awaitScheduling(schedulingSpecId); @@ -458,11 +460,11 @@ void injectedResultsLoaded() throws IOException{ List.of(new ProfileSegment("0h", false, Json.createValue(400)))); // Insert Goal - final int plantGoal = hasura.insertSchedulingGoal( + final int plantGoal = hasura.createSchedulingSpecGoal( "Scheduling Test: When Plant < 300", - modelId, - plantCountGoalDefinition); - hasura.createSchedulingSpecGoal(plantGoal, schedulingSpecId, 0); + plantCountGoalDefinition, + schedulingSpecId, + 0); try { hasura.awaitScheduling(schedulingSpecId); @@ -486,11 +488,11 @@ void temporalSubsetExcluded() throws IOException { hasura.awaitSimulation(planId); // Setup: Add Goal - final int coexistenceGoalId = hasura.insertSchedulingGoal( + final int coexistenceGoalId = hasura.createSchedulingSpecGoal( "Coexistence Scheduling Test Goal", - modelId, - coexistenceGoalDefinition); - hasura.createSchedulingSpecGoal(coexistenceGoalId, schedulingSpecId, 0); + coexistenceGoalDefinition, + schedulingSpecId, + 0); try { // Schedule and get Plan @@ -544,16 +546,16 @@ void beforeEach() throws IOException { false); // Add Goal - cardinalityGoalId = hasura.insertSchedulingGoal( + cardinalityGoalId = hasura.createSchedulingSpecGoal( "Cardinality and Decomposition Scheduling Test Goal", - modelId, """ export default function cardinalityGoalExample() { return Goal.CardinalityGoal({ activityTemplate: ActivityTemplates.parent({ label: "unlabeled"}), specification: { duration: Temporal.Duration.from({ seconds: 10 }) }, - });}"""); - hasura.createSchedulingSpecGoal(cardinalityGoalId, schedulingSpecId, 0); + });}""", + schedulingSpecId, + 0); } @AfterEach @@ -632,9 +634,8 @@ void beforeEach() throws IOException { List.of(myBooleanProfile)); // Insert Goal - edGoalId = hasura.insertSchedulingGoal( + edGoalId = hasura.createSchedulingSpecGoal( "On my_boolean true", - modelId, """ export default function myGoal() { return Goal.CoexistenceGoal({ @@ -642,9 +643,9 @@ export default function myGoal() { activityTemplate: ActivityTemplates.BiteBanana({ biteSize: 1, }), startsAt:TimingConstraint.singleton(WindowProperty.END) }) - }"""); - // Add the goal - hasura.createSchedulingSpecGoal(edGoalId, schedulingSpecId, 0); + }""", + schedulingSpecId, + 0); } @AfterEach @@ -730,7 +731,7 @@ void beforeEach() throws IOException, InterruptedException { gateway.uploadFooJar(), "Foo (e2e tests)", "aerie_e2e_tests", - "Simulation Tests"); + "Scheduling Tests"); } // Insert the Plan fooPlan = hasura.createPlan( @@ -749,17 +750,17 @@ void beforeEach() throws IOException, InterruptedException { false); // Add Goal - fooGoalId = hasura.insertSchedulingGoal( + fooGoalId = hasura.createSchedulingSpecGoal( "Foo Recurrence Test Goal", - fooId, """ export default function recurrenceGoalExample() { return Goal.ActivityRecurrenceGoal({ activityTemplate: ActivityTemplates.bar(), interval: Temporal.Duration.from({ hours: 2 }), }); - }"""); - hasura.createSchedulingSpecGoal(fooGoalId, fooSchedulingSpecId, 0); + }""", + fooSchedulingSpecId, + 0); } @AfterEach @@ -795,4 +796,80 @@ void cancelingSchedulingUpdatesRequestReason() throws IOException { assertEquals("Scheduling was interrupted while "+ reasonData.getString("location"), reasonData.getString("message")); } } + + @Nested + class VersioningSchedulingGoals { + @Test + void goalVersionLocking() throws IOException { + final int goalId = hasura.createSchedulingSpecGoal( + "coexistence goal", + coexistenceGoalDefinition, + schedulingSpecId, + 0); + + try { + // Update the plan's constraint specification to use a specific version + hasura.updateSchedulingSpecVersion(schedulingSpecId, goalId, 0); + + // Update definition to have invalid syntax + final int newRevision = hasura.updateGoalDefinition( + goalId, + "error :-("); + + // Schedule -- should succeed + final var initResults = hasura.awaitScheduling(schedulingSpecId); + assertEquals("complete", initResults.status()); + + // Update scheduling spec to use invalid definition + hasura.updateSchedulingSpecVersion(schedulingSpecId, goalId, newRevision); + + // Schedule -- should fail + final var error = Assertions.assertThrows( + AssertionFailedError.class, + () -> hasura.awaitScheduling(schedulingSpecId)); + final var expectedMsg = "Scheduling returned bad status failed with reason {\"data\":[{\"errors\":[" + + "{\"location\":{\"column\":1,\"line\":1},\"message\":\"TypeError: TS2306 No default " + + "export. Expected a default export function with the signature:"; + if (!error.getMessage().contains(expectedMsg)) { + throw error; + } + } finally { + hasura.deleteSchedulingGoal(goalId); + } + } + + @Test + void schedulingIgnoreDisabledGoals() throws IOException { + // Add a problematic goal to the spec, then disable it + final int problemGoalId = hasura.createSchedulingSpecGoal( + "bad goal", + "error :-(", + "Goal that won't compile", + schedulingSpecId, + 0); + try { + hasura.updateSchedulingSpecEnabled(schedulingSpecId, problemGoalId, false); + + // Schedule -- Validate that the plan didn't change + hasura.awaitScheduling(schedulingSpecId); + assertEquals(0, hasura.getPlan(planId).activityDirectives().size()); + + // Enable disabled constraint + hasura.updateSchedulingSpecEnabled(schedulingSpecId, problemGoalId, true); + + // Schedule -- Assert Fail + final var error = Assertions.assertThrows( + AssertionFailedError.class, + () -> hasura.awaitScheduling(schedulingSpecId)); + final var expectedMsg = "Scheduling returned bad status failed with reason {\"data\":[{\"errors\":[" + + "{\"location\":{\"column\":1,\"line\":1},\"message\":\"TypeError: TS2306 No default " + + "export. Expected a default export function with the signature:"; + if (!error.getMessage().contains(expectedMsg)) { + throw error; + } + } finally { + hasura.deleteSchedulingGoal(problemGoalId); + } + } + } } 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 6a687d9445..a03db44013 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 @@ -27,15 +27,13 @@ mutation AssignTemplateToSimulation($simulation_id: Int!, $simulation_template_i }"""), CANCEL_SCHEDULING(""" mutation cancelScheduling($analysis_id: Int!) { - update_scheduling_request(where: {analysis_id: {_eq: $analysis_id}}, _set: {canceled: true}) { - returning { - analysis_id - specification_id - specification_revision - canceled - reason - status - } + update_scheduling_request_by_pk(pk_columns: {analysis_id: $analysis_id}, _set: {canceled: true}) { + analysis_id + specification_id + specification_revision + canceled + reason + status } }"""), CANCEL_SIMULATION(""" @@ -97,21 +95,6 @@ mutation CreatePlan($plan: plan_insert_input!) { revision } }"""), - CREATE_SCHEDULING_GOAL(""" - mutation CreateSchedulingGoal($goal: scheduling_goal_insert_input!) { - goal: insert_scheduling_goal_one(object: $goal) { - author - created_date - definition - description - id - last_modified_by - model_id - modified_date - name - revision - } - }"""), CREATE_SCHEDULING_SPEC_GOAL(""" mutation CreateSchedulingSpecGoal($spec_goal: scheduling_specification_goals_insert_input!) { insert_scheduling_specification_goals_one(object: $spec_goal) { @@ -175,9 +158,11 @@ mutation DeletePlan($id: Int!) { }"""), DELETE_SCHEDULING_GOAL(""" mutation DeleteSchedulingGoal($goalId: Int!) { - delete_scheduling_goal_by_pk(id: $goalId) { + delete_scheduling_specification_goals(where: {goal_id: {_eq: $goalId}}){ + affected_rows + } + delete_scheduling_goal_metadata_by_pk(id: $goalId) { name - definition } }"""), DELETE_SIMULATION_PRESET(""" @@ -368,8 +353,8 @@ query GetSchedulingDslTypeScript($missionModelId: Int!, $planId: Int) { } }"""), GET_SCHEDULING_REQUEST(""" - query GetSchedulingRequest($specificationId: Int!, $specificationRev: Int!) { - scheduling_request_by_pk(specification_id: $specificationId, specification_revision: $specificationRev) { + query GetSchedulingRequest($analysisId: Int!) { + scheduling_request_by_pk(analysis_id: $analysisId) { specification_id specification_revision analysis_id @@ -503,6 +488,12 @@ mutation updateConstraint($constraintId: Int!, $constraintDefinition: String!) { } } }"""), + UPDATE_GOAL_DEFINITION(""" + mutation updateGoalDefinition($goal_id: Int!, $definition: String!) { + definition: insert_scheduling_goal_definition_one(object: {goal_id: $goal_id, definition: $definition}) { + revision + } + }"""), UPDATE_ROLE_ACTION_PERMISSIONS(""" mutation updateRolePermissions($role: user_roles_enum!, $action_permissions: jsonb!) { permissions: update_user_role_permission_by_pk( @@ -512,6 +503,26 @@ mutation updateRolePermissions($role: user_roles_enum!, $action_permissions: jso action_permissions } }"""), + UPDATE_SCHEDULING_SPEC_GOALS_ENABLED(""" + mutation updateSchedulingSpecGoalVersion($spec_id: Int!, $goal_id: Int!, $enabled: Boolean!) { + update_scheduling_specification_goals_by_pk( + pk_columns: {specification_id: $spec_id, goal_id: $goal_id}, + _set: {enabled: $enabled}) + { + goal_revision + enabled + } + }"""), + UPDATE_SCHEDULING_SPEC_GOALS_VERSION(""" + mutation updateSchedulingSpecGoalVersion($spec_id: Int!, $goal_id: Int!, $goal_revision: Int!) { + update_scheduling_specification_goals_by_pk( + pk_columns: {specification_id: $spec_id, goal_id: $goal_id}, + _set: {goal_revision: $goal_revision}) + { + goal_revision + enabled + } + }"""), UPDATE_SCHEDULING_SPECIFICATION_PLAN_REVISION(""" mutation updateSchedulingSpec($planId: Int!, $planRev: Int!) { update_scheduling_specification( diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java index ed15f52718..9151ac6f05 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java @@ -20,7 +20,6 @@ import java.util.function.Function; import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; /** @@ -387,18 +386,12 @@ private SchedulingResponse schedule(int schedulingSpecId) throws IOException { private SchedulingRequest cancelSchedulingRun(int analysisId, int timeout) throws IOException { final var variables = Json.createObjectBuilder().add("analysis_id", analysisId).build(); - //assert that we only canceled one task - final var cancelRequest = makeRequest(GQL.CANCEL_SCHEDULING, variables) - .getJsonObject("update_scheduling_request") - .getJsonArray("returning"); - assertEquals(1, cancelRequest.size()); - final int specId = cancelRequest.getJsonObject(0).getInt("specification_id"); - final int specRev = cancelRequest.getJsonObject(0).getInt("specification_revision"); + makeRequest(GQL.CANCEL_SCHEDULING, variables); for(int i = 0; i