Tests for ErrorResponse hierarchy to verify the output

See gh-27052
This commit is contained in:
rstoyanchev
2022-02-28 11:20:07 +00:00
parent 679432ece6
commit b045e5baef
20 changed files with 400 additions and 42 deletions

View File

@@ -0,0 +1,349 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* 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
*
* https://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 org.springframework.web;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
import org.springframework.lang.Nullable;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingMatrixVariableException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingRequestCookieException;
import org.springframework.web.bind.MissingRequestHeaderException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.server.MethodNotAllowedException;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import org.springframework.web.testfixture.method.ResolvableMethod;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests that verify the HTTP response details exposed by exceptions in the
* {@link ErrorResponse} hierarchy.
*
* @author Rossen Stoyanchev
*/
public class ErrorResponseExceptionTests {
private final MethodParameter methodParameter =
new MethodParameter(ResolvableMethod.on(getClass()).resolveMethod("handle"), 0);
@Test
void httpMediaTypeNotSupportedException() {
List<MediaType> mediaTypes =
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
ErrorResponse ex = new HttpMediaTypeNotSupportedException(
MediaType.APPLICATION_XML, mediaTypes, HttpMethod.PATCH, "Custom message");
assertStatus(ex, HttpStatus.UNSUPPORTED_MEDIA_TYPE);
assertDetail(ex, "Content-Type 'application/xml' is not supported.");
HttpHeaders headers = ex.getHeaders();
assertThat(headers.getAccept()).isEqualTo(mediaTypes);
assertThat(headers.getAcceptPatch()).isEqualTo(mediaTypes);
}
@Test
void httpMediaTypeNotSupportedExceptionWithParseError() {
ErrorResponse ex = new HttpMediaTypeNotSupportedException(
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
assertStatus(ex, HttpStatus.UNSUPPORTED_MEDIA_TYPE);
assertDetail(ex, "Could not parse Content-Type.");
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void httpMediaTypeNotAcceptableException() {
List<MediaType> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
ErrorResponse ex = new HttpMediaTypeNotAcceptableException(mediaTypes);
assertStatus(ex, HttpStatus.NOT_ACCEPTABLE);
assertDetail(ex, "Acceptable representations: 'application/json, application/cbor'.");
assertThat(ex.getHeaders()).hasSize(1);
assertThat(ex.getHeaders().getAccept()).isEqualTo(mediaTypes);
}
@Test
void httpMediaTypeNotAcceptableExceptionWithParseError() {
ErrorResponse ex = new HttpMediaTypeNotAcceptableException(
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
assertStatus(ex, HttpStatus.NOT_ACCEPTABLE);
assertDetail(ex, "Could not parse Accept header.");
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void asyncRequestTimeoutException() {
ErrorResponse ex = new AsyncRequestTimeoutException();
assertStatus(ex, HttpStatus.SERVICE_UNAVAILABLE);
assertDetail(ex, null);
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void httpRequestMethodNotSupportedException() {
String[] supportedMethods = new String[] { "GET", "POST" };
ErrorResponse ex = new HttpRequestMethodNotSupportedException("PUT", supportedMethods, "Custom message");
assertStatus(ex, HttpStatus.METHOD_NOT_ALLOWED);
assertDetail(ex, "Method 'PUT' is not supported.");
assertThat(ex.getHeaders()).hasSize(1);
assertThat(ex.getHeaders().getAllow()).containsExactly(HttpMethod.GET, HttpMethod.POST);
}
@Test
void missingRequestHeaderException() {
ErrorResponse ex = new MissingRequestHeaderException("Authorization", this.methodParameter);
assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Required header 'Authorization' is not present.");
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void missingServletRequestParameterException() {
ErrorResponse ex = new MissingServletRequestParameterException("query", "String");
assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Required parameter 'query' is not present.");
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void missingMatrixVariableException() {
ErrorResponse ex = new MissingMatrixVariableException("region", this.methodParameter);
assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Required path parameter 'region' is not present.");
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void missingPathVariableException() {
ErrorResponse ex = new MissingPathVariableException("id", this.methodParameter);
assertStatus(ex, HttpStatus.INTERNAL_SERVER_ERROR);
assertDetail(ex, "Required path variable 'id' is not present.");
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void missingRequestCookieException() {
ErrorResponse ex = new MissingRequestCookieException("oreo", this.methodParameter);
assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Required cookie 'oreo' is not present.");
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void unsatisfiedServletRequestParameterException() {
ErrorResponse ex = new UnsatisfiedServletRequestParameterException(
new String[] { "foo=bar", "bar=baz" }, Collections.singletonMap("q", new String[] {"1"}));
assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Invalid request parameters.");
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void missingServletRequestPartException() {
ErrorResponse ex = new MissingServletRequestPartException("file");
assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Required part 'file' is not present.");
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void methodArgumentNotValidException() {
BindingResult bindingResult = new BindException(new Object(), "object");
bindingResult.addError(new FieldError("object", "field", "message"));
ErrorResponse ex = new MethodArgumentNotValidException(this.methodParameter, bindingResult);
assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Invalid request content.");
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void unsupportedMediaTypeStatusException() {
List<MediaType> mediaTypes =
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
ErrorResponse ex = new UnsupportedMediaTypeStatusException(
MediaType.APPLICATION_XML, mediaTypes, HttpMethod.PATCH);
assertStatus(ex, HttpStatus.UNSUPPORTED_MEDIA_TYPE);
assertDetail(ex, "Content-Type 'application/xml' is not supported.");
HttpHeaders headers = ex.getHeaders();
assertThat(headers.getAccept()).isEqualTo(mediaTypes);
assertThat(headers.getAcceptPatch()).isEqualTo(mediaTypes);
}
@Test
void unsupportedMediaTypeStatusExceptionWithParseError() {
ErrorResponse ex = new UnsupportedMediaTypeStatusException(
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
assertStatus(ex, HttpStatus.UNSUPPORTED_MEDIA_TYPE);
assertDetail(ex, "Could not parse Content-Type.");
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void notAcceptableStatusException() {
List<MediaType> mediaTypes =
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
ErrorResponse ex = new NotAcceptableStatusException(mediaTypes);
assertStatus(ex, HttpStatus.NOT_ACCEPTABLE);
assertDetail(ex, "Acceptable representations: 'application/json, application/cbor'.");
assertThat(ex.getHeaders()).hasSize(1);
assertThat(ex.getHeaders().getAccept()).isEqualTo(mediaTypes);
}
@Test
void notAcceptableStatusExceptionWithParseError() {
ErrorResponse ex = new NotAcceptableStatusException(
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
assertStatus(ex, HttpStatus.NOT_ACCEPTABLE);
assertDetail(ex, "Could not parse Accept header.");
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void webExchangeBindException() {
BindingResult bindingResult = new BindException(new Object(), "object");
bindingResult.addError(new FieldError("object", "field", "message"));
ErrorResponse ex = new WebExchangeBindException(this.methodParameter, bindingResult);
assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Invalid request content.");
assertThat(ex.getHeaders()).isEmpty();
}
@Test
void methodNotAllowedException() {
List<HttpMethod> supportedMethods = Arrays.asList(HttpMethod.GET, HttpMethod.POST);
ErrorResponse ex = new MethodNotAllowedException(HttpMethod.PUT, supportedMethods);
assertStatus(ex, HttpStatus.METHOD_NOT_ALLOWED);
assertDetail(ex, "Supported methods: 'GET', 'POST'");
assertThat(ex.getHeaders()).hasSize(1);
assertThat(ex.getHeaders().getAllow()).containsExactly(HttpMethod.GET, HttpMethod.POST);
}
@Test
void methodNotAllowedExceptionWithoutSupportedMethods() {
ErrorResponse ex = new MethodNotAllowedException(HttpMethod.PUT, Collections.emptyList());
assertStatus(ex, HttpStatus.METHOD_NOT_ALLOWED);
assertDetail(ex, "Request method 'PUT' is not supported.");
assertThat(ex.getHeaders()).isEmpty();
}
private void assertStatus(ErrorResponse ex, HttpStatus status) {
ProblemDetail body = ex.getBody();
assertThat(ex.getStatus()).isEqualTo(status);
assertThat(body.getStatus()).isEqualTo(status.value());
assertThat(body.getTitle()).isEqualTo(status.getReasonPhrase());
}
private void assertDetail(ErrorResponse ex, @Nullable String detail) {
if (detail != null) {
assertThat(ex.getBody().getDetail()).isEqualTo(detail);
}
else {
assertThat(ex.getBody().getDetail()).isNull();
}
}
@SuppressWarnings("unused")
private void handle(String arg) {}
}