blob: 18b7fe36a4ff4ae88955dac58a54d6a59eb299af [file] [log] [blame]
// 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.plugins.checks.testing.PendingChecksInfoSubject.assertThat;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static org.hamcrest.CoreMatchers.instanceOf;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
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.acceptance.testsuite.CheckerTestData;
import com.google.gerrit.plugins.checks.api.CheckState;
import com.google.gerrit.plugins.checks.api.PendingCheckInfo;
import com.google.gerrit.plugins.checks.api.PendingChecksInfo;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.project.testing.Util;
import com.google.gerrit.testing.TestTimeUtil;
import com.google.inject.Inject;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class ListPendingChecksIT extends AbstractCheckersTest {
@Inject private RequestScopeOperations requestScopeOperations;
private PatchSet.Id patchSetId;
@Before
public void setUp() throws Exception {
TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
TestTimeUtil.setClock(Timestamp.from(Instant.EPOCH));
patchSetId = createChange().getPatchSetId();
}
@After
public void resetTime() {
TestTimeUtil.useSystemTime();
}
@Test
public void specifyingCheckerUuidIsRequired() throws Exception {
// The extension API doesn't allow to not specify a checker UUID. Call the endpoint over REST to
// test this.
RestResponse response = adminRestSession.get("/plugins/checks/checks.pending/");
response.assertBadRequest();
assertThat(response.getEntityContent()).isEqualTo("checker UUID is required");
}
@Test
public void cannotListPendingChecksForInvalidCheckerUuid() throws Exception {
exception.expect(BadRequestException.class);
exception.expectMessage("invalid checker UUID: " + CheckerTestData.INVALID_UUID);
pendingChecksApi.list(CheckerTestData.INVALID_UUID);
}
@Test
public void cannotListPendingChecksForNonExistingChecker() throws Exception {
exception.expect(UnprocessableEntityException.class);
exception.expectMessage("checker non:existing not found");
pendingChecksApi.list("non:existing");
}
@Test
public void listPendingChecksNotStartedStateAssumed() throws Exception {
CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create();
// Create a check with state "NOT_STARTED" that we expect to be returned.
checkOperations
.newCheck(CheckKey.create(project, patchSetId, checkerUuid))
.setState(CheckState.NOT_STARTED)
.upsert();
// Create a check with state "FAILED" that we expect to be ignored.
PatchSet.Id patchSetId2 = createChange().getPatchSetId();
checkOperations
.newCheck(CheckKey.create(project, patchSetId2, checkerUuid))
.setState(CheckState.FAILED)
.upsert();
// Create a check with state "NOT_STARTED" for other checker that we expect to be ignored.
CheckerUuid checkerUuid2 = checkerOperations.newChecker().repository(project).create();
checkOperations
.newCheck(CheckKey.create(project, patchSetId, checkerUuid2))
.setState(CheckState.NOT_STARTED)
.upsert();
List<PendingChecksInfo> pendingChecksList = pendingChecksApi.list(checkerUuid);
assertThat(pendingChecksList).hasSize(1);
PendingChecksInfo pendingChecks = Iterables.getOnlyElement(pendingChecksList);
assertThat(pendingChecks).hasProject(project);
assertThat(pendingChecks).hasPatchSet(patchSetId);
assertThat(pendingChecks)
.hasPendingChecksMapThat()
.containsExactly(checkerUuid.toString(), new PendingCheckInfo(CheckState.NOT_STARTED));
}
@Test
public void listPendingChecksForSpecifiedState() throws Exception {
CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create();
// Create a check with state "FAILED" that we expect to be returned.
checkOperations
.newCheck(CheckKey.create(project, patchSetId, checkerUuid))
.setState(CheckState.FAILED)
.upsert();
// Create a check with state "NOT_STARTED" that we expect to be ignored.
PatchSet.Id patchSetId2 = createChange().getPatchSetId();
checkOperations
.newCheck(CheckKey.create(project, patchSetId2, checkerUuid))
.setState(CheckState.NOT_STARTED)
.upsert();
// Create a check with state "FAILED" for other checker that we expect to be ignored.
CheckerUuid checkerUuid2 = checkerOperations.newChecker().repository(project).create();
checkOperations
.newCheck(CheckKey.create(project, patchSetId, checkerUuid2))
.setState(CheckState.FAILED)
.upsert();
List<PendingChecksInfo> pendingChecksList =
pendingChecksApi.list(checkerUuid, CheckState.FAILED);
assertThat(pendingChecksList).hasSize(1);
PendingChecksInfo pendingChecks = Iterables.getOnlyElement(pendingChecksList);
assertThat(pendingChecks).hasProject(project);
assertThat(pendingChecks).hasPatchSet(patchSetId);
assertThat(pendingChecks)
.hasPendingChecksMapThat()
.containsExactly(checkerUuid.toString(), new PendingCheckInfo(CheckState.FAILED));
}
@Test
public void listPendingChecksForMultipleSpecifiedStates() throws Exception {
CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create();
// Create a check with state "NOT_STARTED" that we expect to be returned.
checkOperations
.newCheck(CheckKey.create(project, patchSetId, checkerUuid))
.setState(CheckState.NOT_STARTED)
.upsert();
// Create a check with state "SCHEDULED" that we expect to be returned.
PatchSet.Id patchSetId2 = createChange().getPatchSetId();
checkOperations
.newCheck(CheckKey.create(project, patchSetId2, checkerUuid))
.setState(CheckState.SCHEDULED)
.upsert();
// Create a check with state "SUCCESSFUL" that we expect to be ignored.
PatchSet.Id patchSetId3 = createChange().getPatchSetId();
checkOperations
.newCheck(CheckKey.create(project, patchSetId3, checkerUuid))
.setState(CheckState.SUCCESSFUL)
.upsert();
// Create a check with state "NOT_STARTED" for other checker that we expect to be ignored.
CheckerUuid checkerUuid2 = checkerOperations.newChecker().repository(project).create();
checkOperations
.newCheck(CheckKey.create(project, patchSetId, checkerUuid2))
.setState(CheckState.NOT_STARTED)
.upsert();
List<PendingChecksInfo> pendingChecksList =
pendingChecksApi.list(checkerUuid, CheckState.NOT_STARTED, CheckState.SCHEDULED);
assertThat(pendingChecksList).hasSize(2);
// The sorting of the pendingChecksList matches the sorting in which the matching changes are
// returned from the change index, which is by last updated timestamp. Use this knowledge here
// to do the assertions although the REST endpoint doesn't document a guaranteed sort order.
PendingChecksInfo pendingChecksChange2 = pendingChecksList.get(0);
assertThat(pendingChecksChange2).hasProject(project);
assertThat(pendingChecksChange2).hasPatchSet(patchSetId2);
assertThat(pendingChecksChange2)
.hasPendingChecksMapThat()
.containsExactly(checkerUuid.toString(), new PendingCheckInfo(CheckState.SCHEDULED));
PendingChecksInfo pendingChecksChange1 = pendingChecksList.get(1);
assertThat(pendingChecksChange1).hasProject(project);
assertThat(pendingChecksChange1).hasPatchSet(patchSetId);
assertThat(pendingChecksChange1)
.hasPendingChecksMapThat()
.containsExactly(checkerUuid.toString(), new PendingCheckInfo(CheckState.NOT_STARTED));
}
@Test
public void backfillForApplyingChecker() throws Exception {
CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create();
List<PendingChecksInfo> pendingChecksList = pendingChecksApi.list(checkerUuid);
assertThat(pendingChecksList).hasSize(1);
PendingChecksInfo pendingChecks = Iterables.getOnlyElement(pendingChecksList);
assertThat(pendingChecks).hasProject(project);
assertThat(pendingChecks).hasPatchSet(patchSetId);
assertThat(pendingChecks)
.hasPendingChecksMapThat()
.containsExactly(checkerUuid.toString(), new PendingCheckInfo(CheckState.NOT_STARTED));
}
@Test
public void noBackfillForCheckerThatDoesNotApplyToTheProject() throws Exception {
CheckerUuid checkerUuid = checkerOperations.newChecker().repository(allProjects).create();
assertThat(pendingChecksApi.list(checkerUuid)).isEmpty();
}
@Test
public void noBackfillForCheckerThatDoesNotApplyToTheChange() throws Exception {
CheckerUuid checkerUuid =
checkerOperations.newChecker().repository(project).query("message:not-matching").create();
assertThat(pendingChecksApi.list(checkerUuid)).isEmpty();
}
@Test
public void listPendingChecksForDisabledChecker() throws Exception {
CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create();
checkOperations
.newCheck(CheckKey.create(project, patchSetId, checkerUuid))
.setState(CheckState.NOT_STARTED)
.upsert();
List<PendingChecksInfo> pendingChecksList =
pendingChecksApi.list(checkerUuid, CheckState.NOT_STARTED);
assertThat(pendingChecksList).isNotEmpty();
checkerOperations.checker(checkerUuid).forUpdate().disable().update();
pendingChecksList = pendingChecksApi.list(checkerUuid, CheckState.NOT_STARTED);
assertThat(pendingChecksList).isEmpty();
}
@Test
public void listPendingChecksFiltersOutChecksForClosedChangesIfQueryDoesntSpecifyStatus()
throws Exception {
CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).query("").create();
checkOperations
.newCheck(CheckKey.create(project, patchSetId, checkerUuid))
.setState(CheckState.NOT_STARTED)
.upsert();
PatchSet.Id patchSetId2 = createChange().getPatchSetId();
checkOperations
.newCheck(CheckKey.create(project, patchSetId2, checkerUuid))
.setState(CheckState.NOT_STARTED)
.upsert();
List<PendingChecksInfo> pendingChecksList =
pendingChecksApi.list(checkerUuid, CheckState.NOT_STARTED);
assertThat(pendingChecksList).hasSize(2);
gApi.changes().id(patchSetId2.getParentKey().toString()).abandon();
pendingChecksList = pendingChecksApi.list(checkerUuid, CheckState.NOT_STARTED);
assertThat(pendingChecksList).hasSize(1);
}
@Test
public void listPendingChecksReturnsChecksForClosedChangesIfQuerySpecifiesStatus()
throws Exception {
CheckerUuid checkerUuid =
checkerOperations.newChecker().repository(project).query("is:open OR is:closed").create();
checkOperations
.newCheck(CheckKey.create(project, patchSetId, checkerUuid))
.setState(CheckState.NOT_STARTED)
.upsert();
PatchSet.Id patchSetId2 = createChange().getPatchSetId();
checkOperations
.newCheck(CheckKey.create(project, patchSetId2, checkerUuid))
.setState(CheckState.NOT_STARTED)
.upsert();
List<PendingChecksInfo> pendingChecksList =
pendingChecksApi.list(checkerUuid, CheckState.NOT_STARTED);
assertThat(pendingChecksList).hasSize(2);
gApi.changes().id(patchSetId2.getParentKey().toString()).abandon();
pendingChecksList = pendingChecksApi.list(checkerUuid, CheckState.NOT_STARTED);
assertThat(pendingChecksList).hasSize(2);
}
@Test
public void listPendingChecksForInvalidCheckerFails() throws Exception {
CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create();
checkerOperations.checker(checkerUuid).forUpdate().forceInvalidConfig().update();
exception.expect(RestApiException.class);
exception.expectMessage("Cannot list pending checks");
exception.expectCause(instanceOf(ConfigInvalidException.class));
pendingChecksApi.list(checkerUuid);
}
@Test
public void listPendingChecksWithoutAdministrateCheckersCapabilityWorks() throws Exception {
CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create();
checkOperations
.newCheck(CheckKey.create(project, patchSetId, checkerUuid))
.setState(CheckState.NOT_STARTED)
.upsert();
requestScopeOperations.setApiUser(user.getId());
List<PendingChecksInfo> pendingChecksList =
pendingChecksApi.list(checkerUuid, CheckState.NOT_STARTED);
assertThat(pendingChecksList).hasSize(1);
PendingChecksInfo pendingChecks = Iterables.getOnlyElement(pendingChecksList);
assertThat(pendingChecks).hasProject(project);
assertThat(pendingChecks).hasPatchSet(patchSetId);
assertThat(pendingChecks)
.hasPendingChecksMapThat()
.containsExactly(checkerUuid.toString(), new PendingCheckInfo(CheckState.NOT_STARTED));
}
@Test
public void pendingChecksDontIncludeChecksForNonVisibleChanges() throws Exception {
// restrict project visibility so that it is only visible to administrators
try (ProjectConfigUpdate u = updateProject(project)) {
Util.allow(u.getConfig(), Permission.READ, adminGroupUuid(), "refs/*");
Util.block(u.getConfig(), Permission.READ, REGISTERED_USERS, "refs/*");
u.save();
}
CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create();
checkOperations
.newCheck(CheckKey.create(project, patchSetId, checkerUuid))
.setState(CheckState.NOT_STARTED)
.upsert();
// Check is returned for admin user.
List<PendingChecksInfo> pendingChecksList =
pendingChecksApi.list(checkerUuid, CheckState.NOT_STARTED);
assertThat(pendingChecksList).isNotEmpty();
// Check is not returned for non-admin user.
requestScopeOperations.setApiUser(user.getId());
pendingChecksList = pendingChecksApi.list(checkerUuid, CheckState.NOT_STARTED);
assertThat(pendingChecksList).isEmpty();
}
@Test
public void pendingChecksDontIncludeChecksForPrivateChangesOfOtherUsers() throws Exception {
// make change private so that it is only visible to the admin user
gApi.changes().id(patchSetId.getParentKey().get()).setPrivate(true);
CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create();
checkOperations
.newCheck(CheckKey.create(project, patchSetId, checkerUuid))
.setState(CheckState.NOT_STARTED)
.upsert();
// Check is returned for admin user.
List<PendingChecksInfo> pendingChecksList =
pendingChecksApi.list(checkerUuid, CheckState.NOT_STARTED);
assertThat(pendingChecksList).isNotEmpty();
// Check is not returned for non-admin user.
requestScopeOperations.setApiUser(user.getId());
pendingChecksList = pendingChecksApi.list(checkerUuid, CheckState.NOT_STARTED);
assertThat(pendingChecksList).isEmpty();
}
}