| // 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.acceptance.testsuite.project.TestProjectUpdate.allow; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block; |
| import static com.google.gerrit.plugins.checks.testing.PendingChecksInfoSubject.assertThat; |
| import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; |
| import static com.google.gerrit.testing.GerritJUnit.assertThrows; |
| |
| import com.google.common.collect.Iterables; |
| import com.google.gerrit.acceptance.RestResponse; |
| import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; |
| 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.ResourceConflictException; |
| import com.google.gerrit.extensions.restapi.RestApiException; |
| 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.plugins.checks.api.QueryPendingChecks; |
| import com.google.gerrit.reviewdb.client.PatchSet; |
| import com.google.gerrit.testing.TestTimeUtil; |
| import com.google.gson.reflect.TypeToken; |
| import com.google.inject.Inject; |
| import java.sql.Timestamp; |
| import java.time.Instant; |
| import java.util.List; |
| import java.util.StringJoiner; |
| import java.util.concurrent.TimeUnit; |
| import java.util.stream.Stream; |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| public class QueryPendingChecksIT extends AbstractCheckersTest { |
| @Inject private ProjectOperations projectOperations; |
| @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 specifyingQueryIsRequired() throws Exception { |
| assertInvalidQuery(null, "query is required"); |
| } |
| |
| @Test |
| public void queryCannotBeEmpty() throws Exception { |
| assertInvalidQuery("", "query is empty"); |
| } |
| |
| @Test |
| public void queryCannotBeEmptyAfterTrim() throws Exception { |
| assertInvalidQuery(" ", "query is empty"); |
| } |
| |
| @Test |
| public void specifyingCheckerOrSchemeIsRequired() throws Exception { |
| assertInvalidQuery( |
| "state:NOT_STARTED", |
| "query must contain exactly 1 'checker' operator or 'scheme' operator"); |
| } |
| |
| @Test |
| public void cannotQueryPendingChecksForInvalidCheckerUuid() throws Exception { |
| assertInvalidQuery( |
| "checker:" + CheckerTestData.INVALID_UUID, |
| "invalid checker UUID: " + CheckerTestData.INVALID_UUID); |
| } |
| |
| @Test |
| public void cannotSpecifyMultipleCheckers() throws Exception { |
| CheckerUuid checkerUuid1 = checkerOperations.newChecker().repository(project).create(); |
| CheckerUuid checkerUuid2 = checkerOperations.newChecker().repository(project).create(); |
| |
| String expectedMessage = "query must contain exactly 1 'checker' operator or 'scheme' operator"; |
| assertInvalidQuery( |
| String.format("checker:\"%s\" checker:\"%s\"", checkerUuid1, checkerUuid2), |
| expectedMessage); |
| assertInvalidQuery( |
| String.format("checker:\"%s\" OR checker:\"%s\"", checkerUuid1, checkerUuid2), |
| expectedMessage); |
| assertInvalidQuery( |
| String.format( |
| "checker:\"%s\" (state:NOT_STARTED checker:\"%s\")", checkerUuid1, checkerUuid2), |
| expectedMessage); |
| } |
| |
| @Test |
| public void cannotSpecifyMultipleSchemes() throws Exception { |
| CheckerUuid checkerUuid1 = checkerOperations.newChecker().repository(project).create(); |
| CheckerUuid checkerUuid2 = checkerOperations.newChecker().repository(project).create(); |
| |
| String expectedMessage = "query must contain exactly 1 'checker' operator or 'scheme' operator"; |
| assertInvalidQuery( |
| String.format("scheme:\"%s\" scheme:\"%s\"", checkerUuid1.scheme(), checkerUuid2.scheme()), |
| expectedMessage); |
| assertInvalidQuery( |
| String.format( |
| "scheme:\"%s\" OR scheme:\"%s\"", checkerUuid1.scheme(), checkerUuid2.scheme()), |
| expectedMessage); |
| assertInvalidQuery( |
| String.format( |
| "scheme:\"%s\" (state:NOT_STARTED scheme:\"%s\")", |
| checkerUuid1.scheme(), checkerUuid2.scheme()), |
| expectedMessage); |
| } |
| |
| @Test |
| public void canSpecifyCheckerAsRootPredicate() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| assertThat(queryPendingChecks(String.format("checker:\"%s\"", checkerUuid))).hasSize(1); |
| } |
| |
| @Test |
| public void canSpecifyCheckerInAndCondition() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| assertThat( |
| queryPendingChecks(String.format("checker:\"%s\" AND state:NOT_STARTED", checkerUuid))) |
| .hasSize(1); |
| } |
| |
| @Test |
| public void cannotSpecifyCheckerInAndConditionIfNotImmediateChild() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| |
| String expectedMessage = |
| "query must be 'checker:<checker-uuid>' or 'checker:<checker-uuid> AND <other-operators>' or 'scheme:<checker-scheme>' or 'scheme:<checker-scheme> AND <other-operators>'"; |
| assertInvalidQuery( |
| String.format("state:NOT_STARTED AND (checker:\"%s\" OR state:NOT_STARTED)", checkerUuid), |
| expectedMessage); |
| assertInvalidQuery( |
| String.format("state:NOT_STARTED AND NOT checker:\"%s\"", checkerUuid), expectedMessage); |
| } |
| |
| @Test |
| public void cannotSpecifySchemeInAndConditionIfNotImmediateChild() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| |
| String expectedMessage = |
| "query must be 'checker:<checker-uuid>' or 'checker:<checker-uuid> AND <other-operators>' or 'scheme:<checker-scheme>' or 'scheme:<checker-scheme> AND <other-operators>'"; |
| assertInvalidQuery( |
| String.format( |
| "state:NOT_STARTED AND (scheme:\"%s\" OR state:NOT_STARTED)", checkerUuid.scheme()), |
| expectedMessage); |
| assertInvalidQuery( |
| String.format("state:NOT_STARTED AND NOT scheme:\"%s\"", checkerUuid.scheme()), |
| expectedMessage); |
| } |
| |
| @Test |
| public void andConditionAtRootCanContainAnyCombinationOfOtherPredicates() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| |
| assertThat( |
| queryPendingChecks( |
| String.format( |
| "checker:\"%s\" AND (state:NOT_STARTED OR state:RUNNING)", checkerUuid))) |
| .hasSize(1); |
| assertThat( |
| queryPendingChecks( |
| String.format("checker:\"%s\" AND NOT state:NOT_STARTED)", checkerUuid))) |
| .isEmpty(); |
| assertThat( |
| queryPendingChecks( |
| String.format( |
| "checker:\"%s\" AND (NOT state:FAILED AND NOT (state:RUNNING OR" |
| + " state:SUCCESSFUL))", |
| checkerUuid))) |
| .hasSize(1); |
| } |
| |
| @Test |
| public void cannotSpecifyCheckerInOrCondition() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| |
| String expectedMessage = |
| "query must be 'checker:<checker-uuid>' or 'checker:<checker-uuid> AND <other-operators>' or 'scheme:<checker-scheme>' or 'scheme:<checker-scheme> AND <other-operators>'"; |
| assertInvalidQuery( |
| String.format("checker:\"%s\" OR state:NOT_STARTED", checkerUuid), expectedMessage); |
| assertInvalidQuery( |
| String.format("state:NOT_STARTED OR checker:\"%s\"", checkerUuid), expectedMessage); |
| } |
| |
| @Test |
| public void cannotSpecifySchemeInOrCondition() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| |
| String expectedMessage = |
| "query must be 'checker:<checker-uuid>' or 'checker:<checker-uuid> AND <other-operators>' or 'scheme:<checker-scheme>' or 'scheme:<checker-scheme> AND <other-operators>'"; |
| assertInvalidQuery( |
| String.format("scheme:\"%s\" OR state:NOT_STARTED", checkerUuid.scheme()), expectedMessage); |
| assertInvalidQuery( |
| String.format("state:NOT_STARTED OR scheme:\"%s\"", checkerUuid.scheme()), expectedMessage); |
| } |
| |
| @Test |
| public void cannotSpecifyCheckerInNotCondition() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| assertInvalidQuery( |
| String.format("NOT checker:\"%s\"", checkerUuid), |
| "query must be 'checker:<checker-uuid>' or 'checker:<checker-uuid> AND <other-operators>' or 'scheme:<checker-scheme>' or 'scheme:<checker-scheme> AND <other-operators>'"); |
| } |
| |
| @Test |
| public void cannotSpecifySchemeInNotCondition() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| assertInvalidQuery( |
| String.format("NOT scheme:\"%s\"", checkerUuid.scheme()), |
| "query must be 'checker:<checker-uuid>' or 'checker:<checker-uuid> AND <other-operators>' or 'scheme:<checker-scheme>' or 'scheme:<checker-scheme> AND <other-operators>'"); |
| } |
| |
| @Test |
| public void queryPendingChecksForNonExistingChecker() throws Exception { |
| assertThat(pendingChecksApi.query("checker:\"non:existing\"").get()).isEmpty(); |
| } |
| |
| @Test |
| public void queryPendingChecksNotStartedStateAssumed() 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)) |
| .state(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)) |
| .state(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)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| List<PendingChecksInfo> pendingChecksList = queryPendingChecks(checkerUuid); |
| assertThat(pendingChecksList).hasSize(1); |
| PendingChecksInfo pendingChecks = Iterables.getOnlyElement(pendingChecksList); |
| assertThat(pendingChecks).hasRepository(project); |
| assertThat(pendingChecks).hasPatchSet(patchSetId); |
| assertThat(pendingChecks) |
| .hasPendingChecksMapThat() |
| .containsExactly(checkerUuid.get(), new PendingCheckInfo(CheckState.NOT_STARTED)); |
| } |
| |
| @Test |
| public void queryPendingChecksForSpecifiedState() 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)) |
| .state(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)) |
| .state(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)) |
| .state(CheckState.FAILED) |
| .upsert(); |
| |
| List<PendingChecksInfo> pendingChecksList = queryPendingChecks(checkerUuid, CheckState.FAILED); |
| assertThat(pendingChecksList).hasSize(1); |
| PendingChecksInfo pendingChecks = Iterables.getOnlyElement(pendingChecksList); |
| assertThat(pendingChecks).hasRepository(project); |
| assertThat(pendingChecks).hasPatchSet(patchSetId); |
| assertThat(pendingChecks) |
| .hasPendingChecksMapThat() |
| .containsExactly(checkerUuid.get(), new PendingCheckInfo(CheckState.FAILED)); |
| } |
| |
| @Test |
| public void queryPendingChecksForSpecifiedStateByIsOperator() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| |
| // Create the check once so that in the for-loop we can always update an existing check, rather |
| // than needing to check if the check already exists and then depending on this either create or |
| // update it. |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| for (CheckState checkState : CheckState.values()) { |
| checkOperations |
| .check(CheckKey.create(project, patchSetId, checkerUuid)) |
| .forUpdate() |
| .state(checkState) |
| .upsert(); |
| |
| assertThat(queryPendingChecks(String.format("checker:\"%s\" is:%s", checkerUuid, checkState))) |
| .hasSize(1); |
| } |
| } |
| |
| @Test |
| public void queryPendingChecksByIsInprogressOperator() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| |
| // Create the check once so that in the for-loop we can always update an existing check, rather |
| // than needing to check if the check already exists and then depending on this either create or |
| // update it. |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| for (CheckState checkState : CheckState.values()) { |
| checkOperations |
| .check(CheckKey.create(project, patchSetId, checkerUuid)) |
| .forUpdate() |
| .state(checkState) |
| .upsert(); |
| |
| List<PendingChecksInfo> pendingChecks = |
| queryPendingChecks(String.format("checker:\"%s\" is:inprogress", checkerUuid)); |
| if (checkState.isInProgress()) { |
| assertThat(pendingChecks).hasSize(1); |
| } else { |
| assertThat(pendingChecks).isEmpty(); |
| } |
| |
| pendingChecks = |
| queryPendingChecks(String.format("checker:\"%s\" is:in_progress", checkerUuid)); |
| if (checkState.isInProgress()) { |
| assertThat(pendingChecks).hasSize(1); |
| } else { |
| assertThat(pendingChecks).isEmpty(); |
| } |
| } |
| } |
| |
| @Test |
| public void invalidStateInIsOperatorIsRejected() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| assertInvalidQuery( |
| String.format("checker:%s is:foo", checkerUuid), "unsupported operator: is:foo"); |
| } |
| |
| @Test |
| public void queryPendingChecksForMultipleSpecifiedStates() 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)) |
| .state(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)) |
| .state(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)) |
| .state(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)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| List<PendingChecksInfo> pendingChecksList = |
| queryPendingChecks(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).hasRepository(project); |
| assertThat(pendingChecksChange2).hasPatchSet(patchSetId2); |
| assertThat(pendingChecksChange2) |
| .hasPendingChecksMapThat() |
| .containsExactly(checkerUuid.get(), new PendingCheckInfo(CheckState.SCHEDULED)); |
| |
| PendingChecksInfo pendingChecksChange1 = pendingChecksList.get(1); |
| assertThat(pendingChecksChange1).hasRepository(project); |
| assertThat(pendingChecksChange1).hasPatchSet(patchSetId); |
| assertThat(pendingChecksChange1) |
| .hasPendingChecksMapThat() |
| .containsExactly(checkerUuid.get(), new PendingCheckInfo(CheckState.NOT_STARTED)); |
| } |
| |
| @Test |
| public void queryPendingChecksForSpecifiedStateUnderscoreCanBeOmitted() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| |
| // Create the check once so that in the for-loop we can always update an existing check, rather |
| // than needing to check if the check already exists and then depending on this either create or |
| // update it. |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| for (CheckState checkState : CheckState.values()) { |
| checkOperations |
| .check(CheckKey.create(project, patchSetId, checkerUuid)) |
| .forUpdate() |
| .state(checkState) |
| .upsert(); |
| |
| List<PendingChecksInfo> pendingChecks = |
| queryPendingChecks( |
| String.format( |
| "checker:\"%s\" is:%s", checkerUuid, checkState.name().replace("_", ""))); |
| assertThat(pendingChecks).hasSize(1); |
| } |
| } |
| |
| @Test |
| public void queryPendingChecksForSpecifiedStateDifferentCases() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| |
| assertThat(queryPendingChecks(buildQueryString(checkerUuid) + " state:NOT_STARTED")).hasSize(1); |
| assertThat(queryPendingChecks(buildQueryString(checkerUuid) + " state:not_started")).hasSize(1); |
| assertThat(queryPendingChecks(buildQueryString(checkerUuid) + " state:NoT_StArTeD")).hasSize(1); |
| assertThat(queryPendingChecks(buildQueryString(checkerUuid) + " state:NOTSTARTED")).hasSize(1); |
| assertThat(queryPendingChecks(buildQueryString(checkerUuid) + " state:notstarted")).hasSize(1); |
| assertThat(queryPendingChecks(buildQueryString(checkerUuid) + " state:NoTStArTeD")).hasSize(1); |
| } |
| |
| @Test |
| public void backfillForApplyingChecker() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| List<PendingChecksInfo> pendingChecksList = queryPendingChecks(checkerUuid); |
| assertThat(pendingChecksList).hasSize(1); |
| PendingChecksInfo pendingChecks = Iterables.getOnlyElement(pendingChecksList); |
| assertThat(pendingChecks).hasRepository(project); |
| assertThat(pendingChecks).hasPatchSet(patchSetId); |
| assertThat(pendingChecks) |
| .hasPendingChecksMapThat() |
| .containsExactly(checkerUuid.get(), new PendingCheckInfo(CheckState.NOT_STARTED)); |
| } |
| |
| @Test |
| public void noBackfillForCheckerThatDoesNotApplyToTheProject() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(allProjects).create(); |
| assertThat(queryPendingChecks(checkerUuid)).isEmpty(); |
| } |
| |
| @Test |
| public void noBackfillForCheckerThatDoesNotApplyToTheChange() throws Exception { |
| CheckerUuid checkerUuid = |
| checkerOperations.newChecker().repository(project).query("message:not-matching").create(); |
| assertThat(queryPendingChecks(checkerUuid)).isEmpty(); |
| } |
| |
| @Test |
| public void queryPendingChecksForDisabledChecker() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| List<PendingChecksInfo> pendingChecksList = |
| queryPendingChecks(checkerUuid, CheckState.NOT_STARTED); |
| assertThat(pendingChecksList).isNotEmpty(); |
| |
| checkerOperations.checker(checkerUuid).forUpdate().disable().update(); |
| pendingChecksList = queryPendingChecks(checkerUuid, CheckState.NOT_STARTED); |
| assertThat(pendingChecksList).isEmpty(); |
| } |
| |
| @Test |
| public void queryPendingChecksFiltersOutChecksForClosedChangesIfQueryDoesntSpecifyStatus() |
| throws Exception { |
| CheckerUuid checkerUuid = |
| checkerOperations.newChecker().repository(project).clearQuery().create(); |
| |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| PatchSet.Id patchSetId2 = createChange().getPatchSetId(); |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId2, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| List<PendingChecksInfo> pendingChecksList = |
| queryPendingChecks(checkerUuid, CheckState.NOT_STARTED); |
| assertThat(pendingChecksList).hasSize(2); |
| |
| gApi.changes().id(patchSetId2.changeId().toString()).abandon(); |
| |
| pendingChecksList = queryPendingChecks(checkerUuid, CheckState.NOT_STARTED); |
| assertThat(pendingChecksList).hasSize(1); |
| } |
| |
| @Test |
| public void queryPendingChecksReturnsChecksForClosedChangesIfQuerySpecifiesStatus() |
| throws Exception { |
| CheckerUuid checkerUuid = |
| checkerOperations.newChecker().repository(project).query("is:open OR is:closed").create(); |
| |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| PatchSet.Id patchSetId2 = createChange().getPatchSetId(); |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId2, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| List<PendingChecksInfo> pendingChecksList = |
| queryPendingChecks(checkerUuid, CheckState.NOT_STARTED); |
| assertThat(pendingChecksList).hasSize(2); |
| |
| gApi.changes().id(patchSetId2.changeId().toString()).abandon(); |
| |
| pendingChecksList = queryPendingChecks(checkerUuid, CheckState.NOT_STARTED); |
| assertThat(pendingChecksList).hasSize(2); |
| } |
| |
| @Test |
| public void queryPendingChecksForInvalidCheckerFails() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| checkerOperations.checker(checkerUuid).forInvalidation().nonParseableConfig().invalidate(); |
| |
| RestApiException thrown = |
| assertThrows(RestApiException.class, () -> queryPendingChecks(checkerUuid)); |
| assertThat(thrown).hasMessageThat().contains("Cannot query pending checks"); |
| assertThat(thrown).hasCauseThat().isInstanceOf(ConfigInvalidException.class); |
| } |
| |
| @Test |
| public void queryPendingChecksForCheckerWithInvalidQueryFails() throws Exception { |
| CheckerUuid checkerUuid = |
| checkerOperations |
| .newChecker() |
| .repository(project) |
| .query(CheckerTestData.INVALID_QUERY) |
| .create(); |
| |
| RestApiException thrown = |
| assertThrows(RestApiException.class, () -> queryPendingChecks(checkerUuid)); |
| assertThat(thrown).hasMessageThat().contains("Cannot query pending checks"); |
| assertThat(thrown).hasCauseThat().isInstanceOf(ConfigInvalidException.class); |
| } |
| |
| @Test |
| public void queryPendingChecksWithoutAdministrateCheckersCapabilityWorks() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| List<PendingChecksInfo> pendingChecksList = |
| queryPendingChecks(checkerUuid, CheckState.NOT_STARTED); |
| assertThat(pendingChecksList).hasSize(1); |
| PendingChecksInfo pendingChecks = Iterables.getOnlyElement(pendingChecksList); |
| assertThat(pendingChecks).hasRepository(project); |
| assertThat(pendingChecks).hasPatchSet(patchSetId); |
| assertThat(pendingChecks) |
| .hasPendingChecksMapThat() |
| .containsExactly(checkerUuid.get(), new PendingCheckInfo(CheckState.NOT_STARTED)); |
| } |
| |
| @Test |
| public void queryPendingChecksAnonymouslyWorks() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| requestScopeOperations.setApiUserAnonymous(); |
| List<PendingChecksInfo> pendingChecksList = |
| queryPendingChecks(checkerUuid, CheckState.NOT_STARTED); |
| assertThat(pendingChecksList).hasSize(1); |
| PendingChecksInfo pendingChecks = Iterables.getOnlyElement(pendingChecksList); |
| assertThat(pendingChecks).hasRepository(project); |
| assertThat(pendingChecks).hasPatchSet(patchSetId); |
| assertThat(pendingChecks) |
| .hasPendingChecksMapThat() |
| .containsExactly(checkerUuid.get(), new PendingCheckInfo(CheckState.NOT_STARTED)); |
| } |
| |
| @Test |
| public void pendingChecksDontIncludeChecksForNonVisibleChanges() throws Exception { |
| // restrict project visibility so that it is only visible to administrators |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(allow(Permission.READ).ref("refs/*").group(adminGroupUuid())) |
| .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS)) |
| .update(); |
| |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| // Check is returned for admin user. |
| List<PendingChecksInfo> pendingChecksList = |
| queryPendingChecks(checkerUuid, CheckState.NOT_STARTED); |
| assertThat(pendingChecksList).isNotEmpty(); |
| |
| // Check is not returned for non-admin user. |
| requestScopeOperations.setApiUser(user.id()); |
| pendingChecksList = queryPendingChecks(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.changeId().get()).setPrivate(true); |
| |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| // Check is returned for admin user. |
| List<PendingChecksInfo> pendingChecksList = |
| queryPendingChecks(checkerUuid, CheckState.NOT_STARTED); |
| assertThat(pendingChecksList).isNotEmpty(); |
| |
| // Check is not returned for non-admin user. |
| requestScopeOperations.setApiUser(user.id()); |
| pendingChecksList = queryPendingChecks(checkerUuid, CheckState.NOT_STARTED); |
| assertThat(pendingChecksList).isEmpty(); |
| } |
| |
| @Test |
| public void pendingChecksViaRest() throws Exception { |
| CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create(); |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| RestResponse r = |
| adminRestSession.get( |
| String.format("/plugins/checks/checks.pending/?q=checker:%s", checkerUuid.get())); |
| r.assertOK(); |
| List<PendingChecksInfo> pendingChecksList = |
| newGson().fromJson(r.getReader(), new TypeToken<List<PendingChecksInfo>>() {}.getType()); |
| r.consume(); |
| assertThat(pendingChecksList).isNotEmpty(); |
| } |
| |
| @Test |
| public void queryPendingChecksWithScheme() throws Exception { |
| // create a check with a scheme that we expect to never be returned. |
| CheckerUuid checkerUuidOtherScheme = |
| checkerOperations |
| .newChecker() |
| .uuid(CheckerUuid.parse("otherscheme:checker-1")) |
| .repository(project) |
| .create(); |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuidOtherScheme)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| |
| CheckerUuid checkerUuid = |
| checkerOperations |
| .newChecker() |
| .uuid(CheckerUuid.parse("test:checker-1")) |
| .repository(project) |
| .create(); |
| // Create a check with state "NOT_STARTED" that we expect to be returned. |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid)) |
| .state(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)) |
| .state(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)) |
| .state(CheckState.SUCCESSFUL) |
| .upsert(); |
| |
| // Create a check with state "SUCCESSFUL" that we expect to be ignored, for patchsetId. |
| // by default, patchSetId2 and patchSetId3 should be NOT_STARTED, and should be returned. |
| CheckerUuid checkerUuid2 = |
| checkerOperations |
| .newChecker() |
| .uuid(CheckerUuid.parse("test:checker-2")) |
| .repository(project) |
| .create(); |
| assertThat(checkerUuid2.scheme()).isEqualTo("test"); |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid2)) |
| .state(CheckState.FAILED) |
| .upsert(); |
| |
| List<PendingChecksInfo> pendingChecksList = |
| queryPendingChecks("test", CheckState.NOT_STARTED, CheckState.SCHEDULED); |
| assertThat(pendingChecksList).hasSize(4); |
| |
| // 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 pendingChecksChange = pendingChecksList.get(0); |
| assertThat(pendingChecksChange).hasRepository(project); |
| assertThat(pendingChecksChange).hasPatchSet(patchSetId2); |
| assertThat(pendingChecksChange) |
| .hasPendingChecksMapThat() |
| .containsExactly(checkerUuid.get(), new PendingCheckInfo(CheckState.SCHEDULED)); |
| |
| pendingChecksChange = pendingChecksList.get(1); |
| assertThat(pendingChecksChange).hasRepository(project); |
| assertThat(pendingChecksChange).hasPatchSet(patchSetId); |
| assertThat(pendingChecksChange) |
| .hasPendingChecksMapThat() |
| .containsExactly(checkerUuid.get(), new PendingCheckInfo(CheckState.NOT_STARTED)); |
| |
| pendingChecksChange = pendingChecksList.get(2); |
| assertThat(pendingChecksChange).hasRepository(project); |
| assertThat(pendingChecksChange).hasPatchSet(patchSetId3); |
| assertThat(pendingChecksChange) |
| .hasPendingChecksMapThat() |
| .containsExactly(checkerUuid2.get(), new PendingCheckInfo(CheckState.NOT_STARTED)); |
| |
| pendingChecksChange = pendingChecksList.get(3); |
| assertThat(pendingChecksChange).hasRepository(project); |
| assertThat(pendingChecksChange).hasPatchSet(patchSetId2); |
| assertThat(pendingChecksChange) |
| .hasPendingChecksMapThat() |
| .containsExactly(checkerUuid2.get(), new PendingCheckInfo(CheckState.NOT_STARTED)); |
| } |
| |
| @Test |
| public void queryPendingChecksWithSchemeTooManyChecksThrowsError() throws Exception { |
| for (int i = 0; i < QueryPendingChecks.MAX_ALLOWED_QUERIES + 1; i++) { |
| checkerOperations |
| .newChecker() |
| .repository(project) |
| .uuid(CheckerUuid.parse(String.format("test:checker-%d", i))) |
| .create(); |
| } |
| assertThrows( |
| ResourceConflictException.class, |
| () -> queryPendingChecks("test", CheckState.NOT_STARTED, CheckState.SCHEDULED)); |
| } |
| |
| @Test |
| public void queryOnlyExactSchemas() throws Exception { |
| CheckerUuid checkerUuid = |
| checkerOperations |
| .newChecker() |
| .uuid(CheckerUuid.parse("foobar:checker-1")) |
| .repository(project) |
| .create(); |
| // Create a check with state "NOT_STARTED" that we expect to be returned. |
| checkOperations |
| .newCheck(CheckKey.create(project, patchSetId, checkerUuid)) |
| .state(CheckState.NOT_STARTED) |
| .upsert(); |
| assertThat(queryPendingChecks("foo", CheckState.NOT_STARTED, CheckState.SCHEDULED)).isEmpty(); |
| assertThat(queryPendingChecks("bar", CheckState.NOT_STARTED, CheckState.RUNNING)).isEmpty(); |
| assertThat(queryPendingChecks("foobar", CheckState.NOT_STARTED)).hasSize(1); |
| } |
| |
| private void assertInvalidQuery(String query, String expectedMessage) { |
| BadRequestException thrown = |
| assertThrows(BadRequestException.class, () -> pendingChecksApi.query(query).get()); |
| assertThat(thrown).hasMessageThat().isEqualTo(expectedMessage); |
| } |
| |
| private List<PendingChecksInfo> queryPendingChecks(String queryString) throws RestApiException { |
| return pendingChecksApi.query(queryString).get(); |
| } |
| |
| private List<PendingChecksInfo> queryPendingChecks( |
| CheckerUuid checkerUuid, CheckState... checkStates) throws RestApiException { |
| return pendingChecksApi.query(buildQueryString(checkerUuid, checkStates)).get(); |
| } |
| |
| private List<PendingChecksInfo> queryPendingChecks(String scheme, CheckState... checkStates) |
| throws RestApiException { |
| return pendingChecksApi.query(buildQueryString(scheme, checkStates)).get(); |
| } |
| |
| private String buildQueryString(CheckerUuid checkerUuid, CheckState... checkStates) { |
| StringBuilder queryString = new StringBuilder(); |
| queryString.append(String.format("checker:%s", checkerUuid)); |
| |
| StringJoiner stateJoiner = new StringJoiner(" OR state:", " (state:", ")"); |
| Stream.of(checkStates).map(CheckState::name).forEach(stateJoiner::add); |
| queryString.append(stateJoiner.toString()); |
| |
| return queryString.toString(); |
| } |
| |
| private String buildQueryString(String scheme, CheckState... checkStates) { |
| StringBuilder queryString = new StringBuilder(); |
| queryString.append(String.format("scheme:%s", scheme)); |
| |
| StringJoiner stateJoiner = new StringJoiner(" OR state:", " (state:", ")"); |
| Stream.of(checkStates).map(CheckState::name).forEach(stateJoiner::add); |
| queryString.append(stateJoiner.toString()); |
| |
| return queryString.toString(); |
| } |
| } |