Handle mutually exclusive star labels when change is starred or ignored

The star labels 'star' (default star) and 'ignore' (for ignoring a
change) are mutually exclusive. Trying to set one if the other is
already set failed with an internal server error. Handle this case and
return 409 Conflict instead.

In addition this adds a test to verify that setting an invalid star
label is correctly rejected with 400 Bad Request. This case was already
correctly handled in the Stars.Post REST endpoint (sets multiple stars
at once), but not in the StarredChanges.Create REST endpoint (adds a
single star).

Bug: Issue 7238
Change-Id: Id4bb5604116f4959d8ecba66146bab72437205f6
Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
Edwin Kempin
2017-09-18 14:06:42 +02:00
parent 17ecf653a7
commit c8cfdb0855
4 changed files with 80 additions and 12 deletions

View File

@@ -81,6 +81,7 @@ import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
import com.google.gerrit.extensions.api.changes.ReviewResult;
import com.google.gerrit.extensions.api.changes.RevisionApi;
import com.google.gerrit.extensions.api.changes.StarsInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.client.ChangeKind;
@@ -122,6 +123,7 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.PostReview;
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
@@ -3465,4 +3467,51 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(changeId).ignore(true);
assertThat(gApi.changes().id(changeId).ignored()).isFalse();
}
@Test
public void cannotIgnoreStarredChange() throws Exception {
String changeId = createChange().getChangeId();
setApiUser(user);
gApi.accounts().self().starChange(changeId);
assertThat(gApi.changes().id(changeId).get().starred).isTrue();
exception.expect(ResourceConflictException.class);
exception.expectMessage(
"The labels "
+ StarredChangesUtil.DEFAULT_LABEL
+ " and "
+ StarredChangesUtil.IGNORE_LABEL
+ " are mutually exclusive. Only one of them can be set.");
gApi.changes().id(changeId).ignore(true);
}
@Test
public void cannotStarIgnoredChange() throws Exception {
String changeId = createChange().getChangeId();
setApiUser(user);
gApi.changes().id(changeId).ignore(true);
assertThat(gApi.changes().id(changeId).ignored()).isTrue();
exception.expect(ResourceConflictException.class);
exception.expectMessage(
"The labels "
+ StarredChangesUtil.DEFAULT_LABEL
+ " and "
+ StarredChangesUtil.IGNORE_LABEL
+ " are mutually exclusive. Only one of them can be set.");
gApi.accounts().self().starChange(changeId);
}
@Test
public void cannotSetInvalidLabel() throws Exception {
String changeId = createChange().getChangeId();
// label cannot contain whitespace
String invalidLabel = "invalid label";
exception.expect(BadRequestException.class);
exception.expectMessage("invalid labels: " + invalidLabel);
gApi.accounts().self().setStars(changeId, new StarsInput(ImmutableSet.of(invalidLabel)));
}
}

View File

@@ -126,21 +126,28 @@ public class StarredChangesUtil {
public static class IllegalLabelException extends IllegalArgumentException {
private static final long serialVersionUID = 1L;
static IllegalLabelException invalidLabels(Set<String> invalidLabels) {
return new IllegalLabelException(
String.format("invalid labels: %s", Joiner.on(", ").join(invalidLabels)));
IllegalLabelException(String message) {
super(message);
}
}
static IllegalLabelException mutuallyExclusiveLabels(String label1, String label2) {
return new IllegalLabelException(
public static class InvalidLabelsException extends IllegalLabelException {
private static final long serialVersionUID = 1L;
InvalidLabelsException(Set<String> invalidLabels) {
super(String.format("invalid labels: %s", Joiner.on(", ").join(invalidLabels)));
}
}
public static class MutuallyExclusiveLabelsException extends IllegalLabelException {
private static final long serialVersionUID = 1L;
MutuallyExclusiveLabelsException(String label1, String label2) {
super(
String.format(
"The labels %s and %s are mutually exclusive. Only one of them can be set.",
label1, label2));
}
IllegalLabelException(String message) {
super(message);
}
}
private static final Logger log = LoggerFactory.getLogger(StarredChangesUtil.class);
@@ -382,7 +389,7 @@ public class StarredChangesUtil {
private static void checkMutuallyExclusiveLabels(Set<String> labels) {
if (labels.containsAll(ImmutableSet.of(DEFAULT_LABEL, IGNORE_LABEL))) {
throw IllegalLabelException.mutuallyExclusiveLabels(DEFAULT_LABEL, IGNORE_LABEL);
throw new MutuallyExclusiveLabelsException(DEFAULT_LABEL, IGNORE_LABEL);
}
}
@@ -398,7 +405,7 @@ public class StarredChangesUtil {
}
}
if (!invalidLabels.isEmpty()) {
throw IllegalLabelException.invalidLabels(invalidLabels);
throw new InvalidLabelsException(invalidLabels);
}
}

View File

@@ -20,8 +20,10 @@ import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
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.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
@@ -30,6 +32,8 @@ import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
import com.google.gerrit.server.StarredChangesUtil.MutuallyExclusiveLabelsException;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -129,7 +133,7 @@ public class StarredChanges
@Override
public Response<?> apply(AccountResource rsrc, EmptyInput in)
throws AuthException, OrmException, IOException {
throws RestApiException, OrmException, IOException {
if (self.get() != rsrc.getUser()) {
throw new AuthException("not allowed to add starred change");
}
@@ -140,6 +144,10 @@ public class StarredChanges
change.getId(),
StarredChangesUtil.DEFAULT_LABELS,
null);
} catch (MutuallyExclusiveLabelsException e) {
throw new ResourceConflictException(e.getMessage());
} catch (IllegalLabelException e) {
throw new BadRequestException(e.getMessage());
} catch (OrmDuplicateKeyException e) {
return Response.none();
}

View File

@@ -14,11 +14,13 @@
package com.google.gerrit.server.change;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
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.extensions.webui.UiAction;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.StarredChangesUtil.MutuallyExclusiveLabelsException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -55,6 +57,8 @@ public class Ignore
stars.ignore(rsrc);
}
return Response.ok("");
} catch (MutuallyExclusiveLabelsException e) {
throw new ResourceConflictException(e.getMessage());
} catch (OrmException e) {
throw new RestApiException("failed to ignore change", e);
}