Return Bad Request on bad reviewers to PostReview

Now that posting a review can be used to add reviewers, it's possible
for the entire post to fail due to bad input (e.g., an unresolvable
account, an oversized group). This information is already returned in
the JSON response body, but we should use a status code other than
200 OK when this happens so the client doesn't erroneously assume that
the review was successfully posted.

Change-Id: I763b5c73878b7c70a95953759b371a182c8a8dad
This commit is contained in:
Logan Hanks
2016-07-22 15:54:52 -07:00
committed by David Pursehouse
parent b86c905fd6
commit e2aacef35c
4 changed files with 65 additions and 16 deletions

View File

@@ -2846,9 +2846,7 @@ with a review.
Each element of the `reviewers` list is an instance of Each element of the `reviewers` list is an instance of
link:#reviewer-input[ReviewerInput]. The corresponding result of link:#reviewer-input[ReviewerInput]. The corresponding result of
adding each reviewer will be returned in a list of adding each reviewer will be returned in a list of
link:#add-reviewer-result[AddReviewerResult]. If there are any link:#add-reviewer-result[AddReviewerResult].
errors returned for reviewers, the entire review request will
be rejected.
.Response .Response
---- ----
@@ -2886,6 +2884,27 @@ be rejected.
} }
---- ----
If there are any errors returned for reviewers, the entire review request will
be rejected with `400 Bad Request`.
.Error Response
----
HTTP/1.1 400 Bad Request
Content-Disposition: attachment
Content-Type: application/json; charset=UTF-8
)]}'
{
"reviewers": {
"MyProjectVerifiers": {
"input": "MyProjectVerifiers",
"error": "The group My Group has 15 members. Do you want to add them all as reviewers?",
"confirm": true
}
}
}
----
[[rebase-revision]] [[rebase-revision]]
=== Rebase Revision === Rebase Revision
-- --

View File

@@ -17,6 +17,8 @@ package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.extensions.client.ReviewerState.CC; import static com.google.gerrit.extensions.client.ReviewerState.CC;
import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER; import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.acceptance.PushOneCommit;
@@ -272,7 +274,7 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
// In legacy mode, change owner should be the only reviewer. // In legacy mode, change owner should be the only reviewer.
assertReviewers(c, REVIEWER, admin); assertReviewers(c, REVIEWER, admin);
assertReviewers(c, CC, user, observer); assertReviewers(c, CC, user, observer);
} }
// Verify emails were sent to added reviewers. // Verify emails were sent to added reviewers.
List<Message> messages = sender.getMessages(); List<Message> messages = sender.getMessages();
@@ -329,7 +331,8 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
.reviewer(user.email) .reviewer(user.email)
.reviewer(observer.email, CC, false) .reviewer(observer.email, CC, false)
.reviewer(largeGroup); .reviewer(largeGroup);
ReviewResult result = review(r.getChangeId(), r.getCommit().name(), input); ReviewResult result = review(
r.getChangeId(), r.getCommit().name(), input, SC_BAD_REQUEST);
assertThat(result.labels).isNull(); assertThat(result.labels).isNull();
assertThat(result.reviewers).isNotNull(); assertThat(result.reviewers).isNotNull();
assertThat(result.reviewers).hasSize(3); assertThat(result.reviewers).hasSize(3);
@@ -353,7 +356,8 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
.reviewer(user.email) .reviewer(user.email)
.reviewer(observer.email, CC, false) .reviewer(observer.email, CC, false)
.reviewer(mediumGroup); .reviewer(mediumGroup);
result = review(r.getChangeId(), r.getCommit().name(), input); result = review(r.getChangeId(), r.getCommit().name(), input,
SC_BAD_REQUEST);
assertThat(result.labels).isNull(); assertThat(result.labels).isNull();
assertThat(result.reviewers).isNotNull(); assertThat(result.reviewers).isNotNull();
assertThat(result.reviewers).hasSize(3); assertThat(result.reviewers).hasSize(3);
@@ -400,27 +404,46 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
private AddReviewerResult addReviewer(String changeId, String reviewer) private AddReviewerResult addReviewer(String changeId, String reviewer)
throws Exception { throws Exception {
return addReviewer(changeId, reviewer, SC_OK);
}
private AddReviewerResult addReviewer(
String changeId, String reviewer, int expectedStatus) throws Exception {
AddReviewerInput in = new AddReviewerInput(); AddReviewerInput in = new AddReviewerInput();
in.reviewer = reviewer; in.reviewer = reviewer;
return addReviewer(changeId, in); return addReviewer(changeId, in, expectedStatus);
} }
private AddReviewerResult addReviewer(String changeId, AddReviewerInput in) private AddReviewerResult addReviewer(String changeId, AddReviewerInput in)
throws Exception { throws Exception {
return addReviewer(changeId, in, SC_OK);
}
private AddReviewerResult addReviewer(String changeId, AddReviewerInput in,
int expectedStatus) throws Exception {
RestResponse resp = RestResponse resp =
adminRestSession.post("/changes/" + changeId + "/reviewers", in); adminRestSession.post("/changes/" + changeId + "/reviewers", in);
return readContentFromJson(resp, AddReviewerResult.class); return readContentFromJson(
resp, expectedStatus, AddReviewerResult.class);
} }
private ReviewResult review(String changeId, String revisionId, ReviewInput in) throws Exception { private ReviewResult review(
String changeId, String revisionId, ReviewInput in) throws Exception {
return review(changeId, revisionId, in, SC_OK);
}
private ReviewResult review(
String changeId, String revisionId, ReviewInput in, int expectedStatus)
throws Exception {
RestResponse resp = adminRestSession.post( RestResponse resp = adminRestSession.post(
"/changes/" + changeId + "/revisions/" + revisionId + "/review", in); "/changes/" + changeId + "/revisions/" + revisionId + "/review", in);
return readContentFromJson(resp, ReviewResult.class); return readContentFromJson(resp, expectedStatus, ReviewResult.class);
} }
private static <T> T readContentFromJson(RestResponse r, Class<T> clazz) private static <T> T readContentFromJson(
RestResponse r, int expectedStatus, Class<T> clazz)
throws Exception { throws Exception {
r.assertOK(); r.assertStatus(expectedStatus);
JsonReader jsonReader = new JsonReader(r.getReader()); JsonReader jsonReader = new JsonReader(r.getReader());
jsonReader.setLenient(true); jsonReader.setLenient(true);
return newGson().fromJson(jsonReader, clazz); return newGson().fromJson(jsonReader, clazz);

View File

@@ -52,6 +52,11 @@ public abstract class Response<T> {
return new Redirect(location); return new Redirect(location);
} }
/** Arbitrary status code with wrapped result. */
public static <T> Response<T> withStatusCode(int statusCode, T value) {
return new Impl<>(statusCode, value);
}
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
public static <T> T unwrap(T obj) { public static <T> T unwrap(T obj) {
while (obj instanceof Response) { while (obj instanceof Response) {

View File

@@ -20,6 +20,7 @@ import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
import static com.google.gerrit.server.change.PutDraftComment.side; import static com.google.gerrit.server.change.PutDraftComment.side;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER; import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
@@ -48,6 +49,7 @@ import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException; 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.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView; import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException; import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
@@ -145,12 +147,12 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
} }
@Override @Override
public ReviewResult apply(RevisionResource revision, ReviewInput input) public Response<ReviewResult> apply(RevisionResource revision, ReviewInput input)
throws RestApiException, UpdateException, OrmException, IOException { throws RestApiException, UpdateException, OrmException, IOException {
return apply(revision, input, TimeUtil.nowTs()); return apply(revision, input, TimeUtil.nowTs());
} }
public ReviewResult apply(RevisionResource revision, ReviewInput input, public Response<ReviewResult> apply(RevisionResource revision, ReviewInput input,
Timestamp ts) Timestamp ts)
throws RestApiException, UpdateException, OrmException, IOException { throws RestApiException, UpdateException, OrmException, IOException {
// Respect timestamp, but truncate at change created-on time. // Respect timestamp, but truncate at change created-on time.
@@ -197,7 +199,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
ReviewResult output = new ReviewResult(); ReviewResult output = new ReviewResult();
output.reviewers = reviewerJsonResults; output.reviewers = reviewerJsonResults;
if (hasError || confirm) { if (hasError || confirm) {
return output; return Response.withStatusCode(SC_BAD_REQUEST, output);
} }
output.labels = input.labels; output.labels = input.labels;
@@ -218,7 +220,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
} }
} }
return output; return Response.ok(output);
} }
private RevisionResource onBehalfOf(RevisionResource rev, ReviewInput in) private RevisionResource onBehalfOf(RevisionResource rev, ReviewInput in)