From b2ee901605a6f5d7dc850e17f407bd08cc36703b Mon Sep 17 00:00:00 2001 From: Theresa Kamerman Date: Mon, 11 Mar 2024 09:56:27 -0700 Subject: [PATCH] Add new e2eTests --- .../gov/nasa/jpl/aerie/e2e/BindingsTests.java | 55 +++++++++++++++--- .../nasa/jpl/aerie/e2e/SimulationTests.java | 46 +++++++++++++++ .../e2e/types/SimulationConfiguration.java | 25 ++++++++ .../gov/nasa/jpl/aerie/e2e/utils/GQL.java | 20 +++++++ .../jpl/aerie/e2e/utils/HasuraRequests.java | 58 +++++++++++++++++++ 5 files changed, 195 insertions(+), 9 deletions(-) create mode 100644 e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/SimulationConfiguration.java diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/BindingsTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/BindingsTests.java index 571b9a24fc..7d652eedc5 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/BindingsTests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/BindingsTests.java @@ -21,6 +21,9 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import javax.json.Json; import javax.json.JsonArray; @@ -30,8 +33,10 @@ import java.io.StringReader; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Named.named; /** * Test the Action Bindings for the Merlin and Scheduler Servers @@ -156,16 +161,17 @@ void invalidPlanId() { // Returns a 404 if the PlanId is invalid // message is "no such plan" final String data = Json.createObjectBuilder() - .add("action", Json.createObjectBuilder().add("name", "simulate")) - .add("input", Json.createObjectBuilder().add("planId", -1)) - .add("request_query", "") - .add("session_variables", admin.getSession()) - .build() - .toString(); + .add("action", Json.createObjectBuilder().add("name", "simulate")) + .add("input", Json.createObjectBuilder().add("planId", -1)) + .add("request_query", "") + .add("session_variables", admin.getSession()) + .build() + .toString(); final var response = request.post("/getSimulationResults", RequestOptions.create().setData(data)); assertEquals(404, response.status()); assertEquals("no such plan", getBody(response).getString("message")); } + @Test void unauthorized() { // Returns a 403 if Unauthorized @@ -178,10 +184,12 @@ void unauthorized() { .toString(); final var response = request.post("/getSimulationResults", RequestOptions.create().setData(data)); assertEquals(403, response.status()); - assertEquals("User '"+nonOwner.name()+"' with role 'user' cannot perform 'simulate' because they are not " - + "a 'PLAN_OWNER_COLLABORATOR' for plan with id '"+planId+"'", - getBody(response).getString("message")); + assertEquals( + "User '" + nonOwner.name() + "' with role 'user' cannot perform 'simulate' because they are not " + + "a 'PLAN_OWNER_COLLABORATOR' for plan with id '" + planId + "'", + getBody(response).getString("message")); } + @Test void valid() throws InterruptedException { // Returns a 200 otherwise @@ -199,6 +207,35 @@ void valid() throws InterruptedException { // Delay 1s to allow any workers to finish with the request Thread.sleep(1000); } + + static Stream forceArgs() { + return Stream.of( + Arguments.arguments(named("valid, force is NULL", JsonValue.NULL)), + Arguments.arguments(named("valid, force is TRUE", JsonValue.TRUE)), + Arguments.arguments(named("valid, force is FALSE", JsonValue.FALSE)) + ); + } + + @ParameterizedTest + @MethodSource("forceArgs") + void validWithForce(JsonValue force) throws InterruptedException { + // Returns a 200 otherwise + // "status" is not "failed" + final String data = Json.createObjectBuilder() + .add("action", Json.createObjectBuilder().add("name", "simulate")) + .add( + "input", + Json.createObjectBuilder().add("planId", planId).add("force", force)) + .add("request_query", "") + .add("session_variables", admin.getSession()) + .build() + .toString(); + final var response = request.post("/getSimulationResults", RequestOptions.create().setData(data)); + assertEquals(200, response.status()); + assertNotEquals("failed", getBody(response).getString("status")); + // Delay 1s to allow any workers to finish with the request + Thread.sleep(1000); + } } @Nested diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/SimulationTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/SimulationTests.java index fb19099fd9..28a482e199 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/SimulationTests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/SimulationTests.java @@ -319,4 +319,50 @@ void cancelingSimReturnsPartialResults() throws IOException { assertEquals(startedActivities, results.activities().size()); } } + + @Nested + class ForceResimulation { + private int simulationDatasetId; + @BeforeEach + void beforeEach() throws IOException { + simulationDatasetId = hasura.awaitSimulation(planId).simDatasetId(); + } + @Test + void noResimWhenNull() throws IOException { + assertEquals(simulationDatasetId, hasura.awaitSimulation(planId, null).simDatasetId()); + } + + @Test + void noResimWhenFalse() throws IOException { + assertEquals(simulationDatasetId, hasura.awaitSimulation(planId, false).simDatasetId()); + } + + @Test + void noResimWhenAbsent() throws IOException { + assertEquals(simulationDatasetId, hasura.awaitSimulation(planId).simDatasetId()); + } + + @Test + void resimOnlyUpdatesConfigRevision() throws IOException { + final int planRevision = hasura.getPlanRevision(planId); + final var simConfig = hasura.getSimConfig(planId); + + // Assert forcibly resimming returned a new simulation dataset + final var newSimDatasetId = hasura.awaitSimulation(planId, true).simDatasetId(); + assertNotEquals(simulationDatasetId, newSimDatasetId); + + // Assert that the plan revision is unchanged + assertEquals(planRevision, hasura.getPlanRevision(planId)); + + // Assert that the simulation configuration has only had its revision updated + final var newSimConfig = hasura.getSimConfig(planId); + assertNotEquals(simConfig.revision(), newSimConfig.revision()); + assertEquals(simConfig.id(), newSimConfig.id()); + assertEquals(simConfig.planId(), newSimConfig.planId()); + assertEquals(simConfig.simulationTemplateId(), newSimConfig.simulationTemplateId()); + assertEquals(simConfig.arguments(), newSimConfig.arguments()); + assertEquals(simConfig.simulationStartTime(), newSimConfig.simulationStartTime()); + assertEquals(simConfig.simulationEndTime(), newSimConfig.simulationEndTime()); + } + } } diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/SimulationConfiguration.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/SimulationConfiguration.java new file mode 100644 index 0000000000..3c305faccf --- /dev/null +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/SimulationConfiguration.java @@ -0,0 +1,25 @@ +package gov.nasa.jpl.aerie.e2e.types; + +import javax.json.JsonObject; +import java.util.Optional; + +public record SimulationConfiguration( + int id, + int revision, + int planId, + Optional simulationTemplateId, + JsonObject arguments, + String simulationStartTime, + String simulationEndTime +) { + public static SimulationConfiguration fromJSON(JsonObject json) { + return new SimulationConfiguration( + json.getInt("id"), + json.getInt("revision"), + json.getInt("plan_id"), + json.isNull("simulation_template_id") ? Optional.empty() : Optional.of(json.getInt("simulation_template_id")), + json.getJsonObject("arguments"), + json.getString("simulation_start_time"), + json.getString("simulation_end_time")); + } +} 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 68554dedff..a2dda9d2d1 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 @@ -384,6 +384,18 @@ query GetSchedulingRequest($specificationId: Int!, $specificationRev: Int!) { status } }"""), + GET_SIMULATION_CONFIGURATION(""" + query GetSimConfig($planId: Int!) { + sim_config: simulation(where: {plan_id: {_eq:$planId}}) { + id + revision + plan_id + simulation_template_id + arguments + simulation_start_time + simulation_end_time + } + }"""), GET_SIMULATION_DATASET(""" query GetSimulationDataset($id: Int!) { simulationDataset: simulation_dataset_by_pk(id: $id) { @@ -501,6 +513,14 @@ query Simulate($plan_id: Int!) { simulationDatasetId } }"""), + SIMULATE_FORCE(""" + query SimulateForce($plan_id: Int!, $force: Boolean) { + simulate(planId: $plan_id, force: $force){ + status + reason + simulationDatasetId + } + }"""), UPDATE_ACTIVITY_DIRECTIVE_ARGUMENTS(""" mutation updateActivityDirectiveArguments($id: Int!, $plan_id: Int!, $arguments: jsonb!) { updateActivityDirectiveArguments: update_activity_directive_by_pk( 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 d854205198..6d66ea2333 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 @@ -260,6 +260,16 @@ private SimulationResponse simulate(int planId) throws IOException { return SimulationResponse.fromJSON(makeRequest(GQL.SIMULATE, variables).getJsonObject("simulate")); } + private SimulationResponse simulateForce(int planId, Boolean force) throws IOException { + final var variables = Json.createObjectBuilder().add("plan_id", planId); + if (force == null) { + variables.add("force", JsonValue.NULL); + } else { + variables.add("force", force); + } + return SimulationResponse.fromJSON(makeRequest(GQL.SIMULATE_FORCE, variables.build()).getJsonObject("simulate")); + } + private SimulationDataset cancelSimulation(int simDatasetId, int timeout) throws IOException { final var variables = Json.createObjectBuilder().add("id", simDatasetId).build(); makeRequest(GQL.CANCEL_SIMULATION, variables); @@ -307,6 +317,47 @@ public SimulationResponse awaitSimulation(int planId, int timeout) throws IOExce throw new TimeoutError("Simulation timed out after " + timeout + " seconds"); } + /** + * Simulate the specified plan, potentially forcibly, with a timeout of 30 seconds + * @param planId the plan to simulate + * @param force whether to forcibly resimulate in the event of an existing dataset. + */ + public SimulationResponse awaitSimulation(int planId, Boolean force) throws IOException { + return awaitSimulation(planId, force, 30); + } + + /** + * Simulate the specified plan, potentially forcibly, with a set timeout + * @param planId the plan to simulate + * @param force whether to forcibly resimulate in the event of an existing dataset. + * @param timeout the length of the timeout, in seconds + */ + public SimulationResponse awaitSimulation(int planId, Boolean force, int timeout) throws IOException { + for (int i = 0; i < timeout; ++i) { + final SimulationResponse response; + // Only use force on the initial request to avoid an infinite loop of making new sim requests + if (i == 0) { + response = simulateForce(planId, force); + } else { + response = simulate(planId); + } + switch (response.status()) { + case "pending", "incomplete" -> { + try { + Thread.sleep(1000); // 1s + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + case "complete" -> { + return response; + } + default -> fail("Simulation returned bad status " + response.status() + " with reason " + response.reason()); + } + } + throw new TimeoutError("Simulation timed out after " + timeout + " seconds"); + } + /** * Start and immediately cancel a simulation with a timeout of 30 seconds * @param planId the plan to simulate @@ -351,6 +402,13 @@ public int getSimulationId(int planId) throws IOException { return makeRequest(GQL.GET_SIMULATION_ID, variables).getJsonArray("simulation").getJsonObject(0).getInt("id"); } + public SimulationConfiguration getSimConfig(int planId) throws IOException { + final var variables = Json.createObjectBuilder().add("planId", planId).build(); + final var simConfig = makeRequest(GQL.GET_SIMULATION_CONFIGURATION, variables).getJsonArray("sim_config"); + assertEquals(1, simConfig.size()); + return SimulationConfiguration.fromJSON(simConfig.getJsonObject(0)); + } + public int insertAndAssociateSimTemplate(int modelId, String description, JsonObject arguments, int simConfigId) throws IOException {