Tests for ErrorResponse hierarchy to verify the output
See gh-27052
This commit is contained in:
parent
679432ece6
commit
b045e5baef
|
@ -61,7 +61,7 @@ public class ErrorResponseException extends NestedRuntimeException implements Er
|
|||
* Constructor with a well-known {@link HttpStatus} and an optional cause.
|
||||
*/
|
||||
public ErrorResponseException(HttpStatus status, @Nullable Throwable cause) {
|
||||
this(status.value(), null);
|
||||
this(status.value(), cause);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.web;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
@ -39,15 +40,17 @@ public class HttpMediaTypeNotAcceptableException extends HttpMediaTypeException
|
|||
*/
|
||||
public HttpMediaTypeNotAcceptableException(String message) {
|
||||
super(message);
|
||||
getBody().setDetail("Could not parse Accept header");
|
||||
getBody().setDetail("Could not parse Accept header.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new HttpMediaTypeNotSupportedException.
|
||||
* @param supportedMediaTypes the list of supported media types
|
||||
* @param mediaTypes the list of supported media types
|
||||
*/
|
||||
public HttpMediaTypeNotAcceptableException(List<MediaType> supportedMediaTypes) {
|
||||
super("No acceptable representation", supportedMediaTypes);
|
||||
public HttpMediaTypeNotAcceptableException(List<MediaType> mediaTypes) {
|
||||
super("No acceptable representation", mediaTypes);
|
||||
getBody().setDetail("Acceptable representations: " +
|
||||
mediaTypes.stream().map(MediaType::toString).collect(Collectors.joining(", ", "'", "'")) + ".");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -51,29 +51,29 @@ public class HttpMediaTypeNotSupportedException extends HttpMediaTypeException {
|
|||
super(message);
|
||||
this.contentType = null;
|
||||
this.httpMethod = null;
|
||||
getBody().setDetail("Could not parse Content-Type");
|
||||
getBody().setDetail("Could not parse Content-Type.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new HttpMediaTypeNotSupportedException.
|
||||
* @param contentType the unsupported content type
|
||||
* @param supportedMediaTypes the list of supported media types
|
||||
* @param mediaTypes the list of supported media types
|
||||
*/
|
||||
public HttpMediaTypeNotSupportedException(@Nullable MediaType contentType, List<MediaType> supportedMediaTypes) {
|
||||
this(contentType, supportedMediaTypes, null);
|
||||
public HttpMediaTypeNotSupportedException(@Nullable MediaType contentType, List<MediaType> mediaTypes) {
|
||||
this(contentType, mediaTypes, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new HttpMediaTypeNotSupportedException.
|
||||
* @param contentType the unsupported content type
|
||||
* @param supportedMediaTypes the list of supported media types
|
||||
* @param mediaTypes the list of supported media types
|
||||
* @param httpMethod the HTTP method of the request
|
||||
* @since 6.0
|
||||
*/
|
||||
public HttpMediaTypeNotSupportedException(@Nullable MediaType contentType,
|
||||
List<MediaType> supportedMediaTypes, @Nullable HttpMethod httpMethod) {
|
||||
public HttpMediaTypeNotSupportedException(
|
||||
@Nullable MediaType contentType, List<MediaType> mediaTypes, @Nullable HttpMethod httpMethod) {
|
||||
|
||||
this(contentType, supportedMediaTypes, httpMethod,
|
||||
this(contentType, mediaTypes, httpMethod,
|
||||
"Content-Type " + (contentType != null ? "'" + contentType + "' " : "") + "is not supported");
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ public class HttpMediaTypeNotSupportedException extends HttpMediaTypeException {
|
|||
super(message, supportedMediaTypes);
|
||||
this.contentType = contentType;
|
||||
this.httpMethod = httpMethod;
|
||||
getBody().setDetail("Content-Type " + this.contentType + " is not supported");
|
||||
getBody().setDetail("Content-Type '" + this.contentType + "' is not supported.");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -93,8 +93,9 @@ public class HttpRequestMethodNotSupportedException extends ServletException imp
|
|||
super(msg);
|
||||
this.method = method;
|
||||
this.supportedMethods = supportedMethods;
|
||||
this.body = ProblemDetail.forRawStatusCode(getRawStatusCode())
|
||||
.withDetail("Method '" + method + "' is not supported");
|
||||
|
||||
String detail = "Method '" + method + "' is not supported.";
|
||||
this.body = ProblemDetail.forRawStatusCode(getRawStatusCode()).withDetail(detail);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ public class MethodArgumentNotValidException extends BindException implements Er
|
|||
public MethodArgumentNotValidException(MethodParameter parameter, BindingResult bindingResult) {
|
||||
super(bindingResult);
|
||||
this.parameter = parameter;
|
||||
this.body = ProblemDetail.forRawStatusCode(getRawStatusCode()).withDetail(initMessage(parameter));
|
||||
this.body = ProblemDetail.forRawStatusCode(getRawStatusCode()).withDetail("Invalid request content.");
|
||||
}
|
||||
|
||||
|
||||
|
@ -71,13 +71,9 @@ public class MethodArgumentNotValidException extends BindException implements Er
|
|||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return initMessage(this.parameter);
|
||||
}
|
||||
|
||||
private String initMessage(MethodParameter parameter) {
|
||||
StringBuilder sb = new StringBuilder("Validation failed for argument [")
|
||||
.append(parameter.getParameterIndex()).append("] in ")
|
||||
.append(parameter.getExecutable().toGenericString());
|
||||
.append(this.parameter.getParameterIndex()).append("] in ")
|
||||
.append(this.parameter.getExecutable().toGenericString());
|
||||
BindingResult bindingResult = getBindingResult();
|
||||
if (bindingResult.getErrorCount() > 1) {
|
||||
sb.append(" with ").append(bindingResult.getErrorCount()).append(" errors");
|
||||
|
|
|
@ -57,7 +57,7 @@ public class MissingMatrixVariableException extends MissingRequestValueException
|
|||
super("", missingAfterConversion);
|
||||
this.variableName = variableName;
|
||||
this.parameter = parameter;
|
||||
getBody().setDetail("Required path parameter '" + this.variableName + "' is not present");
|
||||
getBody().setDetail("Required path parameter '" + this.variableName + "' is not present.");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ public class MissingPathVariableException extends MissingRequestValueException {
|
|||
super("", missingAfterConversion);
|
||||
this.variableName = variableName;
|
||||
this.parameter = parameter;
|
||||
getBody().setDetail("Required URI variable '" + this.variableName + "' is not present");
|
||||
getBody().setDetail("Required path variable '" + this.variableName + "' is not present.");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ public class MissingRequestCookieException extends MissingRequestValueException
|
|||
super("", missingAfterConversion);
|
||||
this.cookieName = cookieName;
|
||||
this.parameter = parameter;
|
||||
getBody().setDetail("Required cookie '" + this.cookieName + "' is not present");
|
||||
getBody().setDetail("Required cookie '" + this.cookieName + "' is not present.");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ public class MissingRequestHeaderException extends MissingRequestValueException
|
|||
super("", missingAfterConversion);
|
||||
this.headerName = headerName;
|
||||
this.parameter = parameter;
|
||||
getBody().setDetail("Required header '" + this.headerName + "' is not present");
|
||||
getBody().setDetail("Required header '" + this.headerName + "' is not present.");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ public class MissingServletRequestParameterException extends MissingRequestValue
|
|||
super("", missingAfterConversion);
|
||||
this.parameterName = parameterName;
|
||||
this.parameterType = parameterType;
|
||||
getBody().setDetail("Required parameter '" + this.parameterName + "' is not present");
|
||||
getBody().setDetail("Required parameter '" + this.parameterName + "' is not present.");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.web.bind;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -48,9 +47,7 @@ public class UnsatisfiedServletRequestParameterException extends ServletRequestB
|
|||
* @param actualParams the actual parameter Map associated with the ServletRequest
|
||||
*/
|
||||
public UnsatisfiedServletRequestParameterException(String[] paramConditions, Map<String, String[]> actualParams) {
|
||||
super("");
|
||||
this.paramConditions = Arrays.<String[]>asList(paramConditions);
|
||||
this.actualParams = actualParams;
|
||||
this(List.<String[]>of(paramConditions), actualParams);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,6 +63,7 @@ public class UnsatisfiedServletRequestParameterException extends ServletRequestB
|
|||
Assert.notEmpty(paramConditions, "Parameter conditions must not be empty");
|
||||
this.paramConditions = paramConditions;
|
||||
this.actualParams = actualParams;
|
||||
getBody().setDetail("Invalid request parameters.");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ public class WebExchangeBindException extends ServerWebInputException implements
|
|||
public WebExchangeBindException(MethodParameter parameter, BindingResult bindingResult) {
|
||||
super("Validation failure", parameter);
|
||||
this.bindingResult = bindingResult;
|
||||
getBody().setDetail("Invalid request content.");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ public class MissingServletRequestPartException extends ServletRequestBindingExc
|
|||
* @param requestPartName the name of the missing part of the multipart request
|
||||
*/
|
||||
public MissingServletRequestPartException(String requestPartName) {
|
||||
super("Required request part '" + requestPartName + "' is not present");
|
||||
super("Required part '" + requestPartName + "' is not present.");
|
||||
this.requestPartName = requestPartName;
|
||||
getBody().setDetail(getMessage());
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.util.Collection;
|
|||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
@ -47,13 +48,17 @@ public class MethodNotAllowedException extends ResponseStatusException {
|
|||
}
|
||||
|
||||
public MethodNotAllowedException(String method, @Nullable Collection<HttpMethod> supportedMethods) {
|
||||
super(HttpStatus.METHOD_NOT_ALLOWED, "Request method '" + method + "' not supported");
|
||||
super(HttpStatus.METHOD_NOT_ALLOWED, "Request method '" + method + "' is not supported.");
|
||||
Assert.notNull(method, "'method' is required");
|
||||
if (supportedMethods == null) {
|
||||
supportedMethods = Collections.emptySet();
|
||||
}
|
||||
this.method = method;
|
||||
this.httpMethods = Collections.unmodifiableSet(new LinkedHashSet<>(supportedMethods));
|
||||
|
||||
getBody().setDetail(this.httpMethods.isEmpty() ? getReason() :
|
||||
"Supported methods: " + this.httpMethods.stream()
|
||||
.map(HttpMethod::toString).collect(Collectors.joining("', '", "'", "'")));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.springframework.web.server;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
@ -42,14 +43,17 @@ public class NotAcceptableStatusException extends ResponseStatusException {
|
|||
public NotAcceptableStatusException(String reason) {
|
||||
super(HttpStatus.NOT_ACCEPTABLE, reason);
|
||||
this.supportedMediaTypes = Collections.emptyList();
|
||||
getBody().setDetail("Could not parse Accept header.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for when the requested Content-Type is not supported.
|
||||
*/
|
||||
public NotAcceptableStatusException(List<MediaType> supportedMediaTypes) {
|
||||
public NotAcceptableStatusException(List<MediaType> mediaTypes) {
|
||||
super(HttpStatus.NOT_ACCEPTABLE, "Could not find acceptable representation");
|
||||
this.supportedMediaTypes = Collections.unmodifiableList(supportedMediaTypes);
|
||||
this.supportedMediaTypes = Collections.unmodifiableList(mediaTypes);
|
||||
getBody().setDetail("Acceptable representations: " +
|
||||
mediaTypes.stream().map(MediaType::toString).collect(Collectors.joining(", ", "'", "'")) + ".");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -77,7 +77,6 @@ public class ResponseStatusException extends ErrorResponseException {
|
|||
public ResponseStatusException(int rawStatusCode, @Nullable String reason, @Nullable Throwable cause) {
|
||||
super(rawStatusCode, cause);
|
||||
this.reason = reason;
|
||||
setDetail(reason);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ public class UnsupportedMediaTypeStatusException extends ResponseStatusException
|
|||
this.supportedMediaTypes = Collections.emptyList();
|
||||
this.bodyType = null;
|
||||
this.method = null;
|
||||
getBody().setDetail("Could not parse Content-Type.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,8 +101,7 @@ public class UnsupportedMediaTypeStatusException extends ResponseStatusException
|
|||
this.bodyType = bodyType;
|
||||
this.method = method;
|
||||
|
||||
// Set explicitly to avoid implementation details
|
||||
setDetail(contentType != null ? "Content-Type '" + contentType + "' is not supported" : null);
|
||||
setDetail(contentType != null ? "Content-Type '" + contentType + "' is not supported." : null);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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) {}
|
||||
|
||||
}
|
|
@ -45,7 +45,7 @@ public class NoHandlerFoundException extends ServletException implements ErrorRe
|
|||
|
||||
private final HttpHeaders headers;
|
||||
|
||||
private final ProblemDetail detail = ProblemDetail.forRawStatusCode(getRawStatusCode());
|
||||
private final ProblemDetail body;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -55,10 +55,11 @@ public class NoHandlerFoundException extends ServletException implements ErrorRe
|
|||
* @param headers the HTTP request headers
|
||||
*/
|
||||
public NoHandlerFoundException(String httpMethod, String requestURL, HttpHeaders headers) {
|
||||
super("No handler found for " + httpMethod + " " + requestURL);
|
||||
super("No endpoint " + httpMethod + " " + requestURL + ".");
|
||||
this.httpMethod = httpMethod;
|
||||
this.requestURL = requestURL;
|
||||
this.headers = headers;
|
||||
this.body = ProblemDetail.forRawStatusCode(getRawStatusCode()).withDetail(getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
@ -81,7 +82,7 @@ public class NoHandlerFoundException extends ServletException implements ErrorRe
|
|||
|
||||
@Override
|
||||
public ProblemDetail getBody() {
|
||||
return this.detail;
|
||||
return this.body;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -154,6 +154,7 @@ public abstract class ResponseEntityExceptionHandler {
|
|||
return handleAsyncRequestTimeoutException(subEx, subEx.getHeaders(), subEx.getStatus(), request);
|
||||
}
|
||||
else {
|
||||
// Another ErrorResponseException
|
||||
return handleExceptionInternal(ex, null, errorEx.getHeaders(), errorEx.getStatus(), request);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue