Merge "Add checker description and checker message to the checks table"
diff --git a/java/com/google/gerrit/plugins/checks/api/ApiModule.java b/java/com/google/gerrit/plugins/checks/api/ApiModule.java
index ad1e7e3..88fb5cf 100644
--- a/java/com/google/gerrit/plugins/checks/api/ApiModule.java
+++ b/java/com/google/gerrit/plugins/checks/api/ApiModule.java
@@ -50,7 +50,7 @@
             postOnCollection(CHECK_KIND).to(PostCheck.class);
             get(CHECK_KIND).to(GetCheck.class);
             post(CHECK_KIND).to(UpdateCheck.class);
-
+            post(CHECK_KIND, "rerun").to(RerunCheck.class);
             DynamicMap.mapOf(binder(), PENDING_CHECK_KIND);
           }
         });
diff --git a/java/com/google/gerrit/plugins/checks/api/CheckApi.java b/java/com/google/gerrit/plugins/checks/api/CheckApi.java
index d137645..14acb41 100644
--- a/java/com/google/gerrit/plugins/checks/api/CheckApi.java
+++ b/java/com/google/gerrit/plugins/checks/api/CheckApi.java
@@ -26,6 +26,8 @@
   /** Updates a check and returns the {@link CheckInfo} for the updated resource. */
   CheckInfo update(CheckInput input) throws RestApiException;
 
+  /** Reruns the check and returns the {@link CheckInfo} for the updated check. */
+  CheckInfo rerun() throws RestApiException;
   /**
    * A default implementation which allows source compatibility when adding new methods to the
    * interface.
@@ -40,5 +42,10 @@
     public CheckInfo update(CheckInput input) throws RestApiException {
       throw new NotImplementedException();
     }
+
+    @Override
+    public CheckInfo rerun() throws RestApiException {
+      throw new NotImplementedException();
+    }
   }
 }
diff --git a/java/com/google/gerrit/plugins/checks/api/CheckApiImpl.java b/java/com/google/gerrit/plugins/checks/api/CheckApiImpl.java
index 5cd20f6..3be0a74 100644
--- a/java/com/google/gerrit/plugins/checks/api/CheckApiImpl.java
+++ b/java/com/google/gerrit/plugins/checks/api/CheckApiImpl.java
@@ -30,12 +30,18 @@
   private final GetCheck getCheck;
   private final UpdateCheck updateCheck;
   private final CheckResource checkResource;
+  private final RerunCheck rerunCheck;
 
   @Inject
-  CheckApiImpl(GetCheck getCheck, UpdateCheck updateCheck, @Assisted CheckResource checkResource) {
+  CheckApiImpl(
+      GetCheck getCheck,
+      UpdateCheck updateCheck,
+      @Assisted CheckResource checkResource,
+      RerunCheck rerunCheck) {
     this.getCheck = getCheck;
     this.updateCheck = updateCheck;
     this.checkResource = checkResource;
+    this.rerunCheck = rerunCheck;
   }
 
   @Override
@@ -56,4 +62,13 @@
       throw asRestApiException("Cannot update check", e);
     }
   }
+
+  @Override
+  public CheckInfo rerun() throws RestApiException {
+    try {
+      return rerunCheck.apply(checkResource, null).value();
+    } catch (Exception e) {
+      throw asRestApiException("Cannot rerun check", e);
+    }
+  }
 }
diff --git a/java/com/google/gerrit/plugins/checks/api/RerunCheck.java b/java/com/google/gerrit/plugins/checks/api/RerunCheck.java
new file mode 100644
index 0000000..4c6b0a4
--- /dev/null
+++ b/java/com/google/gerrit/plugins/checks/api/RerunCheck.java
@@ -0,0 +1,119 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.checks.api;
+
+import com.google.gerrit.extensions.common.Input;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.plugins.checks.AdministrateCheckersPermission;
+import com.google.gerrit.plugins.checks.Check;
+import com.google.gerrit.plugins.checks.CheckJson;
+import com.google.gerrit.plugins.checks.CheckKey;
+import com.google.gerrit.plugins.checks.CheckUpdate;
+import com.google.gerrit.plugins.checks.Checker;
+import com.google.gerrit.plugins.checks.CheckerUuid;
+import com.google.gerrit.plugins.checks.Checkers;
+import com.google.gerrit.plugins.checks.Checks;
+import com.google.gerrit.plugins.checks.Checks.GetCheckOptions;
+import com.google.gerrit.plugins.checks.ChecksUpdate;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.UserInitiated;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.Optional;
+import javax.inject.Provider;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+@Singleton
+public class RerunCheck implements RestModifyView<CheckResource, Input> {
+  private final Provider<CurrentUser> self;
+  private final PermissionBackend permissionBackend;
+  private final AdministrateCheckersPermission permission;
+  private final Checks checks;
+  private final Provider<ChecksUpdate> checksUpdate;
+  private final CheckJson.Factory checkJsonFactory;
+  private final Checkers checkers;
+
+  @Inject
+  RerunCheck(
+      Provider<CurrentUser> self,
+      PermissionBackend permissionBackend,
+      AdministrateCheckersPermission permission,
+      Checks checks,
+      @UserInitiated Provider<ChecksUpdate> checksUpdate,
+      CheckJson.Factory checkJsonFactory,
+      Checkers checkers) {
+    this.self = self;
+    this.permissionBackend = permissionBackend;
+    this.permission = permission;
+    this.checks = checks;
+    this.checksUpdate = checksUpdate;
+    this.checkJsonFactory = checkJsonFactory;
+    this.checkers = checkers;
+  }
+
+  @Override
+  public Response<CheckInfo> apply(CheckResource checkResource, Input input)
+      throws RestApiException, IOException, PermissionBackendException, ConfigInvalidException {
+    if (!self.get().isIdentifiedUser()) {
+      throw new AuthException("Authentication required");
+    }
+    permissionBackend.currentUser().check(permission);
+    if (checkResource.getRevisionResource().getEdit().isPresent()) {
+      throw new ResourceConflictException("checks are not supported on a change edit");
+    }
+    CheckKey key =
+        CheckKey.create(
+            checkResource.getRevisionResource().getProject(),
+            checkResource.getRevisionResource().getPatchSet().id(),
+            checkResource.getCheckerUuid());
+    Optional<Check> check = checks.getCheck(key, GetCheckOptions.defaults());
+    CheckerUuid checkerUuid = checkResource.getCheckerUuid();
+    Check updatedCheck;
+    if (!check.isPresent()) {
+      Checker checker =
+          checkers
+              .getChecker(checkerUuid)
+              .orElseThrow(
+                  () ->
+                      new ResourceNotFoundException(
+                          String.format("checker %s not found", checkerUuid)));
+      // This error should not be thrown since this case is filtered before reaching this code.
+      // Also return a backfilled check for checkers that do not apply to the change.
+      updatedCheck =
+          Check.newBackfilledCheck(
+              checkResource.getRevisionResource().getProject(),
+              checkResource.getRevisionResource().getPatchSet(),
+              checker);
+    } else {
+      CheckUpdate.Builder builder = CheckUpdate.builder();
+      builder
+          .setState(CheckState.NOT_STARTED)
+          .unsetFinished()
+          .unsetStarted()
+          .setMessage("")
+          .setUrl("");
+      updatedCheck = checksUpdate.get().updateCheck(key, builder.build());
+    }
+    return Response.ok(checkJsonFactory.noOptions().format(updatedCheck));
+  }
+}
diff --git a/java/com/google/gerrit/plugins/checks/rules/ChecksSubmitRule.java b/java/com/google/gerrit/plugins/checks/rules/ChecksSubmitRule.java
index 4850ef4..a1df4f5 100644
--- a/java/com/google/gerrit/plugins/checks/rules/ChecksSubmitRule.java
+++ b/java/com/google/gerrit/plugins/checks/rules/ChecksSubmitRule.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.plugins.checks.rules;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.data.SubmitRecord.Status;
@@ -31,7 +30,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
-import java.util.Collection;
+import java.util.Optional;
 
 @Singleton
 public class ChecksSubmitRule implements SubmitRule {
@@ -60,7 +59,7 @@
   }
 
   @Override
-  public Collection<SubmitRecord> evaluate(ChangeData changeData) {
+  public Optional<SubmitRecord> evaluate(ChangeData changeData) {
     Project.NameKey project = changeData.project();
     Change.Id changeId = changeData.getId();
 
@@ -71,7 +70,7 @@
       String errorMessage =
           String.format("failed to load the current patch set of change %s", changeId);
       logger.atSevere().withCause(e).log(errorMessage);
-      return singletonRecordForRuleError(errorMessage);
+      return recordForRuleError(errorMessage);
     }
 
     boolean areAllRequiredCheckersPassing;
@@ -82,24 +81,24 @@
       String errorMessage =
           String.format("failed to evaluate check states for change %s", changeId);
       logger.atSevere().withCause(e).log(errorMessage);
-      return singletonRecordForRuleError(errorMessage);
+      return recordForRuleError(errorMessage);
     }
 
     SubmitRecord submitRecord = new SubmitRecord();
     if (areAllRequiredCheckersPassing) {
       submitRecord.status = Status.OK;
-      return ImmutableList.of(submitRecord);
+      return Optional.of(submitRecord);
     }
 
     submitRecord.status = Status.NOT_READY;
     submitRecord.requirements = ImmutableList.of(DEFAULT_SUBMIT_REQUIREMENT_FOR_CHECKS);
-    return ImmutableSet.of(submitRecord);
+    return Optional.of(submitRecord);
   }
 
-  private static Collection<SubmitRecord> singletonRecordForRuleError(String reason) {
+  private static Optional<SubmitRecord> recordForRuleError(String reason) {
     SubmitRecord submitRecord = new SubmitRecord();
     submitRecord.errorMessage = reason;
     submitRecord.status = SubmitRecord.Status.RULE_ERROR;
-    return ImmutableList.of(submitRecord);
+    return Optional.of(submitRecord);
   }
 }
diff --git a/javatests/com/google/gerrit/plugins/checks/acceptance/ChecksRestApiBindingsIT.java b/javatests/com/google/gerrit/plugins/checks/acceptance/ChecksRestApiBindingsIT.java
index 557aab0..03a9e7d 100644
--- a/javatests/com/google/gerrit/plugins/checks/acceptance/ChecksRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/plugins/checks/acceptance/ChecksRestApiBindingsIT.java
@@ -47,7 +47,8 @@
   private static final ImmutableList<RestCall> SCOPED_CHECK_ENDPOINTS =
       ImmutableList.of(
           RestCall.get("/changes/%s/revisions/%s/checks~checks/%s"),
-          RestCall.post("/changes/%s/revisions/%s/checks~checks/%s"));
+          RestCall.post("/changes/%s/revisions/%s/checks~checks/%s"),
+          RestCall.post("/changes/%s/revisions/%s/checks~checks/%s/rerun"));
 
   @Test
   public void rootEndpoints() throws Exception {
diff --git a/javatests/com/google/gerrit/plugins/checks/acceptance/api/RerunCheckIT.java b/javatests/com/google/gerrit/plugins/checks/acceptance/api/RerunCheckIT.java
new file mode 100644
index 0000000..9cc539f
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/checks/acceptance/api/RerunCheckIT.java
@@ -0,0 +1,150 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.checks.acceptance.api;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.plugins.checks.CheckKey;
+import com.google.gerrit.plugins.checks.CheckerUuid;
+import com.google.gerrit.plugins.checks.acceptance.AbstractCheckersTest;
+import com.google.gerrit.plugins.checks.api.CheckInfo;
+import com.google.gerrit.plugins.checks.api.CheckState;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.gerrit.testing.TestTimeUtil;
+import com.google.inject.Inject;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RerunCheckIT extends AbstractCheckersTest {
+  @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject private ProjectOperations projectOperations;
+
+  private PatchSet.Id patchSetId;
+  private CheckKey checkKey;
+
+  @Before
+  public void setUp() throws Exception {
+    TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
+    TestTimeUtil.setClock(Timestamp.from(Instant.EPOCH));
+
+    patchSetId = createChange().getPatchSetId();
+
+    CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create();
+    checkKey = CheckKey.create(project, patchSetId, checkerUuid);
+  }
+
+  @After
+  public void resetTime() {
+    TestTimeUtil.useSystemTime();
+  }
+
+  @Test
+  public void rerunResetsCheckInfo() throws Exception {
+    checkOperations
+        .newCheck(checkKey)
+        .state(CheckState.FAILED)
+        .started(TimeUtil.nowTs())
+        .finished(TimeUtil.nowTs())
+        .message("message")
+        .url("url.com")
+        .upsert();
+    Timestamp created = checkOperations.check(checkKey).get().created();
+    Timestamp updated = checkOperations.check(checkKey).get().updated();
+    CheckInfo info = checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun();
+    assertThat(info.state).isEqualTo(CheckState.NOT_STARTED);
+    assertThat(info.message).isEqualTo(null);
+    assertThat(info.url).isEqualTo(null);
+    assertThat(info.started).isEqualTo(null);
+    assertThat(info.finished).isEqualTo(null);
+    assertThat(info.created).isEqualTo(created);
+    assertThat(info.updated).isGreaterThan(updated);
+  }
+
+  @Test
+  public void rerunNotStartedCheck() throws Exception {
+    checkOperations.newCheck(checkKey).state(CheckState.NOT_STARTED).upsert();
+    CheckInfo info = checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun();
+    assertThat(info.state).isEqualTo(CheckState.NOT_STARTED);
+  }
+
+  @Test
+  public void rerunFinishedCheck() throws Exception {
+    checkOperations.newCheck(checkKey).state(CheckState.SUCCESSFUL).upsert();
+    CheckInfo info = checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun();
+    assertThat(info.state).isEqualTo(CheckState.NOT_STARTED);
+    assertThat(info.updated).isGreaterThan(info.created);
+  }
+
+  @Test
+  public void rerunCheckNotExistingButBackfilled() throws Exception {
+    CheckInfo info = checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun();
+    assertThat(info.state).isEqualTo(CheckState.NOT_STARTED);
+    assertThat(checkOperations.check(checkKey).exists()).isFalse();
+  }
+
+  @Test
+  public void rerunExistingCheckWithCheckerNotAppliedToChange() throws Exception {
+    Project.NameKey otherProject = projectOperations.newProject().create();
+    checkerOperations.checker(checkKey.checkerUuid()).forUpdate().repository(otherProject).update();
+    checkOperations.newCheck(checkKey).upsert();
+    CheckInfo info = checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun();
+    assertThat(info.state).isEqualTo(CheckState.NOT_STARTED);
+  }
+
+  @Test
+  public void rerunNonExistingCheckWithCheckerNotAppliedToChange() throws Exception {
+    Project.NameKey otherProject = projectOperations.newProject().create();
+    checkerOperations.checker(checkKey.checkerUuid()).forUpdate().repository(otherProject).update();
+    assertThrows(
+        ResourceNotFoundException.class,
+        () -> checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun());
+    assertThat(checkOperations.check(checkKey).exists()).isFalse();
+  }
+
+  @Test
+  public void cannotUpdateCheckWithoutAdministrateCheckers() throws Exception {
+    requestScopeOperations.setApiUser(user.id());
+    checkOperations.newCheck(checkKey).state(CheckState.SUCCESSFUL).upsert();
+
+    AuthException thrown =
+        assertThrows(
+            AuthException.class,
+            () -> checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun());
+    assertThat(thrown).hasMessageThat().contains("not permitted");
+  }
+
+  @Test
+  public void cannotUpdateCheckAnonymously() throws Exception {
+    requestScopeOperations.setApiUserAnonymous();
+    checkOperations.newCheck(checkKey).state(CheckState.SUCCESSFUL).upsert();
+
+    AuthException thrown =
+        assertThrows(
+            AuthException.class,
+            () -> checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun());
+    assertThat(thrown).hasMessageThat().contains("Authentication required");
+  }
+}
diff --git a/javatests/com/google/gerrit/plugins/checks/acceptance/api/UpdateCheckIT.java b/javatests/com/google/gerrit/plugins/checks/acceptance/api/UpdateCheckIT.java
index c6aa0c1..7cc2ce8 100644
--- a/javatests/com/google/gerrit/plugins/checks/acceptance/api/UpdateCheckIT.java
+++ b/javatests/com/google/gerrit/plugins/checks/acceptance/api/UpdateCheckIT.java
@@ -17,9 +17,11 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.plugins.checks.CheckKey;
 import com.google.gerrit.plugins.checks.CheckerUuid;
 import com.google.gerrit.plugins.checks.acceptance.AbstractCheckersTest;
@@ -42,6 +44,7 @@
 
 public class UpdateCheckIT extends AbstractCheckersTest {
   @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject private ProjectOperations projectOperations;
 
   private PatchSet.Id patchSetId;
   private CheckKey checkKey;
@@ -227,8 +230,9 @@
   }
 
   @Test
-  public void canUpdateCheckForCheckerThatDoesNotApplyToTheProject() throws Exception {
-    Project.NameKey otherProject = createProjectOverAPI("other", null, true, null);
+  public void canUpdateCheckForCheckerThatDoesNotApplyToTheProjectAndCheckExists()
+      throws Exception {
+    Project.NameKey otherProject = projectOperations.newProject().create();
     checkerOperations.checker(checkKey.checkerUuid()).forUpdate().repository(otherProject).update();
 
     CheckInput input = new CheckInput();
@@ -239,6 +243,22 @@
   }
 
   @Test
+  public void
+      throwExceptionForUpdateCheckForCheckerThatDoesNotApplyToTheProjectAndCheckDoesNotExist()
+          throws Exception {
+    Project.NameKey otherProject = projectOperations.newProject().create();
+    CheckerUuid checkerUuid = checkerOperations.newChecker().repository(otherProject).create();
+    CheckKey checkKey = CheckKey.create(otherProject, patchSetId, checkerUuid);
+    assertThrows(
+        ResourceNotFoundException.class,
+        () ->
+            checksApiFactory
+                .revision(patchSetId)
+                .id(checkKey.checkerUuid())
+                .update(new CheckInput()));
+  }
+
+  @Test
   public void canUpdateCheckForCheckerWithUnsupportedOperatorInQuery() throws Exception {
     checkerOperations
         .checker(checkKey.checkerUuid())
diff --git a/javatests/com/google/gerrit/plugins/checks/rules/BUILD b/javatests/com/google/gerrit/plugins/checks/rules/BUILD
index 17fdb2a..2e76709 100644
--- a/javatests/com/google/gerrit/plugins/checks/rules/BUILD
+++ b/javatests/com/google/gerrit/plugins/checks/rules/BUILD
@@ -14,6 +14,7 @@
         "//lib:guava",
         "//lib/jgit/org.eclipse.jgit:jgit",
         "//lib/truth",
+        "//lib/truth:truth-java8-extension",
         "//plugins/checks:checks__plugin",
     ],
 )
diff --git a/javatests/com/google/gerrit/plugins/checks/rules/ChecksSubmitRuleTest.java b/javatests/com/google/gerrit/plugins/checks/rules/ChecksSubmitRuleTest.java
index cdb533d..2d75e6b 100644
--- a/javatests/com/google/gerrit/plugins/checks/rules/ChecksSubmitRuleTest.java
+++ b/javatests/com/google/gerrit/plugins/checks/rules/ChecksSubmitRuleTest.java
@@ -15,11 +15,11 @@
 package com.google.gerrit.plugins.checks.rules;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
 import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
 
-import com.google.common.collect.Iterables;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.plugins.checks.Checks;
 import com.google.gerrit.reviewdb.client.Account;
@@ -29,7 +29,7 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.util.time.TimeUtil;
 import java.io.IOException;
-import java.util.Collection;
+import java.util.Optional;
 import org.easymock.EasyMock;
 import org.eclipse.jgit.lib.ObjectId;
 import org.junit.Test;
@@ -46,7 +46,7 @@
     expect(cd.currentPatchSet()).andThrow(new IllegalStateException("Fail for test"));
     replay(cd);
 
-    Collection<SubmitRecord> submitRecords = checksSubmitRule.evaluate(cd);
+    Optional<SubmitRecord> submitRecords = checksSubmitRule.evaluate(cd);
     assertErrorRecord(submitRecords, "failed to load the current patch set of change 1");
   }
 
@@ -73,16 +73,15 @@
                 .build());
     replay(cd);
 
-    Collection<SubmitRecord> submitRecords = checksSubmitRule.evaluate(cd);
+    Optional<SubmitRecord> submitRecords = checksSubmitRule.evaluate(cd);
     assertErrorRecord(submitRecords, "failed to evaluate check states for change 1");
   }
 
   private static void assertErrorRecord(
-      Collection<SubmitRecord> submitRecords, String expectedErrorMessage) {
-    assertThat(submitRecords).hasSize(1);
+      Optional<SubmitRecord> submitRecord, String expectedErrorMessage) {
+    assertThat(submitRecord).isPresent();
 
-    SubmitRecord submitRecord = Iterables.getOnlyElement(submitRecords);
-    assertThat(submitRecord.status).isEqualTo(SubmitRecord.Status.RULE_ERROR);
-    assertThat(submitRecord.errorMessage).isEqualTo(expectedErrorMessage);
+    assertThat(submitRecord.get().status).isEqualTo(SubmitRecord.Status.RULE_ERROR);
+    assertThat(submitRecord.get().errorMessage).isEqualTo(expectedErrorMessage);
   }
 }
diff --git a/resources/Documentation/rest-api-checks.md b/resources/Documentation/rest-api-checks.md
index 4bcd1a1..4395644 100644
--- a/resources/Documentation/rest-api-checks.md
+++ b/resources/Documentation/rest-api-checks.md
@@ -1,4 +1,4 @@
-# /changes/<id>/revisions/<id>/checks/ REST API
+# /changes/`id`/revisions/`id`/checks/ REST API
 
 This page describes the check-related REST endpoints that are added by the
 @PLUGIN@ plugin.
@@ -143,6 +143,7 @@
 
 ### <a id="update-check"> Update Check
 _'POST /changes/1/revisions/1/checks/'_
+
 _'POST /changes/1/revisions/1/checks/test:my-checker'_
 
 Updates a check. The semantics are the same as for [CreateCheck](#create-check).
@@ -156,6 +157,17 @@
 the URL, it must either match the value provided in the request body via
 [CheckInput](#check-input) or the value in the request body is omitted.
 
+### <a id="rerun-check"> Rerun Check
+
+_'POST /changes/1/revisions/1/checks/test:my-checker/rerun'_
+
+Reruns a check. As response the [CheckInfo](#check-info) entity is returned that
+describes the created check.
+
+
+This REST endpoint supports rerunning a check. It also resets all relevant check
+fields such as `message`, `url`, `started` and `finished`.
+
 ## <a id="json-entities"> JSON Entities
 
 ### <a id="check-info"> CheckInfo