diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/ConstraintsTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/ConstraintsTests.java index fd91e68acc..7417f0e4ec 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/ConstraintsTests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/ConstraintsTests.java @@ -2,6 +2,7 @@ import com.microsoft.playwright.Playwright; import gov.nasa.jpl.aerie.e2e.types.ConstraintError; +import gov.nasa.jpl.aerie.e2e.types.ConstraintRecord; import gov.nasa.jpl.aerie.e2e.types.ExternalDataset.ProfileInput; import gov.nasa.jpl.aerie.e2e.types.ExternalDataset.ProfileInput.ProfileSegmentInput; import gov.nasa.jpl.aerie.e2e.types.ValueSchema; @@ -12,6 +13,7 @@ import javax.json.Json; import javax.json.JsonValue; import java.io.IOException; +import java.util.Comparator; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -109,7 +111,6 @@ void constraintsSucceedOneViolation() throws IOException { assertTrue(constraintResponse.success()); assertEquals(constraintId, constraintResponse.constraintId()); assertEquals(constraintName, constraintResponse.constraintName()); - assertEquals("plan", constraintResponse.type()); // Check the Result assertTrue(constraintResponse.result().isPresent()); final var constraintResult = constraintResponse.result().get(); @@ -143,7 +144,6 @@ void constraintsSucceedNoViolations() throws IOException { assertTrue(constraintResponses.get(0).success()); assertEquals(constraintId, constraintResponses.get(0).constraintId()); assertEquals(constraintName, constraintResponses.get(0).constraintName()); - assertEquals("plan", constraintResponses.get(0).type()); assertTrue( constraintResponses.get(0).result().isPresent()); assertEquals(0, constraintResponses.get(0).result().get().violations().size()); } @@ -161,7 +161,6 @@ void constraintCachedViolation() throws IOException { // Check properties final var cachedRun = cachedRuns.get(0); - assertFalse(cachedRun.definitionOutdated()); assertEquals(constraintId, cachedRun.constraintId()); assertEquals(simDatasetId, cachedRun.simDatasetId()); assertEquals(constraintDefinition, cachedRun.constraintDefinition()); @@ -179,15 +178,16 @@ void constraintCachedNoViolations() throws IOException { // Delete activity to avoid violation hasura.deleteActivity(planId, activityId); final var simDatasetId = hasura.awaitSimulation(planId).simDatasetId(); - hasura.checkConstraints(planId); + final var constraintsResults = hasura.checkConstraints(planId); final var cachedRuns = hasura.getConstraintRuns(simDatasetId); // There's the correct number of results assertEquals(1, cachedRuns.size()); + assertEquals(1, constraintsResults.size()); // Check properties final var cachedRun = cachedRuns.get(0); - assertFalse(cachedRun.definitionOutdated()); + assertEquals(constraintsResults.get(0).constraintRevision(), cachedRun.constraintRevision()); assertEquals(constraintId, cachedRun.constraintId()); assertEquals(simDatasetId, cachedRun.simDatasetId()); assertEquals(constraintDefinition, cachedRun.constraintDefinition()); @@ -202,21 +202,6 @@ void constraintCachedNoViolations() throws IOException { assertEquals(0,results.gaps().size()); } - @Test - void constraintCacheInvalidatedDefinition() throws IOException { - final int simDatasetId = hasura.awaitSimulation(planId).simDatasetId(); - hasura.checkConstraints(planId); - - // Updating the constraint definition should mark the constraint run as invalid - final String newDefinition = - "export default (): Constraint => Real.Resource(\"/peel\").equal(Real.Resource(\"/fruit\"))"; - hasura.updateConstraint(constraintId, newDefinition); - - final var cachedRuns = hasura.getConstraintRuns(simDatasetId); - assertEquals(1, cachedRuns.size()); - assertTrue(cachedRuns.get(0).definitionOutdated()); - } - /** * Test that an activity with a duration longer than one month is written to and read back from the database * successfully @@ -228,7 +213,7 @@ void constraintCacheInvalidatedDefinition() throws IOException { @Test void constraintsWorkMonthLongActivity() throws IOException { // Setup - hasura.updateConstraint( + hasura.updateConstraintDefinition( constraintId, "export default (): Constraint => Windows.During(ActivityType.ControllableDurationActivity).not()"); hasura.deleteActivity(planId, activityId); @@ -247,7 +232,6 @@ void constraintsWorkMonthLongActivity() throws IOException { assertTrue(constraintResponse.success()); assertEquals(constraintId,constraintResponse.constraintId()); assertEquals(constraintName, constraintResponse.constraintName()); - assertEquals("plan", constraintResponse.type()); //Check Result assertTrue(constraintResponse.result().isPresent()); @@ -281,7 +265,6 @@ void runConstraintsOnOldSimulation() throws IOException { assertEquals(1, newConstraintResponses.size()); assertEquals(constraintId, newConstraintResponses.get(0).constraintId()); assertEquals(constraintName, newConstraintResponses.get(0).constraintName()); - assertEquals("plan", newConstraintResponses.get(0).type()); assertTrue(newConstraintResponses.get(0).result().isPresent()); final var newConstraintResult = newConstraintResponses.get(0).result().get(); assertTrue(newConstraintResult.violations().isEmpty()); @@ -296,7 +279,6 @@ void runConstraintsOnOldSimulation() throws IOException { assertTrue(oldConstraintResponse.success()); assertEquals(constraintId, oldConstraintResponse.constraintId()); assertEquals(constraintName, oldConstraintResponse.constraintName()); - assertEquals("plan", oldConstraintResponse.type()); assertTrue(oldConstraintResponse.result().isPresent()); final var constraintResult = oldConstraintResponse.result().get(); @@ -319,19 +301,103 @@ void runConstraintsOnOldSimulation() throws IOException { assertTrue(constraintResult.gaps().isEmpty()); } + /** + * If a plan specifies a version of a constraint to use, then that version will be used + * when checking constraints. + * + * If the test fails with a compilation error, that means it used the latest version of the constraint + */ @Test - void cachedRunsNotOutdatedOnResim() throws IOException { - final int oldSimDatasetId = hasura.awaitSimulation(planId).simDatasetId(); - hasura.checkConstraints(planId); - - // Delete Activity to make the simulation outdated, then resim - hasura.deleteActivity(planId, activityId); + void constraintVersionLocking() throws IOException { hasura.awaitSimulation(planId); - // Get the old run - final var cachedRuns = hasura.getConstraintRuns(oldSimDatasetId); - assertEquals(1, cachedRuns.size()); - assertFalse(cachedRuns.get(0).definitionOutdated()); + // Update the plan's constraint specification to use a specific version + hasura.updatePlanConstraintSpecVersion(planId, constraintId, 0); + + // Update definition to have invalid syntax + final int newRevision = hasura.updateConstraintDefinition( + constraintId, + " error :-("); + + // Check constraints -- should succeed + final var initResults = hasura.checkConstraints(planId); + assertEquals(1, initResults.size()); + final var initConstraint = initResults.get(0); + assertEquals(constraintId, initConstraint.constraintId()); + assertEquals(0, initConstraint.constraintRevision()); + assertTrue(initConstraint.success()); + assertTrue(initConstraint.errors().isEmpty()); + assertTrue(initConstraint.result().isPresent()); + + // Update constraint spec to use invalid definition + hasura.updatePlanConstraintSpecVersion(planId, constraintId, newRevision); + + // Check constraints -- should fail + final var badDefinitionResults = hasura.checkConstraints(planId); + assertEquals(1, badDefinitionResults.size()); + final var badConstraint = badDefinitionResults.get(0); + assertEquals(constraintId, badConstraint.constraintId()); + assertFalse(badConstraint.success()); + assertEquals(constraintName, badConstraint.constraintName()); + assertEquals(2, badConstraint.errors().size()); + assertEquals("Constraint 'fruit_equal_peel' compilation failed:\n" + + " TypeError: TS2306 No default export. Expected a default export function with the signature: " + + "\"(...args: []) => Constraint | Promise\".", + badConstraint.errors().get(0).message()); + assertEquals("Constraint 'fruit_equal_peel' compilation failed:\n TypeError: TS1109 Expression expected.", + badConstraint.errors().get(1).message()); + + // Update constraint spec to use initial definition + hasura.updatePlanConstraintSpecVersion(planId, constraintId, 0); + + // Check constraints -- should match + assertEquals(initResults, hasura.checkConstraints(planId)); + } + + @Test + @DisplayName("Disabled Constraints are not checked") + void constraintIgnoreDisabled() throws IOException { + hasura.awaitSimulation(planId); + // Add a problematic constraint to the spec, then disable it + final String problemConstraintName = "bad constraint"; + final int problemConstraintId = hasura.insertPlanConstraint( + problemConstraintName, + planId, + "error :-(", + "constraint that shouldn't compile"); + try { + hasura.updatePlanConstraintSpecEnabled(planId, problemConstraintId, false); + + // Check constraints -- Validate that only the enabled constraint is included + final var initResults = hasura.checkConstraints(planId); + assertEquals(1, initResults.size()); + assertEquals(constraintId, initResults.get(0).constraintId()); + assertTrue(initResults.get(0).success()); + + // Enable disabled constraint + hasura.updatePlanConstraintSpecEnabled(planId, problemConstraintId, true); + + // Check constraints -- Validate that the other constraint is present and a failure + final var results = hasura.checkConstraints(planId); + results.sort(Comparator.comparing(ConstraintRecord::constraintId)); + assertEquals(2, results.size()); + assertEquals(constraintId, results.get(0).constraintId()); + assertTrue(results.get(0).success()); + + final var problemResults = results.get(1); + assertEquals(problemConstraintId, problemResults.constraintId()); + assertFalse(problemResults.success()); + assertEquals(problemConstraintName, problemResults.constraintName()); + assertEquals(2, problemResults.errors().size()); + assertEquals("Constraint 'bad constraint' compilation failed:\n" + + " TypeError: TS2306 No default export. Expected a default export function with the signature: " + + "\"(...args: []) => Constraint | Promise\".", + problemResults.errors().get(0).message()); + assertEquals("Constraint 'bad constraint' compilation failed:\n TypeError: TS1109 Expression expected.", + problemResults.errors().get(1).message()); + } finally { + hasura.deleteConstraint(problemConstraintId); + } } @Nested @@ -353,7 +419,7 @@ class WithExternalDatasets { @BeforeEach void beforeEach() throws IOException { // Change Constraint to be about MyBoolean - hasura.updateConstraint( + hasura.updateConstraintDefinition( constraintId, constraintDefinition); // Simulate Plan @@ -382,7 +448,6 @@ void oneViolationCurrentSimulation() throws IOException { assertTrue(noDatasetResponses.get(0).success()); assertEquals(constraintId, noDatasetResponses.get(0).constraintId()); assertEquals(constraintName, noDatasetResponses.get(0).constraintName()); - assertEquals("plan", noDatasetResponses.get(0).type()); assertTrue(noDatasetResponses.get(0).result().isPresent()); final var nRecordResults = noDatasetResponses.get(0).result().get(); @@ -392,7 +457,6 @@ void oneViolationCurrentSimulation() throws IOException { assertTrue(withDatasetResponses.get(0).success()); assertEquals(constraintId, withDatasetResponses.get(0).constraintId()); assertEquals(constraintName, withDatasetResponses.get(0).constraintName()); - assertEquals("plan", withDatasetResponses.get(0).type()); assertTrue(withDatasetResponses.get(0).result().isPresent()); final var wRecordResults = withDatasetResponses.get(0).result().get(); @@ -445,7 +509,6 @@ void oneViolationOutdatedSimIdPassed() throws IOException { assertTrue(constraintResponse.success()); assertEquals(constraintId, constraintResponse.constraintId()); assertEquals(constraintName, constraintResponse.constraintName()); - assertEquals("plan", constraintResponse.type()); assertTrue(constraintResponse.result().isPresent()); final var record = constraintResponse.result().get(); @@ -493,7 +556,6 @@ void compilationFailsOutdatedSimulationSimDatasetId() throws IOException { final var constraintResponse = constraintResponses.get(0); assertEquals(constraintId, constraintResponse.constraintId()); assertEquals(constraintName, constraintResponse.constraintName()); - assertEquals("plan", constraintResponse.type()); assertFalse(constraintResponse.success()); assertTrue(constraintResponse.result().isEmpty()); assertEquals(1,constraintResponse.errors().size()); @@ -517,62 +579,11 @@ void compilationFailsOutdatedSimulationNoSimDataset() throws IOException { assertFalse(constraintResponse.success()); assertEquals(constraintId, constraintResponse.constraintId()); assertEquals(constraintName, constraintResponse.constraintName()); - assertEquals("plan", constraintResponse.type()); assertTrue(constraintResponse.result().isEmpty()); assertEquals(1,constraintResponse.errors().size()); final ConstraintError error = constraintResponse.errors().get(0); assertEquals("Constraint 'fruit_equal_peel' compilation failed:\n" + " TypeError: TS2345 Argument of type '\"/my_boolean\"' is not assignable to parameter of type 'ResourceName'.",error.message()); - - } - - /** - * This test case evaluates the scenario where a user initially has a functioning constraint. - * However, during the modification process, they introduce a compilation error. - * The goal is to confirm that the user can successfully fix the compilation error, - * ensuring that the constraint compiles correctly and returns a valid response. - */ - @Test - @DisplayName("If a constraint is updated and then reverted, Constraints will load the cached value from before the update.") - void constraintInvalidModification() throws IOException { - // Simulate the plan to insure a new simulation set - hasura.awaitSimulation(planId); - - // Check the initial constraints responses it should compile - final var constraintsResponses = hasura.checkConstraints(planId); - assertEquals(1, constraintsResponses.size()); - assertTrue(constraintsResponses.get(0).success()); - assertEquals(constraintId, constraintsResponses.get(0).constraintId()); - assertEquals(constraintName, constraintsResponses.get(0).constraintName()); - assertEquals("plan", constraintsResponses.get(0).type()); - // Attempt to update a constraint with an invalid syntax and capture the error response - hasura.updateConstraint( - constraintId, - WithExternalDatasets.constraintDefinition + "; error"); - - // Check the constraints response after the invalid modification - final var constraintsErrorResponses = hasura.checkConstraints(planId); - assertEquals(1, constraintsErrorResponses.size()); - assertFalse(constraintsErrorResponses.get(0).success()); - assertEquals(constraintId, constraintsErrorResponses.get(0).constraintId()); - assertEquals(constraintName, constraintsErrorResponses.get(0).constraintName()); - assertEquals("plan", constraintsErrorResponses.get(0).type()); - assertTrue(constraintsErrorResponses.get(0).result().isEmpty()); - assertEquals(1, constraintsErrorResponses.get(0).errors().size()); - - // Restore the original valid constraint - hasura.updateConstraint( - constraintId, - WithExternalDatasets.constraintDefinition); - - // Check the constraints response after reverting to the valid constraint - final var constraintsCacheResponses = hasura.checkConstraints(planId); - assertEquals(1, constraintsCacheResponses.size()); - assertTrue(constraintsCacheResponses.get(0).success()); - - // Ensure that the constraints responses are the same before and after the invalid modification - assertEquals(constraintsResponses, constraintsCacheResponses); } - } } diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/CachedConstraintRun.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/CachedConstraintRun.java index 663f92bb15..8ba9efd4e5 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/CachedConstraintRun.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/CachedConstraintRun.java @@ -5,17 +5,17 @@ public record CachedConstraintRun( int constraintId, + int constraintRevision, int simDatasetId, String constraintDefinition, - boolean definitionOutdated, Optional results ) { public static CachedConstraintRun fromJSON(JsonObject json) { return new CachedConstraintRun( json.getInt("constraint_id"), + json.getInt("constraint_revision"), json.getInt("simulation_dataset_id"), - json.getString("constraint_definition"), - json.getBoolean("definition_outdated"), + json.getJsonObject("constraint_definition").getString("definition"), json.getJsonObject("results").isEmpty() ? Optional.empty() : Optional.of(ConstraintResult.fromJSON(json.getJsonObject("results"))) diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintRecord.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintRecord.java index 48cfebcfbb..5cac2eb096 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintRecord.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ConstraintRecord.java @@ -7,8 +7,8 @@ public record ConstraintRecord( boolean success, int constraintId, + int constraintRevision, String constraintName, - String type, Optional result, List errors @@ -17,8 +17,8 @@ public static ConstraintRecord fromJSON(JsonObject json){ return new ConstraintRecord( json.getBoolean("success"), json.getInt("constraintId"), + json.getInt("constraintRevision"), json.getString("constraintName"), - json.getString("type"), json.getJsonObject("results").isEmpty() ? Optional.empty() : Optional.of(ConstraintResult.fromJSON(json.getJsonObject("results"))), json.getJsonArray("errors").getValuesAs(ConstraintError::fromJSON)); } 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..e2f6ace7ec 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 @@ -52,8 +52,8 @@ query checkConstraints($planId: Int!, $simulationDatasetId: Int) { constraintViolations(planId: $planId, simulationDatasetId: $simulationDatasetId) { success constraintId + constraintRevision constraintName - type results { resourceIds gaps { @@ -141,7 +141,10 @@ mutation DeleteActivityDirective($id: Int!, $plan_id: Int!) { }"""), DELETE_CONSTRAINT(""" mutation DeleteConstraint($id: Int!) { - delete_constraint_by_pk(id: $id) { + delete_constraint_specification(where: {constraint_id: {_eq: $id}}){ + affected_rows + } + delete_constraint_metadata_by_pk(id: $id) { id } }"""), @@ -172,6 +175,12 @@ mutation DeletePlan($id: Int!) { id } } + deleteConstraintSpec: delete_constraint_specification(where: {plan_id: {_eq: $id}}){ + returning { + constraint_id + constraint_revision + } + } }"""), DELETE_SCHEDULING_GOAL(""" mutation DeleteSchedulingGoal($goalId: Int!) { @@ -213,11 +222,13 @@ query GetActivityTypes($missionModelId: Int!) { GET_CONSTRAINT_RUNS(""" query getConstraintRuns($simulationDatasetId: Int!) { constraint_run(where: {simulation_dataset_id: {_eq: $simulationDatasetId}}) { - constraint_definition constraint_id + constraint_revision simulation_dataset_id - definition_outdated results + constraint_definition { + definition + } } }"""), GET_EFFECTIVE_ACTIVITY_ARGUMENTS_BULK(""" @@ -279,13 +290,16 @@ query GetPlan($id: Int!) { startOffset: start_offset type } - constraints { - definition - description - id - model_id - name - plan_id + constraint_specification { + constraint_id + constraint_revision + constraint_metadata{ + name + description + } + constraint_definition { + definition + } } duration id @@ -294,14 +308,6 @@ query GetPlan($id: Int!) { name parameters } - constraints { - definition - description - id - model_id - name - plan_id - } id parameters { parameters @@ -436,10 +442,10 @@ query GetTopicsEvents($datasetId: Int!) { } } }"""), - INSERT_CONSTRAINT(""" - mutation insertConstraint($constraint: constraint_insert_input!) { - constraint: insert_constraint_one(object: $constraint) { - id + INSERT_PLAN_SPEC_CONSTRAINT(""" + mutation insertConstraintAssignToPlanSpec($constraint: constraint_specification_insert_input!) { + constraint: insert_constraint_specification_one(object: $constraint){ + constraint_id } }"""), INSERT_PROFILE(""" @@ -497,12 +503,35 @@ query Simulate($plan_id: Int!) { }"""), UPDATE_CONSTRAINT(""" mutation updateConstraint($constraintId: Int!, $constraintDefinition: String!) { - update_constraint(where: {id: {_eq: $constraintId}}, _set: {definition: $constraintDefinition}) { - returning { - definition - } + constraint: insert_constraint_definition_one(object: {constraint_id: $constraintId, definition: $constraintDefinition}) { + definition + revision } }"""), + UPDATE_CONSTRAINT_SPEC_VERSION(""" + mutation updateConstraintSpecVersion($plan_id: Int!, $constraint_id: Int!, $constraint_revision: Int!) { + update_constraint_specification_by_pk( + pk_columns: {constraint_id: $constraint_id, plan_id: $plan_id}, + _set: {constraint_revision: $constraint_revision} + ) { + plan_id + constraint_id + constraint_revision + enabled + } + }"""), + UPDATE_CONSTRAINT_SPEC_ENABLED(""" + mutation updateConstraintSpecVersion($plan_id: Int!, $constraint_id: Int!, $enabled: Boolean!) { + update_constraint_specification_by_pk( + pk_columns: {constraint_id: $constraint_id, plan_id: $plan_id}, + _set: {enabled: $enabled} + ) { + plan_id + constraint_id + constraint_revision + enabled + } + }"""), UPDATE_ROLE_ACTION_PERMISSIONS(""" mutation updateRolePermissions($role: user_roles_enum!, $action_permissions: jsonb!) { permissions: update_user_role_permission_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 ed15f52718..aa5bf80841 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 @@ -35,7 +35,7 @@ public class HasuraRequests implements AutoCloseable { public HasuraRequests(Playwright playwright) { request = playwright.request().newContext( new APIRequest.NewContextOptions() - .setBaseURL(BaseURL.HASURA.url).setTimeout(0)); + .setBaseURL(BaseURL.HASURA.url)); } @Override @@ -735,20 +735,46 @@ public List getConstraintRuns(int simulationDatasetId) thro public int insertPlanConstraint(String name, int planId, String definition, String description) throws IOException { final var constraintInsertBuilder = Json.createObjectBuilder() - .add("name", name) .add("plan_id", planId) - .add("definition", definition) - .add("description", description); + .add("constraint_metadata", + Json.createObjectBuilder() + .add("data", + Json.createObjectBuilder() + .add("name", name) + .add("description", description) + .add("versions", + Json.createObjectBuilder() + .add("data", + Json.createObjectBuilder() + .add("definition", definition))))); final var variables = Json.createObjectBuilder().add("constraint", constraintInsertBuilder).build(); - return makeRequest(GQL.INSERT_CONSTRAINT, variables).getJsonObject("constraint").getInt("id"); + return makeRequest(GQL.INSERT_PLAN_SPEC_CONSTRAINT, variables).getJsonObject("constraint").getInt("constraint_id"); } - public void updateConstraint(int constraintId, String definition) throws IOException{ + public void updatePlanConstraintSpecVersion(int planId, int constraintId, int constraintRevision) throws IOException { + final var variables = Json.createObjectBuilder() + .add("plan_id", planId) + .add("constraint_id", constraintId) + .add("constraint_revision", constraintRevision) + .build(); + makeRequest(GQL.UPDATE_CONSTRAINT_SPEC_VERSION, variables); + } + + public void updatePlanConstraintSpecEnabled(int planId, int constraintId, boolean enabled) throws IOException { + final var variables = Json.createObjectBuilder() + .add("plan_id", planId) + .add("constraint_id", constraintId) + .add("enabled", enabled) + .build(); + makeRequest(GQL.UPDATE_CONSTRAINT_SPEC_ENABLED, variables); + } + + public int updateConstraintDefinition(int constraintId, String definition) throws IOException{ final var variables = Json.createObjectBuilder() .add("constraintId", constraintId) .add("constraintDefinition", definition) .build(); - makeRequest(GQL.UPDATE_CONSTRAINT, variables); + return makeRequest(GQL.UPDATE_CONSTRAINT, variables).getJsonObject("constraint").getInt("revision"); } public void deleteConstraint(int constraintId) throws IOException {