From 7194bb00fd42d5abb5aa8ca96bd99f84a9eaea1b Mon Sep 17 00:00:00 2001 From: Colin DAMON <cdamon@ippon.fr> Date: Sun, 23 Aug 2020 11:48:47 +0200 Subject: [PATCH] Custom errors management --- borestop/pom.xml | 9 +- .../borestop/common/domain/error/Assert.java | 20 ++ .../domain/error/BorestopException.java | 175 ++++++++++ .../common/domain/error/BorestopMessage.java | 8 + .../common/domain/error/ErrorStatus.java | 10 + .../error/MissingMandatoryValueException.java | 32 ++ .../common/domain/error/StandardMessage.java | 19 ++ .../primary/ArgumentsReplacer.java | 24 ++ .../primary/AuthenticationMessage.java | 17 + .../infrastructure/primary/BorestopError.java | 48 +++ .../primary/BorestopErrorHandler.java | 298 ++++++++++++++++++ .../primary/BorestopFieldError.java | 74 +++++ .../primary/ValidationMessage.java | 12 + .../config/AuthenticationErrorsHandler.java | 38 +++ .../com/ippon/borestop/config/Constants.java | 2 +- .../borestop/config/JacksonConfiguration.java | 19 +- .../config/SecurityConfiguration.java | 13 +- .../service/EmailAlreadyUsedException.java | 11 +- .../service/InvalidPasswordException.java | 9 +- .../service/LoginAlreadyUsedException.java | 19 ++ .../ippon/borestop/service/UserMessage.java | 19 ++ .../ippon/borestop/service/UserService.java | 2 +- .../service/UsernameAlreadyUsedException.java | 12 - .../borestop/web/rest/AccountResource.java | 14 +- .../ippon/borestop/web/rest/UserResource.java | 91 ++++-- .../rest/errors/BadRequestAlertException.java | 42 --- .../errors/EmailAlreadyUsedException.java | 12 - .../web/rest/errors/ErrorConstants.java | 18 -- .../web/rest/errors/ExceptionTranslator.java | 136 -------- .../web/rest/errors/FieldErrorVM.java | 33 -- .../rest/errors/InvalidPasswordException.java | 14 - .../errors/LoginAlreadyUsedException.java | 12 - .../web/rest/errors/package-info.java | 6 - .../main/resources/i18n/messages.properties | 49 ++- .../resources/i18n/messages_en.properties | 23 +- .../borestop/common/BorestopIntTest.java | 20 ++ .../com/ippon/borestop/common/LogSpy.java | 62 ++++ .../common/domain/error/AssertUnitTest.java | 45 +++ .../error/BorestopExceptionUnitTest.java | 58 ++++ .../primary/ArgumentsReplacerUnitTest.java | 25 ++ .../primary}/AuthenticationSteps.java | 2 +- .../primary/BorestopErrorHandlerIntTest.java | 210 ++++++++++++ .../primary/BorestopErrorHandlerUnitTest.java | 218 +++++++++++++ .../primary/BorestopErrorUnitTest.java | 39 +++ .../primary/BorestopFieldErrorUnitTest.java | 30 ++ .../primary/ComplicatedRequest.java | 17 + .../primary}/CucumberTestContext.java | 2 +- .../primary/ErrorMessagesUnitTest.java | 83 +++++ .../infrastructure/primary/ErrorResource.java | 77 +++++ .../primary/NotDeserializable.java | 13 + .../primary/QueryParameter.java | 18 ++ .../infrastructure/primary}/TestJson.java | 2 +- .../cucumber/CucumberConfiguration.java | 2 +- .../ippon/borestop/cucumber/HttpSteps.java | 2 +- .../ippon/borestop/service/MailServiceIT.java | 79 +---- .../borestop/web/rest/AccountResourceIT.java | 105 +++--- .../borestop/web/rest/AccountsSteps.java | 2 +- .../rest/errors/ExceptionTranslatorIT.java | 117 ------- .../ExceptionTranslatorTestController.java | 65 ---- .../resources/i18n/messages_en.properties | 4 - 60 files changed, 1950 insertions(+), 687 deletions(-) create mode 100644 borestop/src/main/java/com/ippon/borestop/common/domain/error/Assert.java create mode 100644 borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopException.java create mode 100644 borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopMessage.java create mode 100644 borestop/src/main/java/com/ippon/borestop/common/domain/error/ErrorStatus.java create mode 100644 borestop/src/main/java/com/ippon/borestop/common/domain/error/MissingMandatoryValueException.java create mode 100644 borestop/src/main/java/com/ippon/borestop/common/domain/error/StandardMessage.java create mode 100644 borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacer.java create mode 100644 borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationMessage.java create mode 100644 borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopError.java create mode 100644 borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandler.java create mode 100644 borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldError.java create mode 100644 borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ValidationMessage.java create mode 100644 borestop/src/main/java/com/ippon/borestop/config/AuthenticationErrorsHandler.java create mode 100644 borestop/src/main/java/com/ippon/borestop/service/LoginAlreadyUsedException.java create mode 100644 borestop/src/main/java/com/ippon/borestop/service/UserMessage.java delete mode 100644 borestop/src/main/java/com/ippon/borestop/service/UsernameAlreadyUsedException.java delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/BadRequestAlertException.java delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/EmailAlreadyUsedException.java delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/ErrorConstants.java delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/ExceptionTranslator.java delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/FieldErrorVM.java delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/InvalidPasswordException.java delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/LoginAlreadyUsedException.java delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/package-info.java create mode 100644 borestop/src/test/java/com/ippon/borestop/common/BorestopIntTest.java create mode 100644 borestop/src/test/java/com/ippon/borestop/common/LogSpy.java create mode 100644 borestop/src/test/java/com/ippon/borestop/common/domain/error/AssertUnitTest.java create mode 100644 borestop/src/test/java/com/ippon/borestop/common/domain/error/BorestopExceptionUnitTest.java create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacerUnitTest.java rename borestop/src/test/java/com/ippon/borestop/{infrastructure/primay => common/infrastructure/primary}/AuthenticationSteps.java (96%) create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerIntTest.java create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerUnitTest.java create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorUnitTest.java create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldErrorUnitTest.java create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ComplicatedRequest.java rename borestop/src/test/java/com/ippon/borestop/{infrastructure/primay => common/infrastructure/primary}/CucumberTestContext.java (98%) create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorMessagesUnitTest.java create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorResource.java create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/NotDeserializable.java create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/QueryParameter.java rename borestop/src/test/java/com/ippon/borestop/{infrastructure/primay => common/infrastructure/primary}/TestJson.java (96%) delete mode 100644 borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorIT.java delete mode 100644 borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorTestController.java delete mode 100644 borestop/src/test/resources/i18n/messages_en.properties diff --git a/borestop/pom.xml b/borestop/pom.xml index e984eed4..82e234ff 100644 --- a/borestop/pom.xml +++ b/borestop/pom.xml @@ -245,10 +245,6 @@ <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>org.zalando</groupId> - <artifactId>problem-spring-web</artifactId> - </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> @@ -305,6 +301,11 @@ <artifactId>junit-vintage-engine</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.reflections</groupId> + <artifactId>reflections</artifactId> + <scope>test</scope> + </dependency> </dependencies> <build> diff --git a/borestop/src/main/java/com/ippon/borestop/common/domain/error/Assert.java b/borestop/src/main/java/com/ippon/borestop/common/domain/error/Assert.java new file mode 100644 index 00000000..4d9b2556 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/common/domain/error/Assert.java @@ -0,0 +1,20 @@ +package com.ippon.borestop.common.domain.error; + +public final class Assert { + + private Assert() {} + + public static void notNull(String field, Object input) { + if (input == null) { + throw MissingMandatoryValueException.forNullValue(field); + } + } + + public static void notBlank(String field, String input) { + notNull(field, input); + + if (input.isBlank()) { + throw MissingMandatoryValueException.forBlankValue(field); + } + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopException.java b/borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopException.java new file mode 100644 index 00000000..26efd7fe --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopException.java @@ -0,0 +1,175 @@ +package com.ippon.borestop.common.domain.error; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Parent exception used in Borestop application. Those exceptions will be resolved as human readable errors. + * + * <p> + * You can use this implementation directly: + * </p> + * + * <p> + * <code> + * <pre> + * BorestopException.builder(StandardMessages.USER_MANDATORY) + * .argument("key", "value") + * .argument("other", "test") + * .status(ErrorsHttpStatus.BAD_REQUEST) + * .message("Error message") + * .cause(new RuntimeException()) + * .build(); + * </pre> + * </code> + * </p> + * + * <p> + * Or make extension exceptions: + * </p> + * + * <p> + * <code> + * <pre> + * public class MissingMandatoryValueException extends BorestopException { + * + * public MissingMandatoryValueException(BorestopMessage borestopMessage, String fieldName) { + * this(builder(borestopMessage, fieldName, defaultMessage(fieldName))); + * } + * + * protected MissingMandatoryValueException(BorestopExceptionBuilder builder) { + * super(builder); + * } + * + * private static BorestopExceptionBuilder builder(BorestopMessage borestopMessage, String fieldName, String message) { + * return BorestopException.builder(borestopMessage) + * .status(ErrorsHttpStatus.INTERNAL_SERVER_ERROR) + * .argument("field", fieldName) + * .message(message); + * } + * + * private static String defaultMessage(String fieldName) { + * return "The field \"" + fieldName + "\" is mandatory and wasn't set"; + * } + * } + * </pre> + * </code> + * </p> + */ +public class BorestopException extends RuntimeException { + private final Map<String, String> arguments; + private final ErrorStatus status; + private final BorestopMessage borestopMessage; + + protected BorestopException(BorestopExceptionBuilder builder) { + super(getMessage(builder), getCause(builder)); + arguments = getArguments(builder); + status = getStatus(builder); + borestopMessage = getBorestopMessage(builder); + } + + private static String getMessage(BorestopExceptionBuilder builder) { + if (builder == null) { + return null; + } + + return builder.message; + } + + private static Throwable getCause(BorestopExceptionBuilder builder) { + if (builder == null) { + return null; + } + + return builder.cause; + } + + private static Map<String, String> getArguments(BorestopExceptionBuilder builder) { + if (builder == null) { + return null; + } + + return Collections.unmodifiableMap(builder.arguments); + } + + private static ErrorStatus getStatus(BorestopExceptionBuilder builder) { + if (builder == null) { + return null; + } + + return builder.status; + } + + private static BorestopMessage getBorestopMessage(BorestopExceptionBuilder builder) { + if (builder == null) { + return null; + } + + return builder.borestopMessage; + } + + public static BorestopExceptionBuilder builder(BorestopMessage message) { + return new BorestopExceptionBuilder(message); + } + + public Map<String, String> getArguments() { + return arguments; + } + + public ErrorStatus getStatus() { + return status; + } + + public BorestopMessage getBorestopMessage() { + return borestopMessage; + } + + public static class BorestopExceptionBuilder { + private final Map<String, String> arguments = new HashMap<>(); + private String message; + private ErrorStatus status; + private BorestopMessage borestopMessage; + private Throwable cause; + + public BorestopExceptionBuilder(BorestopMessage borestopMessage) { + this.borestopMessage = borestopMessage; + } + + public BorestopExceptionBuilder argument(String key, Object value) { + arguments.put(key, getStringValue(value)); + + return this; + } + + private String getStringValue(Object value) { + if (value == null) { + return "null"; + } + + return value.toString(); + } + + public BorestopExceptionBuilder status(ErrorStatus status) { + this.status = status; + + return this; + } + + public BorestopExceptionBuilder message(String message) { + this.message = message; + + return this; + } + + public BorestopExceptionBuilder cause(Throwable cause) { + this.cause = cause; + + return this; + } + + public BorestopException build() { + return new BorestopException(this); + } + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopMessage.java b/borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopMessage.java new file mode 100644 index 00000000..6a24d4c2 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopMessage.java @@ -0,0 +1,8 @@ +package com.ippon.borestop.common.domain.error; + +import java.io.Serializable; + +@FunctionalInterface +public interface BorestopMessage extends Serializable { + String getMessageKey(); +} diff --git a/borestop/src/main/java/com/ippon/borestop/common/domain/error/ErrorStatus.java b/borestop/src/main/java/com/ippon/borestop/common/domain/error/ErrorStatus.java new file mode 100644 index 00000000..c8aefe82 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/common/domain/error/ErrorStatus.java @@ -0,0 +1,10 @@ +package com.ippon.borestop.common.domain.error; + +public enum ErrorStatus { + BAD_REQUEST, + UNAUTHORIZED, + FORBIDDEN, + NOT_FOUND, + CONFLICT, + INTERNAL_SERVER_ERROR +} diff --git a/borestop/src/main/java/com/ippon/borestop/common/domain/error/MissingMandatoryValueException.java b/borestop/src/main/java/com/ippon/borestop/common/domain/error/MissingMandatoryValueException.java new file mode 100644 index 00000000..51284aaa --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/common/domain/error/MissingMandatoryValueException.java @@ -0,0 +1,32 @@ +package com.ippon.borestop.common.domain.error; + +public class MissingMandatoryValueException extends BorestopException { + + protected MissingMandatoryValueException(BorestopExceptionBuilder builder) { + super(builder); + } + + private static BorestopExceptionBuilder builder(BorestopMessage borestopMessage, String fieldName, String message) { + return BorestopException + .builder(borestopMessage) + .status(ErrorStatus.INTERNAL_SERVER_ERROR) + .argument("field", fieldName) + .message(message); + } + + private static String defaultMessage(String fieldName) { + return "The field \"" + fieldName + "\" is mandatory and wasn't set"; + } + + public static MissingMandatoryValueException forNullValue(String fieldName) { + return new MissingMandatoryValueException( + builder(StandardMessage.SERVER_MANDATORY_NULL, fieldName, defaultMessage(fieldName) + " (null)") + ); + } + + public static MissingMandatoryValueException forBlankValue(String fieldName) { + return new MissingMandatoryValueException( + builder(StandardMessage.SERVER_MANDATORY_BLANK, fieldName, defaultMessage(fieldName) + " (blank)") + ); + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/common/domain/error/StandardMessage.java b/borestop/src/main/java/com/ippon/borestop/common/domain/error/StandardMessage.java new file mode 100644 index 00000000..91093d58 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/common/domain/error/StandardMessage.java @@ -0,0 +1,19 @@ +package com.ippon.borestop.common.domain.error; + +public enum StandardMessage implements BorestopMessage { + USER_MANDATORY("user.mandatory"), + BAD_REQUEST("user.bad-request"), + INTERNAL_SERVER_ERROR("server.internal-server-error"), + SERVER_MANDATORY_NULL("server.mandatory-null"), + SERVER_MANDATORY_BLANK("server.mandatory-blank"); + private final String messageKey; + + private StandardMessage(String code) { + this.messageKey = code; + } + + @Override + public String getMessageKey() { + return messageKey; + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacer.java b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacer.java new file mode 100644 index 00000000..ca80b959 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacer.java @@ -0,0 +1,24 @@ +package com.ippon.borestop.common.infrastructure.primary; + +import java.util.Map; + +final class ArgumentsReplacer { + private static final String OPEN_MUSTACHE = "\\{\\{\\s*"; + private static final String CLOSE_MUSTACHE = "\\s*\\}\\}"; + + private ArgumentsReplacer() {} + + public static String replaceArguments(String message, Map<String, String> arguments) { + if (message == null || arguments == null) { + return message; + } + + String result = message; + for (Map.Entry<String, String> argument : arguments.entrySet()) { + String argumentRegex = OPEN_MUSTACHE + argument.getKey() + CLOSE_MUSTACHE; + result = result.replaceAll(argumentRegex, argument.getValue()); + } + + return result; + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationMessage.java b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationMessage.java new file mode 100644 index 00000000..7bb80fa4 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationMessage.java @@ -0,0 +1,17 @@ +package com.ippon.borestop.common.infrastructure.primary; + +import com.ippon.borestop.common.domain.error.BorestopMessage; + +enum AuthenticationMessage implements BorestopMessage { + NOT_AUTHENTICATED("user.authentication-not-authenticated"); + private final String messageKey; + + AuthenticationMessage(String messageKey) { + this.messageKey = messageKey; + } + + @Override + public String getMessageKey() { + return messageKey; + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopError.java b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopError.java new file mode 100644 index 00000000..9e4cd83f --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopError.java @@ -0,0 +1,48 @@ +/** + * + */ +package com.ippon.borestop.common.infrastructure.primary; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.util.Collection; +import java.util.List; + +@ApiModel(description = "Error result for a WebService call") +public class BorestopError { + @ApiModelProperty(value = "Technical type of this error", example = "user.mandatory", required = true) + private final String errorType; + + @ApiModelProperty( + value = "Human readable error message", + example = "Une erreur technique est survenue lors du traitement de votre demande", + required = true + ) + private final String message; + + @ApiModelProperty(value = "Invalid fields", required = false) + private final List<BorestopFieldError> fieldsErrors; + + public BorestopError( + @JsonProperty("errorType") String errorType, + @JsonProperty("message") String message, + @JsonProperty("fieldsErrors") List<BorestopFieldError> fieldsErrors + ) { + this.errorType = errorType; + this.message = message; + this.fieldsErrors = fieldsErrors; + } + + public String getErrorType() { + return errorType; + } + + public String getMessage() { + return message; + } + + public Collection<BorestopFieldError> getFieldsErrors() { + return fieldsErrors; + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandler.java b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandler.java new file mode 100644 index 00000000..80578265 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandler.java @@ -0,0 +1,298 @@ +/** + * + */ +package com.ippon.borestop.common.infrastructure.primary; + +import com.ippon.borestop.common.domain.error.BorestopException; +import com.ippon.borestop.common.domain.error.ErrorStatus; +import com.ippon.borestop.common.domain.error.StandardMessage; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.NoSuchMessageException; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.client.RestClientResponseException; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MaxUploadSizeExceededException; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@ControllerAdvice +public class BorestopErrorHandler extends ResponseEntityExceptionHandler { + private static final String MESSAGE_PREFIX = "borestop.error."; + private static final String DEFAULT_KEY = StandardMessage.INTERNAL_SERVER_ERROR.getMessageKey(); + private static final String BAD_REQUEST_KEY = StandardMessage.BAD_REQUEST.getMessageKey(); + private static final String STATUS_EXCEPTION_KEY = "status-exception"; + + private static final Logger logger = LoggerFactory.getLogger(BorestopErrorHandler.class); + + private final MessageSource messages; + + public BorestopErrorHandler(MessageSource messages) { + Locale.setDefault(Locale.FRANCE); + this.messages = messages; + } + + @ExceptionHandler + public ResponseEntity<BorestopError> handleBorestopException(BorestopException exception) { + HttpStatus status = getStatus(exception); + + logError(exception, status); + + String messageKey = getMessageKey(status, exception); + BorestopError error = new BorestopError(messageKey, getMessage(messageKey, exception.getArguments()), null); + return new ResponseEntity<>(error, status); + } + + @ExceptionHandler + public ResponseEntity<BorestopError> handleResponseStatusException(ResponseStatusException exception) { + HttpStatus status = exception.getStatus(); + + logError(exception, status); + + BorestopError error = new BorestopError(STATUS_EXCEPTION_KEY, buildErrorStatusMessage(exception), null); + return new ResponseEntity<>(error, status); + } + + private String buildErrorStatusMessage(ResponseStatusException exception) { + String reason = exception.getReason(); + + if (StringUtils.isBlank(reason)) { + Map<String, String> statusArgument = Map.of("status", String.valueOf(exception.getStatus().value())); + + return getMessage(STATUS_EXCEPTION_KEY, statusArgument); + } + + return reason; + } + + @ExceptionHandler(MaxUploadSizeExceededException.class) + public ResponseEntity<BorestopError> handleFileSizeException(MaxUploadSizeExceededException maxUploadSizeExceededException) { + logger.warn("File size limit exceeded: {}", maxUploadSizeExceededException.getMessage(), maxUploadSizeExceededException); + + BorestopError error = new BorestopError("server.upload-too-big", getMessage("server.upload-too-big", null), null); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(AccessDeniedException.class) + public ResponseEntity<BorestopError> handleAccessDeniedException(AccessDeniedException accessDeniedException) { + BorestopError error = new BorestopError("user.access-denied", getMessage("user.access-denied", null), null); + return new ResponseEntity<>(error, HttpStatus.FORBIDDEN); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity<BorestopError> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException exception) { + Throwable rootCause = ExceptionUtils.getRootCause(exception); + if (rootCause instanceof BorestopException) { + return handleBorestopException((BorestopException) rootCause); + } + + BorestopError error = new BorestopError(BAD_REQUEST_KEY, getMessage(BAD_REQUEST_KEY, null), null); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + private HttpStatus getStatus(BorestopException exception) { + ErrorStatus status = exception.getStatus(); + if (status == null) { + return HttpStatus.INTERNAL_SERVER_ERROR; + } + + switch (status) { + case BAD_REQUEST: + return HttpStatus.BAD_REQUEST; + case UNAUTHORIZED: + return HttpStatus.UNAUTHORIZED; + case FORBIDDEN: + return HttpStatus.FORBIDDEN; + case NOT_FOUND: + return HttpStatus.NOT_FOUND; + case CONFLICT: + return HttpStatus.CONFLICT; + default: + return HttpStatus.INTERNAL_SERVER_ERROR; + } + } + + private void logError(Exception exception, HttpStatus status) { + if (status.is5xxServerError()) { + logger.error("A server error was sent to a user: {}", exception.getMessage(), exception); + + logErrorBody(exception); + } else { + logger.warn("An error was sent to a user: {}", exception.getMessage(), exception); + } + } + + private void logErrorBody(Exception exception) { + Throwable cause = exception.getCause(); + if (cause instanceof RestClientResponseException) { + RestClientResponseException restCause = (RestClientResponseException) cause; + + logger.error("Cause body: {}", restCause.getResponseBodyAsString()); + } + } + + private String getMessageKey(HttpStatus status, BorestopException exception) { + if (exception.getBorestopMessage() == null) { + return getDefaultMessage(status); + } + + return exception.getBorestopMessage().getMessageKey(); + } + + private String getDefaultMessage(HttpStatus status) { + if (status.is5xxServerError()) { + return DEFAULT_KEY; + } + + return BAD_REQUEST_KEY; + } + + @Override + protected ResponseEntity<Object> handleMethodArgumentNotValid( + MethodArgumentNotValidException exception, + HttpHeaders headers, + HttpStatus status, + WebRequest request + ) { + logger.debug("Bean validation error {}", exception.getMessage(), exception); + + BorestopError error = new BorestopError(BAD_REQUEST_KEY, getMessage(BAD_REQUEST_KEY, null), getFieldsError(exception)); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler + public ResponseEntity<BorestopError> handleBeanValidationError(ConstraintViolationException exception) { + logger.debug("Bean validation error {}", exception.getMessage(), exception); + + BorestopError error = new BorestopError(BAD_REQUEST_KEY, getMessage(BAD_REQUEST_KEY, null), getFieldsErrors(exception)); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + @Override + protected ResponseEntity<Object> handleBindException( + BindException exception, + HttpHeaders headers, + HttpStatus status, + WebRequest request + ) { + List<BorestopFieldError> fieldErrors = exception.getFieldErrors().stream().map(toBorestopFieldError()).collect(Collectors.toList()); + + BorestopError error = new BorestopError(BAD_REQUEST_KEY, getMessage(BAD_REQUEST_KEY, null), fieldErrors); + + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + private ConstraintViolation<?> extractSource(FieldError error) { + return error.unwrap(ConstraintViolation.class); + } + + private String getMessage(String messageKey, Map<String, String> arguments) { + String text = getMessageFromSource(messageKey); + + return ArgumentsReplacer.replaceArguments(text, arguments); + } + + private String getMessageFromSource(String messageKey) { + Locale locale = LocaleContextHolder.getLocale(); + + try { + return messages.getMessage(MESSAGE_PREFIX + messageKey, null, locale); + } catch (NoSuchMessageException e) { + return messages.getMessage(MESSAGE_PREFIX + DEFAULT_KEY, null, locale); + } + } + + private List<BorestopFieldError> getFieldsErrors(ConstraintViolationException exception) { + return exception.getConstraintViolations().stream().map(toFieldError()).collect(Collectors.toList()); + } + + private List<BorestopFieldError> getFieldsError(MethodArgumentNotValidException exception) { + return exception.getBindingResult().getFieldErrors().stream().map(toBorestopFieldError()).collect(Collectors.toList()); + } + + private Function<FieldError, BorestopFieldError> toBorestopFieldError() { + return error -> + BorestopFieldError + .builder() + .fieldPath(error.getField()) + .reason(error.getDefaultMessage()) + .message(getMessage(error.getDefaultMessage(), buildArguments(extractSource(error)))) + .build(); + } + + private Map<String, String> buildArguments(ConstraintViolation<?> violation) { + return violation + .getConstraintDescriptor() + .getAttributes() + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().toString())); + } + + private Function<ConstraintViolation<?>, BorestopFieldError> toFieldError() { + return violation -> { + Map<String, String> arguments = buildArguments(violation); + + String message = violation.getMessage(); + return BorestopFieldError + .builder() + .fieldPath(violation.getPropertyPath().toString()) + .reason(message) + .message(getMessage(message, arguments)) + .build(); + }; + } + + @Override + protected ResponseEntity<Object> handleHttpMessageNotReadable( + HttpMessageNotReadableException exception, + HttpHeaders headers, + HttpStatus status, + WebRequest request + ) { + logger.error("Error reading query information: {}", exception.getMessage(), exception); + + BorestopError error = new BorestopError(DEFAULT_KEY, getMessage(DEFAULT_KEY, null), null); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler + public ResponseEntity<BorestopError> handleAuthenticationException(AuthenticationException exception) { + logger.debug("A user tried to do an unauthorized operation: {}", exception.getMessage(), exception); + + String message = AuthenticationMessage.NOT_AUTHENTICATED.getMessageKey(); + BorestopError error = new BorestopError(message, getMessage(message, null), null); + + return new ResponseEntity<>(error, HttpStatus.UNAUTHORIZED); + } + + @ExceptionHandler + public ResponseEntity<BorestopError> handleRuntimeException(Throwable throwable) { + logger.error("An unhandled error occurs: {}", throwable.getMessage(), throwable); + + BorestopError error = new BorestopError(DEFAULT_KEY, getMessage(DEFAULT_KEY, null), null); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldError.java b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldError.java new file mode 100644 index 00000000..7a771603 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldError.java @@ -0,0 +1,74 @@ +/** + * + */ +package com.ippon.borestop.common.infrastructure.primary; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.ippon.borestop.common.infrastructure.primary.BorestopFieldError.BorestopFieldErrorBuilder; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +@JsonDeserialize(builder = BorestopFieldErrorBuilder.class) +@ApiModel(description = "Error for a field validation") +public class BorestopFieldError { + @ApiModelProperty(value = "Path to the field in error", example = "address.country", required = true) + private final String fieldPath; + + @ApiModelProperty(value = "Technical reason for the invalidation", example = "user.mandatory", required = true) + private final String reason; + + @ApiModelProperty(value = "Human readable message for the invalidation", example = "Le champ doit être renseigné", required = true) + private final String message; + + private BorestopFieldError(BorestopFieldErrorBuilder builder) { + fieldPath = builder.fieldPath; + reason = builder.reason; + message = builder.message; + } + + public static BorestopFieldErrorBuilder builder() { + return new BorestopFieldErrorBuilder(); + } + + public String getFieldPath() { + return fieldPath; + } + + public String getReason() { + return reason; + } + + public String getMessage() { + return message; + } + + @JsonPOJOBuilder(withPrefix = "") + public static class BorestopFieldErrorBuilder { + private String fieldPath; + private String reason; + private String message; + + public BorestopFieldErrorBuilder fieldPath(String fieldPath) { + this.fieldPath = fieldPath; + + return this; + } + + public BorestopFieldErrorBuilder reason(String reason) { + this.reason = reason; + + return this; + } + + public BorestopFieldErrorBuilder message(String message) { + this.message = message; + + return this; + } + + public BorestopFieldError build() { + return new BorestopFieldError(this); + } + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ValidationMessage.java b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ValidationMessage.java new file mode 100644 index 00000000..bd7eb0a4 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ValidationMessage.java @@ -0,0 +1,12 @@ +package com.ippon.borestop.common.infrastructure.primary; + +public final class ValidationMessage { + public static final String MANDATORY = "user.mandatory"; + public static final String WRONG_FORMAT = "user.wrong-format"; + public static final String RPPS_FORMAT = "user.wrong-national-id-format"; + public static final String MAIL_FORMAT = "user.wrong-mail-format"; + public static final String VALUE_TOO_LOW = "user.too-low"; + public static final String VALUE_TOO_HIGH = "user.too-high"; + + private ValidationMessage() {} +} diff --git a/borestop/src/main/java/com/ippon/borestop/config/AuthenticationErrorsHandler.java b/borestop/src/main/java/com/ippon/borestop/config/AuthenticationErrorsHandler.java new file mode 100644 index 00000000..cc292bd9 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/config/AuthenticationErrorsHandler.java @@ -0,0 +1,38 @@ +package com.ippon.borestop.config; + +import com.ippon.borestop.common.infrastructure.Generated; +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerExceptionResolver; + +@Generated +@Component +class AuthenticationErrorsHandler implements AuthenticationEntryPoint, AccessDeniedHandler { + private final HandlerExceptionResolver resolver; + + @Autowired + public AuthenticationErrorsHandler(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) { + this.resolver = resolver; + } + + @Override + public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException exception) + throws IOException, ServletException { + resolver.resolveException(request, response, null, exception); + } + + @Override + public void handle(final HttpServletRequest request, final HttpServletResponse response, final AccessDeniedException exception) + throws IOException, ServletException { + resolver.resolveException(request, response, null, exception); + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/config/Constants.java b/borestop/src/main/java/com/ippon/borestop/config/Constants.java index 63741033..a650d3b2 100644 --- a/borestop/src/main/java/com/ippon/borestop/config/Constants.java +++ b/borestop/src/main/java/com/ippon/borestop/config/Constants.java @@ -11,7 +11,7 @@ public final class Constants { public static final String LOGIN_REGEX = "^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)|(?>[_.@A-Za-z0-9-]+)$"; public static final String SYSTEM_ACCOUNT = "system"; - public static final String DEFAULT_LANGUAGE = "en"; + public static final String DEFAULT_LANGUAGE = "fr"; public static final String ANONYMOUS_USER = "anonymoususer"; private Constants() {} diff --git a/borestop/src/main/java/com/ippon/borestop/config/JacksonConfiguration.java b/borestop/src/main/java/com/ippon/borestop/config/JacksonConfiguration.java index d000b8c1..2ce01440 100644 --- a/borestop/src/main/java/com/ippon/borestop/config/JacksonConfiguration.java +++ b/borestop/src/main/java/com/ippon/borestop/config/JacksonConfiguration.java @@ -6,8 +6,6 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.ippon.borestop.common.infrastructure.Generated; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.zalando.problem.ProblemModule; -import org.zalando.problem.violations.ConstraintViolationProblemModule; @Generated @Configuration @@ -15,6 +13,7 @@ public class JacksonConfiguration { /** * Support for Java date and time API. + * * @return the corresponding Jackson module. */ @Bean @@ -34,20 +33,4 @@ public class JacksonConfiguration { public Hibernate5Module hibernate5Module() { return new Hibernate5Module(); } - - /* - * Module for serialization/deserialization of RFC7807 Problem. - */ - @Bean - public ProblemModule problemModule() { - return new ProblemModule(); - } - - /* - * Module for serialization/deserialization of ConstraintViolationProblem. - */ - @Bean - public ConstraintViolationProblemModule constraintViolationProblemModule() { - return new ConstraintViolationProblemModule(); - } } diff --git a/borestop/src/main/java/com/ippon/borestop/config/SecurityConfiguration.java b/borestop/src/main/java/com/ippon/borestop/config/SecurityConfiguration.java index 156106b6..364d50ce 100644 --- a/borestop/src/main/java/com/ippon/borestop/config/SecurityConfiguration.java +++ b/borestop/src/main/java/com/ippon/borestop/config/SecurityConfiguration.java @@ -5,7 +5,6 @@ import com.ippon.borestop.security.AuthoritiesConstants; import com.ippon.borestop.security.jwt.JWTConfigurer; import com.ippon.borestop.security.jwt.TokenProvider; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -22,22 +21,20 @@ import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; import org.springframework.web.filter.CorsFilter; -import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport; @Generated @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) -@Import(SecurityProblemSupport.class) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private final TokenProvider tokenProvider; private final CorsFilter corsFilter; - private final SecurityProblemSupport problemSupport; + private final AuthenticationErrorsHandler errorsHandler; - public SecurityConfiguration(TokenProvider tokenProvider, CorsFilter corsFilter, SecurityProblemSupport problemSupport) { + public SecurityConfiguration(TokenProvider tokenProvider, CorsFilter corsFilter, AuthenticationErrorsHandler errorsHandler) { this.tokenProvider = tokenProvider; this.corsFilter = corsFilter; - this.problemSupport = problemSupport; + this.errorsHandler = errorsHandler; } @Bean @@ -67,8 +64,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { .disable() .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) .exceptionHandling() - .authenticationEntryPoint(problemSupport) - .accessDeniedHandler(problemSupport) + .authenticationEntryPoint(errorsHandler) + .accessDeniedHandler(errorsHandler) .and() .headers() .contentSecurityPolicy("default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:") diff --git a/borestop/src/main/java/com/ippon/borestop/service/EmailAlreadyUsedException.java b/borestop/src/main/java/com/ippon/borestop/service/EmailAlreadyUsedException.java index f2bccc96..3593d0ba 100644 --- a/borestop/src/main/java/com/ippon/borestop/service/EmailAlreadyUsedException.java +++ b/borestop/src/main/java/com/ippon/borestop/service/EmailAlreadyUsedException.java @@ -1,12 +1,19 @@ package com.ippon.borestop.service; +import com.ippon.borestop.common.domain.error.BorestopException; +import com.ippon.borestop.common.domain.error.ErrorStatus; import com.ippon.borestop.common.infrastructure.Generated; @Generated -public class EmailAlreadyUsedException extends RuntimeException { +public class EmailAlreadyUsedException extends BorestopException { private static final long serialVersionUID = 1L; public EmailAlreadyUsedException() { - super("Email is already in use!"); + super( + BorestopException + .builder(UserMessage.EMAIL_ALREADY_USED) + .message("A user tried to create an account with an used email") + .status(ErrorStatus.BAD_REQUEST) + ); } } diff --git a/borestop/src/main/java/com/ippon/borestop/service/InvalidPasswordException.java b/borestop/src/main/java/com/ippon/borestop/service/InvalidPasswordException.java index 3c044746..c296cf52 100644 --- a/borestop/src/main/java/com/ippon/borestop/service/InvalidPasswordException.java +++ b/borestop/src/main/java/com/ippon/borestop/service/InvalidPasswordException.java @@ -1,12 +1,15 @@ package com.ippon.borestop.service; +import com.ippon.borestop.common.domain.error.BorestopException; +import com.ippon.borestop.common.domain.error.ErrorStatus; import com.ippon.borestop.common.infrastructure.Generated; @Generated -public class InvalidPasswordException extends RuntimeException { - private static final long serialVersionUID = 1L; +public class InvalidPasswordException extends BorestopException { public InvalidPasswordException() { - super("Incorrect password"); + super( + BorestopException.builder(UserMessage.INVALID_PASSWORD).status(ErrorStatus.BAD_REQUEST).message("A user entered an invalid password") + ); } } diff --git a/borestop/src/main/java/com/ippon/borestop/service/LoginAlreadyUsedException.java b/borestop/src/main/java/com/ippon/borestop/service/LoginAlreadyUsedException.java new file mode 100644 index 00000000..69cc20d3 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/service/LoginAlreadyUsedException.java @@ -0,0 +1,19 @@ +package com.ippon.borestop.service; + +import com.ippon.borestop.common.domain.error.BorestopException; +import com.ippon.borestop.common.domain.error.ErrorStatus; +import com.ippon.borestop.common.infrastructure.Generated; + +@Generated +public class LoginAlreadyUsedException extends BorestopException { + private static final long serialVersionUID = 1L; + + public LoginAlreadyUsedException() { + super( + BorestopException + .builder(UserMessage.LOGIN_ALREADY_USED) + .status(ErrorStatus.BAD_REQUEST) + .message("A user tried to create an account with an used login") + ); + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/service/UserMessage.java b/borestop/src/main/java/com/ippon/borestop/service/UserMessage.java new file mode 100644 index 00000000..beadd404 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/service/UserMessage.java @@ -0,0 +1,19 @@ +package com.ippon.borestop.service; + +import com.ippon.borestop.common.domain.error.BorestopMessage; + +enum UserMessage implements BorestopMessage { + EMAIL_ALREADY_USED("user.e-mail-already-used"), + LOGIN_ALREADY_USED("user.login-already-used"), + INVALID_PASSWORD("user.invalid-password"); + private final String messageKey; + + private UserMessage(String messageKey) { + this.messageKey = messageKey; + } + + @Override + public String getMessageKey() { + return messageKey; + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/service/UserService.java b/borestop/src/main/java/com/ippon/borestop/service/UserService.java index e8f52e98..25d86ddd 100644 --- a/borestop/src/main/java/com/ippon/borestop/service/UserService.java +++ b/borestop/src/main/java/com/ippon/borestop/service/UserService.java @@ -94,7 +94,7 @@ public class UserService { existingUser -> { boolean removed = removeNonActivatedUser(existingUser); if (!removed) { - throw new UsernameAlreadyUsedException(); + throw new LoginAlreadyUsedException(); } } ); diff --git a/borestop/src/main/java/com/ippon/borestop/service/UsernameAlreadyUsedException.java b/borestop/src/main/java/com/ippon/borestop/service/UsernameAlreadyUsedException.java deleted file mode 100644 index cdcbeb94..00000000 --- a/borestop/src/main/java/com/ippon/borestop/service/UsernameAlreadyUsedException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ippon.borestop.service; - -import com.ippon.borestop.common.infrastructure.Generated; - -@Generated -public class UsernameAlreadyUsedException extends RuntimeException { - private static final long serialVersionUID = 1L; - - public UsernameAlreadyUsedException() { - super("Login name already used!"); - } -} diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/AccountResource.java b/borestop/src/main/java/com/ippon/borestop/web/rest/AccountResource.java index e3323fe1..56cf229c 100644 --- a/borestop/src/main/java/com/ippon/borestop/web/rest/AccountResource.java +++ b/borestop/src/main/java/com/ippon/borestop/web/rest/AccountResource.java @@ -4,21 +4,29 @@ import com.ippon.borestop.common.infrastructure.Generated; import com.ippon.borestop.domain.User; import com.ippon.borestop.repository.UserRepository; import com.ippon.borestop.security.SecurityUtils; +import com.ippon.borestop.service.EmailAlreadyUsedException; +import com.ippon.borestop.service.InvalidPasswordException; +import com.ippon.borestop.service.LoginAlreadyUsedException; import com.ippon.borestop.service.MailService; import com.ippon.borestop.service.UserService; import com.ippon.borestop.service.dto.PasswordChangeDTO; import com.ippon.borestop.service.dto.UserDTO; -import com.ippon.borestop.web.rest.errors.*; import com.ippon.borestop.web.rest.vm.KeyAndPasswordVM; import com.ippon.borestop.web.rest.vm.ManagedUserVM; -import java.util.*; +import java.util.Optional; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; /** * REST controller for managing the current user's account. diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/UserResource.java b/borestop/src/main/java/com/ippon/borestop/web/rest/UserResource.java index 6a608dfe..5f053f1b 100644 --- a/borestop/src/main/java/com/ippon/borestop/web/rest/UserResource.java +++ b/borestop/src/main/java/com/ippon/borestop/web/rest/UserResource.java @@ -1,22 +1,25 @@ package com.ippon.borestop.web.rest; +import com.ippon.borestop.common.domain.error.BorestopException; +import com.ippon.borestop.common.domain.error.ErrorStatus; +import com.ippon.borestop.common.domain.error.StandardMessage; import com.ippon.borestop.common.infrastructure.Generated; import com.ippon.borestop.config.Constants; import com.ippon.borestop.domain.User; import com.ippon.borestop.repository.UserRepository; import com.ippon.borestop.security.AuthoritiesConstants; +import com.ippon.borestop.service.EmailAlreadyUsedException; +import com.ippon.borestop.service.LoginAlreadyUsedException; import com.ippon.borestop.service.MailService; import com.ippon.borestop.service.UserService; import com.ippon.borestop.service.dto.UserDTO; -import com.ippon.borestop.web.rest.errors.BadRequestAlertException; -import com.ippon.borestop.web.rest.errors.EmailAlreadyUsedException; -import com.ippon.borestop.web.rest.errors.LoginAlreadyUsedException; import io.github.jhipster.web.util.HeaderUtil; import io.github.jhipster.web.util.PaginationUtil; import io.github.jhipster.web.util.ResponseUtil; import java.net.URI; import java.net.URISyntaxException; -import java.util.*; +import java.util.List; +import java.util.Optional; import javax.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,7 +30,14 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; /** @@ -35,21 +45,20 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; * <p> * This class accesses the {@link User} entity, and needs to fetch its collection of authorities. * <p> - * For a normal use-case, it would be better to have an eager relationship between User and Authority, - * and send everything to the client side: there would be no View Model and DTO, a lot less code, and an outer-join - * which would be good for performance. + * For a normal use-case, it would be better to have an eager relationship between User and Authority, and send + * everything to the client side: there would be no View Model and DTO, a lot less code, and an outer-join which would + * be good for performance. * <p> * We use a View Model and a DTO for 3 reasons: * <ul> - * <li>We want to keep a lazy association between the user and the authorities, because people will - * quite often do relationships with the user, and we don't want them to get the authorities all - * the time for nothing (for performance reasons). This is the #1 goal: we should not impact our users' - * application because of this use-case.</li> - * <li> Not having an outer join causes n+1 requests to the database. This is not a real issue as - * we have by default a second-level cache. This means on the first HTTP call we do the n+1 requests, - * but then all authorities come from the cache, so in fact it's much better than doing an outer join - * (which will get lots of data from the database, for each HTTP call).</li> - * <li> As this manages users, for security reasons, we'd rather have a DTO layer.</li> + * <li>We want to keep a lazy association between the user and the authorities, because people will quite often do + * relationships with the user, and we don't want them to get the authorities all the time for nothing (for performance + * reasons). This is the #1 goal: we should not impact our users' application because of this use-case.</li> + * <li>Not having an outer join causes n+1 requests to the database. This is not a real issue as we have by default a + * second-level cache. This means on the first HTTP call we do the n+1 requests, but then all authorities come from the + * cache, so in fact it's much better than doing an outer join (which will get lots of data from the database, for each + * HTTP call).</li> + * <li>As this manages users, for security reasons, we'd rather have a DTO layer.</li> * </ul> * <p> * Another option would be to have a specific JPA entity graph to handle this case. @@ -76,16 +85,19 @@ public class UserResource { } /** - * {@code POST /users} : Creates a new user. + * {@code POST /users} : Creates a new user. * <p> - * Creates a new user if the login and email are not already used, and sends an - * mail with an activation link. - * The user needs to be activated on creation. + * Creates a new user if the login and email are not already used, and sends an mail with an activation link. The user + * needs to be activated on creation. * - * @param userDTO the user to create. - * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new user, or with status {@code 400 (Bad Request)} if the login or email is already in use. - * @throws URISyntaxException if the Location URI syntax is incorrect. - * @throws BadRequestAlertException {@code 400 (Bad Request)} if the login or email is already in use. + * @param userDTO + * the user to create. + * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new user, or with status + * {@code 400 (Bad Request)} if the login or email is already in use. + * @throws URISyntaxException + * if the Location URI syntax is incorrect. + * @throws BadRequestAlertException + * {@code 400 (Bad Request)} if the login or email is already in use. */ @PostMapping("/users") @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") @@ -93,8 +105,11 @@ public class UserResource { log.debug("REST request to save User : {}", userDTO); if (userDTO.getId() != null) { - throw new BadRequestAlertException("A new user cannot already have an ID", "userManagement", "idexists"); - // Lowercase the user login before comparing with database + throw BorestopException + .builder(StandardMessage.INTERNAL_SERVER_ERROR) + .message("Can't create a user with a setted login") + .status(ErrorStatus.BAD_REQUEST) + .build(); // Lowercase the user login before comparing with database } else if (userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).isPresent()) { throw new LoginAlreadyUsedException(); } else if (userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()).isPresent()) { @@ -112,10 +127,13 @@ public class UserResource { /** * {@code PUT /users} : Updates an existing User. * - * @param userDTO the user to update. + * @param userDTO + * the user to update. * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated user. - * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already in use. - * @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already in use. + * @throws EmailAlreadyUsedException + * {@code 400 (Bad Request)} if the email is already in use. + * @throws LoginAlreadyUsedException + * {@code 400 (Bad Request)} if the login is already in use. */ @PutMapping("/users") @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") @@ -137,7 +155,8 @@ public class UserResource { /** * {@code GET /users} : get all users. * - * @param pageable the pagination information. + * @param pageable + * the pagination information. * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users. */ @GetMapping("/users") @@ -149,6 +168,7 @@ public class UserResource { /** * Gets a list of all roles. + * * @return a string list of all roles. */ @GetMapping("/users/authorities") @@ -160,8 +180,10 @@ public class UserResource { /** * {@code GET /users/:login} : get the "login" user. * - * @param login the login of the user to find. - * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the "login" user, or with status {@code 404 (Not Found)}. + * @param login + * the login of the user to find. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the "login" user, or with status + * {@code 404 (Not Found)}. */ @GetMapping("/users/{login:" + Constants.LOGIN_REGEX + "}") public ResponseEntity<UserDTO> getUser(@PathVariable String login) { @@ -172,7 +194,8 @@ public class UserResource { /** * {@code DELETE /users/:login} : delete the "login" User. * - * @param login the login of the user to delete. + * @param login + * the login of the user to delete. * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}. */ @DeleteMapping("/users/{login:" + Constants.LOGIN_REGEX + "}") diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/BadRequestAlertException.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/BadRequestAlertException.java deleted file mode 100644 index 4c83f779..00000000 --- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/BadRequestAlertException.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.ippon.borestop.web.rest.errors; - -import com.ippon.borestop.common.infrastructure.Generated; -import java.net.URI; -import java.util.HashMap; -import java.util.Map; -import org.zalando.problem.AbstractThrowableProblem; -import org.zalando.problem.Status; - -@Generated -public class BadRequestAlertException extends AbstractThrowableProblem { - private static final long serialVersionUID = 1L; - - private final String entityName; - - private final String errorKey; - - public BadRequestAlertException(String defaultMessage, String entityName, String errorKey) { - this(ErrorConstants.DEFAULT_TYPE, defaultMessage, entityName, errorKey); - } - - public BadRequestAlertException(URI type, String defaultMessage, String entityName, String errorKey) { - super(type, defaultMessage, Status.BAD_REQUEST, null, null, null, getAlertParameters(entityName, errorKey)); - this.entityName = entityName; - this.errorKey = errorKey; - } - - public String getEntityName() { - return entityName; - } - - public String getErrorKey() { - return errorKey; - } - - private static Map<String, Object> getAlertParameters(String entityName, String errorKey) { - Map<String, Object> parameters = new HashMap<>(); - parameters.put("message", "error." + errorKey); - parameters.put("params", entityName); - return parameters; - } -} diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/EmailAlreadyUsedException.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/EmailAlreadyUsedException.java deleted file mode 100644 index 56c8495b..00000000 --- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/EmailAlreadyUsedException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ippon.borestop.web.rest.errors; - -import com.ippon.borestop.common.infrastructure.Generated; - -@Generated -public class EmailAlreadyUsedException extends BadRequestAlertException { - private static final long serialVersionUID = 1L; - - public EmailAlreadyUsedException() { - super(ErrorConstants.EMAIL_ALREADY_USED_TYPE, "Email is already in use!", "userManagement", "emailexists"); - } -} diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/ErrorConstants.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/ErrorConstants.java deleted file mode 100644 index 581e9c8a..00000000 --- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/ErrorConstants.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.ippon.borestop.web.rest.errors; - -import com.ippon.borestop.common.infrastructure.Generated; -import java.net.URI; - -@Generated -public final class ErrorConstants { - public static final String ERR_CONCURRENCY_FAILURE = "error.concurrencyFailure"; - public static final String ERR_VALIDATION = "error.validation"; - public static final String PROBLEM_BASE_URL = "https://www.jhipster.tech/problem"; - public static final URI DEFAULT_TYPE = URI.create(PROBLEM_BASE_URL + "/problem-with-message"); - public static final URI CONSTRAINT_VIOLATION_TYPE = URI.create(PROBLEM_BASE_URL + "/constraint-violation"); - public static final URI INVALID_PASSWORD_TYPE = URI.create(PROBLEM_BASE_URL + "/invalid-password"); - public static final URI EMAIL_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/email-already-used"); - public static final URI LOGIN_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/login-already-used"); - - private ErrorConstants() {} -} diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/ExceptionTranslator.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/ExceptionTranslator.java deleted file mode 100644 index f2b9a129..00000000 --- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/ExceptionTranslator.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.ippon.borestop.web.rest.errors; - -import com.ippon.borestop.common.infrastructure.Generated; -import io.github.jhipster.web.util.HeaderUtil; -import java.util.List; -import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.dao.ConcurrencyFailureException; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.NativeWebRequest; -import org.zalando.problem.DefaultProblem; -import org.zalando.problem.Problem; -import org.zalando.problem.ProblemBuilder; -import org.zalando.problem.Status; -import org.zalando.problem.spring.web.advice.ProblemHandling; -import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait; -import org.zalando.problem.violations.ConstraintViolationProblem; - -/** - * Controller advice to translate the server side exceptions to client-friendly json structures. - * The error response follows RFC7807 - Problem Details for HTTP APIs (https://tools.ietf.org/html/rfc7807). - */ -@Generated -@ControllerAdvice -public class ExceptionTranslator implements ProblemHandling, SecurityAdviceTrait { - private static final String FIELD_ERRORS_KEY = "fieldErrors"; - private static final String MESSAGE_KEY = "message"; - private static final String PATH_KEY = "path"; - private static final String VIOLATIONS_KEY = "violations"; - - @Value("${jhipster.clientApp.name}") - private String applicationName; - - /** - * Post-process the Problem payload to add the message key for the front-end if needed. - */ - @Override - public ResponseEntity<Problem> process(@Nullable ResponseEntity<Problem> entity, NativeWebRequest request) { - if (entity == null) { - return entity; - } - Problem problem = entity.getBody(); - if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) { - return entity; - } - ProblemBuilder builder = Problem - .builder() - .withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType()) - .withStatus(problem.getStatus()) - .withTitle(problem.getTitle()) - .with(PATH_KEY, request.getNativeRequest(HttpServletRequest.class).getRequestURI()); - - if (problem instanceof ConstraintViolationProblem) { - builder.with(VIOLATIONS_KEY, ((ConstraintViolationProblem) problem).getViolations()).with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION); - } else { - builder.withCause(((DefaultProblem) problem).getCause()).withDetail(problem.getDetail()).withInstance(problem.getInstance()); - problem.getParameters().forEach(builder::with); - if (!problem.getParameters().containsKey(MESSAGE_KEY) && problem.getStatus() != null) { - builder.with(MESSAGE_KEY, "error.http." + problem.getStatus().getStatusCode()); - } - } - return new ResponseEntity<>(builder.build(), entity.getHeaders(), entity.getStatusCode()); - } - - @Override - public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, @Nonnull NativeWebRequest request) { - BindingResult result = ex.getBindingResult(); - List<FieldErrorVM> fieldErrors = result - .getFieldErrors() - .stream() - .map(f -> new FieldErrorVM(f.getObjectName().replaceFirst("DTO$", ""), f.getField(), f.getCode())) - .collect(Collectors.toList()); - - Problem problem = Problem - .builder() - .withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE) - .withTitle("Method argument not valid") - .withStatus(defaultConstraintViolationStatus()) - .with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION) - .with(FIELD_ERRORS_KEY, fieldErrors) - .build(); - return create(ex, problem, request); - } - - @ExceptionHandler - public ResponseEntity<Problem> handleEmailAlreadyUsedException( - com.ippon.borestop.service.EmailAlreadyUsedException ex, - NativeWebRequest request - ) { - EmailAlreadyUsedException problem = new EmailAlreadyUsedException(); - return create( - problem, - request, - HeaderUtil.createFailureAlert(applicationName, true, problem.getEntityName(), problem.getErrorKey(), problem.getMessage()) - ); - } - - @ExceptionHandler - public ResponseEntity<Problem> handleUsernameAlreadyUsedException( - com.ippon.borestop.service.UsernameAlreadyUsedException ex, - NativeWebRequest request - ) { - LoginAlreadyUsedException problem = new LoginAlreadyUsedException(); - return create( - problem, - request, - HeaderUtil.createFailureAlert(applicationName, true, problem.getEntityName(), problem.getErrorKey(), problem.getMessage()) - ); - } - - @ExceptionHandler - public ResponseEntity<Problem> handleInvalidPasswordException( - com.ippon.borestop.service.InvalidPasswordException ex, - NativeWebRequest request - ) { - return create(new InvalidPasswordException(), request); - } - - @ExceptionHandler - public ResponseEntity<Problem> handleBadRequestAlertException(BadRequestAlertException ex, NativeWebRequest request) { - return create(ex, request, HeaderUtil.createFailureAlert(applicationName, true, ex.getEntityName(), ex.getErrorKey(), ex.getMessage())); - } - - @ExceptionHandler - public ResponseEntity<Problem> handleConcurrencyFailure(ConcurrencyFailureException ex, NativeWebRequest request) { - Problem problem = Problem.builder().withStatus(Status.CONFLICT).with(MESSAGE_KEY, ErrorConstants.ERR_CONCURRENCY_FAILURE).build(); - return create(ex, problem, request); - } -} diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/FieldErrorVM.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/FieldErrorVM.java deleted file mode 100644 index 5628a99c..00000000 --- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/FieldErrorVM.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.ippon.borestop.web.rest.errors; - -import com.ippon.borestop.common.infrastructure.Generated; -import java.io.Serializable; - -@Generated -public class FieldErrorVM implements Serializable { - private static final long serialVersionUID = 1L; - - private final String objectName; - - private final String field; - - private final String message; - - public FieldErrorVM(String dto, String field, String message) { - this.objectName = dto; - this.field = field; - this.message = message; - } - - public String getObjectName() { - return objectName; - } - - public String getField() { - return field; - } - - public String getMessage() { - return message; - } -} diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/InvalidPasswordException.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/InvalidPasswordException.java deleted file mode 100644 index 17955eef..00000000 --- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/InvalidPasswordException.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ippon.borestop.web.rest.errors; - -import com.ippon.borestop.common.infrastructure.Generated; -import org.zalando.problem.AbstractThrowableProblem; -import org.zalando.problem.Status; - -@Generated -public class InvalidPasswordException extends AbstractThrowableProblem { - private static final long serialVersionUID = 1L; - - public InvalidPasswordException() { - super(ErrorConstants.INVALID_PASSWORD_TYPE, "Incorrect password", Status.BAD_REQUEST); - } -} diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/LoginAlreadyUsedException.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/LoginAlreadyUsedException.java deleted file mode 100644 index 72896e27..00000000 --- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/LoginAlreadyUsedException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ippon.borestop.web.rest.errors; - -import com.ippon.borestop.common.infrastructure.Generated; - -@Generated -public class LoginAlreadyUsedException extends BadRequestAlertException { - private static final long serialVersionUID = 1L; - - public LoginAlreadyUsedException() { - super(ErrorConstants.LOGIN_ALREADY_USED_TYPE, "Login name already used!", "userManagement", "userexists"); - } -} diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/package-info.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/package-info.java deleted file mode 100644 index a5553029..00000000 --- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Specific errors used with Zalando's "problem-spring-web" library. - * - * More information on https://github.com/zalando/problem-spring-web - */ -package com.ippon.borestop.web.rest.errors; diff --git a/borestop/src/main/resources/i18n/messages.properties b/borestop/src/main/resources/i18n/messages.properties index ea138657..a1ffe275 100644 --- a/borestop/src/main/resources/i18n/messages.properties +++ b/borestop/src/main/resources/i18n/messages.properties @@ -1,21 +1,42 @@ # Error page -error.title=Your request cannot be processed -error.subtitle=Sorry, an error has occurred. -error.status=Status: -error.message=Message: +error.title=Votre demande ne peut être traitée +error.subtitle=Désolé, une erreur s'est produite. +error.status=Statut : +error.message=Message : # Activation email -email.activation.title=borestop account activation is required -email.activation.greeting=Dear {0} -email.activation.text1=Your borestop account has been created, please click on the URL below to activate it: -email.activation.text2=Regards, -email.signature=borestop Team. +email.activation.title=Activation de votre compte borestop +email.activation.greeting=Cher {0} +email.activation.text1=Votre compte borestop a été créé, pour l'activer merci de cliquer sur le lien ci-dessous : +email.activation.text2=Cordialement, +email.signature=borestop. # Creation email -email.creation.text1=Your borestop account has been created, please click on the URL below to access it: +email.creation.text1=Votre compte borestop a été créé, merci de cliquer sur le lien ci-dessous pour y accéder : # Reset email -email.reset.title=borestop password reset -email.reset.greeting=Dear {0} -email.reset.text1=For your borestop account a password reset was requested, please click on the URL below to reset it: -email.reset.text2=Regards, +email.reset.title=borestop Réinitialisation de mot de passe +email.reset.greeting=Cher {0} +email.reset.text1=Un nouveau mot de passe pour votre compte borestop a été demandé, veuillez cliquer sur le lien ci-dessous pour le réinitialiser : +email.reset.text2=Cordialement, + +################################################################ +######################## Error messages ######################## +################################################################ +borestop.error.status-exception=Une erreur {{ status }} est survenue lors du traitement de votre requête. + +borestop.error.user.bad-request=Les données que vous avez saisies sont incorrectes. +borestop.error.user.mandatory=Le champ est obligatoire. +borestop.error.user.wrong-format=Le format n'est pas correct, il doit respecter "{{ regexp }}". +borestop.error.user.too-low=La valeur que vous avez entrée est trop petite, le minimum autorisé est {{ value }}. +borestop.error.user.too-high=La valeur que vous avez entrée est trop grande, le maximum autorisé est {{ value }}. +borestop.error.user.access-denied=Vous n'avez pas les droits suffisants pour acceder à cette ressource. +borestop.error.user.authentication-not-authenticated=Vous devez être authentifié pour acceder à cette ressource. +borestop.error.user.e-mail-already-used=Cette adresse email est déjà utilisée dans borestop. +borestop.error.user.login-already-used=Ce login est déjà utilsié dans borestop. +borestop.error.user.invalid-password=Ce mot de passe n'est pas valide. + +borestop.error.server.internal-server-error=Une erreur est survenue, notre équipe travaille à sa résolution ! +borestop.error.server.mandatory-null=Une erreur est survenue, un champ obligatoire ({{ field }}) est null dans notre système. Notre équipe travaille à la résolution de ce problème ! +borestop.error.server.mandatory-empty=Une erreur est survenue, un champ obligatoire ({{ field }}) est vide dans notre système. Notre équipe travaille à la résolution de ce problème ! +borestop.error.server.mandatory-blank=Une erreur est survenue, un champ obligatoire ({{ field }}) est vide dans notre système. Notre équipe travaille à la résolution de ce problème ! diff --git a/borestop/src/main/resources/i18n/messages_en.properties b/borestop/src/main/resources/i18n/messages_en.properties index 73f69f64..9061824f 100644 --- a/borestop/src/main/resources/i18n/messages_en.properties +++ b/borestop/src/main/resources/i18n/messages_en.properties @@ -5,7 +5,7 @@ error.status=Status: error.message=Message: # Activation email -email.activation.title=borestop account activation +email.activation.title=borestop account activation is required email.activation.greeting=Dear {0} email.activation.text1=Your borestop account has been created, please click on the URL below to activate it: email.activation.text2=Regards, @@ -19,3 +19,24 @@ email.reset.title=borestop password reset email.reset.greeting=Dear {0} email.reset.text1=For your borestop account a password reset was requested, please click on the URL below to reset it: email.reset.text2=Regards, + +################################################################ +######################## Error messages ######################## +################################################################ +borestop.error.status-exception=An error {{ status }} occured while handling your request. + +borestop.error.user.bad-request=The values you entered are incorrects. +borestop.error.user.mandatory=The field is mandatory. +borestop.error.user.wrong-format=The format is incorrect, it has to match "{{ regexp }}". +borestop.error.user.too-low=The value you entered is too low, minimum autorized is {{ value }}. +borestop.error.user.too-high=The value you entered is too high, maximum authorized is {{ value }}. +borestop.error.user.access-denied=You don't have sufficient rights to access this resource. +borestop.error.user.authentication-not-authenticated=You must be authenticated to access this resource. +borestop.error.user.e-mail-already-used=This email address is already used in the borestop. +borestop.error.user.login-already-used=This login is already used in borestop. +borestop.error.user.invalid-password=This password is not valid. + +borestop.error.server.internal-server-error=An error occurs, our team is working to fix it! +borestop.error.server.mandatory-null=An error occurs, a mandatory field ({{ field }}) was null in our system. Our team is working to fix it! +borestop.error.server.mandatory-empty=An error occurs, a mandatory field ({{ field }}) was empty in our system. Our team is working to fix it! +borestop.error.server.mandatory-blank=An error occurs, a mandatory field ({{ field }}) was blank in our system. Our team is working to fix it! diff --git a/borestop/src/test/java/com/ippon/borestop/common/BorestopIntTest.java b/borestop/src/test/java/com/ippon/borestop/common/BorestopIntTest.java new file mode 100644 index 00000000..48faae98 --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/BorestopIntTest.java @@ -0,0 +1,20 @@ +package com.ippon.borestop.common; + +import com.ippon.borestop.BorestopApp; +import io.github.jhipster.config.JHipsterConstants; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.transaction.Transactional; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.ActiveProfiles; + +@Transactional +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@ActiveProfiles(JHipsterConstants.SPRING_PROFILE_TEST) +@SpringBootTest(classes = BorestopApp.class, webEnvironment = WebEnvironment.RANDOM_PORT) +public @interface BorestopIntTest { +} diff --git a/borestop/src/test/java/com/ippon/borestop/common/LogSpy.java b/borestop/src/test/java/com/ippon/borestop/common/LogSpy.java new file mode 100644 index 00000000..8b0036f0 --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/LogSpy.java @@ -0,0 +1,62 @@ +package com.ippon.borestop.common; + +import static org.assertj.core.api.Assertions.*; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import java.util.function.Predicate; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.slf4j.LoggerFactory; + +public final class LogSpy implements BeforeEachCallback, AfterEachCallback, ParameterResolver { + private Logger logger; + private ListAppender<ILoggingEvent> appender; + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + appender = new ListAppender<>(); + logger = (Logger) LoggerFactory.getLogger("com.ippon.borestop"); + logger.addAppender(appender); + logger.setLevel(Level.TRACE); + appender.start(); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + logger.detachAppender(appender); + } + + public void assertLogged(Level level, String content) { + assertThat(appender.list.stream().anyMatch(withLog(level, content))).isTrue(); + } + + public void assertLogged(Level level, String content, int count) { + assertThat(appender.list.stream().filter(withLog(level, content)).count()).isEqualTo(count); + } + + public void assertNotLogged(Level level, String content) { + assertThat(appender.list.stream().noneMatch(withLog(level, content))).isTrue(); + } + + private Predicate<ILoggingEvent> withLog(Level level, String content) { + return event -> level.equals(event.getLevel()) && event.toString().contains(content); + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return parameterContext.getParameter().getType().equals(LogSpy.class); + } + + @Override + public LogSpy resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return this; + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/common/domain/error/AssertUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/domain/error/AssertUnitTest.java new file mode 100644 index 00000000..c7e8e97a --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/domain/error/AssertUnitTest.java @@ -0,0 +1,45 @@ +package com.ippon.borestop.common.domain.error; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class AssertUnitTest { + + @Test + void shouldNotValidateNullInputs() { + assertThatThrownBy(() -> Assert.notNull("field", null)) + .isExactlyInstanceOf(MissingMandatoryValueException.class) + .hasMessageContaining("\"field\""); + } + + @Test + void shouldNotValidateNullString() { + assertThatThrownBy(() -> Assert.notBlank("field", null)) + .isExactlyInstanceOf(MissingMandatoryValueException.class) + .hasMessageContaining("\"field\"") + .hasMessageContaining("(null)"); + } + + @Test + void shouldNotValidateEmptyString() { + assertNotBlankString(""); + } + + @Test + void shouldNotValidateSpaceString() { + assertNotBlankString(" "); + } + + @Test + void shouldNotValidateTabString() { + assertNotBlankString(" "); + } + + private void assertNotBlankString(String input) { + assertThatThrownBy(() -> Assert.notBlank("field", input)) + .isExactlyInstanceOf(MissingMandatoryValueException.class) + .hasMessageContaining("\"field\"") + .hasMessageContaining("(blank)"); + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/common/domain/error/BorestopExceptionUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/domain/error/BorestopExceptionUnitTest.java new file mode 100644 index 00000000..a031eeda --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/domain/error/BorestopExceptionUnitTest.java @@ -0,0 +1,58 @@ +package com.ippon.borestop.common.domain.error; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.entry; + +import com.ippon.borestop.common.domain.error.BorestopException.BorestopExceptionBuilder; +import org.junit.jupiter.api.Test; + +class BorestopExceptionUnitTest { + + @Test + public void shouldGetUnmodifiableArguments() { + assertThatThrownBy(() -> fullBuilder().build().getArguments().clear()).isExactlyInstanceOf(UnsupportedOperationException.class); + } + + @Test + public void shouldBuildWithoutBuilder() { + BorestopException exception = new BorestopException(null); + + assertThat(exception.getCause()).isNull(); + assertThat(exception.getMessage()).isNull(); + assertThat(exception.getStatus()).isNull(); + assertThat(exception.getArguments()).isNull(); + assertThat(exception.getBorestopMessage()).isNull(); + } + + @Test + public void shouldGetExceptionInformation() { + BorestopException exception = fullBuilder().build(); + + assertThat(exception.getArguments()).hasSize(2).contains(entry("key", "value"), entry("other", "test")); + assertThat(exception.getStatus()).isEqualTo(ErrorStatus.BAD_REQUEST); + assertThat(exception.getMessage()).isEqualTo("Error message"); + assertThat(exception.getBorestopMessage()).isEqualTo(StandardMessage.USER_MANDATORY); + assertThat(exception.getCause()).isExactlyInstanceOf(RuntimeException.class); + } + + @Test + void shouldMapNullArgumentAsNullString() { + assertThat(fullBuilder().argument("nullable", null).build().getArguments().get("nullable")).isEqualTo("null"); + } + + @Test + void shouldGetObjectsToString() { + assertThat(fullBuilder().argument("object", 4).build().getArguments().get("object")).isEqualTo("4"); + } + + private BorestopExceptionBuilder fullBuilder() { + return BorestopException + .builder(StandardMessage.USER_MANDATORY) + .argument("key", "value") + .argument("other", "test") + .status(ErrorStatus.BAD_REQUEST) + .message("Error message") + .cause(new RuntimeException()); + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacerUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacerUnitTest.java new file mode 100644 index 00000000..a6b4a9ee --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacerUnitTest.java @@ -0,0 +1,25 @@ +package com.ippon.borestop.common.infrastructure.primary; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ArgumentsReplacerUnitTest { + + @Test + public void shouldNotReplaceArgumentsInNullMessage() { + assertThat(ArgumentsReplacer.replaceArguments(null, Map.of("key", "value"))).isNull(); + } + + @Test + public void shouldNotReplaceArgumentsWithoutArguments() { + assertThat(ArgumentsReplacer.replaceArguments("Hey {{ user }}", null)).isEqualTo("Hey {{ user }}"); + } + + @Test + public void shouldReplaceKnownArguments() { + assertThat(ArgumentsReplacer.replaceArguments("Hey {{ user }}, how's {{ friend }} doing? Say {{user}}", Map.of("user", "Joe"))) + .isEqualTo("Hey Joe, how's {{ friend }} doing? Say Joe"); + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/infrastructure/primay/AuthenticationSteps.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationSteps.java similarity index 96% rename from borestop/src/test/java/com/ippon/borestop/infrastructure/primay/AuthenticationSteps.java rename to borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationSteps.java index cce5c375..6602959f 100644 --- a/borestop/src/test/java/com/ippon/borestop/infrastructure/primay/AuthenticationSteps.java +++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationSteps.java @@ -1,4 +1,4 @@ -package com.ippon.borestop.infrastructure.primay; +package com.ippon.borestop.common.infrastructure.primary; import static org.assertj.core.api.Assertions.assertThat; diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerIntTest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerIntTest.java new file mode 100644 index 00000000..4a3578e3 --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerIntTest.java @@ -0,0 +1,210 @@ +package com.ippon.borestop.common.infrastructure.primary; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.ippon.borestop.common.BorestopIntTest; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.util.Locale; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +@BorestopIntTest +class BorestopErrorHandlerIntTest { + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + private static final String FRANCE_TAG = Locale.FRANCE.toLanguageTag(); + + @Autowired + private BorestopErrorHandler exceptionTranslator; + + @Autowired + private ErrorResource resource; + + private MockMvc mockMvc; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders.standaloneSetup(resource).setControllerAdvice(exceptionTranslator).build(); + } + + @Test + public void shouldMapBorestopExceptions() throws Exception { + String response = mockMvc + .perform(post(errorEndpoint("borestop-exceptions")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG)) + .andExpect(status().isInternalServerError()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + + assertThat(response).contains("Une erreur est survenue, notre équipe travaille à sa résolution !"); + } + + @Test + void shouldMapResponseStatusWithMessageExceptions() throws UnsupportedEncodingException, Exception { + String response = mockMvc + .perform(post(errorEndpoint("responsestatus-with-message-exceptions")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG)) + .andExpect(status().isNotFound()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + + assertThat(response).contains("oops"); + } + + @Test + void shouldMapResponseStatusWithoutMessageExceptions() throws UnsupportedEncodingException, Exception { + String response = mockMvc + .perform(post(errorEndpoint("responsestatus-without-message-exceptions")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG)) + .andExpect(status().isNotFound()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + + assertThat(response).contains("est survenue").contains("404"); + } + + @Test + public void shouldGetMessagesInOtherLanguage() throws Exception { + String response = mockMvc + .perform(post(errorEndpoint("borestop-exceptions")).header(HttpHeaders.ACCEPT_LANGUAGE, Locale.UK.toLanguageTag())) + .andExpect(status().isInternalServerError()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + + assertThat(response).contains("An error occurs, our team is working to fix it!"); + } + + @Test + public void shouldGetMessagesInDefaultLanguage() throws Exception { + String response = mockMvc + .perform(post(errorEndpoint("borestop-exceptions")).header(HttpHeaders.ACCEPT_LANGUAGE, Locale.CHINESE.toLanguageTag())) + .andExpect(status().isInternalServerError()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + + assertThat(response).contains("Une erreur est survenue, notre équipe travaille à sa résolution !"); + } + + @Test + public void shouldMapParametersBeanValidationErrors() throws Exception { + String response = mockMvc + .perform(get(errorEndpoint("oops")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG)) + .andExpect(status().isBadRequest()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + + assertThat(response) + .contains("Les données que vous avez saisies sont incorrectes.") + .contains("Le format n'est pas correct, il doit respecter \\\"complicated\\\"."); + } + + @Test + public void shouldMapMinValueQueryStringBeanValidationErrors() throws Exception { + String response = mockMvc + .perform(get(errorEndpoint("?parameter=1")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG)) + .andExpect(status().isBadRequest()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + + assertThat(response) + .contains("Les données que vous avez saisies sont incorrectes.") + .contains("La valeur que vous avez entrée est trop petite, le minimum autorisé est 42."); + } + + @Test + public void shouldMapMaxValueQueryStringBeanValidationErrors() throws Exception { + String response = mockMvc + .perform(get(errorEndpoint("?parameter=100")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG)) + .andExpect(status().isBadRequest()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + + assertThat(response) + .contains("Les données que vous avez saisies sont incorrectes.") + .contains("La valeur que vous avez entrée est trop grande, le maximum autorisé est 42."); + } + + @Test + public void shouldMapBodyBeanValidationErrors() throws Exception { + String response = mockMvc + .perform( + post(errorEndpoint("oops")) + .header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG) + .contentType(MediaType.APPLICATION_JSON) + .content(TestJson.writeAsString(new ComplicatedRequest("value"))) + ) + .andExpect(status().isBadRequest()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + + assertThat(response) + .contains("Les données que vous avez saisies sont incorrectes.") + .contains("Le format n'est pas correct, il doit respecter \\\"complicated\\\"."); + } + + @Test + public void shouldMapTechnicalError() throws Exception { + String response = mockMvc + .perform( + post(errorEndpoint("not-deserializables")) + .header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG) + .contentType(MediaType.APPLICATION_JSON) + .content(TestJson.writeAsString(new NotDeserializable("value"))) + ) + .andExpect(status().isInternalServerError()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + + assertThat(response).contains("Une erreur est survenue, notre équipe travaille à sa résolution !"); + } + + @Test + public void shouldHandleAccessDeniedException() throws Exception { + String response = mockMvc + .perform(get(errorEndpoint("access-denied")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG)) + .andExpect(status().isForbidden()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + + assertThat(response).contains("Vous n'avez pas les droits suffisants"); + } + + @Test + public void shouldHandleRuntimeException() throws Exception { + String response = mockMvc + .perform(get(errorEndpoint("runtime-exceptions")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG)) + .andExpect(status().isInternalServerError()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + + assertThat(response).contains("Une erreur est survenue, notre équipe travaille à sa résolution !"); + } + + private URI errorEndpoint(String path) { + try { + return new URI("/errors/" + path); + } catch (URISyntaxException e) { + throw new AssertionError(); + } + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerUnitTest.java new file mode 100644 index 00000000..f116c7b8 --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerUnitTest.java @@ -0,0 +1,218 @@ +package com.ippon.borestop.common.infrastructure.primary; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import ch.qos.logback.classic.Level; +import com.ippon.borestop.common.LogSpy; +import com.ippon.borestop.common.domain.error.BorestopException; +import com.ippon.borestop.common.domain.error.ErrorStatus; +import com.ippon.borestop.common.domain.error.StandardMessage; +import java.nio.charset.Charset; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import org.junit.BeforeClass; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.context.MessageSource; +import org.springframework.context.NoSuchMessageException; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.client.RestClientResponseException; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MaxUploadSizeExceededException; + +@ExtendWith({ SpringExtension.class, LogSpy.class }) +class BorestopErrorHandlerUnitTest { + @Mock + private MessageSource messages; + + @InjectMocks + private BorestopErrorHandler handler; + + private final LogSpy logs; + + public BorestopErrorHandlerUnitTest(LogSpy logs) { + this.logs = logs; + } + + @BeforeClass + public void loadUserLocale() { + LocaleContextHolder.setLocale(Locale.FRANCE); + } + + @Test + public void shouldHandleAsServerErrorWithoutStatus() { + ResponseEntity<BorestopError> response = handler.handleBorestopException( + BorestopException.builder(StandardMessage.USER_MANDATORY).build() + ); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + } + + @Test + void shouldHandleAllErrorsAsUniqueHttpCode() { + Set<HttpStatus> statuses = new HashSet<>(); + + for (ErrorStatus status : ErrorStatus.values()) { + ResponseEntity<BorestopError> response = handler.handleBorestopException( + BorestopException.builder(StandardMessage.USER_MANDATORY).status(status).build() + ); + + statuses.add(response.getStatusCode()); + } + + assertThat(statuses.size()).isEqualTo(ErrorStatus.values().length); + } + + @Test + public void shouldGetDefaultBorestopMessageWithoutMessageForBadRequest() { + ResponseEntity<BorestopError> response = handler.handleBorestopException( + BorestopException.builder(null).status(ErrorStatus.BAD_REQUEST).build() + ); + + assertThat(response.getBody().getErrorType()).isEqualTo("user.bad-request"); + } + + @Test + public void shouldGetDefaultBorestopMessageWithoutMessageForServerError() { + ResponseEntity<BorestopError> response = handler.handleBorestopException(BorestopException.builder(null).build()); + + assertThat(response.getBody().getErrorType()).isEqualTo("server.internal-server-error"); + } + + @Test + public void shouldGetDefaultMessageForUnknownMessage() { + when(messages.getMessage("borestop.error.hey", null, Locale.FRANCE)).thenThrow(new NoSuchMessageException("hey")); + when(messages.getMessage("borestop.error.server.internal-server-error", null, Locale.FRANCE)).thenReturn("User message"); + ResponseEntity<BorestopError> response = handler.handleBorestopException(BorestopException.builder(() -> "hey").build()); + + BorestopError body = response.getBody(); + assertThat(body.getErrorType()).isEqualTo("hey"); + assertThat(body.getMessage()).isEqualTo("User message"); + } + + @Test + public void shouldHandleUserBorestopExceptionWithMessageWithoutArguments() { + RuntimeException cause = new RuntimeException(); + BorestopException exception = BorestopException + .builder(StandardMessage.USER_MANDATORY) + .cause(cause) + .status(ErrorStatus.BAD_REQUEST) + .message("Hum") + .build(); + + when(messages.getMessage("borestop.error.user.mandatory", null, Locale.FRANCE)).thenReturn("User message"); + ResponseEntity<BorestopError> response = handler.handleBorestopException(exception); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + + BorestopError body = response.getBody(); + assertThat(body.getErrorType()).isEqualTo("user.mandatory"); + assertThat(body.getMessage()).isEqualTo("User message"); + assertThat(body.getFieldsErrors()).isNull(); + } + + @Test + public void shouldReplaceArgumentsValueFromBorestopException() { + when(messages.getMessage("borestop.error.user.mandatory", null, Locale.FRANCE)) + .thenReturn("User {{ firstName }} {{ lastName}} message"); + + ResponseEntity<BorestopError> response = handler.handleBorestopException( + BorestopException.builder(StandardMessage.USER_MANDATORY).argument("firstName", "Joe").argument("lastName", "Dalton").build() + ); + + assertThat(response.getBody().getMessage()).isEqualTo("User Joe Dalton message"); + } + + @Test + public void shouldHandleAsBadRequestForExceededSizeFileUpload() { + when(messages.getMessage("borestop.error.server.upload-too-big", null, Locale.FRANCE)) + .thenReturn("The file is too big to be send to the server"); + + ResponseEntity<BorestopError> response = handler.handleFileSizeException(new MaxUploadSizeExceededException(1024 * 1024 * 30)); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + assertThat(response.getBody().getErrorType()).isEqualTo("server.upload-too-big"); + assertThat(response.getBody().getMessage()).isEqualTo("The file is too big to be send to the server"); + } + + @Test + public void shouldHandleMethodArgumentTypeMismatchException() { + MethodArgumentTypeMismatchException mismatchException = new MethodArgumentTypeMismatchException(null, null, null, null, null); + + ResponseEntity<BorestopError> response = handler.handleMethodArgumentTypeMismatchException(mismatchException); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + assertThat(response.getBody().getErrorType()).isEqualTo("user.bad-request"); + } + + @Test + public void shouldHandleMethodArgumentTypeMismatchExceptionWithBorestopError() { + RuntimeException cause = BorestopException.builder(StandardMessage.USER_MANDATORY).build(); + MethodArgumentTypeMismatchException mismatchException = new MethodArgumentTypeMismatchException(null, null, null, null, cause); + + ResponseEntity<BorestopError> response = handler.handleMethodArgumentTypeMismatchException(mismatchException); + + assertThat(response.getBody().getErrorType()).isEqualTo("user.mandatory"); + } + + @Test + public void shouldLogUserErrorAsWarn() { + handler.handleBorestopException( + BorestopException.builder(StandardMessage.BAD_REQUEST).status(ErrorStatus.BAD_REQUEST).message("error message").build() + ); + + logs.assertLogged(Level.WARN, "error message"); + } + + @Test + public void shouldLogAuthenticationExceptionAsDebug() { + handler.handleAuthenticationException(new InsufficientAuthenticationException("oops")); + + logs.assertLogged(Level.DEBUG, "oops"); + } + + @Test + public void shouldLogServerErrorAsError() { + handler.handleBorestopException( + BorestopException + .builder(StandardMessage.INTERNAL_SERVER_ERROR) + .status(ErrorStatus.INTERNAL_SERVER_ERROR) + .message("error message") + .build() + ); + + logs.assertLogged(Level.ERROR, "error message"); + } + + @Test + public void shouldLogErrorResponseBody() { + RestClientResponseException cause = new RestClientResponseException( + "error", + 400, + "status", + null, + "service error response".getBytes(), + Charset.defaultCharset() + ); + + handler.handleBorestopException( + BorestopException + .builder(StandardMessage.INTERNAL_SERVER_ERROR) + .status(ErrorStatus.INTERNAL_SERVER_ERROR) + .message("error message") + .cause(cause) + .build() + ); + + logs.assertLogged(Level.ERROR, "error message"); + logs.assertLogged(Level.ERROR, "service error response"); + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorUnitTest.java new file mode 100644 index 00000000..cb97f5e0 --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorUnitTest.java @@ -0,0 +1,39 @@ +package com.ippon.borestop.common.infrastructure.primary; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class BorestopErrorUnitTest { + + @Test + public void shouldGetErrorInformation() { + BorestopFieldError fieldError = BorestopFieldErrorUnitTest.defaultFieldError(); + BorestopError error = defaultError(fieldError); + + assertThat(error.getErrorType()).isEqualTo("type"); + assertThat(error.getMessage()).isEqualTo("message"); + assertThat(error.getFieldsErrors()).containsExactly(fieldError); + } + + @Test + public void shouldSerializeToJson() { + assertThat(TestJson.writeAsString(defaultError(BorestopFieldErrorUnitTest.defaultFieldError()))).isEqualTo(defaultJson()); + } + + @Test + void shouldDeserializeFromJson() { + assertThat(TestJson.readFromJson(defaultJson(), BorestopError.class)) + .usingRecursiveComparison() + .isEqualTo(defaultError(BorestopFieldErrorUnitTest.defaultFieldError())); + } + + private String defaultJson() { + return "{\"errorType\":\"type\",\"message\":\"message\",\"fieldsErrors\":[" + BorestopFieldErrorUnitTest.defaultJson() + "]}"; + } + + private BorestopError defaultError(BorestopFieldError fieldError) { + return new BorestopError("type", "message", List.of(fieldError)); + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldErrorUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldErrorUnitTest.java new file mode 100644 index 00000000..887f0535 --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldErrorUnitTest.java @@ -0,0 +1,30 @@ +package com.ippon.borestop.common.infrastructure.primary; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class BorestopFieldErrorUnitTest { + + @Test + public void shouldGetFieldErrorInformation() { + BorestopFieldError fieldError = defaultFieldError(); + + assertThat(fieldError.getFieldPath()).isEqualTo("path"); + assertThat(fieldError.getReason()).isEqualTo("reason"); + assertThat(fieldError.getMessage()).isEqualTo("message"); + } + + @Test + public void shouldSerializeToJson() { + assertThat(TestJson.writeAsString(defaultFieldError())).isEqualTo(defaultJson()); + } + + static BorestopFieldError defaultFieldError() { + return BorestopFieldError.builder().fieldPath("path").reason("reason").message("message").build(); + } + + static String defaultJson() { + return "{\"fieldPath\":\"path\",\"reason\":\"reason\",\"message\":\"message\"}"; + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ComplicatedRequest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ComplicatedRequest.java new file mode 100644 index 00000000..407584cd --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ComplicatedRequest.java @@ -0,0 +1,17 @@ +package com.ippon.borestop.common.infrastructure.primary; + +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.validation.constraints.Pattern; + +public class ComplicatedRequest { + private String value; + + public ComplicatedRequest(@JsonProperty("value") String value) { + this.value = value; + } + + @Pattern(message = ValidationMessage.WRONG_FORMAT, regexp = "complicated") + public String getValue() { + return value; + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/infrastructure/primay/CucumberTestContext.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/CucumberTestContext.java similarity index 98% rename from borestop/src/test/java/com/ippon/borestop/infrastructure/primay/CucumberTestContext.java rename to borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/CucumberTestContext.java index 0b015bc7..0da329ad 100644 --- a/borestop/src/test/java/com/ippon/borestop/infrastructure/primay/CucumberTestContext.java +++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/CucumberTestContext.java @@ -1,4 +1,4 @@ -package com.ippon.borestop.infrastructure.primay; +package com.ippon.borestop.common.infrastructure.primary; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.JsonPath; diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorMessagesUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorMessagesUnitTest.java new file mode 100644 index 00000000..b87a690b --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorMessagesUnitTest.java @@ -0,0 +1,83 @@ +package com.ippon.borestop.common.infrastructure.primary; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ippon.borestop.common.domain.error.BorestopMessage; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; +import org.reflections.Reflections; +import org.reflections.scanners.SubTypesScanner; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.reflections.util.FilterBuilder; + +public class ErrorMessagesUnitTest { + private static final Set<Class<? extends BorestopMessage>> errors = new Reflections( + new ConfigurationBuilder() + .setUrls(ClasspathHelper.forPackage("com.ippon")) + .setScanners(new SubTypesScanner()) + .filterInputsBy(new FilterBuilder().includePackage("com.ippon")) + ) + .getSubTypesOf(BorestopMessage.class); + + @Test + public void shouldHaveOnlyEnumImplementations() { + errors.forEach( + error -> + assertThat(error.isEnum() || error.isInterface()) + .as("Implementations of " + BorestopMessage.class.getName() + " must be enums and " + error.getName() + " wasn't") + .isTrue() + ); + } + + @Test + public void shouldHaveMessagesForAllKeys() { + Collection<Properties> messages = loadMessages(); + + errors + .stream() + .filter(error -> error.isEnum()) + .forEach(error -> Arrays.stream(error.getEnumConstants()).forEach(value -> messages.forEach(assertMessageExist(value)))); + } + + private List<Properties> loadMessages() { + try { + return Files.list(Paths.get("src/main/resources/i18n")).map(toProperties()).collect(Collectors.toList()); + } catch (IOException e) { + throw new AssertionError(); + } + } + + private Function<Path, Properties> toProperties() { + return file -> { + Properties properties = new Properties(); + try (InputStream input = Files.newInputStream(file)) { + properties.load(input); + } catch (IOException e) { + throw new AssertionError(); + } + + return properties; + }; + } + + private Consumer<Properties> assertMessageExist(BorestopMessage value) { + return currentMessages -> { + assertThat(currentMessages.getProperty("borestop.error." + value.getMessageKey())) + .as("Can't find message for " + value.getMessageKey() + " in all files, check your configuration") + .isNotBlank(); + }; + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorResource.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorResource.java new file mode 100644 index 00000000..c88e3860 --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorResource.java @@ -0,0 +1,77 @@ +package com.ippon.borestop.common.infrastructure.primary; + +import com.ippon.borestop.common.domain.error.BorestopException; +import com.ippon.borestop.common.domain.error.ErrorStatus; +import com.ippon.borestop.common.domain.error.StandardMessage; +import javax.validation.constraints.Pattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +/** + * Resource to expose errors endpoints + */ +@Validated +@RestController +@RequestMapping("/errors") +class ErrorResource { + private static final Logger logger = LoggerFactory.getLogger(ErrorResource.class); + + @GetMapping("/runtime-exceptions") + public void runtimeException() { + throw new RuntimeException(); + } + + @GetMapping("/access-denied") + public void accessDeniedException() { + throw new AccessDeniedException("You shall not pass!"); + } + + @PostMapping("/borestop-exceptions") + public void borestopException() { + throw BorestopException + .builder(StandardMessage.INTERNAL_SERVER_ERROR) + .cause(new RuntimeException()) + .status(ErrorStatus.INTERNAL_SERVER_ERROR) + .message("Oops") + .argument("key", "value") + .build(); + } + + @PostMapping("/responsestatus-with-message-exceptions") + public void responseStatusWithMessageException() { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "oops"); + } + + @PostMapping("/responsestatus-without-message-exceptions") + public void responseStatusWithoutMessageException() { + throw new ResponseStatusException(HttpStatus.NOT_FOUND); + } + + @GetMapping + public void queryStringWithRangedValue(@Validated QueryParameter parameter) {} + + @GetMapping("/{complicated}") + public void complicatedArg( + @Validated @Pattern(message = ValidationMessage.WRONG_FORMAT, regexp = "complicated") @PathVariable("complicated") String complicated + ) { + logger.info("Congratulations you got it right!"); + } + + @PostMapping("/oops") + public void complicatedBody(@Validated @RequestBody ComplicatedRequest request) { + logger.info("You got it right!"); + } + + @PostMapping("/not-deserializables") + public void notDeserializable(@RequestBody NotDeserializable request) {} +} diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/NotDeserializable.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/NotDeserializable.java new file mode 100644 index 00000000..a37e4d32 --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/NotDeserializable.java @@ -0,0 +1,13 @@ +package com.ippon.borestop.common.infrastructure.primary; + +public class NotDeserializable { + private String value; + + public NotDeserializable(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/QueryParameter.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/QueryParameter.java new file mode 100644 index 00000000..b9411022 --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/QueryParameter.java @@ -0,0 +1,18 @@ +package com.ippon.borestop.common.infrastructure.primary; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; + +class QueryParameter { + private int parameter; + + @Min(message = ValidationMessage.VALUE_TOO_LOW, value = 42) + @Max(message = ValidationMessage.VALUE_TOO_HIGH, value = 42) + public int getParameter() { + return parameter; + } + + public void setParameter(int parameter) { + this.parameter = parameter; + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/infrastructure/primay/TestJson.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/TestJson.java similarity index 96% rename from borestop/src/test/java/com/ippon/borestop/infrastructure/primay/TestJson.java rename to borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/TestJson.java index 99e86c5a..578d4527 100644 --- a/borestop/src/test/java/com/ippon/borestop/infrastructure/primay/TestJson.java +++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/TestJson.java @@ -1,4 +1,4 @@ -package com.ippon.borestop.infrastructure.primay; +package com.ippon.borestop.common.infrastructure.primary; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/borestop/src/test/java/com/ippon/borestop/cucumber/CucumberConfiguration.java b/borestop/src/test/java/com/ippon/borestop/cucumber/CucumberConfiguration.java index 093a7045..3a758d7c 100644 --- a/borestop/src/test/java/com/ippon/borestop/cucumber/CucumberConfiguration.java +++ b/borestop/src/test/java/com/ippon/borestop/cucumber/CucumberConfiguration.java @@ -1,8 +1,8 @@ package com.ippon.borestop.cucumber; import com.ippon.borestop.BorestopApp; +import com.ippon.borestop.common.infrastructure.primary.CucumberTestContext; import com.ippon.borestop.cucumber.CucumberConfiguration.CucumberSecurityContextConfiguration; -import com.ippon.borestop.infrastructure.primay.CucumberTestContext; import io.cucumber.java.Before; import io.cucumber.spring.CucumberContextConfiguration; import io.github.jhipster.config.JHipsterConstants; diff --git a/borestop/src/test/java/com/ippon/borestop/cucumber/HttpSteps.java b/borestop/src/test/java/com/ippon/borestop/cucumber/HttpSteps.java index 20dd152e..bd2c39b4 100644 --- a/borestop/src/test/java/com/ippon/borestop/cucumber/HttpSteps.java +++ b/borestop/src/test/java/com/ippon/borestop/cucumber/HttpSteps.java @@ -2,7 +2,7 @@ package com.ippon.borestop.cucumber; import static org.assertj.core.api.Assertions.assertThat; -import com.ippon.borestop.infrastructure.primay.CucumberTestContext; +import com.ippon.borestop.common.infrastructure.primary.CucumberTestContext; import io.cucumber.java.en.Then; import org.springframework.http.HttpStatus; diff --git a/borestop/src/test/java/com/ippon/borestop/service/MailServiceIT.java b/borestop/src/test/java/com/ippon/borestop/service/MailServiceIT.java index 8e9a8619..f48d5129 100644 --- a/borestop/src/test/java/com/ippon/borestop/service/MailServiceIT.java +++ b/borestop/src/test/java/com/ippon/borestop/service/MailServiceIT.java @@ -1,23 +1,17 @@ package com.ippon.borestop.service; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; import com.ippon.borestop.BorestopApp; import com.ippon.borestop.config.Constants; import com.ippon.borestop.domain.User; import io.github.jhipster.config.JHipsterProperties; import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStreamReader; -import java.net.URI; -import java.net.URL; -import java.nio.charset.Charset; -import java.util.Properties; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.mail.Multipart; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; @@ -40,13 +34,6 @@ import org.thymeleaf.spring5.SpringTemplateEngine; */ @SpringBootTest(classes = BorestopApp.class) public class MailServiceIT { - private static final String[] languages = { - "en" - // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array - }; - private static final Pattern PATTERN_LOCALE_3 = Pattern.compile("([a-z]{2})-([a-zA-Z]{4})-([a-z]{2})"); - private static final Pattern PATTERN_LOCALE_2 = Pattern.compile("([a-z]{2})-([a-z]{2})"); - @Autowired private JHipsterProperties jHipsterProperties; @@ -131,22 +118,6 @@ public class MailServiceIT { assertThat(part.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); } - @Test - public void testSendEmailFromTemplate() throws Exception { - User user = new User(); - user.setLogin("john"); - user.setEmail("john.doe@example.com"); - user.setLangKey("en"); - mailService.sendEmailFromTemplate(user, "mail/testEmail", "email.test.title"); - verify(javaMailSender).send(messageCaptor.capture()); - MimeMessage message = messageCaptor.getValue(); - assertThat(message.getSubject()).isEqualTo("test title"); - assertThat(message.getAllRecipients()[0].toString()).isEqualTo(user.getEmail()); - assertThat(message.getFrom()[0].toString()).isEqualTo(jHipsterProperties.getMail().getFrom()); - assertThat(message.getContent().toString()).isEqualToNormalizingNewlines("<html>test title, http://127.0.0.1:8080, john</html>\n"); - assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); - } - @Test public void testSendActivationEmail() throws Exception { User user = new User(); @@ -201,44 +172,4 @@ public class MailServiceIT { fail("Exception shouldn't have been thrown"); } } - - @Test - public void testSendLocalizedEmailForAllSupportedLanguages() throws Exception { - User user = new User(); - user.setLogin("john"); - user.setEmail("john.doe@example.com"); - for (String langKey : languages) { - user.setLangKey(langKey); - mailService.sendEmailFromTemplate(user, "mail/testEmail", "email.test.title"); - verify(javaMailSender, atLeastOnce()).send(messageCaptor.capture()); - MimeMessage message = messageCaptor.getValue(); - - String propertyFilePath = "i18n/messages_" + getJavaLocale(langKey) + ".properties"; - URL resource = this.getClass().getClassLoader().getResource(propertyFilePath); - File file = new File(new URI(resource.getFile()).getPath()); - Properties properties = new Properties(); - properties.load(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"))); - - String emailTitle = (String) properties.get("email.test.title"); - assertThat(message.getSubject()).isEqualTo(emailTitle); - assertThat(message.getContent().toString()) - .isEqualToNormalizingNewlines("<html>" + emailTitle + ", http://127.0.0.1:8080, john</html>\n"); - } - } - - /** - * Convert a lang key to the Java locale. - */ - private String getJavaLocale(String langKey) { - String javaLangKey = langKey; - Matcher matcher2 = PATTERN_LOCALE_2.matcher(langKey); - if (matcher2.matches()) { - javaLangKey = matcher2.group(1) + "_" + matcher2.group(2).toUpperCase(); - } - Matcher matcher3 = PATTERN_LOCALE_3.matcher(langKey); - if (matcher3.matches()) { - javaLangKey = matcher3.group(1) + "_" + matcher3.group(2) + "_" + matcher3.group(3).toUpperCase(); - } - return javaLangKey; - } } diff --git a/borestop/src/test/java/com/ippon/borestop/web/rest/AccountResourceIT.java b/borestop/src/test/java/com/ippon/borestop/web/rest/AccountResourceIT.java index 52726498..b90d5d2e 100644 --- a/borestop/src/test/java/com/ippon/borestop/web/rest/AccountResourceIT.java +++ b/borestop/src/test/java/com/ippon/borestop/web/rest/AccountResourceIT.java @@ -1,11 +1,14 @@ package com.ippon.borestop.web.rest; -import static com.ippon.borestop.web.rest.AccountResourceIT.TEST_USER_LOGIN; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.ippon.borestop.BorestopApp; +import com.ippon.borestop.common.infrastructure.primary.BorestopErrorHandler; import com.ippon.borestop.config.Constants; import com.ippon.borestop.domain.User; import com.ippon.borestop.repository.AuthorityRepository; @@ -17,8 +20,12 @@ import com.ippon.borestop.service.dto.UserDTO; import com.ippon.borestop.web.rest.vm.KeyAndPasswordVM; import com.ippon.borestop.web.rest.vm.ManagedUserVM; import java.time.Instant; -import java.util.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -27,13 +34,14 @@ import org.springframework.http.MediaType; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; /** * Integration tests for the {@link AccountResource} REST controller. */ @AutoConfigureMockMvc -@WithMockUser(value = TEST_USER_LOGIN) +@WithMockUser(value = AccountResourceIT.TEST_USER_LOGIN) @SpringBootTest(classes = BorestopApp.class) public class AccountResourceIT { static final String TEST_USER_LOGIN = "test"; @@ -44,6 +52,9 @@ public class AccountResourceIT { @Autowired private AuthorityRepository authorityRepository; + @Autowired + private BorestopErrorHandler exceptionTranslator; + @Autowired private UserService userService; @@ -51,20 +62,24 @@ public class AccountResourceIT { private PasswordEncoder passwordEncoder; @Autowired - private MockMvc restAccountMockMvc; + private AccountResource accountResource; + + private MockMvc mockMvc; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders.standaloneSetup(accountResource).setControllerAdvice(exceptionTranslator).build(); + } @Test @WithUnauthenticatedMockUser public void testNonAuthenticatedUser() throws Exception { - restAccountMockMvc - .perform(get("/api/authenticate").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().string("")); + mockMvc.perform(get("/api/authenticate").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andExpect(content().string("")); } @Test public void testAuthenticatedUser() throws Exception { - restAccountMockMvc + mockMvc .perform( get("/api/authenticate") .with( @@ -94,7 +109,7 @@ public class AccountResourceIT { user.setAuthorities(authorities); userService.createUser(user); - restAccountMockMvc + mockMvc .perform(get("/api/account").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -109,7 +124,7 @@ public class AccountResourceIT { @Test public void testGetUnknownAccount() throws Exception { - restAccountMockMvc.perform(get("/api/account").accept(MediaType.APPLICATION_PROBLEM_JSON)).andExpect(status().isInternalServerError()); + mockMvc.perform(get("/api/account").accept(MediaType.APPLICATION_PROBLEM_JSON)).andExpect(status().isInternalServerError()); } @Test @@ -126,7 +141,7 @@ public class AccountResourceIT { validUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); assertThat(userRepository.findOneByLogin("test-register-valid").isPresent()).isFalse(); - restAccountMockMvc + mockMvc .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(validUser))) .andExpect(status().isCreated()); @@ -147,7 +162,7 @@ public class AccountResourceIT { invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - restAccountMockMvc + mockMvc .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) .andExpect(status().isBadRequest()); @@ -169,7 +184,7 @@ public class AccountResourceIT { invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - restAccountMockMvc + mockMvc .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) .andExpect(status().isBadRequest()); @@ -191,7 +206,7 @@ public class AccountResourceIT { invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - restAccountMockMvc + mockMvc .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) .andExpect(status().isBadRequest()); @@ -213,7 +228,7 @@ public class AccountResourceIT { invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - restAccountMockMvc + mockMvc .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) .andExpect(status().isBadRequest()); @@ -251,12 +266,12 @@ public class AccountResourceIT { secondUser.setAuthorities(new HashSet<>(firstUser.getAuthorities())); // First user - restAccountMockMvc + mockMvc .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(firstUser))) .andExpect(status().isCreated()); // Second (non activated) user - restAccountMockMvc + mockMvc .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) .andExpect(status().isCreated()); @@ -266,7 +281,7 @@ public class AccountResourceIT { userRepository.save(testUser.get()); // Second (already activated) user - restAccountMockMvc + mockMvc .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) .andExpect(status().is4xxClientError()); } @@ -286,7 +301,7 @@ public class AccountResourceIT { firstUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); // Register first user - restAccountMockMvc + mockMvc .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(firstUser))) .andExpect(status().isCreated()); @@ -305,7 +320,7 @@ public class AccountResourceIT { secondUser.setAuthorities(new HashSet<>(firstUser.getAuthorities())); // Register second (non activated) user - restAccountMockMvc + mockMvc .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) .andExpect(status().isCreated()); @@ -328,7 +343,7 @@ public class AccountResourceIT { userWithUpperCaseEmail.setAuthorities(new HashSet<>(firstUser.getAuthorities())); // Register third (not activated) user - restAccountMockMvc + mockMvc .perform( post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userWithUpperCaseEmail)) ) @@ -342,7 +357,7 @@ public class AccountResourceIT { userService.updateUser((new UserDTO(testUser4.get()))); // Register 4th (already activated) user - restAccountMockMvc + mockMvc .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) .andExpect(status().is4xxClientError()); } @@ -361,7 +376,7 @@ public class AccountResourceIT { validUser.setLangKey(Constants.DEFAULT_LANGUAGE); validUser.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); - restAccountMockMvc + mockMvc .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(validUser))) .andExpect(status().isCreated()); @@ -383,7 +398,7 @@ public class AccountResourceIT { userRepository.saveAndFlush(user); - restAccountMockMvc.perform(get("/api/activate?key={activationKey}", activationKey)).andExpect(status().isOk()); + mockMvc.perform(get("/api/activate?key={activationKey}", activationKey)).andExpect(status().isOk()); user = userRepository.findOneByLogin(user.getLogin()).orElse(null); assertThat(user.getActivated()).isTrue(); @@ -392,7 +407,7 @@ public class AccountResourceIT { @Test @Transactional public void testActivateAccountWithWrongKey() throws Exception { - restAccountMockMvc.perform(get("/api/activate?key=wrongActivationKey")).andExpect(status().isInternalServerError()); + mockMvc.perform(get("/api/activate?key=wrongActivationKey")).andExpect(status().isInternalServerError()); } @Test @@ -416,7 +431,7 @@ public class AccountResourceIT { userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); - restAccountMockMvc + mockMvc .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) .andExpect(status().isOk()); @@ -453,7 +468,7 @@ public class AccountResourceIT { userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); - restAccountMockMvc + mockMvc .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) .andExpect(status().isBadRequest()); @@ -489,7 +504,7 @@ public class AccountResourceIT { userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); - restAccountMockMvc + mockMvc .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) .andExpect(status().isBadRequest()); @@ -518,7 +533,7 @@ public class AccountResourceIT { userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); - restAccountMockMvc + mockMvc .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) .andExpect(status().isOk()); @@ -537,7 +552,7 @@ public class AccountResourceIT { user.setEmail("change-password-wrong-existing-password@example.com"); userRepository.saveAndFlush(user); - restAccountMockMvc + mockMvc .perform( post("/api/account/change-password") .contentType(MediaType.APPLICATION_JSON) @@ -561,7 +576,7 @@ public class AccountResourceIT { user.setEmail("change-password@example.com"); userRepository.saveAndFlush(user); - restAccountMockMvc + mockMvc .perform( post("/api/account/change-password") .contentType(MediaType.APPLICATION_JSON) @@ -586,7 +601,7 @@ public class AccountResourceIT { String newPassword = RandomStringUtils.random(ManagedUserVM.PASSWORD_MIN_LENGTH - 1); - restAccountMockMvc + mockMvc .perform( post("/api/account/change-password") .contentType(MediaType.APPLICATION_JSON) @@ -611,7 +626,7 @@ public class AccountResourceIT { String newPassword = RandomStringUtils.random(ManagedUserVM.PASSWORD_MAX_LENGTH + 1); - restAccountMockMvc + mockMvc .perform( post("/api/account/change-password") .contentType(MediaType.APPLICATION_JSON) @@ -634,7 +649,7 @@ public class AccountResourceIT { user.setEmail("change-password-empty@example.com"); userRepository.saveAndFlush(user); - restAccountMockMvc + mockMvc .perform( post("/api/account/change-password") .contentType(MediaType.APPLICATION_JSON) @@ -656,7 +671,7 @@ public class AccountResourceIT { user.setEmail("password-reset@example.com"); userRepository.saveAndFlush(user); - restAccountMockMvc.perform(post("/api/account/reset-password/init").content("password-reset@example.com")).andExpect(status().isOk()); + mockMvc.perform(post("/api/account/reset-password/init").content("password-reset@example.com")).andExpect(status().isOk()); } @Test @@ -669,16 +684,12 @@ public class AccountResourceIT { user.setEmail("password-reset-upper-case@example.com"); userRepository.saveAndFlush(user); - restAccountMockMvc - .perform(post("/api/account/reset-password/init").content("password-reset-upper-case@EXAMPLE.COM")) - .andExpect(status().isOk()); + mockMvc.perform(post("/api/account/reset-password/init").content("password-reset-upper-case@EXAMPLE.COM")).andExpect(status().isOk()); } @Test public void testRequestPasswordResetWrongEmail() throws Exception { - restAccountMockMvc - .perform(post("/api/account/reset-password/init").content("password-reset-wrong-email@example.com")) - .andExpect(status().isOk()); + mockMvc.perform(post("/api/account/reset-password/init").content("password-reset-wrong-email@example.com")).andExpect(status().isOk()); } @Test @@ -696,7 +707,7 @@ public class AccountResourceIT { keyAndPassword.setKey(user.getResetKey()); keyAndPassword.setNewPassword("new password"); - restAccountMockMvc + mockMvc .perform( post("/api/account/reset-password/finish") .contentType(MediaType.APPLICATION_JSON) @@ -723,7 +734,7 @@ public class AccountResourceIT { keyAndPassword.setKey(user.getResetKey()); keyAndPassword.setNewPassword("foo"); - restAccountMockMvc + mockMvc .perform( post("/api/account/reset-password/finish") .contentType(MediaType.APPLICATION_JSON) @@ -742,7 +753,7 @@ public class AccountResourceIT { keyAndPassword.setKey("wrong reset key"); keyAndPassword.setNewPassword("new password"); - restAccountMockMvc + mockMvc .perform( post("/api/account/reset-password/finish") .contentType(MediaType.APPLICATION_JSON) diff --git a/borestop/src/test/java/com/ippon/borestop/web/rest/AccountsSteps.java b/borestop/src/test/java/com/ippon/borestop/web/rest/AccountsSteps.java index ea9274f8..59404234 100644 --- a/borestop/src/test/java/com/ippon/borestop/web/rest/AccountsSteps.java +++ b/borestop/src/test/java/com/ippon/borestop/web/rest/AccountsSteps.java @@ -2,7 +2,7 @@ package com.ippon.borestop.web.rest; import static org.assertj.core.api.Assertions.assertThat; -import com.ippon.borestop.infrastructure.primay.CucumberTestContext; +import com.ippon.borestop.common.infrastructure.primary.CucumberTestContext; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import org.springframework.beans.factory.annotation.Autowired; diff --git a/borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorIT.java b/borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorIT.java deleted file mode 100644 index 853322e5..00000000 --- a/borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorIT.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.ippon.borestop.web.rest.errors; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import com.ippon.borestop.BorestopApp; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; - -/** - * Integration tests {@link ExceptionTranslator} controller advice. - */ -@WithMockUser -@AutoConfigureMockMvc -@SpringBootTest(classes = BorestopApp.class) -public class ExceptionTranslatorIT { - @Autowired - private MockMvc mockMvc; - - @Test - public void testConcurrencyFailure() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/concurrency-failure")) - .andExpect(status().isConflict()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_CONCURRENCY_FAILURE)); - } - - @Test - public void testMethodArgumentNotValid() throws Exception { - mockMvc - .perform(post("/api/exception-translator-test/method-argument").content("{}").contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_VALIDATION)) - .andExpect(jsonPath("$.fieldErrors.[0].objectName").value("test")) - .andExpect(jsonPath("$.fieldErrors.[0].field").value("test")) - .andExpect(jsonPath("$.fieldErrors.[0].message").value("NotNull")); - } - - @Test - public void testMissingServletRequestPartException() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/missing-servlet-request-part")) - .andExpect(status().isBadRequest()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.400")); - } - - @Test - public void testMissingServletRequestParameterException() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/missing-servlet-request-parameter")) - .andExpect(status().isBadRequest()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.400")); - } - - @Test - public void testAccessDenied() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/access-denied")) - .andExpect(status().isForbidden()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.403")) - .andExpect(jsonPath("$.detail").value("test access denied!")); - } - - @Test - public void testUnauthorized() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/unauthorized")) - .andExpect(status().isUnauthorized()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.401")) - .andExpect(jsonPath("$.path").value("/api/exception-translator-test/unauthorized")) - .andExpect(jsonPath("$.detail").value("test authentication failed!")); - } - - @Test - public void testMethodNotSupported() throws Exception { - mockMvc - .perform(post("/api/exception-translator-test/access-denied")) - .andExpect(status().isMethodNotAllowed()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.405")) - .andExpect(jsonPath("$.detail").value("Request method 'POST' not supported")); - } - - @Test - public void testExceptionWithResponseStatus() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/response-status")) - .andExpect(status().isBadRequest()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.400")) - .andExpect(jsonPath("$.title").value("test response status")); - } - - @Test - public void testInternalServerError() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/internal-server-error")) - .andExpect(status().isInternalServerError()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.500")) - .andExpect(jsonPath("$.title").value("Internal Server Error")); - } -} diff --git a/borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorTestController.java b/borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorTestController.java deleted file mode 100644 index c3effbbe..00000000 --- a/borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorTestController.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.ippon.borestop.web.rest.errors; - -import javax.validation.Valid; -import javax.validation.constraints.NotNull; -import org.springframework.dao.ConcurrencyFailureException; -import org.springframework.http.HttpStatus; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/api/exception-translator-test") -public class ExceptionTranslatorTestController { - - @GetMapping("/concurrency-failure") - public void concurrencyFailure() { - throw new ConcurrencyFailureException("test concurrency failure"); - } - - @PostMapping("/method-argument") - public void methodArgument(@Valid @RequestBody TestDTO testDTO) {} - - @GetMapping("/missing-servlet-request-part") - public void missingServletRequestPartException(@RequestPart String part) {} - - @GetMapping("/missing-servlet-request-parameter") - public void missingServletRequestParameterException(@RequestParam String param) {} - - @GetMapping("/access-denied") - public void accessdenied() { - throw new AccessDeniedException("test access denied!"); - } - - @GetMapping("/unauthorized") - public void unauthorized() { - throw new BadCredentialsException("test authentication failed!"); - } - - @GetMapping("/response-status") - public void exceptionWithResponseStatus() { - throw new TestResponseStatusException(); - } - - @GetMapping("/internal-server-error") - public void internalServerError() { - throw new RuntimeException(); - } - - public static class TestDTO { - @NotNull - private String test; - - public String getTest() { - return test; - } - - public void setTest(String test) { - this.test = test; - } - } - - @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "test response status") - @SuppressWarnings("serial") - public static class TestResponseStatusException extends RuntimeException {} -} diff --git a/borestop/src/test/resources/i18n/messages_en.properties b/borestop/src/test/resources/i18n/messages_en.properties deleted file mode 100644 index 7b3fbb99..00000000 --- a/borestop/src/test/resources/i18n/messages_en.properties +++ /dev/null @@ -1,4 +0,0 @@ -email.test.title=test title -# Value used for English locale unit test in MailServiceIT -# as this file is loaded instead of real file -email.activation.title=borestop account activation -- GitLab