From f7f3328f9173cbda2baa4280f61ff51a9b0beaf9 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Mon, 20 Nov 2023 12:47:35 +0100 Subject: [PATCH 01/38] add new classes --- .../java/ch/puzzle/okr/models/ErrorMsgKey.java | 5 +++++ .../okr/models/OkrResponseStatusException.java | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 backend/src/main/java/ch/puzzle/okr/models/ErrorMsgKey.java create mode 100644 backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java diff --git a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsgKey.java b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsgKey.java new file mode 100644 index 0000000000..d8736c543b --- /dev/null +++ b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsgKey.java @@ -0,0 +1,5 @@ +package ch.puzzle.okr.models; + +public enum ErrorMsgKey { + UNAUTHORIZED, NOT_FOUND, +} diff --git a/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java new file mode 100644 index 0000000000..3240510ae8 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java @@ -0,0 +1,17 @@ +package ch.puzzle.okr.models; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +import java.util.List; + +public class OkrResponseStatusException extends ResponseStatusException { + private final List params; + private final ErrorMsgKey errorKey; + + public OkrResponseStatusException(HttpStatus status, List params, ErrorMsgKey errorKey) { + super(status); + this.params = params; + this.errorKey = errorKey; + } +} From 56b3996b8cef1f6e000d1f13cef9f62087416e20 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Mon, 20 Nov 2023 14:16:38 +0100 Subject: [PATCH 02/38] display custom error attributes --- .../ch/puzzle/okr/OkrErrorAttributes.java | 25 +++++++++++++++++++ .../models/OkrResponseStatusException.java | 16 +++++++++--- .../ObjectivePersistenceService.java | 8 +++--- 3 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java diff --git a/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java b/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java new file mode 100644 index 0000000000..3555033db7 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java @@ -0,0 +1,25 @@ +package ch.puzzle.okr; + +import ch.puzzle.okr.models.OkrResponseStatusException; +import org.springframework.boot.web.error.ErrorAttributeOptions; +import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.WebRequest; + +import java.util.Map; + +@Component +public class OkrErrorAttributes extends DefaultErrorAttributes { + + @Override + public Map getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { + Map errorAttributes = super.getErrorAttributes(webRequest, options); + + Throwable throwable = getError(webRequest); + if (throwable instanceof OkrResponseStatusException exception) { + errorAttributes.put("errorMsg", exception.errorKey); + errorAttributes.put("params", exception.params); + } + return errorAttributes; + } +} \ No newline at end of file diff --git a/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java index 3240510ae8..fd5a87155b 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java +++ b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java @@ -1,17 +1,25 @@ package ch.puzzle.okr.models; +import org.springframework.data.annotation.Transient; import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; import java.util.List; public class OkrResponseStatusException extends ResponseStatusException { - private final List params; - private final ErrorMsgKey errorKey; + @Transient + public final List params; + public final ErrorMsgKey errorKey; - public OkrResponseStatusException(HttpStatus status, List params, ErrorMsgKey errorKey) { - super(status); + public OkrResponseStatusException(HttpStatus status, ErrorMsgKey errorKey, List params) { + super(status, errorKey.toString()); this.params = params; this.errorKey = errorKey; } + + public OkrResponseStatusException(HttpStatus status, ErrorMsgKey errorKey) { + super(status, errorKey.toString()); + this.params = List.of(); + this.errorKey = errorKey; + } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java index 6cbe266b09..477446c108 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java @@ -1,14 +1,11 @@ package ch.puzzle.okr.service.persistence; -import ch.puzzle.okr.models.Objective; -import ch.puzzle.okr.models.Quarter; -import ch.puzzle.okr.models.Team; +import ch.puzzle.okr.models.*; import ch.puzzle.okr.models.authorization.AuthorizationUser; import ch.puzzle.okr.repository.ObjectiveRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; import javax.persistence.EntityManager; import javax.persistence.NoResultException; @@ -71,7 +68,8 @@ private Objective findByAnyId(Long id, AuthorizationUser authorizationUser, Stri try { return typedQuery.getSingleResult(); } catch (NoResultException exception) { - throw new ResponseStatusException(UNAUTHORIZED, reason); + throw new OkrResponseStatusException(UNAUTHORIZED, ErrorMsgKey.UNAUTHORIZED, + List.of("Objective", id.toString())); } } } From 1fa73503f4c84f8b8e6570038298b16ec96709cb Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Tue, 21 Nov 2023 13:34:44 +0100 Subject: [PATCH 03/38] rework error flow --- .../ch/puzzle/okr/OkrErrorAttributes.java | 3 +- .../okr/converter/JwtUserConverter.java | 6 ++-- .../ch/puzzle/okr/mapper/OverviewMapper.java | 9 +++--- .../java/ch/puzzle/okr/models/ErrorDto.java | 21 ++++++++++++ .../java/ch/puzzle/okr/models/ErrorMsg.java | 21 ++++++++++++ .../ch/puzzle/okr/models/ErrorMsgKey.java | 5 --- .../models/OkrResponseStatusException.java | 22 ++++++------- .../main/java/ch/puzzle/okr/models/Team.java | 10 +++--- .../business/KeyResultBusinessService.java | 16 +++++----- .../ObjectivePersistenceService.java | 3 +- .../validation/TeamValidationService.java | 16 ++++++---- .../service/validation/ValidationBase.java | 32 +++++++++++++------ 12 files changed, 108 insertions(+), 56 deletions(-) create mode 100644 backend/src/main/java/ch/puzzle/okr/models/ErrorDto.java create mode 100644 backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java delete mode 100644 backend/src/main/java/ch/puzzle/okr/models/ErrorMsgKey.java diff --git a/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java b/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java index 3555033db7..cc72d204a1 100644 --- a/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java +++ b/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java @@ -17,8 +17,7 @@ public Map getErrorAttributes(WebRequest webRequest, ErrorAttrib Throwable throwable = getError(webRequest); if (throwable instanceof OkrResponseStatusException exception) { - errorAttributes.put("errorMsg", exception.errorKey); - errorAttributes.put("params", exception.params); + errorAttributes.put("errorMsg", exception.errors); } return errorAttributes; } diff --git a/backend/src/main/java/ch/puzzle/okr/converter/JwtUserConverter.java b/backend/src/main/java/ch/puzzle/okr/converter/JwtUserConverter.java index 05da5c023b..3bc040c9bf 100644 --- a/backend/src/main/java/ch/puzzle/okr/converter/JwtUserConverter.java +++ b/backend/src/main/java/ch/puzzle/okr/converter/JwtUserConverter.java @@ -1,5 +1,7 @@ package ch.puzzle.okr.converter; +import ch.puzzle.okr.models.ErrorMsg; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -7,8 +9,8 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.stereotype.Component; -import org.springframework.web.server.ResponseStatusException; +import java.util.List; import java.util.Map; import static org.springframework.http.HttpStatus.BAD_REQUEST; @@ -38,7 +40,7 @@ public User convert(Jwt token) { .withEmail(claims.get(email).toString()).build(); } catch (Exception e) { logger.warn("can not convert user from claims {}", claims); - throw new ResponseStatusException(BAD_REQUEST, "can not convert user from token"); + throw new OkrResponseStatusException(BAD_REQUEST, ErrorMsg.CONVERT_TOKEN, List.of("User")); } } } diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java index 35b09a2870..e21174c2cd 100644 --- a/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java +++ b/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java @@ -1,10 +1,11 @@ package ch.puzzle.okr.mapper; import ch.puzzle.okr.dto.overview.*; +import ch.puzzle.okr.models.ErrorMsg; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.overview.Overview; import ch.puzzle.okr.service.business.OrganisationBusinessService; import org.springframework.stereotype.Component; -import org.springframework.web.server.ResponseStatusException; import java.util.ArrayList; import java.util.List; @@ -89,9 +90,9 @@ private OverviewKeyResultDto createKeyResultDto(Overview overview) { } else if (Objects.equals(overview.getKeyResultType(), KEY_RESULT_TYPE_ORDINAL)) { return createKeyResultOrdinalDto(overview); } else { - throw new ResponseStatusException(BAD_REQUEST, - String.format("The key result type %s can not be converted to a metric or ordinal DTO", - overview.getKeyResultType())); + throw new OkrResponseStatusException(BAD_REQUEST, ErrorMsg.KEYRESULT_CONVERSION, + List.of(overview.getKeyResultType())); + // "The key result type %s can not be converted to a metric or ordinal DTO" } } diff --git a/backend/src/main/java/ch/puzzle/okr/models/ErrorDto.java b/backend/src/main/java/ch/puzzle/okr/models/ErrorDto.java new file mode 100644 index 0000000000..2962885d03 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/okr/models/ErrorDto.java @@ -0,0 +1,21 @@ +package ch.puzzle.okr.models; + +import java.util.List; + +public class ErrorDto { + private final String errorKey; + private final List params; + + public ErrorDto(String errorKey, List params) { + this.errorKey = errorKey; + this.params = params.stream().map(Object::toString).toList(); + } + + public String getErrorKey() { + return errorKey; + } + + public List getParams() { + return params; + } +} diff --git a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java new file mode 100644 index 0000000000..24644cd682 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java @@ -0,0 +1,21 @@ +package ch.puzzle.okr.models; + +public class ErrorMsg { + private ErrorMsg() { + } + + public static final String UNAUTHORIZED = "UNAUTHORIZED"; + + public static final String NOT_FOUND = "NOT_FOUND"; + public static final String KEYRESULT_CONVERSION = "KEYRESULT_CONVERSION"; + public static final String ALREADY_EXISTS_SAME_NAME = "ALREADY_EXISTS_SAME_NAME"; + public static final String CONVERT_TOKEN = "CONVERT_TOKEN"; + public static final String UNSUPPORTED_METHOD_IN_CLASS = "UNSUPPORTED_METHOD_IN_CLASS"; + public static final String MODEL_NULL = "MODEL_NULL"; + public static final String ATTRIBUTE_NULL = "ATTRIBUTE_NULL"; + public static final String ATTRIBUTE_NOT_NULL = "ATTRIBUTE_NOT_NULL"; + public static final String ATTRIBUTE_CHANGED = "ATTRIBUTE_CHANGED"; + public static final String SIZE_BETWEEN = "SIZE_BETWEEN_{min}_{max}"; + public static final String EMPTY_ATTRIBUTE_ON_MODEL = "EMPTY_ATTRIBUTE_ON_MODEL"; + public static final String NULL_ATTRIBUTE_ON_MODEL = "NULL_ATTRIBUTE_ON_MODEL"; +} diff --git a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsgKey.java b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsgKey.java deleted file mode 100644 index d8736c543b..0000000000 --- a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsgKey.java +++ /dev/null @@ -1,5 +0,0 @@ -package ch.puzzle.okr.models; - -public enum ErrorMsgKey { - UNAUTHORIZED, NOT_FOUND, -} diff --git a/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java index fd5a87155b..95ce5ac1e4 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java +++ b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java @@ -7,19 +7,19 @@ import java.util.List; public class OkrResponseStatusException extends ResponseStatusException { - @Transient - public final List params; - public final ErrorMsgKey errorKey; - public OkrResponseStatusException(HttpStatus status, ErrorMsgKey errorKey, List params) { - super(status, errorKey.toString()); - this.params = params; - this.errorKey = errorKey; + public final List errors; + + public OkrResponseStatusException(HttpStatus status, String errorKey) { + this(status, errorKey, List.of()); + } + + public OkrResponseStatusException(HttpStatus status, String errorKey, List objectParams) { + this(status, List.of(new ErrorDto(errorKey, objectParams))); } - public OkrResponseStatusException(HttpStatus status, ErrorMsgKey errorKey) { - super(status, errorKey.toString()); - this.params = List.of(); - this.errorKey = errorKey; + public OkrResponseStatusException(HttpStatus status, List errors) { + super(status); + this.errors = errors; } } diff --git a/backend/src/main/java/ch/puzzle/okr/models/Team.java b/backend/src/main/java/ch/puzzle/okr/models/Team.java index 27ae00e149..54f20fddb2 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/Team.java +++ b/backend/src/main/java/ch/puzzle/okr/models/Team.java @@ -13,14 +13,14 @@ public class Team implements WriteableInterface { @GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence_team") private Long id; + @NotBlank(message = ErrorMsg.EMPTY_ATTRIBUTE_ON_MODEL) + @NotNull(message = ErrorMsg.NULL_ATTRIBUTE_ON_MODEL) + @Size(min = 2, max = 250, message = ErrorMsg.SIZE_BETWEEN) + private String name; + @Version private int version; - @NotBlank(message = "Missing attribute name when saving team") - @NotNull(message = "Attribute name can not be null when saving team") - @Size(min = 2, max = 250, message = "Attribute name must have size between 2 and 250 characters when saving team") - private String name; - @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "team_organisation", joinColumns = @JoinColumn(name = "team_id"), inverseJoinColumns = @JoinColumn(name = "organisation_id")) private List authorizationOrganisation; diff --git a/backend/src/main/java/ch/puzzle/okr/service/business/KeyResultBusinessService.java b/backend/src/main/java/ch/puzzle/okr/service/business/KeyResultBusinessService.java index 065717ca00..070fedec19 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/business/KeyResultBusinessService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/business/KeyResultBusinessService.java @@ -1,6 +1,8 @@ package ch.puzzle.okr.service.business; import ch.puzzle.okr.models.Action; +import ch.puzzle.okr.models.ErrorMsg; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.authorization.AuthorizationUser; import ch.puzzle.okr.models.checkin.CheckIn; import ch.puzzle.okr.models.keyresult.KeyResult; @@ -9,24 +11,22 @@ import ch.puzzle.okr.service.validation.KeyResultValidationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; import javax.transaction.Transactional; import java.time.LocalDateTime; import java.util.List; import java.util.Objects; -import static org.springframework.http.HttpStatus.BAD_REQUEST; - @Service public class KeyResultBusinessService implements BusinessServiceInterface { + private static final Logger logger = LoggerFactory.getLogger(KeyResultBusinessService.class); private final KeyResultPersistenceService keyResultPersistenceService; private final CheckInBusinessService checkInBusinessService; private final ActionBusinessService actionBusinessService; private final KeyResultValidationService validator; - private static final Logger logger = LoggerFactory.getLogger(KeyResultBusinessService.class); public KeyResultBusinessService(KeyResultPersistenceService keyResultPersistenceService, KeyResultValidationService validator, CheckInBusinessService checkInBusinessService, @@ -53,10 +53,10 @@ public KeyResult getEntityById(Long id) { } @Override - public KeyResult updateEntity(Long id, KeyResult keyResult, AuthorizationUser authorizationUser) - throws ResponseStatusException { - throw new ResponseStatusException(BAD_REQUEST, - "unsupported method in class " + getClass().getSimpleName() + ", use updateEntities() instead"); + public KeyResult updateEntity(Long id, KeyResult keyResult, AuthorizationUser authorizationUser) { + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.UNSUPPORTED_METHOD_IN_CLASS, + List.of(getClass().getSimpleName())); + // unsupported method in class {} use updateEntities() instead } @Transactional diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java index 477446c108..65787d7372 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java @@ -68,8 +68,7 @@ private Objective findByAnyId(Long id, AuthorizationUser authorizationUser, Stri try { return typedQuery.getSingleResult(); } catch (NoResultException exception) { - throw new OkrResponseStatusException(UNAUTHORIZED, ErrorMsgKey.UNAUTHORIZED, - List.of("Objective", id.toString())); + throw new OkrResponseStatusException(UNAUTHORIZED, ErrorMsg.UNAUTHORIZED, List.of("Objective", id)); } } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/TeamValidationService.java b/backend/src/main/java/ch/puzzle/okr/service/validation/TeamValidationService.java index f469790a08..08173a176a 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/TeamValidationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/TeamValidationService.java @@ -1,11 +1,12 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.models.ErrorMsg; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.Team; import ch.puzzle.okr.repository.TeamRepository; import ch.puzzle.okr.service.persistence.TeamPersistenceService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; import java.util.List; import java.util.Objects; @@ -21,8 +22,8 @@ public TeamValidationService(TeamPersistenceService teamPersistenceService) { public void validateOnCreate(Team model) { throwExceptionWhenModelIsNull(model); throwExceptionWhenIdIsNotNull(model.getId()); - checkIfTeamWithNameAlreadyExists(model.getName(), model.getId(), - "Can't create team with already existing name"); + checkIfTeamWithNameAlreadyExists(model.getName(), model.getId()); + // Can't create team with already existing name validate(model); } @@ -32,16 +33,17 @@ public void validateOnUpdate(Long id, Team model) { throwExceptionWhenIdIsNull(id); throwExceptionWhenIdHasChanged(id, model.getId()); doesEntityExist(model.getId()); - checkIfTeamWithNameAlreadyExists(model.getName(), model.getId(), - "Can't update to team with already existing name"); + checkIfTeamWithNameAlreadyExists(model.getName(), model.getId()); + // Can't update to team with already existing name validate(model); } - private void checkIfTeamWithNameAlreadyExists(String name, Long id, String message) { + private void checkIfTeamWithNameAlreadyExists(String name, Long id) { List filteredTeam = this.getPersistenceService().findTeamsByName(name).stream() .filter(team -> !Objects.equals(team.getId(), id)).toList(); if (!filteredTeam.isEmpty()) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ALREADY_EXISTS_SAME_NAME, + List.of("Team", name)); } } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java index b548216831..25e124f6b6 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java @@ -1,8 +1,10 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.models.ErrorDto; +import ch.puzzle.okr.models.ErrorMsg; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.service.persistence.PersistenceBase; import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; import javax.validation.ConstraintViolation; import javax.validation.Validation; @@ -11,6 +13,9 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * @param @@ -56,28 +61,33 @@ public T doesEntityExist(ID id) { public void throwExceptionWhenModelIsNull(T model) { if (model == null) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, - String.format("Given model %s is null", persistenceService.getModelName())); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.MODEL_NULL, + List.of(persistenceService.getModelName())); + // Given model %s is null } } public void throwExceptionWhenIdIsNull(ID id) { if (id == null) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Id is null"); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NULL, List.of("ID")); + // Id is null } } protected void throwExceptionWhenIdIsNotNull(ID id) { if (id != null) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, String.format( - "Model %s cannot have id while create. Found id %s", persistenceService.getModelName(), id)); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NULL, + List.of(persistenceService.getModelName(), id)); + // Model %s cannot have id while create. Found id %s } } protected void throwExceptionWhenIdHasChanged(ID id, ID modelId) { if (!Objects.equals(id, modelId)) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, - String.format("Id %s has changed to %s during update", id, modelId)); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_CHANGED, + List.of(id, modelId)); + + // Id %s has changed to %s during update } } @@ -88,8 +98,10 @@ public void validate(T model) { private void processViolations(Set> violations) { if (!violations.isEmpty()) { - List reasons = violations.stream().map(ConstraintViolation::getMessage).toList(); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, String.join(". ", reasons) + "."); + List list = violations.stream().map( + e -> new ErrorDto(e.getMessage(), List.of(persistenceService.getModelName(), e.getPropertyPath()))) + .toList(); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, list); } } } From 014ff815be1cf724ca0f149baee92c041b2fd033 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Tue, 21 Nov 2023 14:16:37 +0100 Subject: [PATCH 04/38] replace every occurence of ResponseStatusException with OkrResponseStatusException --- .../java/ch/puzzle/okr/models/ErrorMsg.java | 6 ++++ .../service/persistence/PersistenceBase.java | 31 ++++++++++--------- .../ObjectiveValidationService.java | 26 +++++++++++----- .../service/validation/ValidationBase.java | 2 +- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java index 24644cd682..2409d86e23 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java +++ b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java @@ -18,4 +18,10 @@ private ErrorMsg() { public static final String SIZE_BETWEEN = "SIZE_BETWEEN_{min}_{max}"; public static final String EMPTY_ATTRIBUTE_ON_MODEL = "EMPTY_ATTRIBUTE_ON_MODEL"; public static final String NULL_ATTRIBUTE_ON_MODEL = "NULL_ATTRIBUTE_ON_MODEL"; + public static final String FORBIDDEN_SET_ATTRIBUTE = "FORBIDDEN_SET_ATTRIBUTE"; + public static final String MODIFIED_BY_NOT_SET = "MODIFIED_BY_NOT_SET"; + public static final String ATTRIBUTE_CANNOT_CHANGE = "ATTRIBUTE_CANNOT_CHANGE"; + public static final String MODEL_WITH_ID_NOT_FOUND = "MODEL_WITH_ID_NOT_FOUND"; + public static final String DATA_HAS_BEEN_UPDATED = "DATA_HAS_BEEN_UPDATED"; + } diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java index 39b66fe1fe..bad0470ac9 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java @@ -1,21 +1,18 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.models.ErrorMsg; +import ch.puzzle.okr.models.OkrResponseStatusException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.repository.CrudRepository; import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; import java.util.List; import java.util.Spliterator; import java.util.Spliterators; import java.util.stream.StreamSupport; -import static java.lang.String.format; -import static org.springframework.http.HttpStatus.NOT_FOUND; -import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; - /** * @param * the Type or entity of the repository @@ -38,30 +35,36 @@ public R getRepository() { return (R) repository; } - public T findById(ID id) throws ResponseStatusException { + public T findById(ID id) throws OkrResponseStatusException { checkIdNull(id); return repository.findById(id).orElseThrow(() -> createEntityNotFoundException(id)); } public void checkIdNull(ID id) { if (id == null) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, - format("Missing identifier for %s", getModelName())); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.NULL_ATTRIBUTE_ON_MODEL); + // throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + // format("Missing identifier for %s", getModelName())); } } - public ResponseStatusException createEntityNotFoundException(ID id) { - return new ResponseStatusException(NOT_FOUND, format("%s with id %s not found", getModelName(), id)); + public OkrResponseStatusException createEntityNotFoundException(ID id) { + throw new OkrResponseStatusException(HttpStatus.NOT_FOUND, ErrorMsg.MODEL_WITH_ID_NOT_FOUND, + List.of(getModelName(), id)); + // return new ResponseStatusException(NOT_FOUND, format("%s with id %s not found", getModelName(), id)); } - public T save(T model) throws ResponseStatusException { + public T save(T model) throws OkrResponseStatusException { try { return repository.save(model); } catch (OptimisticLockingFailureException ex) { logger.info("optimistic locking exception while saving {}", model, ex); - throw new ResponseStatusException(UNPROCESSABLE_ENTITY, - String.format("The data of %s has been updated or deleted by another user." - + "\nPlease reload the data and apply your changes again.", getModelName())); + throw new OkrResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY, ErrorMsg.DATA_HAS_BEEN_UPDATED, + List.of(getModelName())); + + // throw new ResponseStatusException(UNPROCESSABLE_ENTITY, + // String.format("The data of %s has been updated or deleted by another user." + + // "Please reload the data and apply your changes again.", getModelName())); } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java b/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java index 51559ef031..41eb0c9adb 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java @@ -1,17 +1,19 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.models.ErrorMsg; import ch.puzzle.okr.models.Objective; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.Team; import ch.puzzle.okr.repository.ObjectiveRepository; import ch.puzzle.okr.service.persistence.ObjectivePersistenceService; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; +import java.util.List; import java.util.Objects; import static java.lang.String.format; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; @Service public class ObjectiveValidationService @@ -42,22 +44,30 @@ public void validateOnUpdate(Long id, Objective model) { private static void throwExceptionWhenModifiedByIsSet(Objective model) { if (model.getModifiedBy() != null) { - throw new ResponseStatusException(BAD_REQUEST, - format("Not allowed to set ModifiedBy %s on create", model.getModifiedBy())); + + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.FORBIDDEN_SET_ATTRIBUTE, + List.of("ModifiedBy")); + // throw new ResponseStatusException(BAD_REQUEST, + // format("Not allowed to set ModifiedBy %s on create", model.getModifiedBy())); } } private static void throwExceptionWhenModifiedByIsNull(Objective model) { if (model.getModifiedBy() == null) { - throw new ResponseStatusException(INTERNAL_SERVER_ERROR, - format("Something went wrong. ModifiedBy %s is not set.", model.getModifiedBy())); + throw new OkrResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, ErrorMsg.MODIFIED_BY_NOT_SET); + + // throw new ResponseStatusException(INTERNAL_SERVER_ERROR, + // format("Something went wrong. ModifiedBy %s is not set.", model.getModifiedBy())); } } private static void throwExceptionWhenTeamHasChanged(Team team, Team savedTeam) { if (!Objects.equals(team, savedTeam)) { - throw new ResponseStatusException(BAD_REQUEST, format( - "The team can not be changed (new team %s, old team %s)", team.getName(), savedTeam.getName())); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_CANNOT_CHANGE, + List.of(team.getName(), savedTeam.getName())); + + // throw new ResponseStatusException(BAD_REQUEST, format( + // "The team can not be changed (new team %s, old team %s)", )); } } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java index 25e124f6b6..27157d3198 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java @@ -76,7 +76,7 @@ public void throwExceptionWhenIdIsNull(ID id) { protected void throwExceptionWhenIdIsNotNull(ID id) { if (id != null) { - throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NULL, + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NOT_NULL, List.of(persistenceService.getModelName(), id)); // Model %s cannot have id while create. Found id %s } From fe9a6ef78043234e3ad4b86f139ba9c9cc2cd042 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Tue, 21 Nov 2023 15:52:41 +0100 Subject: [PATCH 05/38] prepare frontend to intercept error keys --- .../ch/puzzle/okr/OkrErrorAttributes.java | 2 +- .../java/ch/puzzle/okr/models/ErrorMsg.java | 1 - frontend/src/app/app.module.ts | 9 ++++---- .../interceptors/error-interceptor.service.ts | 5 +++++ frontend/src/assets/i18n/de.json | 22 ++++++++++++++++++- frontend/src/main.ts | 13 +++++++++++ 6 files changed, 45 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java b/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java index cc72d204a1..1b9b04a6b9 100644 --- a/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java +++ b/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java @@ -17,7 +17,7 @@ public Map getErrorAttributes(WebRequest webRequest, ErrorAttrib Throwable throwable = getError(webRequest); if (throwable instanceof OkrResponseStatusException exception) { - errorAttributes.put("errorMsg", exception.errors); + errorAttributes.put("errors", exception.errors); } return errorAttributes; } diff --git a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java index 2409d86e23..ab8f892f2d 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java +++ b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java @@ -5,7 +5,6 @@ private ErrorMsg() { } public static final String UNAUTHORIZED = "UNAUTHORIZED"; - public static final String NOT_FOUND = "NOT_FOUND"; public static final String KEYRESULT_CONVERSION = "KEYRESULT_CONVERSION"; public static final String ALREADY_EXISTS_SAME_NAME = "ALREADY_EXISTS_SAME_NAME"; diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 9e5c499ceb..804960f06f 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -2,7 +2,7 @@ import { APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; -import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http'; +import { HTTP_INTERCEPTORS, HttpBackend, HttpClient, HttpClientModule } from '@angular/common/http'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; @@ -16,7 +16,7 @@ import { MatInputModule } from '@angular/material/input'; import { MatDialogModule } from '@angular/material/dialog'; import { ToastrModule } from 'ngx-toastr'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core'; import { MomentDateAdapter } from '@angular/material-moment-adapter'; @@ -80,8 +80,8 @@ function initOauthFactory(configService: ConfigService, oauthService: OAuthServi }; } -export function createTranslateLoader(http: HttpClient) { - return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +export function createTranslateLoader(http: HttpBackend) { + return new TranslateHttpLoader(new HttpClient(http), './assets/i18n/', '.json'); } export function storageFactory(): OAuthStorage { @@ -199,6 +199,7 @@ export const MY_FORMATS = { }, UnitValueTransformationPipe, ParseUnitValuePipe, + TranslateService, ], bootstrap: [AppComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], diff --git a/frontend/src/app/shared/interceptors/error-interceptor.service.ts b/frontend/src/app/shared/interceptors/error-interceptor.service.ts index 0ada1794da..af6c2d186b 100644 --- a/frontend/src/app/shared/interceptors/error-interceptor.service.ts +++ b/frontend/src/app/shared/interceptors/error-interceptor.service.ts @@ -4,6 +4,7 @@ import { catchError, filter, Observable, throwError } from 'rxjs'; import { Router } from '@angular/router'; import { drawerRoutes } from '../constantLibary'; import { ToasterService } from '../services/toaster.service'; +import { TranslateService } from '@ngx-translate/core'; @Injectable() export class ErrorInterceptor implements HttpInterceptor { @@ -11,6 +12,7 @@ export class ErrorInterceptor implements HttpInterceptor { constructor( private router: Router, private toasterService: ToasterService, + private translate: TranslateService, ) {} intercept(request: HttpRequest, next: HttpHandler): Observable> { @@ -26,6 +28,9 @@ export class ErrorInterceptor implements HttpInterceptor { handleErrorToaster(response: any) { if (!this.NO_ERROR_TOASTER_ROUTES.some((route) => response.url.includes(route))) { + const errors = response.error.errors; + console.log(this.translate.instant('TRANSLATIONS.' + errors[0].errorKey)); + console.log(errors[0].errorKey); this.toasterService.showError(response.error.message); } } diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index 17284e28ec..970826612a 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -20,5 +20,25 @@ "KEY_RESULT_Type": { "metric": "Metrisch", "ordinal": "Ordinal" - } + }, + "TRANSLATIONS": { + "UNAUTHORIZED": "Du bist nicht autorisiert um das Objekt '{0}' mit der Id {1} zu öffnen", + "NOT_FOUND": "Das Objekt '{0}' mit der Id {1} konnte nicht gefunden werden", + "KEYRESULT_CONVERSION": "Der Keyresulttyp {1} konnte weder zu metrisch noch zu ordinal konvertiert werden", + "ALREADY_EXISTS_SAME_NAME": "", + "CONVERT_TOKEN": "", + "UNSUPPORTED_METHOD_IN_CLASS": "", + "MODEL_NULL": "", + "ATTRIBUTE_NULL": "", + "ATTRIBUTE_NOT_NULL": "", + "ATTRIBUTE_CHANGED": "", + "SIZE_BETWEEN": "", + "EMPTY_ATTRIBUTE_ON_MODEL": "", + "NULL_ATTRIBUTE_ON_MODEL": "", + "FORBIDDEN_SET_ATTRIBUTE": "", + "MODIFIED_BY_NOT_SET": "", + "ATTRIBUTE_CANNOT_CHANGE": "", + "MODEL_WITH_ID_NOT_FOUND": "", + "DATA_HAS_BEEN_UPDATED": "" + }, } diff --git a/frontend/src/main.ts b/frontend/src/main.ts index d9a2e7e4a5..e559db7e07 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -8,6 +8,19 @@ if (environment.production) { enableProdMode(); } +declare global { + interface String { + format(length: number): string; + } +} + +String.prototype.format = function () { + const args = arguments; + return this.replace(/{([0-9]+)}/g, function (match, index) { + return typeof args[index] == 'undefined' ? match : args[index]; + }); +}; + platformBrowserDynamic() .bootstrapModule(AppModule) .catch((err) => console.error(err)); From 87fad1484d7f726e215030323dbf5b1658a7deaa Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 23 Nov 2023 08:31:18 +0100 Subject: [PATCH 06/38] complete basic structure of error handling --- frontend/src/app/app.module.ts | 2 +- .../app/shared/interceptors/error-interceptor.service.ts | 8 +++++--- frontend/src/assets/i18n/de.json | 2 +- frontend/src/main.ts | 3 ++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 804960f06f..aed99b5e15 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -162,7 +162,7 @@ export const MY_FORMATS = { loader: { provide: TranslateLoader, useFactory: createTranslateLoader, - deps: [HttpClient], + deps: [HttpBackend], }, }), OAuthModule.forRoot(), diff --git a/frontend/src/app/shared/interceptors/error-interceptor.service.ts b/frontend/src/app/shared/interceptors/error-interceptor.service.ts index af6c2d186b..5fad27dc88 100644 --- a/frontend/src/app/shared/interceptors/error-interceptor.service.ts +++ b/frontend/src/app/shared/interceptors/error-interceptor.service.ts @@ -29,9 +29,11 @@ export class ErrorInterceptor implements HttpInterceptor { handleErrorToaster(response: any) { if (!this.NO_ERROR_TOASTER_ROUTES.some((route) => response.url.includes(route))) { const errors = response.error.errors; - console.log(this.translate.instant('TRANSLATIONS.' + errors[0].errorKey)); - console.log(errors[0].errorKey); - this.toasterService.showError(response.error.message); + errors.forEach((error: { errorKey: string; params: string[] }) => { + const template = this.translate.instant('TRANSLATIONS.' + error.errorKey); + const message = template.format(error.params); + this.toasterService.showError(message); + }); } } diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index 970826612a..6af3045b5b 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -40,5 +40,5 @@ "ATTRIBUTE_CANNOT_CHANGE": "", "MODEL_WITH_ID_NOT_FOUND": "", "DATA_HAS_BEEN_UPDATED": "" - }, + } } diff --git a/frontend/src/main.ts b/frontend/src/main.ts index e559db7e07..bf4cb6e9c9 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -3,6 +3,7 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; +import { isArray } from '@angular/compiler-cli/src/ngtsc/annotations/common'; if (environment.production) { enableProdMode(); @@ -15,7 +16,7 @@ declare global { } String.prototype.format = function () { - const args = arguments; + const args = Array.from(arguments).flat(); return this.replace(/{([0-9]+)}/g, function (match, index) { return typeof args[index] == 'undefined' ? match : args[index]; }); From 0f2e503e29ba3c546a81e875edd46e39ec1063dc Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 23 Nov 2023 08:51:38 +0100 Subject: [PATCH 07/38] fix backend errors --- .../main/java/ch/puzzle/okr/{models => dto}/ErrorDto.java | 2 +- .../ch/puzzle/okr/models/OkrResponseStatusException.java | 4 ++-- .../okr/service/business/KeyResultBusinessService.java | 4 +--- .../ch/puzzle/okr/service/validation/ValidationBase.java | 5 +---- .../src/app/shared/interceptors/error-interceptor.service.ts | 2 +- frontend/src/assets/i18n/de.json | 3 ++- 6 files changed, 8 insertions(+), 12 deletions(-) rename backend/src/main/java/ch/puzzle/okr/{models => dto}/ErrorDto.java (93%) diff --git a/backend/src/main/java/ch/puzzle/okr/models/ErrorDto.java b/backend/src/main/java/ch/puzzle/okr/dto/ErrorDto.java similarity index 93% rename from backend/src/main/java/ch/puzzle/okr/models/ErrorDto.java rename to backend/src/main/java/ch/puzzle/okr/dto/ErrorDto.java index 2962885d03..a346f7d215 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/ErrorDto.java +++ b/backend/src/main/java/ch/puzzle/okr/dto/ErrorDto.java @@ -1,4 +1,4 @@ -package ch.puzzle.okr.models; +package ch.puzzle.okr.dto; import java.util.List; diff --git a/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java index 95ce5ac1e4..2134d4e53b 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java +++ b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java @@ -1,6 +1,6 @@ package ch.puzzle.okr.models; -import org.springframework.data.annotation.Transient; +import ch.puzzle.okr.dto.ErrorDto; import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; @@ -19,7 +19,7 @@ public OkrResponseStatusException(HttpStatus status, String errorKey, List errors) { - super(status); + super(status, errors.get(0).getErrorKey()); this.errors = errors; } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/business/KeyResultBusinessService.java b/backend/src/main/java/ch/puzzle/okr/service/business/KeyResultBusinessService.java index 070fedec19..66fa9158df 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/business/KeyResultBusinessService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/business/KeyResultBusinessService.java @@ -54,9 +54,7 @@ public KeyResult getEntityById(Long id) { @Override public KeyResult updateEntity(Long id, KeyResult keyResult, AuthorizationUser authorizationUser) { - throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.UNSUPPORTED_METHOD_IN_CLASS, - List.of(getClass().getSimpleName())); - // unsupported method in class {} use updateEntities() instead + throw new IllegalCallerException("unsupported method 'updateEntity' use updateEntities() instead"); } @Transactional diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java index 27157d3198..52b9b1375c 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java @@ -1,6 +1,6 @@ package ch.puzzle.okr.service.validation; -import ch.puzzle.okr.models.ErrorDto; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.ErrorMsg; import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.service.persistence.PersistenceBase; @@ -13,9 +13,6 @@ import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.IntStream; /** * @param diff --git a/frontend/src/app/shared/interceptors/error-interceptor.service.ts b/frontend/src/app/shared/interceptors/error-interceptor.service.ts index 5fad27dc88..c22518aadc 100644 --- a/frontend/src/app/shared/interceptors/error-interceptor.service.ts +++ b/frontend/src/app/shared/interceptors/error-interceptor.service.ts @@ -30,7 +30,7 @@ export class ErrorInterceptor implements HttpInterceptor { if (!this.NO_ERROR_TOASTER_ROUTES.some((route) => response.url.includes(route))) { const errors = response.error.errors; errors.forEach((error: { errorKey: string; params: string[] }) => { - const template = this.translate.instant('TRANSLATIONS.' + error.errorKey); + const template = this.translate.instant('ERRORS.' + error.errorKey); const message = template.format(error.params); this.toasterService.showError(message); }); diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index 6af3045b5b..4b4d9a919b 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -21,7 +21,8 @@ "metric": "Metrisch", "ordinal": "Ordinal" }, - "TRANSLATIONS": { + + "ERRORS": { "UNAUTHORIZED": "Du bist nicht autorisiert um das Objekt '{0}' mit der Id {1} zu öffnen", "NOT_FOUND": "Das Objekt '{0}' mit der Id {1} konnte nicht gefunden werden", "KEYRESULT_CONVERSION": "Der Keyresulttyp {1} konnte weder zu metrisch noch zu ordinal konvertiert werden", From ff7262b4cdfe27c6d3416c22c451656d28fddd73 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 23 Nov 2023 10:44:02 +0100 Subject: [PATCH 08/38] implement toaster for warn and success --- .../interceptors/error-interceptor.service.ts | 43 +++++++++++++++---- .../app/shared/services/toaster.service.ts | 8 ++-- frontend/src/assets/i18n/de.json | 2 +- frontend/src/main.ts | 2 +- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/shared/interceptors/error-interceptor.service.ts b/frontend/src/app/shared/interceptors/error-interceptor.service.ts index c22518aadc..4a80313062 100644 --- a/frontend/src/app/shared/interceptors/error-interceptor.service.ts +++ b/frontend/src/app/shared/interceptors/error-interceptor.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http'; -import { catchError, filter, Observable, throwError } from 'rxjs'; +import { catchError, filter, Observable, tap, throwError } from 'rxjs'; import { Router } from '@angular/router'; import { drawerRoutes } from '../constantLibary'; import { ToasterService } from '../services/toaster.service'; @@ -9,6 +9,7 @@ import { TranslateService } from '@ngx-translate/core'; @Injectable() export class ErrorInterceptor implements HttpInterceptor { NO_ERROR_TOASTER_ROUTES = ['/token']; + constructor( private router: Router, private toasterService: ToasterService, @@ -18,6 +19,9 @@ export class ErrorInterceptor implements HttpInterceptor { intercept(request: HttpRequest, next: HttpHandler): Observable> { return next.handle(request).pipe( filter((event) => event instanceof HttpResponse), + tap((response) => { + this.handleSuccess(response, request.method); + }), catchError((response) => { this.handleErrorToaster(response); this.handleDrawerError(request); @@ -27,14 +31,20 @@ export class ErrorInterceptor implements HttpInterceptor { } handleErrorToaster(response: any) { - if (!this.NO_ERROR_TOASTER_ROUTES.some((route) => response.url.includes(route))) { - const errors = response.error.errors; - errors.forEach((error: { errorKey: string; params: string[] }) => { - const template = this.translate.instant('ERRORS.' + error.errorKey); - const message = template.format(error.params); - this.toasterService.showError(message); - }); + if (this.NO_ERROR_TOASTER_ROUTES.some((route) => response.url.includes(route))) { + return; + } + + const errors = response.error.errors.map((error: any) => + this.translate.instant('ERRORS.' + error.errorKey).format(error.params), + ); + + if (response.status == 226) { + errors.forEach((error: string) => this.toasterService.showWarn(error)); + return; } + + errors.forEach((error: string) => this.toasterService.showError(error)); } handleDrawerError(request: HttpRequest) { @@ -42,4 +52,21 @@ export class ErrorInterceptor implements HttpInterceptor { this.router.navigate(['']); } } + + handleSuccess(response: HttpEvent, method: string) { + switch (method) { + case 'POST': { + this.toasterService.showSuccess('Element wurde erfolgreich erstellt'); + break; + } + case 'PUT': { + this.toasterService.showSuccess('Element wurde erfolgreich aktualisiert'); + break; + } + case 'DELETE': { + this.toasterService.showSuccess('Element wurde erfolgreich gelöscht'); + break; + } + } + } } diff --git a/frontend/src/app/shared/services/toaster.service.ts b/frontend/src/app/shared/services/toaster.service.ts index 2a8f2c1ddf..1c455fd259 100644 --- a/frontend/src/app/shared/services/toaster.service.ts +++ b/frontend/src/app/shared/services/toaster.service.ts @@ -8,14 +8,14 @@ export class ToasterService { constructor(private toastr: ToastrService) {} showSuccess(msg: string) { - this.toastr.success(msg, 'Success'); + this.toastr.success(msg, 'Erfolgreich'); } showError(msg: string) { - this.toastr.error(msg, 'Error'); + this.toastr.error(msg, 'Fehler'); } - showInfo() { - this.toastr.info('Hello world!', 'Information'); + showWarn(msg: string) { + this.toastr.warning(msg, 'Warnung!'); } } diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index 4b4d9a919b..ace85ff53b 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -25,7 +25,7 @@ "ERRORS": { "UNAUTHORIZED": "Du bist nicht autorisiert um das Objekt '{0}' mit der Id {1} zu öffnen", "NOT_FOUND": "Das Objekt '{0}' mit der Id {1} konnte nicht gefunden werden", - "KEYRESULT_CONVERSION": "Der Keyresulttyp {1} konnte weder zu metrisch noch zu ordinal konvertiert werden", + "KEYRESULT_CONVERSION": "Der Keyresulttyp {0} konnte weder zu metrisch noch zu ordinal konvertiert werden", "ALREADY_EXISTS_SAME_NAME": "", "CONVERT_TOKEN": "", "UNSUPPORTED_METHOD_IN_CLASS": "", diff --git a/frontend/src/main.ts b/frontend/src/main.ts index bf4cb6e9c9..a69a45302c 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -11,7 +11,7 @@ if (environment.production) { declare global { interface String { - format(length: number): string; + format(): string; } } From b4cc2c4475af2a3653570a8d1d1c1a1d4c7e897a Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 23 Nov 2023 11:55:56 +0100 Subject: [PATCH 09/38] implement same error message for all backend models --- .../java/ch/puzzle/okr/models/Action.java | 10 +++---- .../java/ch/puzzle/okr/models/Completed.java | 4 +-- .../java/ch/puzzle/okr/models/ErrorMsg.java | 22 ++++++++++------ .../java/ch/puzzle/okr/models/Objective.java | 18 ++++++------- .../java/ch/puzzle/okr/models/Quarter.java | 6 ++--- .../main/java/ch/puzzle/okr/models/Team.java | 6 ++--- .../main/java/ch/puzzle/okr/models/User.java | 26 +++++++++---------- .../service/persistence/PersistenceBase.java | 2 +- .../ObjectiveValidationService.java | 6 ++--- 9 files changed, 53 insertions(+), 47 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/models/Action.java b/backend/src/main/java/ch/puzzle/okr/models/Action.java index cac4b1e6a0..ac827bb073 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/Action.java +++ b/backend/src/main/java/ch/puzzle/okr/models/Action.java @@ -16,17 +16,17 @@ public class Action implements WriteableInterface { @Version private int version; - @NotNull(message = "Action must not be null") - @Size(max = 4096, message = "Attribute Action has a max length of 4096 characters") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) + @Size(max = 4096, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String action; - @NotNull(message = "Priority must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) private int priority; - @NotNull(message = "IsChecked must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) private boolean isChecked; - @NotNull(message = "KeyResult must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @ManyToOne private KeyResult keyResult; diff --git a/backend/src/main/java/ch/puzzle/okr/models/Completed.java b/backend/src/main/java/ch/puzzle/okr/models/Completed.java index d4a70ba38e..b340c96224 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/Completed.java +++ b/backend/src/main/java/ch/puzzle/okr/models/Completed.java @@ -14,11 +14,11 @@ public class Completed { @Version private int version; - @NotNull(message = "Objective must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @OneToOne private Objective objective; - @Size(max = 4096, message = "Attribute comment has a max length of 4096 characters when completing an objective") + @Size(max = 4096, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String comment; public Completed() { diff --git a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java index ab8f892f2d..d7def5c404 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java +++ b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java @@ -9,18 +9,24 @@ private ErrorMsg() { public static final String KEYRESULT_CONVERSION = "KEYRESULT_CONVERSION"; public static final String ALREADY_EXISTS_SAME_NAME = "ALREADY_EXISTS_SAME_NAME"; public static final String CONVERT_TOKEN = "CONVERT_TOKEN"; - public static final String UNSUPPORTED_METHOD_IN_CLASS = "UNSUPPORTED_METHOD_IN_CLASS"; + public static final String DATA_HAS_BEEN_UPDATED = "DATA_HAS_BEEN_UPDATED"; + // Model public static final String MODEL_NULL = "MODEL_NULL"; + public static final String MODEL_WITH_ID_NOT_FOUND = "MODEL_WITH_ID_NOT_FOUND"; + + // Attributes public static final String ATTRIBUTE_NULL = "ATTRIBUTE_NULL"; public static final String ATTRIBUTE_NOT_NULL = "ATTRIBUTE_NOT_NULL"; public static final String ATTRIBUTE_CHANGED = "ATTRIBUTE_CHANGED"; - public static final String SIZE_BETWEEN = "SIZE_BETWEEN_{min}_{max}"; - public static final String EMPTY_ATTRIBUTE_ON_MODEL = "EMPTY_ATTRIBUTE_ON_MODEL"; - public static final String NULL_ATTRIBUTE_ON_MODEL = "NULL_ATTRIBUTE_ON_MODEL"; - public static final String FORBIDDEN_SET_ATTRIBUTE = "FORBIDDEN_SET_ATTRIBUTE"; - public static final String MODIFIED_BY_NOT_SET = "MODIFIED_BY_NOT_SET"; + public static final String ATTRIBUTE_NOT_BLANK = "ATTRIBUTE_NOT_BLANK"; + + public static final String ATTRIBUTE_NOT_VALID = "ATTRIBUTE_VALID"; + + public static final String ATTRIBUTE_SIZE_BETWEEN = "ATTRIBUTE_SIZE_BETWEEN_{min}_{max}"; + public static final String ATTRIBUTE_EMPTY_ON_MODEL = "ATTRIBUTE_EMPTY_ON_MODEL"; + public static final String ATTRIBUTE_NULL_ON_MODEL = "ATTRIBUTE_NULL_ON_MODEL"; + public static final String ATTRIBUTE_SET_FORBIDDEN = "ATTRIBUTE_SET_FORBIDDEN"; + public static final String ATTRIBUTE_MODIFIEDBY_NOT_SET = "ATTRIBUTE_MODIFIEDBY_NOT_SET"; public static final String ATTRIBUTE_CANNOT_CHANGE = "ATTRIBUTE_CANNOT_CHANGE"; - public static final String MODEL_WITH_ID_NOT_FOUND = "MODEL_WITH_ID_NOT_FOUND"; - public static final String DATA_HAS_BEEN_UPDATED = "DATA_HAS_BEEN_UPDATED"; } diff --git a/backend/src/main/java/ch/puzzle/okr/models/Objective.java b/backend/src/main/java/ch/puzzle/okr/models/Objective.java index c967a06762..a979d9a7d4 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/Objective.java +++ b/backend/src/main/java/ch/puzzle/okr/models/Objective.java @@ -17,31 +17,31 @@ public class Objective implements WriteableInterface { @Version private int version; - @NotBlank(message = "Missing attribute title when saving objective") - @NotNull(message = "Attribute title can not be null when saving objective") - @Size(min = 2, max = 250, message = "Attribute title must have a length between 2 and 250 characters when saving objective") + @NotBlank(message = ErrorMsg.ATTRIBUTE_NOT_BLANK) + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) + @Size(min = 2, max = 250, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String title; - @NotNull(message = "State must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @Enumerated(EnumType.STRING) private State state; - @Size(max = 4096, message = "Attribute description has a max length of 4096 characters when saving objective") + @Size(max = 4096, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String description; - @NotNull(message = "Team must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @ManyToOne private Team team; - @NotNull(message = "Quarter must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @ManyToOne private Quarter quarter; - @NotNull(message = "CreatedBy must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @ManyToOne private User createdBy; - @NotNull(message = "CreatedOn must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) private LocalDateTime createdOn; private LocalDateTime modifiedOn; diff --git a/backend/src/main/java/ch/puzzle/okr/models/Quarter.java b/backend/src/main/java/ch/puzzle/okr/models/Quarter.java index e2900f6acf..f707aee8ae 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/Quarter.java +++ b/backend/src/main/java/ch/puzzle/okr/models/Quarter.java @@ -11,14 +11,14 @@ public class Quarter { @GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence_quarter") private Long id; - @NotNull(message = "Attribute label can not be null when saving quarter") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @Column(unique = true) private String label; - @NotNull(message = "Attribute startDate can not be null when saving quarter") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) private LocalDate startDate; - @NotNull(message = "Attribute endDate can not be null when saving quarter") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) private LocalDate endDate; public Quarter() { diff --git a/backend/src/main/java/ch/puzzle/okr/models/Team.java b/backend/src/main/java/ch/puzzle/okr/models/Team.java index 54f20fddb2..520ec423f8 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/Team.java +++ b/backend/src/main/java/ch/puzzle/okr/models/Team.java @@ -13,9 +13,9 @@ public class Team implements WriteableInterface { @GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence_team") private Long id; - @NotBlank(message = ErrorMsg.EMPTY_ATTRIBUTE_ON_MODEL) - @NotNull(message = ErrorMsg.NULL_ATTRIBUTE_ON_MODEL) - @Size(min = 2, max = 250, message = ErrorMsg.SIZE_BETWEEN) + @NotBlank(message = ErrorMsg.ATTRIBUTE_EMPTY_ON_MODEL) + @NotNull(message = ErrorMsg.ATTRIBUTE_NULL_ON_MODEL) + @Size(min = 2, max = 250, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String name; @Version diff --git a/backend/src/main/java/ch/puzzle/okr/models/User.java b/backend/src/main/java/ch/puzzle/okr/models/User.java index 3ff112f9af..84bde20357 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/User.java +++ b/backend/src/main/java/ch/puzzle/okr/models/User.java @@ -19,25 +19,25 @@ public class User implements WriteableInterface { private int version; @Column(unique = true) - @NotBlank(message = "Missing attribute username when saving user") - @NotNull(message = "Attribute username can not be null when saving user") - @Size(min = 2, max = 20, message = "Attribute username must have size between 2 and 20 characters when saving user") + @NotBlank(message = ErrorMsg.ATTRIBUTE_NOT_BLANK) + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) + @Size(min = 2, max = 20, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String username; - @NotBlank(message = "Missing attribute firstname when saving user") - @NotNull(message = "Attribute firstname can not be null when saving user") - @Size(min = 2, max = 50, message = "Attribute firstname must have size between 2 and 50 characters when saving user") + @NotBlank(message = ErrorMsg.ATTRIBUTE_NOT_BLANK) + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) + @Size(min = 2, max = 50, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String firstname; - @NotBlank(message = "Missing attribute lastname when saving user") - @NotNull(message = "Attribute lastname can not be null when saving user") - @Size(min = 2, max = 50, message = "Attribute lastname must have size between 2 and 50 characters when saving user") + @NotBlank(message = ErrorMsg.ATTRIBUTE_NOT_BLANK) + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) + @Size(min = 2, max = 50, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String lastname; - @Email(message = "Attribute email should be valid when saving user") - @NotNull(message = "Attribute email can not be null when saving user") - @NotBlank(message = "Missing attribute email when saving user") - @Size(min = 2, max = 250, message = "Attribute email must have size between 2 and 250 characters when saving user") + @Email(message = ErrorMsg.ATTRIBUTE_NOT_VALID) + @NotBlank(message = ErrorMsg.ATTRIBUTE_NOT_BLANK) + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) + @Size(min = 2, max = 250, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String email; private transient boolean writeable; diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java index bad0470ac9..36cfa61baf 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java @@ -42,7 +42,7 @@ public T findById(ID id) throws OkrResponseStatusException { public void checkIdNull(ID id) { if (id == null) { - throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.NULL_ATTRIBUTE_ON_MODEL); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NULL_ON_MODEL); // throw new ResponseStatusException(HttpStatus.BAD_REQUEST, // format("Missing identifier for %s", getModelName())); } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java b/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java index 41eb0c9adb..6fdaf12f7d 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java @@ -8,7 +8,6 @@ import ch.puzzle.okr.service.persistence.ObjectivePersistenceService; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; import java.util.List; import java.util.Objects; @@ -45,7 +44,7 @@ public void validateOnUpdate(Long id, Objective model) { private static void throwExceptionWhenModifiedByIsSet(Objective model) { if (model.getModifiedBy() != null) { - throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.FORBIDDEN_SET_ATTRIBUTE, + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_SET_FORBIDDEN, List.of("ModifiedBy")); // throw new ResponseStatusException(BAD_REQUEST, // format("Not allowed to set ModifiedBy %s on create", model.getModifiedBy())); @@ -54,7 +53,8 @@ private static void throwExceptionWhenModifiedByIsSet(Objective model) { private static void throwExceptionWhenModifiedByIsNull(Objective model) { if (model.getModifiedBy() == null) { - throw new OkrResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, ErrorMsg.MODIFIED_BY_NOT_SET); + throw new OkrResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, + ErrorMsg.ATTRIBUTE_MODIFIEDBY_NOT_SET); // throw new ResponseStatusException(INTERNAL_SERVER_ERROR, // format("Something went wrong. ModifiedBy %s is not set.", model.getModifiedBy())); From f3bbdf64bca74b058c27fcafa4ba34fcb594629f Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 23 Nov 2023 13:15:48 +0100 Subject: [PATCH 10/38] getAttributes --- .../service/validation/ValidationBase.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java index 52b9b1375c..d2548ef4a8 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java @@ -10,9 +10,9 @@ import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.*; +import java.util.regex.MatchResult; +import java.util.regex.Pattern; /** * @param @@ -95,10 +95,20 @@ public void validate(T model) { private void processViolations(Set> violations) { if (!violations.isEmpty()) { - List list = violations.stream().map( - e -> new ErrorDto(e.getMessage(), List.of(persistenceService.getModelName(), e.getPropertyPath()))) - .toList(); + List list = violations.stream().map(e -> { + List attributes = new ArrayList<>( + List.of(persistenceService.getModelName(), e.getPropertyPath().toString())); + attributes.addAll(getAttributes(e.getMessage(), e.getMessageTemplate())); + String errorKey = e.getMessage().replaceAll("_\\{.*", ""); + return new ErrorDto(errorKey, Collections.singletonList(attributes)); + }).toList(); throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, list); } } + + private List getAttributes(String message, String messageTemplate) { + String pattern = messageTemplate.replaceAll("\\{([^}]*)\\}", "(.*)"); + + return Pattern.compile(pattern).matcher(message).results().map(MatchResult::group).toList(); + } } From 201e5389c24cffd7b98902b07b880dd36721aa75 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 23 Nov 2023 13:22:12 +0100 Subject: [PATCH 11/38] don't show create for token --- backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java | 2 -- .../src/app/shared/interceptors/error-interceptor.service.ts | 5 ++++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java index d7def5c404..7cfdf6e670 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java +++ b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java @@ -19,9 +19,7 @@ private ErrorMsg() { public static final String ATTRIBUTE_NOT_NULL = "ATTRIBUTE_NOT_NULL"; public static final String ATTRIBUTE_CHANGED = "ATTRIBUTE_CHANGED"; public static final String ATTRIBUTE_NOT_BLANK = "ATTRIBUTE_NOT_BLANK"; - public static final String ATTRIBUTE_NOT_VALID = "ATTRIBUTE_VALID"; - public static final String ATTRIBUTE_SIZE_BETWEEN = "ATTRIBUTE_SIZE_BETWEEN_{min}_{max}"; public static final String ATTRIBUTE_EMPTY_ON_MODEL = "ATTRIBUTE_EMPTY_ON_MODEL"; public static final String ATTRIBUTE_NULL_ON_MODEL = "ATTRIBUTE_NULL_ON_MODEL"; diff --git a/frontend/src/app/shared/interceptors/error-interceptor.service.ts b/frontend/src/app/shared/interceptors/error-interceptor.service.ts index 4a80313062..2d6077deba 100644 --- a/frontend/src/app/shared/interceptors/error-interceptor.service.ts +++ b/frontend/src/app/shared/interceptors/error-interceptor.service.ts @@ -53,7 +53,10 @@ export class ErrorInterceptor implements HttpInterceptor { } } - handleSuccess(response: HttpEvent, method: string) { + handleSuccess(response: any, method: string) { + if (this.NO_ERROR_TOASTER_ROUTES.some((route) => response.url.includes(route))) { + return; + } switch (method) { case 'POST': { this.toasterService.showSuccess('Element wurde erfolgreich erstellt'); From da4fc6580ef08342ef42967971ca6a1f6ef6ae6a Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 23 Nov 2023 13:24:09 +0100 Subject: [PATCH 12/38] fix error msgs --- backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java index 7cfdf6e670..91f1de86e7 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java +++ b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java @@ -19,7 +19,7 @@ private ErrorMsg() { public static final String ATTRIBUTE_NOT_NULL = "ATTRIBUTE_NOT_NULL"; public static final String ATTRIBUTE_CHANGED = "ATTRIBUTE_CHANGED"; public static final String ATTRIBUTE_NOT_BLANK = "ATTRIBUTE_NOT_BLANK"; - public static final String ATTRIBUTE_NOT_VALID = "ATTRIBUTE_VALID"; + public static final String ATTRIBUTE_NOT_VALID = "ATTRIBUTE_NOT_VALID"; public static final String ATTRIBUTE_SIZE_BETWEEN = "ATTRIBUTE_SIZE_BETWEEN_{min}_{max}"; public static final String ATTRIBUTE_EMPTY_ON_MODEL = "ATTRIBUTE_EMPTY_ON_MODEL"; public static final String ATTRIBUTE_NULL_ON_MODEL = "ATTRIBUTE_NULL_ON_MODEL"; From 99b7a71073005dc4921efdf739475a6ae4ede315 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 23 Nov 2023 14:07:20 +0100 Subject: [PATCH 13/38] finish all error msg --- .../java/ch/puzzle/okr/models/ErrorMsg.java | 4 +-- .../main/java/ch/puzzle/okr/models/Team.java | 4 +-- .../ObjectivePersistenceService.java | 2 +- .../service/persistence/PersistenceBase.java | 4 --- .../ObjectiveValidationService.java | 16 +++------- .../validation/TeamValidationService.java | 1 - .../service/validation/ValidationBase.java | 7 +++-- frontend/src/assets/i18n/de.json | 31 +++++++++---------- 8 files changed, 27 insertions(+), 42 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java index 91f1de86e7..37f57f2e03 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java +++ b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java @@ -21,10 +21,8 @@ private ErrorMsg() { public static final String ATTRIBUTE_NOT_BLANK = "ATTRIBUTE_NOT_BLANK"; public static final String ATTRIBUTE_NOT_VALID = "ATTRIBUTE_NOT_VALID"; public static final String ATTRIBUTE_SIZE_BETWEEN = "ATTRIBUTE_SIZE_BETWEEN_{min}_{max}"; - public static final String ATTRIBUTE_EMPTY_ON_MODEL = "ATTRIBUTE_EMPTY_ON_MODEL"; - public static final String ATTRIBUTE_NULL_ON_MODEL = "ATTRIBUTE_NULL_ON_MODEL"; public static final String ATTRIBUTE_SET_FORBIDDEN = "ATTRIBUTE_SET_FORBIDDEN"; - public static final String ATTRIBUTE_MODIFIEDBY_NOT_SET = "ATTRIBUTE_MODIFIEDBY_NOT_SET"; + public static final String ATTRIBUTE_NOT_SET = "ATTRIBUTE_NOT_SET"; public static final String ATTRIBUTE_CANNOT_CHANGE = "ATTRIBUTE_CANNOT_CHANGE"; } diff --git a/backend/src/main/java/ch/puzzle/okr/models/Team.java b/backend/src/main/java/ch/puzzle/okr/models/Team.java index 520ec423f8..dd8277bbe2 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/Team.java +++ b/backend/src/main/java/ch/puzzle/okr/models/Team.java @@ -13,8 +13,8 @@ public class Team implements WriteableInterface { @GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence_team") private Long id; - @NotBlank(message = ErrorMsg.ATTRIBUTE_EMPTY_ON_MODEL) - @NotNull(message = ErrorMsg.ATTRIBUTE_NULL_ON_MODEL) + @NotBlank(message = ErrorMsg.ATTRIBUTE_NOT_BLANK) + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @Size(min = 2, max = 250, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String name; diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java index 65787d7372..8d17df7c94 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java @@ -68,7 +68,7 @@ private Objective findByAnyId(Long id, AuthorizationUser authorizationUser, Stri try { return typedQuery.getSingleResult(); } catch (NoResultException exception) { - throw new OkrResponseStatusException(UNAUTHORIZED, ErrorMsg.UNAUTHORIZED, List.of("Objective", id)); + throw new OkrResponseStatusException(UNAUTHORIZED, ErrorMsg.UNAUTHORIZED, List.of(id)); } } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java index 36cfa61baf..e06718983c 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java @@ -61,10 +61,6 @@ public T save(T model) throws OkrResponseStatusException { logger.info("optimistic locking exception while saving {}", model, ex); throw new OkrResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY, ErrorMsg.DATA_HAS_BEEN_UPDATED, List.of(getModelName())); - - // throw new ResponseStatusException(UNPROCESSABLE_ENTITY, - // String.format("The data of %s has been updated or deleted by another user." + - // "Please reload the data and apply your changes again.", getModelName())); } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java b/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java index 6fdaf12f7d..9d1c4cee9b 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java @@ -45,29 +45,21 @@ private static void throwExceptionWhenModifiedByIsSet(Objective model) { if (model.getModifiedBy() != null) { throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_SET_FORBIDDEN, - List.of("ModifiedBy")); - // throw new ResponseStatusException(BAD_REQUEST, - // format("Not allowed to set ModifiedBy %s on create", model.getModifiedBy())); + List.of("ModifiedBy", model.getModifiedBy())); } } private static void throwExceptionWhenModifiedByIsNull(Objective model) { if (model.getModifiedBy() == null) { - throw new OkrResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, - ErrorMsg.ATTRIBUTE_MODIFIEDBY_NOT_SET); - - // throw new ResponseStatusException(INTERNAL_SERVER_ERROR, - // format("Something went wrong. ModifiedBy %s is not set.", model.getModifiedBy())); + throw new OkrResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, ErrorMsg.ATTRIBUTE_NOT_SET, + List.of(model.getModifiedBy())); } } private static void throwExceptionWhenTeamHasChanged(Team team, Team savedTeam) { if (!Objects.equals(team, savedTeam)) { throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_CANNOT_CHANGE, - List.of(team.getName(), savedTeam.getName())); - - // throw new ResponseStatusException(BAD_REQUEST, format( - // "The team can not be changed (new team %s, old team %s)", )); + List.of("Team", "Objective")); } } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/TeamValidationService.java b/backend/src/main/java/ch/puzzle/okr/service/validation/TeamValidationService.java index 08173a176a..99c8bdc517 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/TeamValidationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/TeamValidationService.java @@ -34,7 +34,6 @@ public void validateOnUpdate(Long id, Team model) { throwExceptionWhenIdHasChanged(id, model.getId()); doesEntityExist(model.getId()); checkIfTeamWithNameAlreadyExists(model.getName(), model.getId()); - // Can't update to team with already existing name validate(model); } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java index d2548ef4a8..a13b38bd2a 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java @@ -66,7 +66,8 @@ public void throwExceptionWhenModelIsNull(T model) { public void throwExceptionWhenIdIsNull(ID id) { if (id == null) { - throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NULL, List.of("ID")); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NULL, + List.of("ID", persistenceService.getModelName())); // Id is null } } @@ -82,7 +83,7 @@ protected void throwExceptionWhenIdIsNotNull(ID id) { protected void throwExceptionWhenIdHasChanged(ID id, ID modelId) { if (!Objects.equals(id, modelId)) { throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_CHANGED, - List.of(id, modelId)); + List.of("ID", id, modelId)); // Id %s has changed to %s during update } @@ -97,7 +98,7 @@ private void processViolations(Set> violations) { if (!violations.isEmpty()) { List list = violations.stream().map(e -> { List attributes = new ArrayList<>( - List.of(persistenceService.getModelName(), e.getPropertyPath().toString())); + List.of(e.getPropertyPath().toString(), persistenceService.getModelName())); attributes.addAll(getAttributes(e.getMessage(), e.getMessageTemplate())); String errorKey = e.getMessage().replaceAll("_\\{.*", ""); return new ErrorDto(errorKey, Collections.singletonList(attributes)); diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index ace85ff53b..390a7680aa 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -23,23 +23,22 @@ }, "ERRORS": { - "UNAUTHORIZED": "Du bist nicht autorisiert um das Objekt '{0}' mit der Id {1} zu öffnen", + "UNAUTHORIZED": "Du bist nicht autorisiert um das Objekt mit der Id {1} zu öffnen", "NOT_FOUND": "Das Objekt '{0}' mit der Id {1} konnte nicht gefunden werden", "KEYRESULT_CONVERSION": "Der Keyresulttyp {0} konnte weder zu metrisch noch zu ordinal konvertiert werden", - "ALREADY_EXISTS_SAME_NAME": "", - "CONVERT_TOKEN": "", - "UNSUPPORTED_METHOD_IN_CLASS": "", - "MODEL_NULL": "", - "ATTRIBUTE_NULL": "", - "ATTRIBUTE_NOT_NULL": "", - "ATTRIBUTE_CHANGED": "", - "SIZE_BETWEEN": "", - "EMPTY_ATTRIBUTE_ON_MODEL": "", - "NULL_ATTRIBUTE_ON_MODEL": "", - "FORBIDDEN_SET_ATTRIBUTE": "", - "MODIFIED_BY_NOT_SET": "", - "ATTRIBUTE_CANNOT_CHANGE": "", - "MODEL_WITH_ID_NOT_FOUND": "", - "DATA_HAS_BEEN_UPDATED": "" + "ALREADY_EXISTS_SAME_NAME": "{0} mit diesem Namen existiert bereits", + "CONVERT_TOKEN": "Benutzer kann nicht aus Token konvertiert werden", + "DATA_HAS_BEEN_UPDATED": "Die Daten des {0} wurden bereits von einem anderen Benutzer geändert oder gelöscht", + "MODEL_NULL": "Objekt {0} ist null", + "MODEL_WITH_ID_NOT_FOUND": "Objekt {0} mit der Id {1} konnte nicht gefunden werden", + "ATTRIBUTE_NULL": "Attribut {0} auf dem Objekt {1} ist null", + "ATTRIBUTE_NOT_NULL": "Das Attribut {0} auf dem Objekt {0} darf nicht null sein", + "ATTRIBUTE_CHANGED": "Das Attribut {0} wurde während des Updated von {1} auf {2} geändert", + "ATTRIBUTE_NOT_BLANK": "Das Attribut {0} auf dem Objekt {1} darf nicht leer sein", + "ATTRIBUTE_NOT_VALID": "Das Attribut {0} auf dem Objekt {1} ist ungültig", + "ATTRIBUTE_SIZE_BETWEEN": "Das Attribut {0} auf dem Objekt {1} muss folgende länge haben: {2}-{3}", + "ATTRIBUTE_SET_FORBIDDEN": "Das Attribut {0} darf nicht während dem erstellen gesetzt sein ", + "ATTRIBUTE_NOT_SET": "Ein Fehler ist aufgetreten.\n Das Attribut {0} ist nicht gesetzt", + "ATTRIBUTE_CANNOT_CHANGE": "Das Attribut {0} auf dem Objekt {1} kann nicht geändert werden" } } From 87d9b2b260b154aa8462cdd951b2b3c4154ac079 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 23 Nov 2023 14:16:44 +0100 Subject: [PATCH 14/38] clean up --- .../src/main/java/ch/puzzle/okr/OkrErrorAttributes.java | 2 +- .../main/java/ch/puzzle/okr/mapper/OverviewMapper.java | 1 - .../service/persistence/ObjectivePersistenceService.java | 8 ++++---- .../puzzle/okr/service/persistence/PersistenceBase.java | 5 +---- .../okr/service/validation/TeamValidationService.java | 1 - .../ch/puzzle/okr/service/validation/ValidationBase.java | 5 ----- 6 files changed, 6 insertions(+), 16 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java b/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java index 1b9b04a6b9..15f6b8d214 100644 --- a/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java +++ b/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java @@ -21,4 +21,4 @@ public Map getErrorAttributes(WebRequest webRequest, ErrorAttrib } return errorAttributes; } -} \ No newline at end of file +} diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java index e21174c2cd..b6d7f3f614 100644 --- a/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java +++ b/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java @@ -92,7 +92,6 @@ private OverviewKeyResultDto createKeyResultDto(Overview overview) { } else { throw new OkrResponseStatusException(BAD_REQUEST, ErrorMsg.KEYRESULT_CONVERSION, List.of(overview.getKeyResultType())); - // "The key result type %s can not be converted to a metric or ordinal DTO" } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java index 8d17df7c94..440779be7c 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java @@ -43,7 +43,7 @@ public Integer countByTeamAndQuarter(Team team, Quarter quarter) { } public Objective findObjectiveById(Long objectiveId, AuthorizationUser authorizationUser, String reason) { - return findByAnyId(objectiveId, authorizationUser, SELECT_OBJECTIVE_BY_ID, reason); + return findByAnyId(objectiveId, authorizationUser, SELECT_OBJECTIVE_BY_ID); } public List findObjectiveByTeamId(Long teamId) { @@ -51,14 +51,14 @@ public List findObjectiveByTeamId(Long teamId) { } public Objective findObjectiveByKeyResultId(Long keyResultId, AuthorizationUser authorizationUser, String reason) { - return findByAnyId(keyResultId, authorizationUser, SELECT_OBJECTIVE_BY_KEY_RESULT_ID, reason); + return findByAnyId(keyResultId, authorizationUser, SELECT_OBJECTIVE_BY_KEY_RESULT_ID); } public Objective findObjectiveByCheckInId(Long checkInId, AuthorizationUser authorizationUser, String reason) { - return findByAnyId(checkInId, authorizationUser, SELECT_OBJECTIVE_BY_CHECK_IN_ID, reason); + return findByAnyId(checkInId, authorizationUser, SELECT_OBJECTIVE_BY_CHECK_IN_ID); } - private Objective findByAnyId(Long id, AuthorizationUser authorizationUser, String queryString, String reason) { + private Objective findByAnyId(Long id, AuthorizationUser authorizationUser, String queryString) { checkIdNull(id); String fullQueryString = queryString + authorizationCriteria.appendObjective(authorizationUser); logger.debug("select objective by id={}: {}", id, fullQueryString); diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java index e06718983c..ddcff5df5b 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java @@ -42,16 +42,13 @@ public T findById(ID id) throws OkrResponseStatusException { public void checkIdNull(ID id) { if (id == null) { - throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NULL_ON_MODEL); - // throw new ResponseStatusException(HttpStatus.BAD_REQUEST, - // format("Missing identifier for %s", getModelName())); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NULL); } } public OkrResponseStatusException createEntityNotFoundException(ID id) { throw new OkrResponseStatusException(HttpStatus.NOT_FOUND, ErrorMsg.MODEL_WITH_ID_NOT_FOUND, List.of(getModelName(), id)); - // return new ResponseStatusException(NOT_FOUND, format("%s with id %s not found", getModelName(), id)); } public T save(T model) throws OkrResponseStatusException { diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/TeamValidationService.java b/backend/src/main/java/ch/puzzle/okr/service/validation/TeamValidationService.java index 99c8bdc517..b5bbf21bb5 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/TeamValidationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/TeamValidationService.java @@ -23,7 +23,6 @@ public void validateOnCreate(Team model) { throwExceptionWhenModelIsNull(model); throwExceptionWhenIdIsNotNull(model.getId()); checkIfTeamWithNameAlreadyExists(model.getName(), model.getId()); - // Can't create team with already existing name validate(model); } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java index a13b38bd2a..22b7dddb2b 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java @@ -60,7 +60,6 @@ public void throwExceptionWhenModelIsNull(T model) { if (model == null) { throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.MODEL_NULL, List.of(persistenceService.getModelName())); - // Given model %s is null } } @@ -68,7 +67,6 @@ public void throwExceptionWhenIdIsNull(ID id) { if (id == null) { throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NULL, List.of("ID", persistenceService.getModelName())); - // Id is null } } @@ -76,7 +74,6 @@ protected void throwExceptionWhenIdIsNotNull(ID id) { if (id != null) { throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NOT_NULL, List.of(persistenceService.getModelName(), id)); - // Model %s cannot have id while create. Found id %s } } @@ -84,8 +81,6 @@ protected void throwExceptionWhenIdHasChanged(ID id, ID modelId) { if (!Objects.equals(id, modelId)) { throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_CHANGED, List.of("ID", id, modelId)); - - // Id %s has changed to %s during update } } From 96c104ab5ab7d54497447196449f8da5ca8ef6d4 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 23 Nov 2023 15:56:19 +0100 Subject: [PATCH 15/38] use error dto for auth responses --- .../main/java/ch/puzzle/okr/Constants.java | 5 ++ .../main/java/ch/puzzle/okr/dto/ErrorDto.java | 4 + .../java/ch/puzzle/okr/models/ErrorMsg.java | 5 +- .../models/OkrResponseStatusException.java | 4 + .../authorization/AuthorizationService.java | 77 +++++++++++-------- .../ObjectivePersistenceService.java | 17 ++-- .../validation/UserValidationService.java | 10 +++ 7 files changed, 80 insertions(+), 42 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/Constants.java b/backend/src/main/java/ch/puzzle/okr/Constants.java index 7f2c3eb6c2..cae4c98e57 100644 --- a/backend/src/main/java/ch/puzzle/okr/Constants.java +++ b/backend/src/main/java/ch/puzzle/okr/Constants.java @@ -6,4 +6,9 @@ private Constants() { public static final String KEY_RESULT_TYPE_METRIC = "metric"; public static final String KEY_RESULT_TYPE_ORDINAL = "ordinal"; + public static final String OBJECTIVE = "Objective"; + public static final String KEY_RESULT = "KeyResult"; + public static final String CHECK_IN = "Check-in"; + public static final String ACTION = "Action"; + } diff --git a/backend/src/main/java/ch/puzzle/okr/dto/ErrorDto.java b/backend/src/main/java/ch/puzzle/okr/dto/ErrorDto.java index a346f7d215..971e44528e 100644 --- a/backend/src/main/java/ch/puzzle/okr/dto/ErrorDto.java +++ b/backend/src/main/java/ch/puzzle/okr/dto/ErrorDto.java @@ -11,6 +11,10 @@ public ErrorDto(String errorKey, List params) { this.params = params.stream().map(Object::toString).toList(); } + public ErrorDto(String errorKey, String param) { + this(errorKey, List.of(param)); + } + public String getErrorKey() { return errorKey; } diff --git a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java index 37f57f2e03..c7bb5bd378 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java +++ b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java @@ -24,5 +24,8 @@ private ErrorMsg() { public static final String ATTRIBUTE_SET_FORBIDDEN = "ATTRIBUTE_SET_FORBIDDEN"; public static final String ATTRIBUTE_NOT_SET = "ATTRIBUTE_NOT_SET"; public static final String ATTRIBUTE_CANNOT_CHANGE = "ATTRIBUTE_CANNOT_CHANGE"; - + public static final String NOT_AUTHORIZED_TO_READ = "NOT_AUTHORIZED_TO_READ"; + public static final String NOT_AUTHORIZED_TO_WRITE = "NOT_AUTHORIZED_TO_WRITE"; + public static final String NOT_AUTHORIZED_TO_DELETE = "NOT_AUTHORIZED_TO_DELETE"; + public static final String TOKEN_NULL = "TOKEN_NULL"; } diff --git a/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java index 2134d4e53b..69b49080e5 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java +++ b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java @@ -18,6 +18,10 @@ public OkrResponseStatusException(HttpStatus status, String errorKey, List errors) { super(status, errors.get(0).getErrorKey()); this.errors = errors; diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java index b264e03750..cb88f822ec 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java @@ -1,5 +1,9 @@ package ch.puzzle.okr.service.authorization; +import ch.puzzle.okr.Constants; +import ch.puzzle.okr.converter.JwtUserConverter; +import ch.puzzle.okr.dto.ErrorDto; +import ch.puzzle.okr.models.*; import ch.puzzle.okr.converter.JwtConverterFactory; import ch.puzzle.okr.models.Action; import ch.puzzle.okr.models.Objective; @@ -10,28 +14,18 @@ import ch.puzzle.okr.models.keyresult.KeyResult; import ch.puzzle.okr.service.persistence.ActionPersistenceService; import ch.puzzle.okr.service.persistence.ObjectivePersistenceService; +import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; import static ch.puzzle.okr.models.authorization.AuthorizationRole.*; -import static org.springframework.http.HttpStatus.UNAUTHORIZED; @Service public class AuthorizationService { - private static final String NOT_AUTHORIZED_TO_READ_OBJECTIVE = "not authorized to read objective"; - private static final String NOT_AUTHORIZED_TO_READ_KEY_RESULT = "not authorized to read key result"; - private static final String NOT_AUTHORIZED_TO_READ_CHECK_IN = "not authorized to read check in"; - private static final String NOT_AUTHORIZED_TO_WRITE_OBJECTIVE = "not authorized to create or update objective"; - private static final String NOT_AUTHORIZED_TO_WRITE_KEY_RESULT = "not authorized to create or update key result"; - private static final String NOT_AUTHORIZED_TO_WRITE_CHECK_IN = "not authorized to create or update check in"; - private static final String NOT_AUTHORIZED_TO_DELETE_OBJECTIVE = "not authorized to delete objective"; - private static final String NOT_AUTHORIZED_TO_DELETE_KEY_RESULT = "not authorized to delete key result"; - private static final String NOT_AUTHORIZED_TO_DELETE_ACTION = "not authorized to delete action"; - private static final String NOT_AUTHORIZED_TO_DELETE_CHECK_IN = "not authorized to delete check in"; + private final AuthorizationRegistrationService authorizationRegistrationService; private final ObjectivePersistenceService objectivePersistenceService; private final ActionPersistenceService actionPersistenceService; @@ -83,33 +77,40 @@ public AuthorizationUser getAuthorizationUser() { } public void hasRoleReadByObjectiveId(Long objectiveId, AuthorizationUser authorizationUser) { - objectivePersistenceService.findObjectiveById(objectiveId, authorizationUser, NOT_AUTHORIZED_TO_READ_OBJECTIVE); + objectivePersistenceService.findObjectiveById(objectiveId, authorizationUser, + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_READ, Constants.OBJECTIVE)); } public void hasRoleReadByKeyResultId(Long keyResultId, AuthorizationUser authorizationUser) { objectivePersistenceService.findObjectiveByKeyResultId(keyResultId, authorizationUser, - NOT_AUTHORIZED_TO_READ_KEY_RESULT); + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_READ, Constants.KEY_RESULT)); } public void hasRoleReadByCheckInId(Long checkInId, AuthorizationUser authorizationUser) { objectivePersistenceService.findObjectiveByCheckInId(checkInId, authorizationUser, - NOT_AUTHORIZED_TO_READ_CHECK_IN); + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_READ, Constants.CHECK_IN)); } public void hasRoleCreateOrUpdate(Objective objective, AuthorizationUser authorizationUser) { - hasRoleWrite(authorizationUser, objective.getTeam(), NOT_AUTHORIZED_TO_WRITE_OBJECTIVE); + + hasRoleWrite(authorizationUser, objective.getTeam(), + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_WRITE, Constants.OBJECTIVE)); } public void hasRoleCreateOrUpdate(KeyResult keyResult, AuthorizationUser authorizationUser) { Objective objective = objectivePersistenceService.findObjectiveById(keyResult.getObjective().getId(), - authorizationUser, NOT_AUTHORIZED_TO_READ_KEY_RESULT); - hasRoleWrite(authorizationUser, objective.getTeam(), NOT_AUTHORIZED_TO_WRITE_KEY_RESULT); + authorizationUser, new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_READ, Constants.KEY_RESULT)); + + hasRoleWrite(authorizationUser, objective.getTeam(), + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_WRITE, Constants.KEY_RESULT)); } public void hasRoleCreateOrUpdate(CheckIn checkIn, AuthorizationUser authorizationUser) { Objective objective = objectivePersistenceService.findObjectiveByKeyResultId(checkIn.getKeyResult().getId(), - authorizationUser, NOT_AUTHORIZED_TO_READ_CHECK_IN); - hasRoleWrite(authorizationUser, objective.getTeam(), NOT_AUTHORIZED_TO_WRITE_CHECK_IN); + authorizationUser, new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_READ, Constants.CHECK_IN)); + + hasRoleWrite(authorizationUser, objective.getTeam(), + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_WRITE, Constants.CHECK_IN)); } public boolean isWriteable(Objective objective, AuthorizationUser authorizationUser) { @@ -118,51 +119,61 @@ public boolean isWriteable(Objective objective, AuthorizationUser authorizationU public boolean isWriteable(KeyResult keyResult, AuthorizationUser authorizationUser) { Objective objective = objectivePersistenceService.findObjectiveById(keyResult.getObjective().getId(), - authorizationUser, NOT_AUTHORIZED_TO_READ_KEY_RESULT); + authorizationUser, new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_READ, Constants.KEY_RESULT)); return isWriteable(authorizationUser, objective.getTeam()); } public boolean isWriteable(CheckIn checkIn, AuthorizationUser authorizationUser) { Objective objective = objectivePersistenceService.findObjectiveByKeyResultId(checkIn.getKeyResult().getId(), - authorizationUser, NOT_AUTHORIZED_TO_READ_CHECK_IN); + authorizationUser, new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_READ, Constants.CHECK_IN)); + return isWriteable(authorizationUser, objective.getTeam()); } public void hasRoleCreateOrUpdateByObjectiveId(Long objectiveId, AuthorizationUser authorizationUser) { Objective objective = objectivePersistenceService.findObjectiveById(objectiveId, authorizationUser, - NOT_AUTHORIZED_TO_READ_OBJECTIVE); - hasRoleWrite(authorizationUser, objective.getTeam(), NOT_AUTHORIZED_TO_WRITE_OBJECTIVE); + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_READ, Constants.OBJECTIVE)); + + hasRoleWrite(authorizationUser, objective.getTeam(), + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_WRITE, Constants.OBJECTIVE)); } public void hasRoleDeleteByObjectiveId(Long objectiveId, AuthorizationUser authorizationUser) { Objective objective = objectivePersistenceService.findObjectiveById(objectiveId, authorizationUser, - NOT_AUTHORIZED_TO_READ_OBJECTIVE); - hasRoleWrite(authorizationUser, objective.getTeam(), NOT_AUTHORIZED_TO_DELETE_OBJECTIVE); + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_READ, Constants.OBJECTIVE)); + + hasRoleWrite(authorizationUser, objective.getTeam(), + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_DELETE, Constants.OBJECTIVE)); } public void hasRoleDeleteByKeyResultId(Long keyResultId, AuthorizationUser authorizationUser) { Objective objective = objectivePersistenceService.findObjectiveByKeyResultId(keyResultId, authorizationUser, - NOT_AUTHORIZED_TO_READ_KEY_RESULT); - hasRoleWrite(authorizationUser, objective.getTeam(), NOT_AUTHORIZED_TO_DELETE_KEY_RESULT); + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_READ, Constants.KEY_RESULT)); + + hasRoleWrite(authorizationUser, objective.getTeam(), + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_DELETE, Constants.KEY_RESULT)); } public void hasRoleDeleteByActionId(Long actionId, AuthorizationUser authorizationUser) { Action action = actionPersistenceService.findById(actionId); hasRoleWrite(authorizationUser, action.getKeyResult().getObjective().getTeam(), - NOT_AUTHORIZED_TO_DELETE_ACTION); + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_DELETE, Constants.ACTION)); + } public void hasRoleDeleteByCheckInId(Long checkInId, AuthorizationUser authorizationUser) { Objective objective = objectivePersistenceService.findObjectiveByCheckInId(checkInId, authorizationUser, - NOT_AUTHORIZED_TO_READ_CHECK_IN); - hasRoleWrite(authorizationUser, objective.getTeam(), NOT_AUTHORIZED_TO_DELETE_CHECK_IN); + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_READ, Constants.CHECK_IN)); + + hasRoleWrite(authorizationUser, objective.getTeam(), + new ErrorDto(ErrorMsg.NOT_AUTHORIZED_TO_DELETE, Constants.CHECK_IN)); } - private void hasRoleWrite(AuthorizationUser authorizationUser, Team team, String reason) { + private void hasRoleWrite(AuthorizationUser authorizationUser, Team team, ErrorDto error) { if (isWriteable(authorizationUser, team)) { return; } - throw new ResponseStatusException(UNAUTHORIZED, reason); + throw new OkrResponseStatusException(HttpStatus.UNAUTHORIZED, error); } private boolean isWriteable(AuthorizationUser authorizationUser, Team team) { diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java index 440779be7c..65a81c9980 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java @@ -1,5 +1,6 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.*; import ch.puzzle.okr.models.authorization.AuthorizationUser; import ch.puzzle.okr.repository.ObjectiveRepository; @@ -42,23 +43,23 @@ public Integer countByTeamAndQuarter(Team team, Quarter quarter) { return getRepository().countByTeamAndQuarter(team, quarter); } - public Objective findObjectiveById(Long objectiveId, AuthorizationUser authorizationUser, String reason) { - return findByAnyId(objectiveId, authorizationUser, SELECT_OBJECTIVE_BY_ID); + public Objective findObjectiveById(Long objectiveId, AuthorizationUser authorizationUser, ErrorDto error) { + return findByAnyId(objectiveId, authorizationUser, SELECT_OBJECTIVE_BY_ID, error); } public List findObjectiveByTeamId(Long teamId) { return getRepository().findObjectivesByTeamId(teamId); } - public Objective findObjectiveByKeyResultId(Long keyResultId, AuthorizationUser authorizationUser, String reason) { - return findByAnyId(keyResultId, authorizationUser, SELECT_OBJECTIVE_BY_KEY_RESULT_ID); + public Objective findObjectiveByKeyResultId(Long keyResultId, AuthorizationUser authorizationUser, ErrorDto error) { + return findByAnyId(keyResultId, authorizationUser, SELECT_OBJECTIVE_BY_KEY_RESULT_ID, error); } - public Objective findObjectiveByCheckInId(Long checkInId, AuthorizationUser authorizationUser, String reason) { - return findByAnyId(checkInId, authorizationUser, SELECT_OBJECTIVE_BY_CHECK_IN_ID); + public Objective findObjectiveByCheckInId(Long checkInId, AuthorizationUser authorizationUser, ErrorDto error) { + return findByAnyId(checkInId, authorizationUser, SELECT_OBJECTIVE_BY_CHECK_IN_ID, error); } - private Objective findByAnyId(Long id, AuthorizationUser authorizationUser, String queryString) { + private Objective findByAnyId(Long id, AuthorizationUser authorizationUser, String queryString, ErrorDto error) { checkIdNull(id); String fullQueryString = queryString + authorizationCriteria.appendObjective(authorizationUser); logger.debug("select objective by id={}: {}", id, fullQueryString); @@ -68,7 +69,7 @@ private Objective findByAnyId(Long id, AuthorizationUser authorizationUser, Stri try { return typedQuery.getSingleResult(); } catch (NoResultException exception) { - throw new OkrResponseStatusException(UNAUTHORIZED, ErrorMsg.UNAUTHORIZED, List.of(id)); + throw new OkrResponseStatusException(UNAUTHORIZED, error); } } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/UserValidationService.java b/backend/src/main/java/ch/puzzle/okr/service/validation/UserValidationService.java index 6a50b82008..d6358ebdd2 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/UserValidationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/UserValidationService.java @@ -1,8 +1,12 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.models.ErrorMsg; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.User; import ch.puzzle.okr.repository.UserRepository; import ch.puzzle.okr.service.persistence.UserPersistenceService; +import org.springframework.http.HttpStatus; +import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.stereotype.Service; @Service @@ -31,4 +35,10 @@ public void validateOnUpdate(Long id, User model) { throwExceptionWhenIdHasChanged(id, model.getId()); validate(model); } + + public void validateAuthorisationToken(Jwt token) { + if (token == null) { + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.TOKEN_NULL); + } + } } From 05285e9e06c74f7c9c1501ccc019cfce64252bc3 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 23 Nov 2023 16:22:03 +0100 Subject: [PATCH 16/38] clean up --- .../src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java | 2 +- .../java/ch/puzzle/okr/models/OkrResponseStatusException.java | 4 ++++ .../ch/puzzle/okr/service/persistence/PersistenceBase.java | 2 +- .../okr/service/validation/ObjectiveValidationService.java | 2 +- .../java/ch/puzzle/okr/service/validation/ValidationBase.java | 3 +-- frontend/src/app/shared/services/toaster.service.ts | 4 ++-- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java index b6d7f3f614..6fbea82e4c 100644 --- a/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java +++ b/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java @@ -91,7 +91,7 @@ private OverviewKeyResultDto createKeyResultDto(Overview overview) { return createKeyResultOrdinalDto(overview); } else { throw new OkrResponseStatusException(BAD_REQUEST, ErrorMsg.KEYRESULT_CONVERSION, - List.of(overview.getKeyResultType())); + overview.getKeyResultType()); } } diff --git a/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java index 69b49080e5..f5e58bf8c5 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java +++ b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java @@ -18,6 +18,10 @@ public OkrResponseStatusException(HttpStatus status, String errorKey, List> violations) { private List getAttributes(String message, String messageTemplate) { String pattern = messageTemplate.replaceAll("\\{([^}]*)\\}", "(.*)"); - return Pattern.compile(pattern).matcher(message).results().map(MatchResult::group).toList(); } } diff --git a/frontend/src/app/shared/services/toaster.service.ts b/frontend/src/app/shared/services/toaster.service.ts index 1c455fd259..7487762257 100644 --- a/frontend/src/app/shared/services/toaster.service.ts +++ b/frontend/src/app/shared/services/toaster.service.ts @@ -8,11 +8,11 @@ export class ToasterService { constructor(private toastr: ToastrService) {} showSuccess(msg: string) { - this.toastr.success(msg, 'Erfolgreich'); + this.toastr.success(msg, 'Erfolgreich!'); } showError(msg: string) { - this.toastr.error(msg, 'Fehler'); + this.toastr.error(msg, 'Fehler!'); } showWarn(msg: string) { From 4fbb26805e12e4d09fb54c8f664bb14f60106729 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 23 Nov 2023 16:30:50 +0100 Subject: [PATCH 17/38] complete translations --- frontend/src/assets/i18n/de.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index 390a7680aa..68113a64df 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -39,6 +39,10 @@ "ATTRIBUTE_SIZE_BETWEEN": "Das Attribut {0} auf dem Objekt {1} muss folgende länge haben: {2}-{3}", "ATTRIBUTE_SET_FORBIDDEN": "Das Attribut {0} darf nicht während dem erstellen gesetzt sein ", "ATTRIBUTE_NOT_SET": "Ein Fehler ist aufgetreten.\n Das Attribut {0} ist nicht gesetzt", - "ATTRIBUTE_CANNOT_CHANGE": "Das Attribut {0} auf dem Objekt {1} kann nicht geändert werden" + "ATTRIBUTE_CANNOT_CHANGE": "Das Attribut {0} auf dem Objekt {1} kann nicht geändert werden", + "NOT_AUTHORIZED_TO_READ": "Du bist nicht autorisiert um dieses {0} anzuzeigen", + "NOT_AUTHORIZED_TO_WRITE":"Du bist nicht autorisiert um dieses {0} zu bearbeiten", + "NOT_AUTHORIZED_TO_DELETE": "Du bist nicht autorisiert um dieses {0} zu löschen", + "TOKEN_NULL": "Das erhaltene Token ist null" } } From 61f68802860892fedbc29791066467d901330bbd Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 23 Nov 2023 16:50:54 +0100 Subject: [PATCH 18/38] backend compoile again --- .../AuthorizationServiceTest.java | 65 ++++++++++++++----- .../KeyResultBusinessServiceTest.java | 6 +- .../persistence/AuthorizationCriteriaIT.java | 7 +- .../ObjectivePersistenceServiceIT.java | 23 ++++--- .../persistence/UserPersistenceServiceIT.java | 4 +- 5 files changed, 71 insertions(+), 34 deletions(-) diff --git a/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java index d191420e8c..ff2653f1b8 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java @@ -2,6 +2,7 @@ import ch.puzzle.okr.converter.JwtConverterFactory; import ch.puzzle.okr.converter.JwtUserConverter; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.Objective; import ch.puzzle.okr.models.Team; import ch.puzzle.okr.models.User; @@ -153,7 +154,8 @@ void hasRoleReadByObjectiveIdShouldPassThroughWhenPermitted() { Long id = 13L; AuthorizationUser authorizationUser = defaultAuthorizationUser(); String reason = "not authorized to read objective"; - when(objectivePersistenceService.findObjectiveById(id, authorizationUser, reason)).thenReturn(new Objective()); + ErrorDto error = new ErrorDto(reason, List.of()); + when(objectivePersistenceService.findObjectiveById(id, authorizationUser, error)).thenReturn(new Objective()); authorizationService.hasRoleReadByObjectiveId(id, authorizationUser); } @@ -163,7 +165,9 @@ void hasRoleReadByKeyResultIdShouldThrowExceptionWhenObjectiveNotFound() { Long id = 13L; AuthorizationUser authorizationUser = defaultAuthorizationUser(); String reason = "not authorized to read key result"; - when(objectivePersistenceService.findObjectiveByKeyResultId(id, authorizationUser, reason)) + ErrorDto error = new ErrorDto(reason, List.of()); + + when(objectivePersistenceService.findObjectiveByKeyResultId(id, authorizationUser, error)) .thenThrow(new ResponseStatusException(HttpStatus.UNAUTHORIZED, reason)); ResponseStatusException exception = assertThrows(ResponseStatusException.class, @@ -177,7 +181,8 @@ void hasRoleReadByKeyResultIdShouldPassThroughWhenPermitted() { Long id = 13L; AuthorizationUser authorizationUser = defaultAuthorizationUser(); String reason = "not authorized to read key result"; - when(objectivePersistenceService.findObjectiveByKeyResultId(id, authorizationUser, reason)) + ErrorDto error = new ErrorDto(reason, List.of()); + when(objectivePersistenceService.findObjectiveByKeyResultId(id, authorizationUser, error)) .thenReturn(new Objective()); authorizationService.hasRoleReadByKeyResultId(id, authorizationUser); @@ -188,7 +193,8 @@ void hasRoleReadByCheckInIdShouldThrowExceptionWhenObjectiveNotFound() { Long id = 13L; AuthorizationUser authorizationUser = defaultAuthorizationUser(); String reason = "not authorized to read check in"; - when(objectivePersistenceService.findObjectiveByCheckInId(id, authorizationUser, reason)) + ErrorDto error = new ErrorDto(reason, List.of()); + when(objectivePersistenceService.findObjectiveByCheckInId(id, authorizationUser, error)) .thenThrow(new ResponseStatusException(HttpStatus.UNAUTHORIZED, reason)); ResponseStatusException exception = assertThrows(ResponseStatusException.class, @@ -202,7 +208,9 @@ void hasRoleReadByCheckInIdShouldPassThroughWhenPermitted() { Long id = 13L; AuthorizationUser authorizationUser = defaultAuthorizationUser(); String reason = "not authorized to read check in"; - when(objectivePersistenceService.findObjectiveByCheckInId(id, authorizationUser, reason)) + ErrorDto error = new ErrorDto(reason, List.of()); + + when(objectivePersistenceService.findObjectiveByCheckInId(id, authorizationUser, error)) .thenReturn(new Objective()); authorizationService.hasRoleReadByCheckInId(id, authorizationUser); @@ -223,7 +231,9 @@ void hasRoleCreateOrUpdateShouldPassThroughWhenAuthorizedForAllTeamsKeyResults() AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_ALL_TEAMS)); String reason = "not authorized to read key result"; - when(objectivePersistenceService.findObjectiveById(keyResult.getObjective().getId(), authorizationUser, reason)) + ErrorDto error = new ErrorDto(reason, List.of()); + + when(objectivePersistenceService.findObjectiveById(keyResult.getObjective().getId(), authorizationUser, error)) .thenReturn(objective); authorizationService.hasRoleCreateOrUpdate(keyResult, authorizationUser); @@ -237,8 +247,10 @@ void hasRoleCreateOrUpdateShouldPassThroughWhenAuthorizedForTeamCheckIns() { AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_TEAM)); String reason = "not authorized to read check in"; + ErrorDto error = new ErrorDto(reason, List.of()); + when(objectivePersistenceService.findObjectiveByKeyResultId(checkIn.getKeyResult().getObjective().getId(), - authorizationUser, reason)).thenReturn(objective); + authorizationUser, error)).thenReturn(objective); authorizationService.hasRoleCreateOrUpdate(checkIn, authorizationUser); } @@ -273,7 +285,9 @@ void hasRoleCreateOrUpdateByObjectiveIdShouldPassThroughWhenAuthorizedForAllObje Objective objective = Objective.Builder.builder().withTeam(Team.Builder.builder().withId(5L).build()).build(); AuthorizationUser authorizationUser = defaultAuthorizationUser(); String reason = "not authorized to read objective"; - when(objectivePersistenceService.findObjectiveById(id, authorizationUser, reason)).thenReturn(objective); + ErrorDto error = new ErrorDto(reason, List.of()); + + when(objectivePersistenceService.findObjectiveById(id, authorizationUser, error)).thenReturn(objective); authorizationService.hasRoleCreateOrUpdateByObjectiveId(id, authorizationUser); } @@ -300,7 +314,9 @@ void isWriteableShouldReturnTrueWhenAuthorizedToWriteAllKeyResults() { KeyResult keyResult = KeyResultMetric.Builder.builder().withObjective(objective).build(); AuthorizationUser authorizationUser = defaultAuthorizationUser(); String reason = "not authorized to read key result"; - when(objectivePersistenceService.findObjectiveById(keyResult.getObjective().getId(), authorizationUser, reason)) + ErrorDto error = new ErrorDto(reason, List.of()); + + when(objectivePersistenceService.findObjectiveById(keyResult.getObjective().getId(), authorizationUser, error)) .thenReturn(objective); assertTrue(authorizationService.isWriteable(keyResult, authorizationUser)); @@ -312,7 +328,9 @@ void isWriteableShouldReturnFalseWhenNotAuthorizedToWriteKeyResults() { KeyResult keyResult = KeyResultMetric.Builder.builder().withObjective(objective).build(); AuthorizationUser authorizationUser = mockAuthorizationUser(user, List.of(4L), 13L, List.of(WRITE_TEAM)); String reason = "not authorized to read key result"; - when(objectivePersistenceService.findObjectiveById(keyResult.getObjective().getId(), authorizationUser, reason)) + ErrorDto error = new ErrorDto(reason, List.of()); + + when(objectivePersistenceService.findObjectiveById(keyResult.getObjective().getId(), authorizationUser, error)) .thenReturn(objective); assertFalse(authorizationService.isWriteable(keyResult, authorizationUser)); @@ -325,8 +343,10 @@ void isWriteableShouldReturnTrueWhenAuthorizedToWriteAllCheckIns() { CheckIn checkIn = CheckInMetric.Builder.builder().withKeyResult(keyResult).build(); AuthorizationUser authorizationUser = defaultAuthorizationUser(); String reason = "not authorized to read check in"; + ErrorDto error = new ErrorDto(reason, List.of()); + when(objectivePersistenceService.findObjectiveByKeyResultId(checkIn.getKeyResult().getId(), authorizationUser, - reason)).thenReturn(objective); + error)).thenReturn(objective); assertTrue(authorizationService.isWriteable(checkIn, authorizationUser)); } @@ -338,8 +358,10 @@ void isWriteableShouldReturnFalseWhenNotAuthorizedToWriteCheckIns() { CheckIn checkIn = CheckInMetric.Builder.builder().withKeyResult(keyResult).build(); AuthorizationUser authorizationUser = mockAuthorizationUser(user, List.of(4L), 13L, List.of(WRITE_TEAM)); String reason = "not authorized to read check in"; + ErrorDto error = new ErrorDto(reason, List.of()); + when(objectivePersistenceService.findObjectiveByKeyResultId(checkIn.getKeyResult().getId(), authorizationUser, - reason)).thenReturn(objective); + error)).thenReturn(objective); assertFalse(authorizationService.isWriteable(checkIn, authorizationUser)); } @@ -350,7 +372,9 @@ void hasRoleDeleteByObjectiveIdShouldPassThroughWhenAuthorizedForAllObjectives() Objective objective = Objective.Builder.builder().withTeam(Team.Builder.builder().withId(5L).build()).build(); AuthorizationUser authorizationUser = defaultAuthorizationUser(); String reason = "not authorized to read objective"; - when(objectivePersistenceService.findObjectiveById(id, authorizationUser, reason)).thenReturn(objective); + ErrorDto error = new ErrorDto(reason, List.of()); + + when(objectivePersistenceService.findObjectiveById(id, authorizationUser, error)).thenReturn(objective); authorizationService.hasRoleDeleteByObjectiveId(id, authorizationUser); } @@ -362,7 +386,9 @@ void hasRoleDeleteByKeyResultIdShouldPassThroughWhenAuthorizedForAllTeamsKeyResu AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_ALL_TEAMS)); String reason = "not authorized to read key result"; - when(objectivePersistenceService.findObjectiveByKeyResultId(id, authorizationUser, reason)) + ErrorDto error = new ErrorDto(reason, List.of()); + + when(objectivePersistenceService.findObjectiveByKeyResultId(id, authorizationUser, error)) .thenReturn(objective); authorizationService.hasRoleDeleteByKeyResultId(id, authorizationUser); @@ -375,7 +401,9 @@ void hasRoleDeleteByCheckInIdShouldPassThroughWhenAuthorizedForTeamCheckIns() { AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_TEAM)); String reason = "not authorized to read check in"; - when(objectivePersistenceService.findObjectiveByCheckInId(id, authorizationUser, reason)).thenReturn(objective); + ErrorDto error = new ErrorDto(reason, List.of()); + + when(objectivePersistenceService.findObjectiveByCheckInId(id, authorizationUser, error)).thenReturn(objective); authorizationService.hasRoleDeleteByCheckInId(id, authorizationUser); } @@ -387,7 +415,8 @@ void hasRoleDeleteByKeyResultIdShouldThrowExceptionWhenNotAuthorizedForFirstLeve AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_ALL_TEAMS)); String reason = "not authorized to read key result"; - when(objectivePersistenceService.findObjectiveByKeyResultId(id, authorizationUser, reason)) + ErrorDto error = new ErrorDto(reason, List.of()); + when(objectivePersistenceService.findObjectiveByKeyResultId(id, authorizationUser, error)) .thenReturn(objective); ResponseStatusException exception = assertThrows(ResponseStatusException.class, @@ -403,7 +432,9 @@ void hasRoleDeleteByCheckInIdShouldThrowExceptionWhenNotAuthorizedForOtherTeamOb AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_TEAM)); String reason = "not authorized to read check in"; - when(objectivePersistenceService.findObjectiveByCheckInId(id, authorizationUser, reason)).thenReturn(objective); + ErrorDto error = new ErrorDto(reason, List.of()); + + when(objectivePersistenceService.findObjectiveByCheckInId(id, authorizationUser, error)).thenReturn(objective); ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> authorizationService.hasRoleDeleteByCheckInId(id, authorizationUser)); diff --git a/backend/src/test/java/ch/puzzle/okr/service/business/KeyResultBusinessServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/business/KeyResultBusinessServiceTest.java index 959adcf4c3..d7180db2e0 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/business/KeyResultBusinessServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/business/KeyResultBusinessServiceTest.java @@ -2,6 +2,7 @@ import ch.puzzle.okr.models.Action; import ch.puzzle.okr.models.Objective; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.User; import ch.puzzle.okr.models.authorization.AuthorizationUser; import ch.puzzle.okr.models.checkin.CheckIn; @@ -100,8 +101,9 @@ void shouldGetOrdinalKeyResultById() { @Test void shouldThrowExceptionWhenDefaultMethodUsed() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> keyResultBusinessService - .updateEntity(metricKeyResult.getId(), metricKeyResult, authorizationUser)); + ResponseStatusException exception = assertThrows(OkrResponseStatusException.class, + () -> keyResultBusinessService.updateEntity(metricKeyResult.getId(), metricKeyResult, + authorizationUser)); assertEquals(BAD_REQUEST, exception.getStatus()); assertEquals("unsupported method in class " + KeyResultBusinessService.class.getSimpleName() diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/AuthorizationCriteriaIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/AuthorizationCriteriaIT.java index 04d590b9ce..9546287112 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/AuthorizationCriteriaIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/AuthorizationCriteriaIT.java @@ -1,5 +1,6 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.Objective; import ch.puzzle.okr.models.authorization.AuthorizationUser; import ch.puzzle.okr.models.overview.Overview; @@ -26,7 +27,7 @@ class AuthorizationCriteriaIT { void appendObjectiveShouldReturnObjectiveWhenFirstLevelRole() { Long objectiveId = 5L; AuthorizationUser authorizationUser = defaultAuthorizationUser(); - Objective objective = objectivePersistenceService.findObjectiveById(objectiveId, authorizationUser, REASON); + Objective objective = objectivePersistenceService.findObjectiveById(objectiveId, authorizationUser, null); assertEquals(objectiveId, objective.getId()); } @@ -36,7 +37,7 @@ void appendObjectiveShouldReturnObjectiveWhenSecondLevelRole() { Long objectiveId = 6L; AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(), 5L, List.of(READ_ALL_PUBLISHED, READ_TEAMS_DRAFT)); - Objective objective = objectivePersistenceService.findObjectiveById(objectiveId, authorizationUser, REASON); + Objective objective = objectivePersistenceService.findObjectiveById(objectiveId, authorizationUser, null); assertEquals(objectiveId, objective.getId()); } @@ -46,7 +47,7 @@ void appendObjectiveShouldReturnObjectiveWhenMemberRole() { Long objectiveId = 6L; AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(), 5L, List.of(READ_ALL_PUBLISHED, READ_TEAM_DRAFT)); - Objective objective = objectivePersistenceService.findObjectiveById(objectiveId, authorizationUser, REASON); + Objective objective = objectivePersistenceService.findObjectiveById(objectiveId, authorizationUser, null); assertEquals(objectiveId, objective.getId()); } diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java index 050f5d604f..3f0c0224a3 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java @@ -1,5 +1,6 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.*; import ch.puzzle.okr.models.authorization.AuthorizationUser; import ch.puzzle.okr.test.SpringIntegrationTest; @@ -19,6 +20,8 @@ class ObjectivePersistenceServiceIT { private static final String REASON = "not authorized to read objective"; private static final String MISSING_IDENTIFIER = "Missing identifier for Objective"; + + private static final ErrorDto error = new ErrorDto(REASON, List.of()); private static final String HIGHER_CUSTOMER_HAPPINESS = "Wir wollen die Kundenzufriedenheit steigern"; private final AuthorizationUser authorizationUser = defaultAuthorizationUser(); private Objective createdObjective; @@ -66,7 +69,7 @@ void findAllShouldReturnListOfObjectives() { @Test void findObjectiveByIdShouldReturnObjectiveProperly() { - Objective objective = objectivePersistenceService.findObjectiveById(3L, authorizationUser, REASON); + Objective objective = objectivePersistenceService.findObjectiveById(3L, authorizationUser, error); assertEquals(3L, objective.getId()); assertEquals(HIGHER_CUSTOMER_HAPPINESS, objective.getTitle()); @@ -74,8 +77,8 @@ void findObjectiveByIdShouldReturnObjectiveProperly() { @Test void findObjectiveByIdShouldThrowExceptionWhenObjectiveNotFound() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, - () -> objectivePersistenceService.findObjectiveById(321L, authorizationUser, REASON)); + ResponseStatusException exception = assertThrows(OkrResponseStatusException.class, + () -> objectivePersistenceService.findObjectiveById(321L, authorizationUser, error)); assertEquals(UNAUTHORIZED, exception.getStatus()); assertEquals(REASON, exception.getReason()); @@ -84,7 +87,7 @@ void findObjectiveByIdShouldThrowExceptionWhenObjectiveNotFound() { @Test void findObjectiveByIdShouldThrowExceptionWhenObjectiveIdIsNull() { ResponseStatusException exception = assertThrows(ResponseStatusException.class, - () -> objectivePersistenceService.findObjectiveById(null, authorizationUser, REASON)); + () -> objectivePersistenceService.findObjectiveById(null, authorizationUser, error)); assertEquals(BAD_REQUEST, exception.getStatus()); assertEquals(MISSING_IDENTIFIER, exception.getReason()); @@ -92,7 +95,7 @@ void findObjectiveByIdShouldThrowExceptionWhenObjectiveIdIsNull() { @Test void findObjectiveByKeyResultIdShouldReturnObjectiveProperly() { - Objective objective = objectivePersistenceService.findObjectiveByKeyResultId(5L, authorizationUser, REASON); + Objective objective = objectivePersistenceService.findObjectiveByKeyResultId(5L, authorizationUser, error); assertEquals(3L, objective.getId()); assertEquals(HIGHER_CUSTOMER_HAPPINESS, objective.getTitle()); @@ -101,7 +104,7 @@ void findObjectiveByKeyResultIdShouldReturnObjectiveProperly() { @Test void findObjectiveByKeyResultIdShouldThrowExceptionWhenObjectiveNotFound() { ResponseStatusException exception = assertThrows(ResponseStatusException.class, - () -> objectivePersistenceService.findObjectiveByKeyResultId(321L, authorizationUser, REASON)); + () -> objectivePersistenceService.findObjectiveByKeyResultId(321L, authorizationUser, error)); assertEquals(UNAUTHORIZED, exception.getStatus()); assertEquals(REASON, exception.getReason()); @@ -110,7 +113,7 @@ void findObjectiveByKeyResultIdShouldThrowExceptionWhenObjectiveNotFound() { @Test void findObjectiveByKeyResultIdShouldThrowExceptionWhenObjectiveIdIsNull() { ResponseStatusException exception = assertThrows(ResponseStatusException.class, - () -> objectivePersistenceService.findObjectiveByKeyResultId(null, authorizationUser, REASON)); + () -> objectivePersistenceService.findObjectiveByKeyResultId(null, authorizationUser, error)); assertEquals(BAD_REQUEST, exception.getStatus()); assertEquals(MISSING_IDENTIFIER, exception.getReason()); @@ -118,7 +121,7 @@ void findObjectiveByKeyResultIdShouldThrowExceptionWhenObjectiveIdIsNull() { @Test void findObjectiveByCheckInIdShouldReturnObjectiveProperly() { - Objective objective = objectivePersistenceService.findObjectiveByCheckInId(7L, authorizationUser, REASON); + Objective objective = objectivePersistenceService.findObjectiveByCheckInId(7L, authorizationUser, error); assertEquals(3L, objective.getId()); assertEquals(HIGHER_CUSTOMER_HAPPINESS, objective.getTitle()); @@ -127,7 +130,7 @@ void findObjectiveByCheckInIdShouldReturnObjectiveProperly() { @Test void findObjectiveByCheckInIdShouldThrowExceptionWhenObjectiveNotFound() { ResponseStatusException exception = assertThrows(ResponseStatusException.class, - () -> objectivePersistenceService.findObjectiveByCheckInId(321L, authorizationUser, REASON)); + () -> objectivePersistenceService.findObjectiveByCheckInId(321L, authorizationUser, error)); assertEquals(UNAUTHORIZED, exception.getStatus()); assertEquals(REASON, exception.getReason()); @@ -136,7 +139,7 @@ void findObjectiveByCheckInIdShouldThrowExceptionWhenObjectiveNotFound() { @Test void findObjectiveByCheckInIdShouldThrowExceptionWhenObjectiveIdIsNull() { ResponseStatusException exception = assertThrows(ResponseStatusException.class, - () -> objectivePersistenceService.findObjectiveByCheckInId(null, authorizationUser, REASON)); + () -> objectivePersistenceService.findObjectiveByCheckInId(null, authorizationUser, error)); assertEquals(BAD_REQUEST, exception.getStatus()); assertEquals(MISSING_IDENTIFIER, exception.getReason()); diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/UserPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/UserPersistenceServiceIT.java index 1a6a3bfc40..d9ea0caa8e 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/UserPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/UserPersistenceServiceIT.java @@ -70,7 +70,7 @@ void shouldThrowExceptionWhenFindingOwnerNotFound() { () -> userPersistenceService.findById(321L)); assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); - assertEquals("User with id 321 not found", exception.getReason()); + assertEquals("MODEL_WITH_ID_NOT_FOUND", exception.getReason()); } @Test @@ -79,7 +79,7 @@ void shouldThrowExceptionWhenFindingOwnerWithNullId() { () -> userPersistenceService.findById(null)); assertEquals(HttpStatus.BAD_REQUEST, exception.getStatus()); - assertEquals("Missing identifier for User", exception.getReason()); + assertEquals("ATTRIBUTE_NULL", exception.getReason()); } @Test From 238d998e8caa98df5dba2373e68f64746f5f88e1 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Fri, 24 Nov 2023 11:31:47 +0100 Subject: [PATCH 19/38] fix keyresultValidationServiceTest --- .../ch/puzzle/okr/OkrErrorAttributes.java | 2 +- .../main/java/ch/puzzle/okr/dto/ErrorDto.java | 16 ++ .../models/OkrResponseStatusException.java | 6 +- .../okr/models/keyresult/KeyResult.java | 17 +- .../okr/models/keyresult/KeyResultMetric.java | 7 +- .../validation/ActionValidationService.java | 18 +- .../validation/CheckInValidationService.java | 9 +- .../KeyResultValidationService.java | 9 +- .../service/validation/ValidationBase.java | 28 +++- .../test/java/ch/puzzle/okr/TestHelper.java | 5 + .../KeyResultBusinessServiceTest.java | 9 +- .../ActionValidationServiceTest.java | 154 ++++++++++-------- .../KeyResultValidationServiceTest.java | 152 +++++++++-------- frontend/src/assets/i18n/de.json | 4 +- 14 files changed, 263 insertions(+), 173 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java b/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java index 15f6b8d214..dc740af47d 100644 --- a/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java +++ b/backend/src/main/java/ch/puzzle/okr/OkrErrorAttributes.java @@ -17,7 +17,7 @@ public Map getErrorAttributes(WebRequest webRequest, ErrorAttrib Throwable throwable = getError(webRequest); if (throwable instanceof OkrResponseStatusException exception) { - errorAttributes.put("errors", exception.errors); + errorAttributes.put("errors", exception.getErrors()); } return errorAttributes; } diff --git a/backend/src/main/java/ch/puzzle/okr/dto/ErrorDto.java b/backend/src/main/java/ch/puzzle/okr/dto/ErrorDto.java index 971e44528e..7e69a4bbde 100644 --- a/backend/src/main/java/ch/puzzle/okr/dto/ErrorDto.java +++ b/backend/src/main/java/ch/puzzle/okr/dto/ErrorDto.java @@ -1,6 +1,7 @@ package ch.puzzle.okr.dto; import java.util.List; +import java.util.Objects; public class ErrorDto { private final String errorKey; @@ -22,4 +23,19 @@ public String getErrorKey() { public List getParams() { return params; } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ErrorDto errorDto = (ErrorDto) o; + return Objects.equals(errorKey, errorDto.errorKey) && Objects.equals(params, errorDto.params); + } + + @Override + public int hashCode() { + return Objects.hash(errorKey, params); + } } diff --git a/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java index f5e58bf8c5..d7055b17f0 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java +++ b/backend/src/main/java/ch/puzzle/okr/models/OkrResponseStatusException.java @@ -8,7 +8,7 @@ public class OkrResponseStatusException extends ResponseStatusException { - public final List errors; + private final List errors; public OkrResponseStatusException(HttpStatus status, String errorKey) { this(status, errorKey, List.of()); @@ -30,4 +30,8 @@ public OkrResponseStatusException(HttpStatus status, List errors) { super(status, errors.get(0).getErrorKey()); this.errors = errors; } + + public List getErrors() { + return errors; + } } diff --git a/backend/src/main/java/ch/puzzle/okr/models/keyresult/KeyResult.java b/backend/src/main/java/ch/puzzle/okr/models/keyresult/KeyResult.java index ec2d248776..fc7dcfc1f8 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/keyresult/KeyResult.java +++ b/backend/src/main/java/ch/puzzle/okr/models/keyresult/KeyResult.java @@ -1,5 +1,6 @@ package ch.puzzle.okr.models.keyresult; +import ch.puzzle.okr.models.ErrorMsg; import ch.puzzle.okr.models.Objective; import ch.puzzle.okr.models.User; import ch.puzzle.okr.models.WriteableInterface; @@ -22,27 +23,27 @@ public abstract class KeyResult implements WriteableInterface { @Version private int version; - @NotNull(message = "Objective must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @ManyToOne private Objective objective; - @NotBlank(message = "Title can not be blank") - @NotNull(message = "Title can not be null") - @Size(min = 2, max = 250, message = "Attribute title must have a length between 2 and 250 characters when saving key result") + @NotBlank(message = ErrorMsg.ATTRIBUTE_NOT_BLANK) + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) + @Size(min = 2, max = 250, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String title; - @Size(max = 4096, message = "Attribute description has a max length of 4096 characters") + @Size(max = 4096, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String description; - @NotNull(message = "Owner must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @ManyToOne private User owner; - @NotNull(message = "CreatedBy must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @ManyToOne private User createdBy; - @NotNull(message = "CreatedOn must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) private LocalDateTime createdOn; private LocalDateTime modifiedOn; diff --git a/backend/src/main/java/ch/puzzle/okr/models/keyresult/KeyResultMetric.java b/backend/src/main/java/ch/puzzle/okr/models/keyresult/KeyResultMetric.java index 2e50122e0f..06e341656d 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/keyresult/KeyResultMetric.java +++ b/backend/src/main/java/ch/puzzle/okr/models/keyresult/KeyResultMetric.java @@ -1,5 +1,6 @@ package ch.puzzle.okr.models.keyresult; +import ch.puzzle.okr.models.ErrorMsg; import ch.puzzle.okr.models.Unit; import javax.persistence.DiscriminatorValue; @@ -14,13 +15,13 @@ @Entity @DiscriminatorValue(KEY_RESULT_TYPE_METRIC) public class KeyResultMetric extends KeyResult { - @NotNull(message = "Baseline must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) private Double baseline; - @NotNull(message = "StretchGoal must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) private Double stretchGoal; - @NotNull(message = "Unit must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @Enumerated(EnumType.STRING) private Unit unit; diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/ActionValidationService.java b/backend/src/main/java/ch/puzzle/okr/service/validation/ActionValidationService.java index 35d741d49e..b11347d8f7 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/ActionValidationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/ActionValidationService.java @@ -1,16 +1,17 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.Constants; import ch.puzzle.okr.models.Action; +import ch.puzzle.okr.models.ErrorMsg; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.repository.ActionRepository; import ch.puzzle.okr.service.persistence.ActionPersistenceService; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; +import java.util.List; import java.util.Objects; -import static java.lang.String.format; -import static org.springframework.http.HttpStatus.BAD_REQUEST; - @Service public class ActionValidationService extends ValidationBase { @@ -46,12 +47,13 @@ public void validateOnUpdate(Long id, Action model) { void throwExceptionWhenKeyResultHasChanged(Action action, Action savedAction) { if (action.getKeyResult() == null || savedAction.getKeyResult() == null) { - throw new ResponseStatusException(BAD_REQUEST, - format("KeyResult must not be null. action=%s, savedAction=%s", action, savedAction)); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NOT_NULL, + List.of(Constants.KEY_RESULT, Constants.ACTION)); } + if (!Objects.equals(action.getKeyResult().getId(), savedAction.getKeyResult().getId())) { - throw new ResponseStatusException(BAD_REQUEST, - format("Not allowed change the association to the key result (id = %s)", savedAction.getId())); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_CANNOT_CHANGE, + List.of(Constants.KEY_RESULT, Constants.ACTION)); } } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/CheckInValidationService.java b/backend/src/main/java/ch/puzzle/okr/service/validation/CheckInValidationService.java index c4b5f03039..3ec1a82171 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/CheckInValidationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/CheckInValidationService.java @@ -1,11 +1,16 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.Constants; +import ch.puzzle.okr.models.ErrorMsg; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.checkin.CheckIn; import ch.puzzle.okr.repository.CheckInRepository; import ch.puzzle.okr.service.persistence.CheckInPersistenceService; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; +import java.util.List; import java.util.Objects; import static java.lang.String.format; @@ -38,8 +43,8 @@ public void validateOnUpdate(Long id, CheckIn model) { private static void throwExceptionWhenKeyResultHasChanged(CheckIn checkIn, CheckIn savedCheckIn) { if (!Objects.equals(checkIn.getKeyResult().getId(), savedCheckIn.getKeyResult().getId())) { - throw new ResponseStatusException(BAD_REQUEST, - format("Not allowed change the association to the key result (id = %s)", savedCheckIn.getId())); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_CANNOT_CHANGE, + List.of(Constants.KEY_RESULT, Constants.CHECK_IN)); } } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/KeyResultValidationService.java b/backend/src/main/java/ch/puzzle/okr/service/validation/KeyResultValidationService.java index 1d5b12fdcf..8c225b9b3f 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/KeyResultValidationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/KeyResultValidationService.java @@ -1,11 +1,16 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.Constants; +import ch.puzzle.okr.models.ErrorMsg; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.keyresult.KeyResult; import ch.puzzle.okr.repository.KeyResultRepository; import ch.puzzle.okr.service.persistence.KeyResultPersistenceService; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; +import java.util.List; import java.util.Objects; import static java.lang.String.format; @@ -38,8 +43,8 @@ public void validateOnUpdate(Long id, KeyResult model) { private static void throwExceptionWhenObjectiveHasChanged(KeyResult keyResult, KeyResult savedKeyResult) { if (!Objects.equals(keyResult.getObjective().getId(), savedKeyResult.getObjective().getId())) { - throw new ResponseStatusException(BAD_REQUEST, - format("Not allowed change the association to the objective (id = %s)", savedKeyResult.getId())); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_CANNOT_CHANGE, + List.of(Constants.OBJECTIVE, Constants.KEY_RESULT)); } } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java index 947a00f262..764b47340b 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/ValidationBase.java @@ -10,8 +10,11 @@ import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; -import java.util.*; -import java.util.regex.MatchResult; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -72,8 +75,8 @@ public void throwExceptionWhenIdIsNull(ID id) { protected void throwExceptionWhenIdIsNotNull(ID id) { if (id != null) { - throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NOT_NULL, - List.of(persistenceService.getModelName(), id)); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NULL, + List.of("ID", persistenceService.getModelName())); } } @@ -92,18 +95,25 @@ public void validate(T model) { private void processViolations(Set> violations) { if (!violations.isEmpty()) { List list = violations.stream().map(e -> { - List attributes = new ArrayList<>( + List attributes = new ArrayList<>( List.of(e.getPropertyPath().toString(), persistenceService.getModelName())); attributes.addAll(getAttributes(e.getMessage(), e.getMessageTemplate())); - String errorKey = e.getMessage().replaceAll("_\\{.*", ""); - return new ErrorDto(errorKey, Collections.singletonList(attributes)); + String errorKey = e.getMessageTemplate().replaceAll("_\\{.*", ""); + return new ErrorDto(errorKey, attributes); }).toList(); throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, list); } } private List getAttributes(String message, String messageTemplate) { - String pattern = messageTemplate.replaceAll("\\{([^}]*)\\}", "(.*)"); - return Pattern.compile(pattern).matcher(message).results().map(MatchResult::group).toList(); + String patternString = messageTemplate.replaceAll("\\{([^}]*)\\}", "(.*)"); + Pattern p = Pattern.compile(patternString); + Matcher m = p.matcher(message); + List arr = new ArrayList<>(); + m.find(); + for (int i = 1; i < m.groupCount() + 1; i++) { + arr.add(m.group(i)); + } + return arr; } } diff --git a/backend/src/test/java/ch/puzzle/okr/TestHelper.java b/backend/src/test/java/ch/puzzle/okr/TestHelper.java index cc08cee2f2..238115d8fc 100644 --- a/backend/src/test/java/ch/puzzle/okr/TestHelper.java +++ b/backend/src/test/java/ch/puzzle/okr/TestHelper.java @@ -1,5 +1,6 @@ package ch.puzzle.okr; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.User; import ch.puzzle.okr.models.authorization.AuthorizationRole; import ch.puzzle.okr.models.authorization.AuthorizationUser; @@ -81,4 +82,8 @@ public static Jwt mockJwtToken(String username, String firstname, String lastnam return new Jwt(exampleToken, Instant.now(), Instant.now().plusSeconds(3600), headers, claims); } + + public static List getAllErrorKeys(List errors) { + return errors.stream().map(ErrorDto::getErrorKey).toList(); + } } diff --git a/backend/src/test/java/ch/puzzle/okr/service/business/KeyResultBusinessServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/business/KeyResultBusinessServiceTest.java index d7180db2e0..fadb48f868 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/business/KeyResultBusinessServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/business/KeyResultBusinessServiceTest.java @@ -101,13 +101,10 @@ void shouldGetOrdinalKeyResultById() { @Test void shouldThrowExceptionWhenDefaultMethodUsed() { - ResponseStatusException exception = assertThrows(OkrResponseStatusException.class, - () -> keyResultBusinessService.updateEntity(metricKeyResult.getId(), metricKeyResult, - authorizationUser)); + IllegalCallerException exception = assertThrows(IllegalCallerException.class, () -> keyResultBusinessService + .updateEntity(metricKeyResult.getId(), metricKeyResult, authorizationUser)); - assertEquals(BAD_REQUEST, exception.getStatus()); - assertEquals("unsupported method in class " + KeyResultBusinessService.class.getSimpleName() - + ", use updateEntities() instead", exception.getReason()); + assertEquals("unsupported method 'updateEntity' use updateEntities() instead", exception.getMessage()); } @Test diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/ActionValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/ActionValidationServiceTest.java index 8a3ad9cc68..90535b8beb 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/ActionValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/ActionValidationServiceTest.java @@ -1,6 +1,9 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.Action; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.keyresult.KeyResult; import ch.puzzle.okr.models.keyresult.KeyResultMetric; import ch.puzzle.okr.service.persistence.ActionPersistenceService; @@ -16,7 +19,6 @@ import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.web.server.ResponseStatusException; import java.util.List; import java.util.stream.Stream; @@ -30,20 +32,13 @@ @ExtendWith(MockitoExtension.class) class ActionValidationServiceTest { - @Mock - ActionPersistenceService actionPersistenceService; - private final KeyResult keyResult = KeyResultMetric.Builder.builder().withId(10L).withTitle("KR Title").build(); private final Action action1 = Action.Builder.builder().withId(null).withAction("Neue Katze").withIsChecked(false) .withPriority(0).withKeyResult(keyResult).build(); private final Action action2 = Action.Builder.builder().withId(2L).withAction("Neues Lama").withIsChecked(true) .withPriority(1).withKeyResult(keyResult).build(); - - @BeforeEach - void setUp() { - Mockito.lenient().when(actionPersistenceService.getModelName()).thenReturn("Action"); - } - + @Mock + ActionPersistenceService actionPersistenceService; @Spy @InjectMocks private ActionValidationService validator; @@ -51,8 +46,13 @@ void setUp() { private static Stream actionValidationArguments() { return Stream.of( arguments(StringUtils.repeat('1', 5000), - List.of("Attribute Action has a max length of 4096 characters")), - arguments(null, List.of("Action must not be null"))); + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN_0_4096", List.of("action", "Action")))), + arguments(null, List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("action", "Action"))))); + } + + @BeforeEach + void setUp() { + Mockito.lenient().when(actionPersistenceService.getModelName()).thenReturn("Action"); } @Test @@ -65,11 +65,14 @@ void validateOnGetShouldBeSuccessfulWhenValidActionId() { @Test void validateOnGetShouldThrowExceptionIfActionIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGet(null)); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertEquals("ATTRIBUTE_NULL", exception.getReason()); + assertEquals(List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Action"))), exception.getErrors()); } @Test @@ -82,51 +85,50 @@ void validateOnCreateShouldBeSuccessfulWhenActionIsValid() { @Test void validateOnCreateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(null)); - assertEquals("Given model Action is null", exception.getReason()); + assertEquals(BAD_REQUEST, exception.getStatus()); + assertEquals("MODEL_NULL", exception.getReason()); + assertEquals(List.of(new ErrorDto("MODEL_NULL", List.of("Action"))), exception.getErrors()); } @Test void validateOnCreateShouldThrowExceptionWhenIdIsNotNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(action2)); - assertEquals("Model Action cannot have id while create. Found id 2", exception.getReason()); + assertEquals(BAD_REQUEST, exception.getStatus()); + assertEquals("ATTRIBUTE_NOT_NULL", exception.getReason()); + assertEquals(List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("Action", "2"))), exception.getErrors()); } @ParameterizedTest @MethodSource("actionValidationArguments") - void validateOnCreateShouldThrowExceptionWhenActionIsInvalid(String actionText, List errors) { + void validateOnCreateShouldThrowExceptionWhenActionIsInvalid(String actionText, List errors) { Action action = Action.Builder.builder().withId(null).withAction(actionText).withIsChecked(false) .withPriority(1).withKeyResult(keyResult).build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(action)); - String[] exceptionParts = exception.getReason().split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - System.out.println(exceptionParts[i].strip()); - errorArray[i] = exceptionParts[i].strip(); - } - - for (int i = 0; i < exceptionParts.length; i++) { - System.out.println(exceptionParts[i]); - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(errors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(errors).contains(exception.getReason())); } @Test void validateOnCreateShouldThrowExceptionWhenAttrsAreMissing() { Action actionInvalid = Action.Builder.builder().withIsChecked(true).build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(actionInvalid)); - assertThat(exception.getReason().strip()).contains("Action must not be null"); - assertThat(exception.getReason().strip()).contains("KeyResult must not be null"); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("action", "Action")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("keyResult", "Action"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -145,45 +147,55 @@ void validateOnUpdateShouldBeSuccessfulWhenActionIsValid() { @Test void validateOnUpdateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(1L, null)); - assertEquals("Given model Action is null", exception.getReason()); + assertEquals(BAD_REQUEST, exception.getStatus()); + assertEquals("MODEL_NULL", exception.getReason()); + assertEquals(List.of(new ErrorDto("MODEL_NULL", List.of("Action"))), exception.getErrors()); } @Test void validateOnUpdateShouldThrowExceptionWhenIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(null, action1)); verify(validator, times(1)).throwExceptionWhenModelIsNull(action1); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertEquals("ATTRIBUTE_NULL", exception.getReason()); + assertEquals(List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Action"))), exception.getErrors()); } @Test void validateOnUpdateShouldThrowExceptionWhenIdHasChanged() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(1L, action2)); verify(validator, times(1)).throwExceptionWhenModelIsNull(action2); verify(validator, times(1)).throwExceptionWhenIdIsNull(action2.getId()); verify(validator, times(1)).throwExceptionWhenIdHasChanged(1L, action2.getId()); - assertEquals("Id 1 has changed to 2 during update", exception.getReason()); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertEquals("ATTRIBUTE_CHANGED", exception.getReason()); + assertEquals(List.of(new ErrorDto("ATTRIBUTE_CHANGED", List.of("ID", "1", "2"))), exception.getErrors()); } @Test void validateOnUpdateShouldThrowExceptionWhenEntityDoesNotExist() { - String reason = "entity not found"; - when(actionPersistenceService.findById(anyLong())).thenThrow(new ResponseStatusException(BAD_REQUEST, reason)); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + String reason = "MODEL_WITH_ID_NOT_FOUND"; + when(actionPersistenceService.findById(anyLong())) + .thenThrow(new OkrResponseStatusException(BAD_REQUEST, reason)); + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(action2.getId(), action2)); verify(validator, times(1)).throwExceptionWhenModelIsNull(action2); verify(validator, times(1)).throwExceptionWhenIdIsNull(action2.getId()); verify(validator, times(1)).throwExceptionWhenIdHasChanged(action2.getId(), action2.getId()); + assertEquals(BAD_REQUEST, exception.getStatus()); - assertEquals(reason, exception.getReason()); + assertEquals("MODEL_WITH_ID_NOT_FOUND", exception.getReason()); } @Test @@ -192,13 +204,17 @@ void validateOnUpdateShouldThrowExceptionWhenKeyResultNotSet() { Action action = Action.Builder.builder().withId(id).withAction("Action").withIsChecked(false).withPriority(1) .build(); when(actionPersistenceService.findById(id)).thenReturn(action); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(id, action)); verify(validator, times(1)).throwExceptionWhenModelIsNull(action); verify(validator, times(1)).throwExceptionWhenIdIsNull(action.getId()); verify(validator, times(1)).throwExceptionWhenIdHasChanged(id, action.getId()); - assertTrue(exception.getReason().startsWith("KeyResult must not be null.")); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertEquals("ATTRIBUTE_NOT_NULL", exception.getReason()); + assertEquals(List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("KeyResult", "Action"))), + exception.getErrors()); } @Test @@ -208,36 +224,32 @@ void validateOnUpdateShouldThrowExceptionWhenKeyResultIdHasChanged() { .withKeyResult(KeyResultMetric.Builder.builder().withId(11L).withTitle("KR Title").build()).build(); when(actionPersistenceService.findById(anyLong())).thenReturn(action2); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(action.getId(), action)); verify(validator, times(1)).throwExceptionWhenModelIsNull(action); verify(validator, times(1)).throwExceptionWhenIdIsNull(action.getId()); verify(validator, times(1)).throwExceptionWhenIdHasChanged(action.getId(), action2.getId()); + assertEquals(BAD_REQUEST, exception.getStatus()); - assertTrue(exception.getReason().startsWith("Not allowed change the association to the key result")); + assertEquals("ATTRIBUTE_CANNOT_CHANGE", exception.getReason()); + assertEquals(List.of(new ErrorDto("ATTRIBUTE_CANNOT_CHANGE", List.of("KeyResult", "Action"))), + exception.getErrors()); } @ParameterizedTest @MethodSource("actionValidationArguments") - void validateOnUpdateShouldThrowExceptionWhenTitleIsInvalid(String actionText, List errors) { + void validateOnUpdateShouldThrowExceptionWhenTitleIsInvalid(String actionText, List errors) { Action action = Action.Builder.builder().withId(3L).withAction(actionText).withIsChecked(false).withPriority(1) .withKeyResult(keyResult).build(); when(actionPersistenceService.findById(anyLong())).thenReturn(action); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(3L, action)); - String[] exceptionParts = exception.getReason().split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - } - - for (int i = 0; i < exceptionParts.length; i++) { - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(errors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(errors).contains(exception.getReason())); } @Test @@ -245,10 +257,13 @@ void validateOnUpdateShouldThrowExceptionWhenKeyResultIsMissing() { Action actionInvalid = Action.Builder.builder().withId(11L).withIsChecked(true).build(); when(actionPersistenceService.findById(anyLong())).thenReturn(actionInvalid); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(11L, actionInvalid)); - assertThat(exception.getReason().strip()).contains("KeyResult must not be null."); + assertEquals(BAD_REQUEST, exception.getStatus()); + assertEquals("ATTRIBUTE_NOT_NULL", exception.getReason()); + assertEquals(List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("KeyResult", "Action"))), + exception.getErrors()); } @Test @@ -257,10 +272,12 @@ void validateOnUpdateShouldThrowExceptionWhenAttrsAreMissing() { .build(); when(actionPersistenceService.findById(anyLong())).thenReturn(actionInvalid); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(11L, actionInvalid)); - assertThat(exception.getReason().strip()).contains("Action must not be null."); + assertEquals(BAD_REQUEST, exception.getStatus()); + assertEquals("ATTRIBUTE_NOT_NULL", exception.getReason()); + assertEquals(List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("action", "Action"))), exception.getErrors()); } @Test @@ -273,11 +290,14 @@ void validateOnDeleteShouldBeSuccessfulWhenValidActionId() { @Test void validateOnDeleteShouldThrowExceptionIfActionIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGet(null)); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertEquals("ATTRIBUTE_NULL", exception.getReason()); + assertEquals(List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Action"))), exception.getErrors()); } } diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/KeyResultValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/KeyResultValidationServiceTest.java index 6b66b676ff..533d64b9b8 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/KeyResultValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/KeyResultValidationServiceTest.java @@ -1,5 +1,7 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.*; import ch.puzzle.okr.models.keyresult.KeyResult; import ch.puzzle.okr.models.keyresult.KeyResultMetric; @@ -25,10 +27,10 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.*; +import static org.springframework.http.HttpStatus.BAD_REQUEST; @ExtendWith(MockitoExtension.class) class KeyResultValidationServiceTest { @@ -69,16 +71,19 @@ void setUp() { private static Stream nameValidationArguments() { return Stream.of( - arguments(StringUtils.repeat('1', 251), List - .of("Attribute title must have a length between 2 and 250 characters when saving key result")), - arguments(StringUtils.repeat('1', 1), List - .of("Attribute title must have a length between 2 and 250 characters when saving key result")), - arguments("", List.of("Title can not be blank", - "Attribute title must have a length between 2 and 250 characters when saving key result")), - arguments(" ", List.of("Title can not be blank", - "Attribute title must have a length between 2 and 250 characters when saving key result")), - arguments(" ", List.of("Title can not be blank")), - arguments(null, List.of("Title can not be blank", "Title can not be null"))); + arguments(StringUtils.repeat('1', 251), + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("title", "KeyResult", "2", "250")))), + arguments(StringUtils.repeat('1', 1), + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("title", "KeyResult", "2", "250")))), + arguments("", + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("title", "KeyResult", "2", "250")), + new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("title", "KeyResult")))), + arguments(" ", + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("title", "KeyResult", "2", "250")), + new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("title", "KeyResult")))), + arguments(" ", List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("title", "KeyResult")))), + arguments(null, List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("title", "KeyResult")), + new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("title", "KeyResult"))))); } @Test @@ -91,11 +96,16 @@ void validateOnGetShouldBeSuccessfulWhenValidKeyResultId() { @Test void validateOnGetShouldThrowExceptionIfKeyResultIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGet(null)); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "KeyResult"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -108,55 +118,59 @@ void validateOnCreateShouldBeSuccessfulWhenKeyResultIsValid() { @Test void validateOnCreateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(null)); - assertEquals("Given model KeyResult is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_NULL", List.of("KeyResult"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnCreateShouldThrowExceptionWhenIdIsNotNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(keyResultMetric)); - assertEquals("Model KeyResult cannot have id while create. Found id 5", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "KeyResult"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @ParameterizedTest @MethodSource("nameValidationArguments") - void validateOnCreateShouldThrowExceptionWhenTitleIsInvalid(String title, List errors) { + void validateOnCreateShouldThrowExceptionWhenTitleIsInvalid(String title, List errors) { KeyResult keyResult = KeyResultMetric.Builder.builder().withBaseline(3.0).withStretchGoal(5.0) .withUnit(Unit.FTE).withId(null).withTitle(title).withOwner(user).withObjective(objective) .withCreatedBy(user).withCreatedOn(LocalDateTime.MIN).build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(keyResult)); - String[] exceptionParts = exception.getReason().split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - System.out.println(exceptionParts[i].strip()); - errorArray[i] = exceptionParts[i].strip(); - } - - for (int i = 0; i < exceptionParts.length; i++) { - System.out.println(exceptionParts[i]); - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(errors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(errors).contains(exception.getReason())); } @Test void validateOnCreateShouldThrowExceptionWhenAttrsAreMissing() { KeyResult keyResultInvalid = KeyResultMetric.Builder.builder().withId(null).withTitle("Title").build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(keyResultInvalid)); - assertThat(exception.getReason().strip()).contains("Baseline must not be null."); - assertThat(exception.getReason().strip()).contains("StretchGoal must not be null."); - assertThat(exception.getReason().strip()).contains("Unit must not be null."); - assertThat(exception.getReason().strip()).contains("CreatedBy must not be null."); - assertThat(exception.getReason().strip()).contains("Owner must not be null."); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("owner", "KeyResult")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("stretchGoal", "KeyResult")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("createdBy", "KeyResult")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("createdOn", "KeyResult")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("objective", "KeyResult")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("baseline", "KeyResult")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("unit", "KeyResult"))); + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -178,55 +192,56 @@ void validateOnUpdateShouldBeSuccessfulWhenKeyResultIsValid() { @Test void validateOnUpdateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(1L, null)); - assertEquals("Given model KeyResult is null", exception.getReason()); + assertEquals(BAD_REQUEST, exception.getStatus()); + assertEquals("MODEL_NULL", exception.getReason()); + assertEquals(List.of(new ErrorDto("MODEL_NULL", List.of("KeyResult"))), exception.getErrors()); } @Test void validateOnUpdateShouldThrowExceptionWhenIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(null, keyResultOrdinal)); verify(validator, times(1)).throwExceptionWhenModelIsNull(keyResultOrdinal); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertEquals("ATTRIBUTE_NULL", exception.getReason()); + assertEquals(List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "KeyResult"))), exception.getErrors()); } @Test void validateOnUpdateShouldThrowExceptionWhenIdHasChanged() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(1L, keyResultMetric)); verify(validator, times(1)).throwExceptionWhenModelIsNull(keyResultMetric); verify(validator, times(1)).throwExceptionWhenIdIsNull(keyResultMetric.getId()); verify(validator, times(1)).throwExceptionWhenIdHasChanged(1L, keyResultMetric.getId()); - assertEquals("Id 1 has changed to 5 during update", exception.getReason()); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertEquals("ATTRIBUTE_CHANGED", exception.getReason()); + assertEquals(List.of(new ErrorDto("ATTRIBUTE_CHANGED", List.of("ID", "1", "5"))), exception.getErrors()); } @ParameterizedTest @MethodSource("nameValidationArguments") - void validateOnUpdateShouldThrowExceptionWhenTitleIsInvalid(String title, List errors) { + void validateOnUpdateShouldThrowExceptionWhenTitleIsInvalid(String title, List errors) { Long id = 3L; KeyResult keyResult = KeyResultMetric.Builder.builder().withBaseline(3.0).withStretchGoal(5.0) .withUnit(Unit.FTE).withId(id).withTitle(title).withOwner(user).withObjective(objective) .withCreatedBy(user).withCreatedOn(LocalDateTime.MIN).build(); when(keyResultPersistenceService.findById(id)).thenReturn(keyResult); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(id, keyResult)); - String[] exceptionParts = exception.getReason().split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - } - - for (int i = 0; i < exceptionParts.length; i++) { - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(errors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(errors).contains(exception.getReason())); } @Test @@ -236,14 +251,19 @@ void validateOnUpdateShouldThrowExceptionWhenAttrsAreMissing() { .withObjective(objective).build(); when(keyResultPersistenceService.findById(id)).thenReturn(keyResultInvalid); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(id, keyResultInvalid)); - assertThat(exception.getReason().strip()).contains("Baseline must not be null."); - assertThat(exception.getReason().strip()).contains("StretchGoal must not be null."); - assertThat(exception.getReason().strip()).contains("Unit must not be null."); - assertThat(exception.getReason().strip()).contains("CreatedBy must not be null."); - assertThat(exception.getReason().strip()).contains("Owner must not be null."); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("baseline", "KeyResult")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("stretchGoal", "KeyResult")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("unit", "KeyResult")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("createdBy", "KeyResult")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("createdOn", "KeyResult")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("owner", "KeyResult"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -256,11 +276,15 @@ void validateOnDeleteShouldBeSuccessfulWhenValidKeyResultId() { @Test void validateOnDeleteShouldThrowExceptionIfKeyResultIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGet(null)); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "KeyResult"))); + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } } diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index 68113a64df..efc82d4911 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -31,8 +31,8 @@ "DATA_HAS_BEEN_UPDATED": "Die Daten des {0} wurden bereits von einem anderen Benutzer geändert oder gelöscht", "MODEL_NULL": "Objekt {0} ist null", "MODEL_WITH_ID_NOT_FOUND": "Objekt {0} mit der Id {1} konnte nicht gefunden werden", - "ATTRIBUTE_NULL": "Attribut {0} auf dem Objekt {1} ist null", - "ATTRIBUTE_NOT_NULL": "Das Attribut {0} auf dem Objekt {0} darf nicht null sein", + "ATTRIBUTE_NULL": "Attribut {0} auf dem Objekt {1} ist nicht null", + "ATTRIBUTE_NOT_NULL": "Das Attribut {0} auf dem Objekt {1} darf nicht null sein", "ATTRIBUTE_CHANGED": "Das Attribut {0} wurde während des Updated von {1} auf {2} geändert", "ATTRIBUTE_NOT_BLANK": "Das Attribut {0} auf dem Objekt {1} darf nicht leer sein", "ATTRIBUTE_NOT_VALID": "Das Attribut {0} auf dem Objekt {1} ist ungültig", From 62acae79aabdc3503000334606ed1feb2d7cb326 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Fri, 24 Nov 2023 12:13:17 +0100 Subject: [PATCH 20/38] fix backend tests --- .../puzzle/okr/mapper/OverviewMapperTest.java | 14 +++- .../AuthorizationServiceTest.java | 79 ++++++++++--------- .../CompletedPersistenceServiceIT.java | 36 ++++++--- .../CompletedValidationServiceTest.java | 58 ++++++++------ 4 files changed, 113 insertions(+), 74 deletions(-) diff --git a/backend/src/test/java/ch/puzzle/okr/mapper/OverviewMapperTest.java b/backend/src/test/java/ch/puzzle/okr/mapper/OverviewMapperTest.java index 6dadaf848b..a9f85497ac 100644 --- a/backend/src/test/java/ch/puzzle/okr/mapper/OverviewMapperTest.java +++ b/backend/src/test/java/ch/puzzle/okr/mapper/OverviewMapperTest.java @@ -1,9 +1,12 @@ package ch.puzzle.okr.mapper; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.dto.overview.OverviewDto; import ch.puzzle.okr.dto.overview.OverviewKeyResultDto; import ch.puzzle.okr.dto.overview.OverviewKeyResultMetricDto; import ch.puzzle.okr.dto.overview.OverviewKeyResultOrdinalDto; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.overview.Overview; import ch.puzzle.okr.models.overview.OverviewId; import ch.puzzle.okr.service.business.OrganisationBusinessService; @@ -18,6 +21,7 @@ import static ch.puzzle.okr.Constants.KEY_RESULT_TYPE_METRIC; import static ch.puzzle.okr.Constants.KEY_RESULT_TYPE_ORDINAL; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.http.HttpStatus.BAD_REQUEST; @@ -181,11 +185,15 @@ void toDtoShouldThrowExceptionWhenKeyResultTypeNotSupported() { .withTeamName("Puzzle ITC").withObjectiveTitle("Objective 1").withKeyResultTitle("Key Result 1") .withKeyResultType("unknown").withCheckInZone("TARGET").build()); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> overviewMapper.toDto(overviews)); assertEquals(BAD_REQUEST, exception.getStatus()); - assertEquals("The key result type unknown can not be converted to a metric or ordinal DTO", - exception.getReason()); + + List expectedErrors = List.of(new ErrorDto("KEYRESULT_CONVERSION", List.of("unknown"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } } diff --git a/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java index ff2653f1b8..2447dc333a 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java @@ -1,11 +1,10 @@ package ch.puzzle.okr.service.authorization; +import ch.puzzle.okr.TestHelper; import ch.puzzle.okr.converter.JwtConverterFactory; import ch.puzzle.okr.converter.JwtUserConverter; import ch.puzzle.okr.dto.ErrorDto; -import ch.puzzle.okr.models.Objective; -import ch.puzzle.okr.models.Team; -import ch.puzzle.okr.models.User; +import ch.puzzle.okr.models.*; import ch.puzzle.okr.models.authorization.AuthorizationUser; import ch.puzzle.okr.models.checkin.CheckIn; import ch.puzzle.okr.models.checkin.CheckInMetric; @@ -32,9 +31,12 @@ import static ch.puzzle.okr.TestHelper.*; import static ch.puzzle.okr.models.authorization.AuthorizationRole.*; import static ch.puzzle.okr.service.authorization.AuthorizationService.*; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; -import static org.springframework.http.HttpStatus.UNAUTHORIZED; +import static org.springframework.http.HttpStatus.*; @ExtendWith(MockitoExtension.class) class AuthorizationServiceTest { @@ -230,8 +232,7 @@ void hasRoleCreateOrUpdateShouldPassThroughWhenAuthorizedForAllTeamsKeyResults() KeyResult keyResult = KeyResultMetric.Builder.builder().withObjective(objective).build(); AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_ALL_TEAMS)); - String reason = "not authorized to read key result"; - ErrorDto error = new ErrorDto(reason, List.of()); + ErrorDto error = new ErrorDto("NOT_AUTHORIZED_TO_READ", List.of("KeyResult")); when(objectivePersistenceService.findObjectiveById(keyResult.getObjective().getId(), authorizationUser, error)) .thenReturn(objective); @@ -246,8 +247,7 @@ void hasRoleCreateOrUpdateShouldPassThroughWhenAuthorizedForTeamCheckIns() { CheckIn checkIn = CheckInMetric.Builder.builder().withKeyResult(keyResult).build(); AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_TEAM)); - String reason = "not authorized to read check in"; - ErrorDto error = new ErrorDto(reason, List.of()); + ErrorDto error = new ErrorDto("NOT_AUTHORIZED_TO_READ", List.of("Check-in")); when(objectivePersistenceService.findObjectiveByKeyResultId(checkIn.getKeyResult().getObjective().getId(), authorizationUser, error)).thenReturn(objective); @@ -261,10 +261,14 @@ void hasRoleCreateOrUpdateShouldThrowExceptionWhenNotAuthorizedForFirstLevelObje AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_ALL_TEAMS)); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> authorizationService.hasRoleCreateOrUpdate(objective, authorizationUser)); + + List expectedErrors = List.of(new ErrorDto("NOT_AUTHORIZED_TO_WRITE", List.of("Objective"))); + assertEquals(UNAUTHORIZED, exception.getStatus()); - assertEquals("not authorized to create or update objective", exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -284,8 +288,7 @@ void hasRoleCreateOrUpdateByObjectiveIdShouldPassThroughWhenAuthorizedForAllObje Long id = 13L; Objective objective = Objective.Builder.builder().withTeam(Team.Builder.builder().withId(5L).build()).build(); AuthorizationUser authorizationUser = defaultAuthorizationUser(); - String reason = "not authorized to read objective"; - ErrorDto error = new ErrorDto(reason, List.of()); + ErrorDto error = new ErrorDto("NOT_AUTHORIZED_TO_READ", List.of("Objective")); when(objectivePersistenceService.findObjectiveById(id, authorizationUser, error)).thenReturn(objective); @@ -313,8 +316,7 @@ void isWriteableShouldReturnTrueWhenAuthorizedToWriteAllKeyResults() { Objective objective = Objective.Builder.builder().withTeam(Team.Builder.builder().withId(4L).build()).build(); KeyResult keyResult = KeyResultMetric.Builder.builder().withObjective(objective).build(); AuthorizationUser authorizationUser = defaultAuthorizationUser(); - String reason = "not authorized to read key result"; - ErrorDto error = new ErrorDto(reason, List.of()); + ErrorDto error = new ErrorDto("NOT_AUTHORIZED_TO_READ", List.of("KeyResult")); when(objectivePersistenceService.findObjectiveById(keyResult.getObjective().getId(), authorizationUser, error)) .thenReturn(objective); @@ -328,7 +330,7 @@ void isWriteableShouldReturnFalseWhenNotAuthorizedToWriteKeyResults() { KeyResult keyResult = KeyResultMetric.Builder.builder().withObjective(objective).build(); AuthorizationUser authorizationUser = mockAuthorizationUser(user, List.of(4L), 13L, List.of(WRITE_TEAM)); String reason = "not authorized to read key result"; - ErrorDto error = new ErrorDto(reason, List.of()); + ErrorDto error = new ErrorDto("NOT_AUTHORIZED_TO_READ", List.of("KeyResult")); when(objectivePersistenceService.findObjectiveById(keyResult.getObjective().getId(), authorizationUser, error)) .thenReturn(objective); @@ -342,11 +344,9 @@ void isWriteableShouldReturnTrueWhenAuthorizedToWriteAllCheckIns() { KeyResult keyResult = KeyResultMetric.Builder.builder().withObjective(objective).build(); CheckIn checkIn = CheckInMetric.Builder.builder().withKeyResult(keyResult).build(); AuthorizationUser authorizationUser = defaultAuthorizationUser(); - String reason = "not authorized to read check in"; - ErrorDto error = new ErrorDto(reason, List.of()); - when(objectivePersistenceService.findObjectiveByKeyResultId(checkIn.getKeyResult().getId(), authorizationUser, - error)).thenReturn(objective); + when(objectivePersistenceService.findObjectiveByKeyResultId(eq(checkIn.getKeyResult().getId()), + eq(authorizationUser), any())).thenReturn(objective); assertTrue(authorizationService.isWriteable(checkIn, authorizationUser)); } @@ -357,11 +357,9 @@ void isWriteableShouldReturnFalseWhenNotAuthorizedToWriteCheckIns() { KeyResult keyResult = KeyResultMetric.Builder.builder().withObjective(objective).build(); CheckIn checkIn = CheckInMetric.Builder.builder().withKeyResult(keyResult).build(); AuthorizationUser authorizationUser = mockAuthorizationUser(user, List.of(4L), 13L, List.of(WRITE_TEAM)); - String reason = "not authorized to read check in"; - ErrorDto error = new ErrorDto(reason, List.of()); - when(objectivePersistenceService.findObjectiveByKeyResultId(checkIn.getKeyResult().getId(), authorizationUser, - error)).thenReturn(objective); + when(objectivePersistenceService.findObjectiveByKeyResultId(eq(checkIn.getKeyResult().getId()), + eq(authorizationUser), any())).thenReturn(objective); assertFalse(authorizationService.isWriteable(checkIn, authorizationUser)); } @@ -371,10 +369,8 @@ void hasRoleDeleteByObjectiveIdShouldPassThroughWhenAuthorizedForAllObjectives() Long id = 13L; Objective objective = Objective.Builder.builder().withTeam(Team.Builder.builder().withId(5L).build()).build(); AuthorizationUser authorizationUser = defaultAuthorizationUser(); - String reason = "not authorized to read objective"; - ErrorDto error = new ErrorDto(reason, List.of()); - when(objectivePersistenceService.findObjectiveById(id, authorizationUser, error)).thenReturn(objective); + when(objectivePersistenceService.findObjectiveById(eq(id), eq(authorizationUser), any())).thenReturn(objective); authorizationService.hasRoleDeleteByObjectiveId(id, authorizationUser); } @@ -386,9 +382,8 @@ void hasRoleDeleteByKeyResultIdShouldPassThroughWhenAuthorizedForAllTeamsKeyResu AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_ALL_TEAMS)); String reason = "not authorized to read key result"; - ErrorDto error = new ErrorDto(reason, List.of()); - when(objectivePersistenceService.findObjectiveByKeyResultId(id, authorizationUser, error)) + when(objectivePersistenceService.findObjectiveByKeyResultId(eq(id), eq(authorizationUser), any())) .thenReturn(objective); authorizationService.hasRoleDeleteByKeyResultId(id, authorizationUser); @@ -401,9 +396,9 @@ void hasRoleDeleteByCheckInIdShouldPassThroughWhenAuthorizedForTeamCheckIns() { AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_TEAM)); String reason = "not authorized to read check in"; - ErrorDto error = new ErrorDto(reason, List.of()); - when(objectivePersistenceService.findObjectiveByCheckInId(id, authorizationUser, error)).thenReturn(objective); + when(objectivePersistenceService.findObjectiveByCheckInId(eq(id), eq(authorizationUser), any())) + .thenReturn(objective); authorizationService.hasRoleDeleteByCheckInId(id, authorizationUser); } @@ -415,14 +410,17 @@ void hasRoleDeleteByKeyResultIdShouldThrowExceptionWhenNotAuthorizedForFirstLeve AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_ALL_TEAMS)); String reason = "not authorized to read key result"; - ErrorDto error = new ErrorDto(reason, List.of()); - when(objectivePersistenceService.findObjectiveByKeyResultId(id, authorizationUser, error)) + when(objectivePersistenceService.findObjectiveByKeyResultId(eq(id), eq(authorizationUser), any())) .thenReturn(objective); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> authorizationService.hasRoleDeleteByKeyResultId(id, authorizationUser)); + + List expectedErrors = List.of(new ErrorDto("NOT_AUTHORIZED_TO_DELETE", List.of("KeyResult"))); + assertEquals(UNAUTHORIZED, exception.getStatus()); - assertEquals("not authorized to delete key result", exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -431,15 +429,18 @@ void hasRoleDeleteByCheckInIdShouldThrowExceptionWhenNotAuthorizedForOtherTeamOb Objective objective = Objective.Builder.builder().withTeam(Team.Builder.builder().withId(5L).build()).build(); AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_TEAM)); - String reason = "not authorized to read check in"; - ErrorDto error = new ErrorDto(reason, List.of()); - when(objectivePersistenceService.findObjectiveByCheckInId(id, authorizationUser, error)).thenReturn(objective); + when(objectivePersistenceService.findObjectiveByCheckInId(eq(id), eq(authorizationUser), any())) + .thenReturn(objective); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> authorizationService.hasRoleDeleteByCheckInId(id, authorizationUser)); + + List expectedErrors = List.of(new ErrorDto("NOT_AUTHORIZED_TO_DELETE", List.of("Check-in"))); + assertEquals(UNAUTHORIZED, exception.getStatus()); - assertEquals("not authorized to delete check in", exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } private void setSecurityContext(Jwt token) { diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/CompletedPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/CompletedPersistenceServiceIT.java index a501a64f7d..5ff58fd754 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/CompletedPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/CompletedPersistenceServiceIT.java @@ -1,7 +1,10 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.Completed; import ch.puzzle.okr.models.Objective; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.test.SpringIntegrationTest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -9,8 +12,11 @@ import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; -import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; +import static org.springframework.http.HttpStatus.*; @SpringIntegrationTest class CompletedPersistenceServiceIT { @@ -70,10 +76,14 @@ void updateCompletedShouldThrowExceptionWhenAlreadyUpdated() { Completed updateCompleted = createCompleted(createdCompleted.getId(), 0); updateCompleted.setComment("Updated completed"); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> completedPersistenceService.save(updateCompleted)); + + List expectedErrors = List.of(new ErrorDto("DATA_HAS_BEEN_UPDATED", List.of("Completed"))); + assertEquals(UNPROCESSABLE_ENTITY, exception.getStatus()); - assertTrue(exception.getReason().contains("updated or deleted by another user")); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -91,10 +101,14 @@ void deleteCompletedIdShouldDeleteExistingCompletedByObjectiveId() { completedPersistenceService.deleteById(3L); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> completedPersistenceService.findById(3L)); - assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); - assertEquals(String.format("Completed with id %d not found", 3), exception.getReason()); + + List expectedErrors = List.of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("Completed", "3"))); + + assertEquals(NOT_FOUND, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -103,9 +117,13 @@ void deleteCompletedShouldThrowExceptionWhenCompletedNotFound() { completedPersistenceService.deleteById(createdCompleted.getId()); Long completedId = createdCompleted.getId(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> completedPersistenceService.findById(completedId)); - assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); - assertEquals(String.format("Completed with id %d not found", createdCompleted.getId()), exception.getReason()); + + List expectedErrors = List.of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("Completed", "200"))); + + assertEquals(NOT_FOUND, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } } diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/CompletedValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/CompletedValidationServiceTest.java index da2b7bace0..ac08aa0fb5 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/CompletedValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/CompletedValidationServiceTest.java @@ -1,5 +1,7 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.*; import ch.puzzle.okr.service.persistence.CompletedPersistenceService; import org.apache.commons.lang3.StringUtils; @@ -22,10 +24,11 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.*; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; @ExtendWith(MockitoExtension.class) class CompletedValidationServiceTest { @@ -67,7 +70,7 @@ void setUp() { private static Stream nameValidationArguments() { return Stream.of(arguments(StringUtils.repeat('1', 5000), - List.of("Attribute comment has a max length of 4096 characters when completing an objective"))); + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("comment", "Completed", "0", "4096"))))); } @Test @@ -80,50 +83,55 @@ void validateOnCreateShouldBeSuccessfulWhenCompletedIsValid() { @Test void validateOnCreateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(null)); - assertEquals("Given model Completed is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_NULL", List.of("Completed"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnCreateShouldThrowExceptionWhenIdIsNotNull() { Completed completed = Completed.Builder.builder().withId(300L).withObjective(this.objective) .withComment("Not valid").build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(completed)); - assertEquals("Model Completed cannot have id while create. Found id 300", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Completed"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @ParameterizedTest @MethodSource("nameValidationArguments") - void validateOnCreateShouldThrowExceptionWhenCommentIsInvalid(String comment, List errors) { + void validateOnCreateShouldThrowExceptionWhenCommentIsInvalid(String comment, List expectedErrors) { Completed completed = Completed.Builder.builder().withObjective(this.objective).withComment(comment).build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(completed)); - String[] exceptionParts = exception.getReason().split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - } - - for (int i = 0; i < exceptionParts.length; i++) { - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnCreateShouldThrowExceptionWhenAttrsAreMissing() { Completed completedInvalid = Completed.Builder.builder().withId(null).withComment("Valid comment") .withObjective(null).build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(completedInvalid)); - assertThat(exception.getReason().strip()).contains("Objective must not be null."); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("objective", "Completed"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -136,11 +144,15 @@ void validateOnDeleteShouldBeSuccessfulWhenValidCompletedId() { @Test void validateOnDeleteShouldThrowExceptionIfCompletedIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGet(null)); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Completed"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } } From 551ba3dc639edf883b7e0f0d2bbf2f6b9780dc34 Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Fri, 24 Nov 2023 14:09:57 +0100 Subject: [PATCH 21/38] Adjust tests for actionval objectiveper objectiveval teamper teamval and userval --- .../ObjectivePersistenceServiceIT.java | 26 ++++- .../persistence/TeamPersistenceServiceIT.java | 29 +++-- .../ActionValidationServiceTest.java | 6 +- .../ObjectiveValidationServiceTest.java | 106 +++++++++++------ .../validation/TeamValidationServiceTest.java | 109 ++++++++++++++---- .../validation/UserValidationServiceTest.java | 102 ++++++++++++---- 6 files changed, 285 insertions(+), 93 deletions(-) diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java index 3f0c0224a3..0d459e3879 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java @@ -1,5 +1,6 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.TestHelper; import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.*; import ch.puzzle.okr.models.authorization.AuthorizationUser; @@ -13,6 +14,7 @@ import java.util.List; import static ch.puzzle.okr.TestHelper.defaultAuthorizationUser; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.http.HttpStatus.*; @@ -188,27 +190,39 @@ void deleteObjectiveShouldThrowExceptionWhenKeyResultNotFound() { objectivePersistenceService.deleteById(createdObjective.getId()); Long objectiveId = createdObjective.getId(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> objectivePersistenceService.findById(objectiveId)); + + List expectedErrors = List.of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("Objective", "200"))); + assertEquals(NOT_FOUND, exception.getStatus()); - assertEquals(String.format("Objective with id %d not found", createdObjective.getId()), exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void countByTeamAndQuarterShouldThrowErrorIfQuarterDoesNotExist() { Team teamId5 = teamPersistenceService.findById(5L); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> objectivePersistenceService.countByTeamAndQuarter(teamId5, quarterPersistenceService.findById(12L))); + + List expectedErrors = List.of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("Quarter", "12"))); + assertEquals(NOT_FOUND, exception.getStatus()); - assertEquals(String.format("Quarter with id %d not found", 12), exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); Quarter quarterId2 = quarterPersistenceService.findById(2L); - ResponseStatusException exceptionTeam = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exceptionTeam = assertThrows(OkrResponseStatusException.class, () -> objectivePersistenceService.countByTeamAndQuarter(teamPersistenceService.findById(500L), quarterId2)); + + List expectedErrorsTeam = List.of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("Team", "500"))); + assertEquals(NOT_FOUND, exceptionTeam.getStatus()); - assertEquals(String.format("Team with id %d not found", 500), exceptionTeam.getReason()); + assertThat(expectedErrorsTeam).hasSameElementsAs(exceptionTeam.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrorsTeam).contains(exceptionTeam.getReason())); } diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java index f1cfc68165..20d12fe35b 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java @@ -1,5 +1,8 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.Team; import ch.puzzle.okr.test.SpringIntegrationTest; import org.junit.jupiter.api.AfterEach; @@ -10,7 +13,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; import java.util.List; @@ -20,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; @@ -56,20 +59,26 @@ void getTeamByIdShouldReturnTeam() throws ResponseStatusException { @Test void getTeamByIdShouldThrowExceptionWhenTeamNotFound() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> teamPersistenceService.findById(321L)); + List expectedErrors = List.of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("Team", "321"))); + assertEquals(NOT_FOUND, exception.getStatus()); - assertEquals("Team with id 321 not found", exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void getTeamByIdShouldThrowExceptionWhenTeamIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> teamPersistenceService.findById(null)); - assertEquals(HttpStatus.BAD_REQUEST, exception.getStatus()); - assertEquals("Missing identifier for Team", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of())); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -112,10 +121,14 @@ void shouldDeleteTeam() { createdTeam = teamPersistenceService.save(team); teamPersistenceService.deleteById(createdTeam.getId()); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> teamPersistenceService.findById(createdTeam.getId())); + + List expectedErrors = List.of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("Team", "200"))); + assertEquals(NOT_FOUND, exception.getStatus()); - assertEquals(String.format("Team with id %d not found", createdTeam.getId()), exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @ParameterizedTest diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/ActionValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/ActionValidationServiceTest.java index 90535b8beb..44d41c567c 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/ActionValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/ActionValidationServiceTest.java @@ -98,9 +98,11 @@ void validateOnCreateShouldThrowExceptionWhenIdIsNotNull() { OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(action2)); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Action"))); + assertEquals(BAD_REQUEST, exception.getStatus()); - assertEquals("ATTRIBUTE_NOT_NULL", exception.getReason()); - assertEquals(List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("Action", "2"))), exception.getErrors()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @ParameterizedTest diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java index 2bffeb3b86..9f4e14d150 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java @@ -1,5 +1,7 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.*; import ch.puzzle.okr.service.persistence.ObjectivePersistenceService; import org.apache.commons.lang3.StringUtils; @@ -22,10 +24,10 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.*; +import static org.springframework.http.HttpStatus.BAD_REQUEST; @ExtendWith(MockitoExtension.class) class ObjectiveValidationServiceTest { @@ -89,11 +91,15 @@ void validateOnGetShouldBeSuccessfulWhenValidObjectiveId() { @Test void validateOnGetShouldThrowExceptionIfObjectiveIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGet(null)); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Objective"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -106,18 +112,26 @@ void validateOnCreateShouldBeSuccessfulWhenTeamIsValid() { @Test void validateOnCreateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(null)); - assertEquals("Given model Objective is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_NULL", List.of("Objective"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnCreateShouldThrowExceptionWhenIdIsNotNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(objective1)); - assertEquals("Model Objective cannot have id while create. Found id 1", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Objective"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @ParameterizedTest @@ -145,14 +159,18 @@ void validateOnCreateShouldThrowExceptionWhenTitleIsInvalid(String title, List validator.validateOnCreate(objectiveInvalid)); - assertThat(exception.getReason().strip()).contains("CreatedOn must not be null."); - assertThat(exception.getReason().strip()).contains("CreatedBy must not be null."); - assertThat(exception.getReason().strip()).contains("Quarter must not be null."); - assertThat(exception.getReason().strip()).contains("Team must not be null."); - assertThat(exception.getReason().strip()).contains("State must not be null."); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("team", "Objective")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("createdBy", "Objective")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("createdOn", "Objective")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("state", "Objective")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("quarter", "Objective"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -161,10 +179,14 @@ void validateOnCreateShouldThrowExceptionWhenAttrModifiedByIsSet() { .withTitle("ModifiedBy is not null on create").withCreatedBy(user).withCreatedOn(LocalDateTime.MAX) .withState(State.DRAFT).withTeam(team).withQuarter(quarter).withModifiedBy(user).build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(objectiveInvalid)); - assertEquals(HttpStatus.BAD_REQUEST, exception.getStatus()); - assertEquals(String.format("Not allowed to set ModifiedBy %s on create", user), exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_SET_FORBIDDEN", List.of("ModifiedBy", + "User{id=1, username='bkaufmann', firstname='Bob', lastname='Kaufmann', email='kaufmann@puzzle.ch'}"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -179,31 +201,43 @@ void validateOnUpdateShouldBeSuccessfulWhenObjectiveIsValid() { @Test void validateOnUpdateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(1L, null)); - assertEquals("Given model Objective is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_NULL", List.of("Objective"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnUpdateShouldThrowExceptionWhenIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(null, objectiveMinimal)); verify(validator, times(1)).throwExceptionWhenModelIsNull(objectiveMinimal); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Objective"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnUpdateShouldThrowExceptionWhenIdHasChanged() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(7L, objective1)); verify(validator, times(1)).throwExceptionWhenModelIsNull(objective1); verify(validator, times(1)).throwExceptionWhenIdIsNull(objective1.getId()); verify(validator, times(1)).throwExceptionWhenIdHasChanged(7L, objective1.getId()); - assertEquals("Id 7 has changed to 1 during update", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_CHANGED", List.of("ID", "7", "1"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @ParameterizedTest @@ -235,14 +269,17 @@ void validateOnUpdateShouldThrowExceptionWhenAttrsAreMissing() { Objective objective = Objective.Builder.builder().withId(5L).withTitle("Title").withModifiedBy(user).build(); when(objectivePersistenceService.findById(objective.getId())).thenReturn(objective); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(5L, objective)); - - assertThat(exception.getReason().strip()).contains("CreatedOn must not be null."); - assertThat(exception.getReason().strip()).contains("CreatedBy must not be null."); - assertThat(exception.getReason().strip()).contains("Quarter must not be null."); - assertThat(exception.getReason().strip()).contains("Team must not be null."); - assertThat(exception.getReason().strip()).contains("State must not be null."); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("quarter", "Objective")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("team", "Objective")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("createdBy", "Objective")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("createdOn", "Objective")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("state", "Objective"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -251,10 +288,13 @@ void validateOnUpdateShouldThrowExceptionWhenAttrModifiedByIsNotSet() { .withTitle("ModifiedBy is not null on create").withCreatedBy(user).withCreatedOn(LocalDateTime.MAX) .withState(State.DRAFT).withTeam(team).withQuarter(quarter).withModifiedBy(null).build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(1L, objectiveInvalid)); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, exception.getStatus()); - assertEquals(String.format("Something went wrong. ModifiedBy %s is not set.", null), exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_CHANGED", List.of("ID", "7", "1"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/TeamValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/TeamValidationServiceTest.java index e5efe933b3..7d9d8441c3 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/TeamValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/TeamValidationServiceTest.java @@ -1,5 +1,8 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.Team; import ch.puzzle.okr.service.persistence.TeamPersistenceService; import org.junit.jupiter.api.BeforeEach; @@ -17,9 +20,9 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import static org.springframework.http.HttpStatus.BAD_REQUEST; @ExtendWith(MockitoExtension.class) class TeamValidationServiceTest { @@ -61,11 +64,15 @@ void validateOnGetShouldBeSuccessfulWhenValidTeamId() { @Test void validateOnGetShouldThrowExceptionIfTeamIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGet(null)); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Team"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -78,73 +85,131 @@ void validateOnDeleteShouldBeSuccessfulWhenValidTeamId() { @Test void validateOnDeleteShouldThrowExceptionIfTeamIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGet(null)); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Team"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); + } + + @Test + void validateOnGetActiveObjectivesShouldBeSuccessfulWhenValidTeamId() { + validator.validateOnGetActiveObjectives(team1); + + verify(validator, times(1)).validateOnGetActiveObjectives(team1); + verify(validator, times(1)).throwExceptionWhenIdIsNull(1L); + verify(validator, times(1)).throwExceptionWhenModelIsNull(team1); + verify(validator, times(1)).throwExceptionWhenIdIsNull(team1.getId()); + verify(validator, times(1)).doesEntityExist(team1.getId()); + } + + @Test + void validateOnGetActiveObjectivesShouldThrowExceptionWhenIdIsNull() { + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, + () -> validator.validateOnGetActiveObjectives(teamWithIdNull)); + + verify(validator, times(1)).throwExceptionWhenModelIsNull(teamWithIdNull); + verify(validator, times(1)).throwExceptionWhenIdIsNull(null); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Team"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnCreateShouldThrowExceptionWhenIdIsNotNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(team1)); verify(validator, times(1)).throwExceptionWhenIdIsNotNull(team1.getId()); - assertEquals("Model Team cannot have id while create. Found id 1", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Team"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnCreateShouldThrowExceptionWhenTeamAlreadyExists() { BDDMockito.given(teamPersistenceService.findTeamsByName(anyString())).willReturn(List.of(team1)); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(teamWithIdNull)); - assertEquals("Can't create team with already existing name", exception.getReason()); + + List expectedErrors = List.of(new ErrorDto("ALREADY_EXISTS_SAME_NAME", List.of("Team", "Team null"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnCreateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(null)); verify(validator, times(1)).throwExceptionWhenModelIsNull(null); - assertEquals("Given model Team is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_NULL", List.of("Team"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnCreateShouldThrowExceptionWhenModelIsNameIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(teamWithoutName)); verify(validator, times(1)).throwExceptionWhenModelIsNull(teamWithoutName); verify(validator, times(1)).throwExceptionWhenIdIsNotNull(teamWithoutName.getId()); verify(validator, times(1)).validate(teamWithoutName); - assertThat(exception.getReason()).contains("Missing attribute name when saving team."); - assertThat(exception.getReason()).contains("Attribute name can not be null when saving team."); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("name", "Team")), + new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("name", "Team"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnUpdateShouldThrowExceptionWhenModelIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(teamWithIdNull.getId(), teamWithIdNull)); verify(validator, times(1)).throwExceptionWhenModelIsNull(teamWithIdNull); verify(validator, times(1)).throwExceptionWhenIdIsNull(teamWithIdNull.getId()); - assertEquals("Id is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Team"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnUpdateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(null, null)); verify(validator, times(1)).throwExceptionWhenModelIsNull(null); - assertEquals("Given model Team is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_NULL", List.of("Team"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnUpdateShouldThrowExceptionWhenModelIsNameIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(teamWithoutNameWithId.getId(), teamWithoutNameWithId)); verify(validator, times(1)).throwExceptionWhenModelIsNull(teamWithoutNameWithId); verify(validator, times(1)).throwExceptionWhenIdIsNull(teamWithoutNameWithId.getId()); verify(validator, times(1)).validate(teamWithoutNameWithId); - assertThat(exception.getReason()).contains("Missing attribute name when saving team."); - assertThat(exception.getReason()).contains("Attribute name can not be null when saving team."); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("name", "Team")), + new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("name", "Team"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } } diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java index 9d4695255f..5231f79280 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java @@ -1,6 +1,8 @@ package ch.puzzle.okr.service.validation; import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.User; import ch.puzzle.okr.service.persistence.UserPersistenceService; import org.apache.commons.lang3.StringUtils; @@ -25,10 +27,10 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.*; +import static org.springframework.http.HttpStatus.BAD_REQUEST; @ExtendWith(MockitoExtension.class) class UserValidationServiceTest { @@ -143,11 +145,14 @@ void validateOnGetShouldBeSuccessfulWhenValidUserId() { @Test void validateOnGetShouldThrowExceptionIfUserIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGet(null)); - verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "User"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -177,18 +182,26 @@ void validateOnCreateShouldBeSuccessfulWhenUserIsValid() { @Test void validateOnCreateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(null)); - assertEquals("Given model User is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_NULL", List.of("User"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnCreateShouldThrowExceptionWhenIdIsNotNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(user)); - assertEquals("Model User cannot have id while create. Found id 1", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "User"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @ParameterizedTest @@ -281,10 +294,14 @@ void validateOnCreateShouldThrowExceptionWhenEmailIsInvalid(String email, List validator.validateOnCreate(userInvalid)); - assertThat(exception.getReason().strip()).contains("Attribute email should be valid when saving user"); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NOT_VALID", List.of("email", "User"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -299,31 +316,45 @@ void validateOnUpdateShouldBeSuccessfulWhenUserIsValid() { @Test void validateOnUpdateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(1L, null)); - assertEquals("Given model User is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_NULL", List.of("User"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnUpdateShouldThrowExceptionWhenIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(null, userMinimal)); verify(validator, times(1)).throwExceptionWhenModelIsNull(userMinimal); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "User"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnUpdateShouldThrowExceptionWhenIdHasChanged() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(7L, user)); verify(validator, times(1)).throwExceptionWhenModelIsNull(user); verify(validator, times(1)).throwExceptionWhenIdIsNull(user.getId()); verify(validator, times(1)).throwExceptionWhenIdHasChanged(7L, user.getId()); - assertEquals("Id 7 has changed to 1 during update", exception.getReason()); + + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_CHANGED", List.of("ID", "7", "1"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @ParameterizedTest @@ -416,10 +447,33 @@ void validateOnUpdateShouldThrowExceptionWhenEmailIsInvalid(String email, List validator.validateOnUpdate(3L, userInvalid)); - assertThat(exception.getReason().strip()).contains("Attribute email should be valid when saving user"); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NOT_VALID", List.of("email", "User"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); + } + + @Test + void validateAuthorisationTokenShouldNotThrowError() { + assertDoesNotThrow(() -> validator.validateAuthorisationToken(mockJwt)); + + verify(validator).validateAuthorisationToken(mockJwt); + } + + @Test + void validateAuthorisationTokenShouldThrowErrorWhenNull() { + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, + () -> validator.validateAuthorisationToken(null)); + + List expectedErrors = List.of(new ErrorDto("TOKEN_NULL", List.of())); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -432,11 +486,15 @@ void validateOnDeleteShouldBeSuccessfulWhenValidObjectiveId() { @Test void validateOnDeleteShouldThrowExceptionIfObjectiveIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, - () -> validator.validateOnDelete(null)); + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, + () -> validator.validateOnGet(null)); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "User"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } } From 7fc2254bd73a3ac6bc7d11abc5c068701644b06a Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Fri, 24 Nov 2023 12:59:19 +0100 Subject: [PATCH 22/38] fix authorisation service tests --- .../AuthorizationServiceTest.java | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java index 2447dc333a..4e94b3cb39 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java @@ -22,7 +22,6 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.web.server.ResponseStatusException; import java.util.Collection; import java.util.List; @@ -155,8 +154,7 @@ void getAuthorizationUserShouldReturnAuthorizationUser() { void hasRoleReadByObjectiveIdShouldPassThroughWhenPermitted() { Long id = 13L; AuthorizationUser authorizationUser = defaultAuthorizationUser(); - String reason = "not authorized to read objective"; - ErrorDto error = new ErrorDto(reason, List.of()); + ErrorDto error = new ErrorDto("NOT_AUTHORIZED_TO_READ", List.of("Objective")); when(objectivePersistenceService.findObjectiveById(id, authorizationUser, error)).thenReturn(new Objective()); authorizationService.hasRoleReadByObjectiveId(id, authorizationUser); @@ -166,24 +164,26 @@ void hasRoleReadByObjectiveIdShouldPassThroughWhenPermitted() { void hasRoleReadByKeyResultIdShouldThrowExceptionWhenObjectiveNotFound() { Long id = 13L; AuthorizationUser authorizationUser = defaultAuthorizationUser(); - String reason = "not authorized to read key result"; - ErrorDto error = new ErrorDto(reason, List.of()); + ErrorDto error = new ErrorDto("NOT_AUTHORIZED_TO_READ", List.of("KeyResult")); when(objectivePersistenceService.findObjectiveByKeyResultId(id, authorizationUser, error)) - .thenThrow(new ResponseStatusException(HttpStatus.UNAUTHORIZED, reason)); + .thenThrow(new OkrResponseStatusException(HttpStatus.UNAUTHORIZED, error)); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> authorizationService.hasRoleReadByKeyResultId(id, authorizationUser)); + + List expectedErrors = List.of(new ErrorDto("NOT_AUTHORIZED_TO_READ", List.of("KeyResult"))); + assertEquals(UNAUTHORIZED, exception.getStatus()); - assertEquals(reason, exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void hasRoleReadByKeyResultIdShouldPassThroughWhenPermitted() { Long id = 13L; AuthorizationUser authorizationUser = defaultAuthorizationUser(); - String reason = "not authorized to read key result"; - ErrorDto error = new ErrorDto(reason, List.of()); + ErrorDto error = new ErrorDto("NOT_AUTHORIZED_TO_READ", List.of("KeyResult")); when(objectivePersistenceService.findObjectiveByKeyResultId(id, authorizationUser, error)) .thenReturn(new Objective()); @@ -194,23 +194,26 @@ void hasRoleReadByKeyResultIdShouldPassThroughWhenPermitted() { void hasRoleReadByCheckInIdShouldThrowExceptionWhenObjectiveNotFound() { Long id = 13L; AuthorizationUser authorizationUser = defaultAuthorizationUser(); - String reason = "not authorized to read check in"; - ErrorDto error = new ErrorDto(reason, List.of()); + + ErrorDto error = new ErrorDto("NOT_AUTHORIZED_TO_READ", List.of("Check-in")); when(objectivePersistenceService.findObjectiveByCheckInId(id, authorizationUser, error)) - .thenThrow(new ResponseStatusException(HttpStatus.UNAUTHORIZED, reason)); + .thenThrow(new OkrResponseStatusException(HttpStatus.UNAUTHORIZED, error)); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> authorizationService.hasRoleReadByCheckInId(id, authorizationUser)); + + List expectedErrors = List.of(new ErrorDto("NOT_AUTHORIZED_TO_READ", List.of("Check-in"))); + assertEquals(UNAUTHORIZED, exception.getStatus()); - assertEquals(reason, exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void hasRoleReadByCheckInIdShouldPassThroughWhenPermitted() { Long id = 13L; AuthorizationUser authorizationUser = defaultAuthorizationUser(); - String reason = "not authorized to read check in"; - ErrorDto error = new ErrorDto(reason, List.of()); + ErrorDto error = new ErrorDto("NOT_AUTHORIZED_TO_READ", List.of("Check-in")); when(objectivePersistenceService.findObjectiveByCheckInId(id, authorizationUser, error)) .thenReturn(new Objective()); @@ -277,10 +280,14 @@ void hasRoleCreateOrUpdateShouldThrowExceptionWhenNotAuthorizedForOtherTeamObjec AuthorizationUser authorizationUser = mockAuthorizationUser(defaultUser(null), List.of(1L), 5L, List.of(WRITE_TEAM)); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> authorizationService.hasRoleCreateOrUpdate(objective, authorizationUser)); + + List expectedErrors = List.of(new ErrorDto("NOT_AUTHORIZED_TO_WRITE", List.of("Objective"))); + assertEquals(UNAUTHORIZED, exception.getStatus()); - assertEquals("not authorized to create or update objective", exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test From e76daca8d2878c1474d503f6b05108a769d0b6cc Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Fri, 24 Nov 2023 13:39:04 +0100 Subject: [PATCH 23/38] fix tests --- .../service/persistence/PersistenceBase.java | 3 +- .../okr/converter/JwtUserConverterTest.java | 18 ++++- .../KeyResultPersistenceServiceIT.java | 79 +++++++++++++------ 3 files changed, 71 insertions(+), 29 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java index 0c652424e1..2cfe430aa3 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/PersistenceBase.java @@ -42,7 +42,8 @@ public T findById(ID id) throws OkrResponseStatusException { public void checkIdNull(ID id) { if (id == null) { - throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NULL); + throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorMsg.ATTRIBUTE_NULL, + List.of("ID", getModelName())); } } diff --git a/backend/src/test/java/ch/puzzle/okr/converter/JwtUserConverterTest.java b/backend/src/test/java/ch/puzzle/okr/converter/JwtUserConverterTest.java index 1b684ca6a0..592a5fa3b5 100644 --- a/backend/src/test/java/ch/puzzle/okr/converter/JwtUserConverterTest.java +++ b/backend/src/test/java/ch/puzzle/okr/converter/JwtUserConverterTest.java @@ -1,14 +1,19 @@ package ch.puzzle.okr.converter; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.User; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.web.server.ResponseStatusException; +import java.util.List; + import static ch.puzzle.okr.TestHelper.defaultJwtToken; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.test.util.ReflectionTestUtils.setField; @@ -36,9 +41,14 @@ void convertShouldReturnUserWhenValidJwt() { void convertShouldThrowExceptionWhenClaimNameDoesNotMatch() { setUsername("foo_name"); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> converter.convert(jwt)); + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, + () -> converter.convert(jwt)); + + List expectedErrors = List.of(new ErrorDto("CONVERT_TOKEN", List.of("User"))); + assertEquals(BAD_REQUEST, exception.getStatus()); - assertEquals("can not convert user from token", exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } private void setUsername(String claimRealm) { diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/KeyResultPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/KeyResultPersistenceServiceIT.java index 6576cfe8d6..1151e0536e 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/KeyResultPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/KeyResultPersistenceServiceIT.java @@ -1,6 +1,9 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.Objective; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.Unit; import ch.puzzle.okr.models.User; import ch.puzzle.okr.models.keyresult.KeyResult; @@ -10,14 +13,13 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; -import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; +import static org.springframework.http.HttpStatus.*; @SpringIntegrationTest class KeyResultPersistenceServiceIT { @@ -56,7 +58,7 @@ void tearDown() { keyResultPersistenceService.findById(createdKeyResult.getId()); keyResultPersistenceService.deleteById(createdKeyResult.getId()); } - } catch (ResponseStatusException ex) { + } catch (OkrResponseStatusException ex) { // created key result already deleted } finally { createdKeyResult = null; @@ -87,20 +89,26 @@ void getKeyResultByIdShouldReturnKeyResultProperly() { @Test void getKeyResultByIdShouldThrowExceptionWhenKeyResultNotFound() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> keyResultPersistenceService.findById(321L)); - assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); - assertEquals("KeyResult with id 321 not found", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("KeyResult", "321"))); + + assertEquals(NOT_FOUND, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void getKeyResultByIdShouldThrowExceptionWhenKeyResultIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> keyResultPersistenceService.findById(null)); - assertEquals(HttpStatus.BAD_REQUEST, exception.getStatus()); - assertEquals("Missing identifier for KeyResult", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "KeyResult"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -119,9 +127,14 @@ void recreateEntityShouldUpdateKeyResultNoTypeChange() { Long keyResultId = createdKeyResult.getId(); // Should delete the old KeyResult - ResponseStatusException exception = assertThrows(ResponseStatusException.class, this::execute); - assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); - assertEquals("KeyResult with id " + keyResultId + " not found", exception.getReason()); + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, this::execute); + + List expectedErrors = List + .of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("KeyResult", keyResultId))); + + assertEquals(NOT_FOUND, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); // delete re-created key result in tearDown() createdKeyResult = recreatedKeyResult; @@ -148,10 +161,15 @@ void recreateEntityShouldUpdateKeyResultWithTypeChange() { Long keyResultId = createdKeyResult.getId(); // Should delete the old KeyResult - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> keyResultPersistenceService.findById(keyResultId)); - assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); - assertEquals("KeyResult with id " + keyResultId + " not found", exception.getReason()); + + List expectedErrors = List + .of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("KeyResult", keyResultId))); + + assertEquals(NOT_FOUND, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); // delete re-created key result in tearDown() createdKeyResult = recreatedKeyResult; @@ -184,10 +202,13 @@ void updateEntityShouldThrowExceptionWhenAlreadyUpdated() { updateKeyResult.setTitle(KEY_RESULT_UPDATED); updateKeyResult.setDescription(THIS_IS_DESCRIPTION); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> keyResultPersistenceService.updateEntity(updateKeyResult)); + List expectedErrors = List.of(new ErrorDto("DATA_HAS_BEEN_UPDATED", List.of("KeyResult"))); + assertEquals(UNPROCESSABLE_ENTITY, exception.getStatus()); - assertTrue(exception.getReason().contains("updated or deleted by another user")); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -204,10 +225,15 @@ void deleteKeyResultByIdShouldDeleteExistingKeyResult() { keyResultPersistenceService.deleteById(createdKeyResult.getId()); Long keyResultId = createdKeyResult.getId(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> keyResultPersistenceService.findById(keyResultId)); - assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); - assertEquals(String.format("KeyResult with id %d not found", createdKeyResult.getId()), exception.getReason()); + + List expectedErrors = List + .of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("KeyResult", keyResultId))); + + assertEquals(NOT_FOUND, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -217,10 +243,15 @@ void deleteKeyResultShouldThrowExceptionWhenKeyResultNotFound() { keyResultPersistenceService.deleteById(newKeyResult.getId()); Long keyResultId = newKeyResult.getId(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> keyResultPersistenceService.findById(keyResultId)); - assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); - assertEquals(String.format("KeyResult with id %d not found", newKeyResult.getId()), exception.getReason()); + + List expectedErrors = List + .of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("KeyResult", keyResultId))); + + assertEquals(NOT_FOUND, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } private void execute() { From c0819262bb1e9a1129ff8760305555ee17fd60ed Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Fri, 24 Nov 2023 14:27:58 +0100 Subject: [PATCH 24/38] Adjust tests for objectiveper objectiveval quarterper --- .../ObjectivePersistenceServiceIT.java | 28 +++++++++++++------ .../QuarterPersistenceServiceIT.java | 26 +++++++++++------ .../ObjectiveValidationServiceTest.java | 26 +++++++++-------- 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java index 0d459e3879..7b360a7815 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java @@ -88,11 +88,14 @@ void findObjectiveByIdShouldThrowExceptionWhenObjectiveNotFound() { @Test void findObjectiveByIdShouldThrowExceptionWhenObjectiveIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> objectivePersistenceService.findObjectiveById(null, authorizationUser, error)); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Objective"))); + assertEquals(BAD_REQUEST, exception.getStatus()); - assertEquals(MISSING_IDENTIFIER, exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -114,11 +117,14 @@ void findObjectiveByKeyResultIdShouldThrowExceptionWhenObjectiveNotFound() { @Test void findObjectiveByKeyResultIdShouldThrowExceptionWhenObjectiveIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> objectivePersistenceService.findObjectiveByKeyResultId(null, authorizationUser, error)); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Objective"))); + assertEquals(BAD_REQUEST, exception.getStatus()); - assertEquals(MISSING_IDENTIFIER, exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -140,11 +146,14 @@ void findObjectiveByCheckInIdShouldThrowExceptionWhenObjectiveNotFound() { @Test void findObjectiveByCheckInIdShouldThrowExceptionWhenObjectiveIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> objectivePersistenceService.findObjectiveByCheckInId(null, authorizationUser, error)); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Objective"))); + assertEquals(BAD_REQUEST, exception.getStatus()); - assertEquals(MISSING_IDENTIFIER, exception.getReason()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -177,10 +186,13 @@ void updateObjectiveShouldThrowExceptionWhenAlreadyUpdated() { Objective updateObjective = createObjective(createdObjective.getId(), 0); updateObjective.setState(State.ONGOING); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> objectivePersistenceService.save(updateObjective)); + List expectedErrors = List.of(new ErrorDto("DATA_HAS_BEEN_UPDATED", List.of("Objective"))); + assertEquals(UNPROCESSABLE_ENTITY, exception.getStatus()); - assertTrue(exception.getReason().contains("updated or deleted by another user")); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/QuarterPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/QuarterPersistenceServiceIT.java index 25671cc7d3..c3d5bc22ae 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/QuarterPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/QuarterPersistenceServiceIT.java @@ -1,16 +1,20 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.Quarter; import ch.puzzle.okr.test.SpringIntegrationTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; import java.time.LocalDate; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; @SpringIntegrationTest class QuarterPersistenceServiceIT { @@ -30,20 +34,26 @@ void shouldReturnSingleQuarterWhenFindingByValidId() { @Test void shouldThrowExceptionWhenFindingQuarterNotFound() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> quarterPersistenceService.findById(321L)); - assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); - assertEquals("Quarter with id 321 not found", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("Quarter", "321"))); + + assertEquals(NOT_FOUND, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void shouldThrowExceptionWhenFindingQuarterWithIdNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> quarterPersistenceService.findById(null)); - assertEquals(HttpStatus.BAD_REQUEST, exception.getStatus()); - assertEquals("Missing identifier for Quarter", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Quarter"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java index 9f4e14d150..f48abbbbf0 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java @@ -290,11 +290,9 @@ void validateOnUpdateShouldThrowExceptionWhenAttrModifiedByIsNotSet() { OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(1L, objectiveInvalid)); - List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_CHANGED", List.of("ID", "7", "1"))); - assertEquals(BAD_REQUEST, exception.getStatus()); - assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); - assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, exception.getStatus()); + assertEquals(String.format("Something went wrong. ModifiedBy %s is not set.", null), exception.getReason()); } @Test @@ -308,13 +306,13 @@ void validateOnUpdateShouldThrowExceptionWheTeamHasChanged() { .withModifiedBy(user).build(); when(objectivePersistenceService.findById(savedObjective.getId())).thenReturn(savedObjective); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(1L, updatedObjective)); - assertEquals(HttpStatus.BAD_REQUEST, exception.getStatus()); - assertEquals( - String.format("The team can not be changed (new team %s, old team %s)", - updatedObjective.getTeam().getName(), savedObjective.getTeam().getName()), - exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_CANNOT_CHANGE", List.of("Team", "Objective"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -327,10 +325,14 @@ void validateOnDeleteShouldBeSuccessfulWhenValidObjectiveId() { @Test void validateOnDeleteShouldThrowExceptionIfObjectiveIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGet(null)); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Objective"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } } From 42defbd4d729ea697b9def9a39df411227abe145 Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Fri, 24 Nov 2023 15:22:41 +0100 Subject: [PATCH 25/38] Adjust userval --- .../validation/UserValidationServiceTest.java | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java index 5231f79280..d009f75ba7 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java @@ -65,18 +65,18 @@ void setUp() { private static Stream userNameValidationArguments() { return Stream.of( arguments(StringUtils.repeat('1', 21), - List.of("Attribute username must have size between 2 and 20 characters when saving user")), + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("username", "User", "2", "20")))), arguments(StringUtils.repeat('1', 1), - List.of("Attribute username must have size between 2 and 20 characters when saving user")), + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("username", "User", "2", "20")))), arguments("", - List.of("Missing attribute username when saving user", - "Attribute username must have size between 2 and 20 characters when saving user")), + List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("username", "User")), + new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("username", "User", "2", "20")))), arguments(" ", - List.of("Missing attribute username when saving user", - "Attribute username must have size between 2 and 20 characters when saving user")), - arguments(" ", List.of("Missing attribute username when saving user")), - arguments(null, List.of("Missing attribute username when saving user", - "Attribute username can not be null when saving user"))); + List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("username", "User")), + new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("username", "User", "2", "20")))), + arguments(" ", List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("username", "User")))), + arguments(null, List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("username", "User")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("username", "User"))))); } private static Stream firstNameValidationArguments() { @@ -206,23 +206,16 @@ void validateOnCreateShouldThrowExceptionWhenIdIsNotNull() { @ParameterizedTest @MethodSource("userNameValidationArguments") - void validateOnCreateShouldThrowExceptionWhenUsernameIsInvalid(String name, List errors) { + void validateOnCreateShouldThrowExceptionWhenUsernameIsInvalid(String name, List errors) { User user2 = User.Builder.builder().withEmail("max@mail.com").withFirstname("firstname") .withLastname("lastname").withUsername(name).build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(user2)); - String[] exceptionParts = Objects.requireNonNull(exception.getReason()).split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - } - - for (int i = 0; i < exceptionParts.length; i++) { - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(errors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(errors).contains(exception.getReason())); } @ParameterizedTest From 2d76083b05912823171faf0289dee35ccaf66f89 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Fri, 24 Nov 2023 14:43:51 +0100 Subject: [PATCH 26/38] implement test for checkin validation --- .../java/ch/puzzle/okr/models/ErrorMsg.java | 3 + .../ch/puzzle/okr/models/checkin/CheckIn.java | 17 +- .../okr/models/checkin/CheckInMetric.java | 4 +- .../okr/models/checkin/CheckInOrdinal.java | 4 +- .../AlignmentPersistenceServiceIT.java | 13 +- .../CheckInValidationServiceTest.java | 152 +++++++++++------- .../QuarterValidationServiceTest.java | 18 ++- frontend/src/assets/i18n/de.json | 2 + 8 files changed, 140 insertions(+), 73 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java index c7bb5bd378..dc35f24705 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java +++ b/backend/src/main/java/ch/puzzle/okr/models/ErrorMsg.java @@ -24,6 +24,9 @@ private ErrorMsg() { public static final String ATTRIBUTE_SET_FORBIDDEN = "ATTRIBUTE_SET_FORBIDDEN"; public static final String ATTRIBUTE_NOT_SET = "ATTRIBUTE_NOT_SET"; public static final String ATTRIBUTE_CANNOT_CHANGE = "ATTRIBUTE_CANNOT_CHANGE"; + public static final String ATTRIBUTE_MIN_VALUE = "ATTRIBUTE_MIN_VALUE_{value}"; + public static final String ATTRIBUTE_MAX_VALUE = "ATTRIBUTE_MAX_VALUE_{value}"; + public static final String NOT_AUTHORIZED_TO_READ = "NOT_AUTHORIZED_TO_READ"; public static final String NOT_AUTHORIZED_TO_WRITE = "NOT_AUTHORIZED_TO_WRITE"; public static final String NOT_AUTHORIZED_TO_DELETE = "NOT_AUTHORIZED_TO_DELETE"; diff --git a/backend/src/main/java/ch/puzzle/okr/models/checkin/CheckIn.java b/backend/src/main/java/ch/puzzle/okr/models/checkin/CheckIn.java index 66bcb85cfc..b665cab377 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/checkin/CheckIn.java +++ b/backend/src/main/java/ch/puzzle/okr/models/checkin/CheckIn.java @@ -1,5 +1,6 @@ package ch.puzzle.okr.models.checkin; +import ch.puzzle.okr.models.ErrorMsg; import ch.puzzle.okr.models.User; import ch.puzzle.okr.models.WriteableInterface; import ch.puzzle.okr.models.keyresult.KeyResult; @@ -23,26 +24,26 @@ public abstract class CheckIn implements WriteableInterface { @Version private int version; - @Size(max = 4096, message = "Attribute changeInfo has a max length of 4096 characters") + @Size(max = 4096, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String changeInfo; - @Size(max = 4096, message = "Attribute initiatives has a max length of 4096 characters") + @Size(max = 4096, message = ErrorMsg.ATTRIBUTE_SIZE_BETWEEN) private String initiatives; - @Max(value = 10, message = "Attribute confidence has a max value of 10") - @Min(value = 1, message = "Attribute confidence has a min value of 1") - @NotNull(message = "Confidence must not be null") + @Max(value = 10, message = ErrorMsg.ATTRIBUTE_MAX_VALUE) + @Min(value = 1, message = ErrorMsg.ATTRIBUTE_MIN_VALUE) + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) private Integer confidence; - @NotNull(message = "KeyResult must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @ManyToOne private KeyResult keyResult; - @NotNull(message = "CreatedBy must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @ManyToOne private User createdBy; - @NotNull(message = "CreatedOn must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) private LocalDateTime createdOn; private LocalDateTime modifiedOn; diff --git a/backend/src/main/java/ch/puzzle/okr/models/checkin/CheckInMetric.java b/backend/src/main/java/ch/puzzle/okr/models/checkin/CheckInMetric.java index a9a1ee3e39..c77f18778a 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/checkin/CheckInMetric.java +++ b/backend/src/main/java/ch/puzzle/okr/models/checkin/CheckInMetric.java @@ -1,5 +1,7 @@ package ch.puzzle.okr.models.checkin; +import ch.puzzle.okr.models.ErrorMsg; + import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import javax.validation.constraints.NotNull; @@ -10,7 +12,7 @@ @Entity @DiscriminatorValue(KEY_RESULT_TYPE_METRIC) public class CheckInMetric extends CheckIn { - @NotNull(message = "Value must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) private Double valueMetric; /* Getter and Setter */ diff --git a/backend/src/main/java/ch/puzzle/okr/models/checkin/CheckInOrdinal.java b/backend/src/main/java/ch/puzzle/okr/models/checkin/CheckInOrdinal.java index 73fa919514..d21aef6359 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/checkin/CheckInOrdinal.java +++ b/backend/src/main/java/ch/puzzle/okr/models/checkin/CheckInOrdinal.java @@ -1,5 +1,7 @@ package ch.puzzle.okr.models.checkin; +import ch.puzzle.okr.models.ErrorMsg; + import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import javax.persistence.EnumType; @@ -12,7 +14,7 @@ @Entity @DiscriminatorValue(KEY_RESULT_TYPE_ORDINAL) public class CheckInOrdinal extends CheckIn { - @NotNull(message = "Zone must not be null") + @NotNull(message = ErrorMsg.ATTRIBUTE_NOT_NULL) @Enumerated(EnumType.STRING) private Zone zone; diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/AlignmentPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/AlignmentPersistenceServiceIT.java index b151b7f5aa..c2466efdb0 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/AlignmentPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/AlignmentPersistenceServiceIT.java @@ -1,6 +1,9 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.Objective; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.alignment.Alignment; import ch.puzzle.okr.models.alignment.KeyResultAlignment; import ch.puzzle.okr.models.alignment.ObjectiveAlignment; @@ -13,7 +16,9 @@ import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; @SpringIntegrationTest @@ -92,10 +97,14 @@ void updateAlignmentShouldThrowExceptionWhenAlreadyUpdated() { Alignment updateAlignment = createKeyResultAlignment(createdAlignment.getId(), 0); updateAlignment.setAlignedObjective(Objective.Builder.builder().withId(8L).build()); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> alignmentPersistenceService.save(updateAlignment)); + + List expectedErrors = List.of(new ErrorDto("DATA_HAS_BEEN_UPDATED", List.of("Alignment"))); + assertEquals(UNPROCESSABLE_ENTITY, exception.getStatus()); - assertTrue(exception.getReason().contains("updated or deleted by another user")); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/CheckInValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/CheckInValidationServiceTest.java index 132b92d2d0..4f620427a1 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/CheckInValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/CheckInValidationServiceTest.java @@ -1,5 +1,7 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.*; import ch.puzzle.okr.models.checkin.CheckIn; import ch.puzzle.okr.models.checkin.CheckInMetric; @@ -9,6 +11,7 @@ import ch.puzzle.okr.models.keyresult.KeyResultMetric; import ch.puzzle.okr.models.keyresult.KeyResultOrdinal; import ch.puzzle.okr.service.persistence.CheckInPersistenceService; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -20,18 +23,15 @@ import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Stream; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.*; +import static org.springframework.http.HttpStatus.BAD_REQUEST; @ExtendWith(MockitoExtension.class) class CheckInValidationServiceTest { @@ -68,9 +68,10 @@ class CheckInValidationServiceTest { private CheckInValidationService validator; private static Stream confidenceValidationArguments() { - return Stream.of(arguments(-1, List.of("Attribute confidence has a min value of 1")), - arguments(11, List.of("Attribute confidence has a max value of 10")), - arguments(null, List.of("Confidence must not be null"))); + return Stream.of( + arguments(-1, List.of(new ErrorDto("ATTRIBUTE_MIN_VALUE", List.of("confidence", "CheckIn", "1")))), + arguments(11, List.of(new ErrorDto("ATTRIBUTE_MAX_VALUE", List.of("confidence", "CheckIn", "10")))), + arguments(null, List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("confidence", "CheckIn"))))); } @BeforeEach @@ -87,11 +88,15 @@ void validateOnGetShouldBeSuccessfulWhenValidCheckInId() { @Test void validateOnGetShouldThrowExceptionIfCheckInIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGet(null)); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); - assertEquals(HttpStatus.BAD_REQUEST, exception.getStatus()); + + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "CheckIn"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + Assertions.assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -107,53 +112,60 @@ void validateOnCreateShouldBeSuccessfulWhenCheckInIsValid() { @Test void validateOnCreateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(null)); - assertEquals("Given model CheckIn is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_NULL", List.of("CheckIn"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + Assertions.assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnCreateShouldThrowExceptionWhenIdIsNotNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(fullCheckIn)); - assertEquals("Model CheckIn cannot have id while create. Found id 1", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "CheckIn"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + Assertions.assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @ParameterizedTest @MethodSource("confidenceValidationArguments") - void validateOnCreateShouldThrowExceptionWhenConfidenceIsInvalid(Integer confidence, List errors) { + void validateOnCreateShouldThrowExceptionWhenConfidenceIsInvalid(Integer confidence, + List expectedErrors) { CheckIn checkIn = CheckInMetric.Builder.builder().withValue(40.9).withChangeInfo("ChangeInfo") .withInitiatives("Initiatives").withConfidence(confidence).withCreatedBy(user) .withKeyResult(keyResultMetric).withCreatedOn(LocalDateTime.MAX).withModifiedOn(LocalDateTime.MAX) .build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(checkIn)); - String[] exceptionParts = exception.getReason().split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - } - for (int i = 0; i < exceptionParts.length; i++) { - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + Assertions.assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnCreateShouldThrowExceptionWhenAttrsAreMissing() { CheckIn checkInInvalid = CheckInMetric.Builder.builder().withId(null).withChangeInfo("ChangeInfo").build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(checkInInvalid)); - assertThat(exception.getReason().strip()).contains("Confidence must not be null"); - assertThat(exception.getReason().strip()).contains("KeyResult must not be null"); - assertThat(exception.getReason().strip()).contains("CreatedBy must not be null"); - assertThat(exception.getReason().strip()).contains("CreatedOn must not be null"); - assertThat(exception.getReason().strip()).contains("Value must not be null"); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("confidence", "CheckIn")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("keyResult", "CheckIn")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("createdBy", "CheckIn")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("createdOn", "CheckIn")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("valueMetric", "CheckIn"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + Assertions.assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -170,36 +182,51 @@ void validateOnUpdateShouldBeSuccessfulWhenCheckInIsValid() { @Test void validateOnUpdateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(1L, null)); - assertEquals("Given model CheckIn is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_NULL", List.of("CheckIn"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + Assertions.assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnUpdateShouldThrowExceptionWhenIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(null, checkInOrdinal)); verify(validator, times(1)).throwExceptionWhenModelIsNull(checkInOrdinal); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "CheckIn"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + Assertions.assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void validateOnUpdateShouldThrowExceptionWhenIdIsHasChanged() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(2L, checkInOrdinal)); verify(validator, times(1)).throwExceptionWhenModelIsNull(checkInOrdinal); verify(validator, times(1)).throwExceptionWhenIdIsNull(2L); verify(validator, times(1)).throwExceptionWhenIdHasChanged(2L, checkInOrdinal.getId()); - assertEquals("Id 2 has changed to 1 during update", exception.getReason()); + + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_CHANGED", List.of("ID", "2", "1"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + Assertions.assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @ParameterizedTest @MethodSource("confidenceValidationArguments") - void validateOnUpdateShouldThrowExceptionWhenConfidenceIsInvalid(Integer confidence, List errors) { + void validateOnUpdateShouldThrowExceptionWhenConfidenceIsInvalid(Integer confidence, + List expectedErrors) { Long id = 2L; CheckIn checkIn = CheckInMetric.Builder.builder().withValue(40.9).withId(id).withChangeInfo("ChangeInfo") .withInitiatives("Initiatives").withConfidence(confidence).withCreatedBy(user) @@ -207,18 +234,12 @@ void validateOnUpdateShouldThrowExceptionWhenConfidenceIsInvalid(Integer confide .build(); when(checkInPersistenceService.findById(id)).thenReturn(checkIn); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(id, checkIn)); - String[] exceptionParts = exception.getReason().split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - } - for (int i = 0; i < exceptionParts.length; i++) { - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + Assertions.assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -232,12 +253,18 @@ void validateOnUpdateShouldThrowExceptionWhenCheckInsOfKeyResultIsEmpty() { .withKeyResult(KeyResultMetric.Builder.builder().withId(13L).build()).build(); when(checkInPersistenceService.findById(id)).thenReturn(savedCheckIn); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(id, checkIn)); verify(validator, times(1)).throwExceptionWhenModelIsNull(checkIn); verify(validator, times(1)).throwExceptionWhenIdIsNull(checkIn.getId()); verify(validator, times(1)).throwExceptionWhenIdHasChanged(checkIn.getId(), checkIn.getId()); - assertEquals("Not allowed change the association to the key result (id = 2)", exception.getReason()); + + List expectedErrors = List + .of(new ErrorDto("ATTRIBUTE_CANNOT_CHANGE", List.of("KeyResult", "Check-in"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + Assertions.assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @@ -247,13 +274,17 @@ void validateOnUpdateShouldThrowExceptionWhenAttrsAreMissing() { CheckIn checkInInvalid = CheckInMetric.Builder.builder().withId(id).withChangeInfo("ChangeInfo") .withKeyResult(KeyResultMetric.Builder.builder().withId(13L).build()).build(); when(checkInPersistenceService.findById(id)).thenReturn(checkInInvalid); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(id, checkInInvalid)); - assertThat(exception.getReason().strip()).contains("Confidence must not be null"); - assertThat(exception.getReason().strip()).contains("CreatedBy must not be null"); - assertThat(exception.getReason().strip()).contains("CreatedOn must not be null"); - assertThat(exception.getReason().strip()).contains("Value must not be null"); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("confidence", "CheckIn")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("createdBy", "CheckIn")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("createdOn", "CheckIn")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("valueMetric", "CheckIn"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + Assertions.assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -265,11 +296,16 @@ void validateOnDeleteShouldBeSuccessfulWhenValidKeyResultId() { @Test void validateOnDeleteShouldThrowExceptionIfKeyResultIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnDelete(null)); verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - assertEquals("Id is null", exception.getReason()); + + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "CheckIn"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + Assertions.assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } } \ No newline at end of file diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/QuarterValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/QuarterValidationServiceTest.java index f3e96de0db..23f5a43b55 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/QuarterValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/QuarterValidationServiceTest.java @@ -1,5 +1,8 @@ package ch.puzzle.okr.service.validation; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.service.persistence.QuarterPersistenceService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -11,8 +14,12 @@ import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import static org.springframework.http.HttpStatus.BAD_REQUEST; @ExtendWith(MockitoExtension.class) class QuarterValidationServiceTest { @@ -31,11 +38,16 @@ void validateOnGetShouldBeSuccessfulWhenValidId() { @Test void validateOnGetShouldThrowExceptionWhenIdIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + Mockito.when(quarterPersistenceService.getModelName()).thenReturn("Quarter"); + + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGet(null)); - assertEquals("Id is null", exception.getReason()); - assertEquals(HttpStatus.BAD_REQUEST, exception.getStatus()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Quarter"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index efc82d4911..277fe68737 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -40,6 +40,8 @@ "ATTRIBUTE_SET_FORBIDDEN": "Das Attribut {0} darf nicht während dem erstellen gesetzt sein ", "ATTRIBUTE_NOT_SET": "Ein Fehler ist aufgetreten.\n Das Attribut {0} ist nicht gesetzt", "ATTRIBUTE_CANNOT_CHANGE": "Das Attribut {0} auf dem Objekt {1} kann nicht geändert werden", + "ATTRIBUTE_MIN_VALUE": "", + "ATTRIBUTE_MAX_VALUE":"", "NOT_AUTHORIZED_TO_READ": "Du bist nicht autorisiert um dieses {0} anzuzeigen", "NOT_AUTHORIZED_TO_WRITE":"Du bist nicht autorisiert um dieses {0} zu bearbeiten", "NOT_AUTHORIZED_TO_DELETE": "Du bist nicht autorisiert um dieses {0} zu löschen", From b3576514591ab4af8a2b2394a5b66b9e925351f7 Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Fri, 24 Nov 2023 15:55:24 +0100 Subject: [PATCH 27/38] Adjust userval --- .../validation/UserValidationServiceTest.java | 187 +++++++----------- 1 file changed, 66 insertions(+), 121 deletions(-) diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java index d009f75ba7..aae2d2cb57 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java @@ -21,9 +21,7 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.web.server.ResponseStatusException; -import java.util.Arrays; import java.util.List; -import java.util.Objects; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -82,57 +80,57 @@ private static Stream userNameValidationArguments() { private static Stream firstNameValidationArguments() { return Stream.of( arguments(StringUtils.repeat('1', 51), - List.of("Attribute firstname must have size between 2 and 50 characters when saving user")), + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("firstname", "User", "2", "50")))), arguments(StringUtils.repeat('1', 1), - List.of("Attribute firstname must have size between 2 and 50 characters when saving user")), + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("firstname", "User", "2", "50")))), arguments("", - List.of("Missing attribute firstname when saving user", - "Attribute firstname must have size between 2 and 50 characters when saving user")), + List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("firstname", "User")), + new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("firstname", "User", "2", "50")))), arguments(" ", - List.of("Missing attribute firstname when saving user", - "Attribute firstname must have size between 2 and 50 characters when saving user")), - arguments(" ", List.of("Missing attribute firstname when saving user")), - arguments(null, List.of("Missing attribute firstname when saving user", - "Attribute firstname can not be null when saving user"))); + List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("firstname", "User")), + new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("firstname", "User", "2", "50")))), + arguments(" ", List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("firstname", "User")))), + arguments(null, List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("firstname", "User")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("firstname", "User"))))); } private static Stream lastNameValidationArguments() { return Stream.of( arguments(StringUtils.repeat('1', 51), - List.of("Attribute lastname must have size between 2 and 50 characters when saving user")), + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("lastname", "User", "2", "50")))), arguments(StringUtils.repeat('1', 1), - List.of("Attribute lastname must have size between 2 and 50 characters when saving user")), + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("lastname", "User", "2", "50")))), arguments("", - List.of("Missing attribute lastname when saving user", - "Attribute lastname must have size between 2 and 50 characters when saving user")), + List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("lastname", "User")), + new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("lastname", "User", "2", "50")))), arguments(" ", - List.of("Missing attribute lastname when saving user", - "Attribute lastname must have size between 2 and 50 characters when saving user")), - arguments(" ", List.of("Missing attribute lastname when saving user")), - arguments(null, List.of("Missing attribute lastname when saving user", - "Attribute lastname can not be null when saving user"))); + List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("lastname", "User")), + new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("lastname", "User", "2", "50")))), + arguments(" ", List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("lastname", "User")))), + arguments(null, List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("lastname", "User")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("lastname", "User"))))); } private static Stream emailValidationArguments() { return Stream.of( arguments(("1".repeat(251)), - List.of("Attribute email must have size between 2 and 250 characters when saving user", - "Attribute email should be valid when saving user")), + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("email", "User", "2", "250")), + new ErrorDto("ATTRIBUTE_NOT_VALID", List.of("email", "User")))), arguments(("1"), - List.of("Attribute email should be valid when saving user", - "Attribute email must have size between 2 and 250 characters when saving user")), + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("email", "User", "2", "250")), + new ErrorDto("ATTRIBUTE_NOT_VALID", List.of("email", "User")))), arguments((""), - List.of("Missing attribute email when saving user", - "Attribute email must have size between 2 and 250 characters when saving user")), + List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("email", "User")), + new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("email", "User", "2", "250")))), arguments((" "), - List.of("Missing attribute email when saving user", - "Attribute email should be valid when saving user", - "Attribute email must have size between 2 and 250 characters when saving user")), + List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("email", "User")), + new ErrorDto("ATTRIBUTE_NOT_VALID", List.of("email", "User")), + new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("email", "User", "2", "250")))), arguments((" "), - List.of("Missing attribute email when saving user", - "Attribute email should be valid when saving user")), - arguments(null, List.of("Attribute email can not be null when saving user", - "Missing attribute email when saving user"))); + List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("email", "User")), + new ErrorDto("ATTRIBUTE_NOT_VALID", List.of("email", "User")))), + arguments(null, List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("email", "User")), + new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("email", "User"))))); } @Test @@ -220,67 +218,44 @@ void validateOnCreateShouldThrowExceptionWhenUsernameIsInvalid(String name, List @ParameterizedTest @MethodSource("firstNameValidationArguments") - void validateOnCreateShouldThrowExceptionWhenFirstnameIsInvalid(String name, List errors) { + void validateOnCreateShouldThrowExceptionWhenFirstnameIsInvalid(String name, List errors) { User user2 = User.Builder.builder().withEmail("max@mail.com").withFirstname(name).withLastname("lastname") .withUsername("username").build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(user2)); - String[] exceptionParts = Objects.requireNonNull(exception.getReason()).split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - } - - for (int i = 0; i < exceptionParts.length; i++) { - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(errors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(errors).contains(exception.getReason())); } @ParameterizedTest @MethodSource("lastNameValidationArguments") - void validateOnCreateShouldThrowExceptionWhenLastnameIsInvalid(String name, List errors) { + void validateOnCreateShouldThrowExceptionWhenLastnameIsInvalid(String name, List errors) { User user2 = User.Builder.builder().withEmail("max@mail.com").withFirstname("firstname").withLastname(name) .withUsername("username").build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(user2)); - String[] exceptionParts = Objects.requireNonNull(exception.getReason()).split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - } - - for (int i = 0; i < exceptionParts.length; i++) { - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(errors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(errors).contains(exception.getReason())); } @ParameterizedTest @MethodSource("emailValidationArguments") - void validateOnCreateShouldThrowExceptionWhenEmailIsInvalid(String email, List errors) { + void validateOnCreateShouldThrowExceptionWhenEmailIsInvalid(String email, List errors) { User user2 = User.Builder.builder().withEmail(email).withFirstname("firstname").withLastname("lastname") .withUsername("username").build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(user2)); - String[] exceptionParts = Objects.requireNonNull(exception.getReason()).split("\\."); - System.out.println(Arrays.toString(Arrays.stream(exceptionParts).toArray())); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - System.out.println(errorArray[i].strip()); - } - - for (int i = 0; i < exceptionParts.length; i++) { - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(errors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(errors).contains(exception.getReason())); } @Test @@ -352,88 +327,58 @@ void validateOnUpdateShouldThrowExceptionWhenIdHasChanged() { @ParameterizedTest @MethodSource("userNameValidationArguments") - void validateOnUpdateShouldThrowExceptionWhenUsernameIsInvalid(String name, List errors) { + void validateOnUpdateShouldThrowExceptionWhenUsernameIsInvalid(String name, List errors) { User user2 = User.Builder.builder().withId(3L).withEmail("max@mail.com").withFirstname("firstname") .withLastname("lastname").withUsername(name).build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(3L, user2)); - String[] exceptionParts = Objects.requireNonNull(exception.getReason()).split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - } - - for (int i = 0; i < exceptionParts.length; i++) { - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(errors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(errors).contains(exception.getReason())); } @ParameterizedTest @MethodSource("firstNameValidationArguments") - void validateOnUpdateShouldThrowExceptionWhenFirstnameIsInvalid(String name, List errors) { + void validateOnUpdateShouldThrowExceptionWhenFirstnameIsInvalid(String name, List errors) { User user2 = User.Builder.builder().withId(3L).withEmail("max@mail.com").withFirstname(name) .withLastname("lastname").withUsername("username").build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(3L, user2)); - String[] exceptionParts = Objects.requireNonNull(exception.getReason()).split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - } - - for (int i = 0; i < exceptionParts.length; i++) { - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(errors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(errors).contains(exception.getReason())); } @ParameterizedTest @MethodSource("lastNameValidationArguments") - void validateOnUpdateShouldThrowExceptionWhenLastnameIsInvalid(String name, List errors) { + void validateOnUpdateShouldThrowExceptionWhenLastnameIsInvalid(String name, List errors) { User user2 = User.Builder.builder().withId(3L).withEmail("max@mail.com").withFirstname("firstname") .withLastname(name).withUsername("username").build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(3L, user2)); - String[] exceptionParts = Objects.requireNonNull(exception.getReason()).split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - } - - for (int i = 0; i < exceptionParts.length; i++) { - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(errors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(errors).contains(exception.getReason())); } @ParameterizedTest @MethodSource("emailValidationArguments") - void validateOnUpdateShouldThrowExceptionWhenEmailIsInvalid(String email, List errors) { + void validateOnUpdateShouldThrowExceptionWhenEmailIsInvalid(String email, List errors) { User user2 = User.Builder.builder().withId(3L).withEmail(email).withFirstname("firstname") .withLastname("lastname").withUsername("username").build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(3L, user2)); - String[] exceptionParts = Objects.requireNonNull(exception.getReason()).split("\\."); - System.out.println(Arrays.toString(Arrays.stream(exceptionParts).toArray())); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - System.out.println(errorArray[i].strip()); - } - - for (int i = 0; i < exceptionParts.length; i++) { - assert (errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(errors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(errors).contains(exception.getReason())); } @Test From 348dcdd70ee11cd312d9e3fea58717236c9f72f6 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Fri, 24 Nov 2023 15:29:55 +0100 Subject: [PATCH 28/38] fix tests --- .../CheckInPersistenceServiceIT.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/CheckInPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/CheckInPersistenceServiceIT.java index 8ec5261b3e..17a82bcbd2 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/CheckInPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/CheckInPersistenceServiceIT.java @@ -1,6 +1,9 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.Objective; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.User; import ch.puzzle.okr.models.checkin.CheckIn; import ch.puzzle.okr.models.checkin.CheckInMetric; @@ -8,6 +11,8 @@ import ch.puzzle.okr.test.SpringIntegrationTest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Spy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.server.ResponseStatusException; @@ -15,12 +20,15 @@ import java.util.List; import java.util.Objects; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; @SpringIntegrationTest class CheckInPersistenceServiceIT { CheckIn createdCheckIn; + @Autowired private CheckInPersistenceService checkInPersistenceService; @@ -88,10 +96,15 @@ void updateKeyResultShouldThrowExceptionWhenAlreadyUpdated() { CheckIn updateCheckIn = createCheckIn(createdCheckIn.getId(), 0); updateCheckIn.setChangeInfo(UPDATED_CHECKIN); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> checkInPersistenceService.save(updateCheckIn)); + + List expectedErrors = List.of(new ErrorDto("DATA_HAS_BEEN_UPDATED", List.of("CheckIn"))); + assertEquals(UNPROCESSABLE_ENTITY, exception.getStatus()); - assertTrue(exception.getReason().contains("updated or deleted by another user")); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); + } @Test From 47e7dfefc1c3d9610293ec517aa90291a38473a9 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Fri, 24 Nov 2023 15:35:20 +0100 Subject: [PATCH 29/38] fix organisation persistence serviceIT --- .../OrganisationPersistenceIT.java | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceIT.java index 6d4605caef..43275a3527 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/OrganisationPersistenceIT.java @@ -1,5 +1,8 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.Organisation; import ch.puzzle.okr.models.OrganisationState; import ch.puzzle.okr.repository.OrganisationRepository; @@ -8,14 +11,14 @@ import org.junit.jupiter.api.Test; import org.mockito.Spy; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.springframework.http.HttpStatus.*; @SpringIntegrationTest class OrganisationPersistenceIT { @@ -44,7 +47,7 @@ void tearDown() { organisationPersistenceService.findById(createdOrganisation.getId()); organisationPersistenceService.deleteById(createdOrganisation.getId()); } - } catch (ResponseStatusException ex) { + } catch (OkrResponseStatusException ex) { // created alignment already deleted } finally { createdOrganisation = null; @@ -60,20 +63,27 @@ void shouldReturnSingleOrganisationWhenFindingByValidId() { @Test void shouldThrowExceptionWhenFindingOrganisationNotFound() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> organisationPersistenceService.findById(321L)); - assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); - assertEquals("Organisation with id 321 not found", exception.getReason()); + List expectedErrors = List + .of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("Organisation", "321"))); + + assertEquals(NOT_FOUND, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test void shouldThrowExceptionWhenFindingOrganisationWithIdNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> organisationPersistenceService.findById(null)); - assertEquals(HttpStatus.BAD_REQUEST, exception.getStatus()); - assertEquals("Missing identifier for Organisation", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Organisation"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test From 7937e98a9e70fcc463b1a08dd5fdd17ed77143e5 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Fri, 24 Nov 2023 15:49:20 +0100 Subject: [PATCH 30/38] fix --- .../okr/service/validation/ActionValidationServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/ActionValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/ActionValidationServiceTest.java index 44d41c567c..9c07a7e88a 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/ActionValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/ActionValidationServiceTest.java @@ -46,7 +46,7 @@ class ActionValidationServiceTest { private static Stream actionValidationArguments() { return Stream.of( arguments(StringUtils.repeat('1', 5000), - List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN_0_4096", List.of("action", "Action")))), + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("action", "Action", "0", "4096")))), arguments(null, List.of(new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("action", "Action"))))); } From 1e6c76374256321c109c760903bddfedc4ba636c Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Fri, 24 Nov 2023 16:00:43 +0100 Subject: [PATCH 31/38] Adjust teampers --- .../okr/service/persistence/TeamPersistenceServiceIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java index 20d12fe35b..52034d8bdf 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java @@ -74,7 +74,7 @@ void getTeamByIdShouldThrowExceptionWhenTeamIdIsNull() { OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> teamPersistenceService.findById(null)); - List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of())); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Team"))); assertEquals(BAD_REQUEST, exception.getStatus()); assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); From 53e56544612408f272502c1a4a1bad4dcb6d0a41 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Fri, 24 Nov 2023 16:07:08 +0100 Subject: [PATCH 32/38] fix objective tests --- .../ObjectiveValidationService.java | 2 +- .../ObjectiveValidationServiceTest.java | 87 +++++++++---------- frontend/src/assets/i18n/de.json | 6 +- 3 files changed, 44 insertions(+), 51 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java b/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java index f1ab1664d4..e6eaed11b5 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java @@ -52,7 +52,7 @@ private static void throwExceptionWhenModifiedByIsSet(Objective model) { private static void throwExceptionWhenModifiedByIsNull(Objective model) { if (model.getModifiedBy() == null) { throw new OkrResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, ErrorMsg.ATTRIBUTE_NOT_SET, - model.getModifiedBy().getUsername()); + "modifiedBy"); } } diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java index f48abbbbf0..2e53f7e4c6 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java @@ -17,7 +17,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; import java.util.List; @@ -28,6 +27,7 @@ import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.*; import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; @ExtendWith(MockitoExtension.class) class ObjectiveValidationServiceTest { @@ -39,6 +39,29 @@ class ObjectiveValidationServiceTest { User user; Quarter quarter; Team team; + @Spy + @InjectMocks + private ObjectiveValidationService validator; + + private static Stream nameValidationArguments() { + return Stream.of( + arguments(StringUtils.repeat('1', 251), + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("title", "Objective", "2", "250")))), + arguments(StringUtils.repeat('1', 1), + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("title", "Objective", "2", "250")))), + + arguments("", + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("title", "Objective", "2", "250")), + new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("title", "Objective")))), + + arguments(" ", + List.of(new ErrorDto("ATTRIBUTE_SIZE_BETWEEN", List.of("title", "Objective", "2", "250")), + new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("title", "Objective")))), + + arguments(" ", List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("title", "Objective")))), + arguments(null, List.of(new ErrorDto("ATTRIBUTE_NOT_BLANK", List.of("title", "Objective")), + new ErrorDto("ATTRIBUTE_NOT_NULL", List.of("title", "Objective"))))); + } @BeforeEach void setUp() { @@ -57,30 +80,11 @@ void setUp() { when(objectivePersistenceService.findById(1L)).thenReturn(objective1); when(objectivePersistenceService.getModelName()).thenReturn("Objective"); - doThrow(new ResponseStatusException(HttpStatus.NOT_FOUND, + doThrow(new OkrResponseStatusException(HttpStatus.NOT_FOUND, String.format("%s with id %s not found", objectivePersistenceService.getModelName(), 2L))) .when(objectivePersistenceService).findById(2L); } - @Spy - @InjectMocks - private ObjectiveValidationService validator; - - private static Stream nameValidationArguments() { - return Stream.of( - arguments(StringUtils.repeat('1', 251), List - .of("Attribute title must have a length between 2 and 250 characters when saving objective")), - arguments(StringUtils.repeat('1', 1), List - .of("Attribute title must have a length between 2 and 250 characters when saving objective")), - arguments("", List.of("Missing attribute title when saving objective", - "Attribute title must have a length between 2 and 250 characters when saving objective")), - arguments(" ", List.of("Missing attribute title when saving objective", - "Attribute title must have a length between 2 and 250 characters when saving objective")), - arguments(" ", List.of("Missing attribute title when saving objective")), - arguments(null, List.of("Missing attribute title when saving objective", - "Attribute title can not be null when saving objective"))); - } - @Test void validateOnGetShouldBeSuccessfulWhenValidObjectiveId() { validator.validateOnGet(1L); @@ -136,24 +140,17 @@ void validateOnCreateShouldThrowExceptionWhenIdIsNotNull() { @ParameterizedTest @MethodSource("nameValidationArguments") - void validateOnCreateShouldThrowExceptionWhenTitleIsInvalid(String title, List errors) { + void validateOnCreateShouldThrowExceptionWhenTitleIsInvalid(String title, List expectedErrors) { Objective objective = Objective.Builder.builder().withId(null).withTitle(title).withCreatedBy(this.user) .withTeam(this.team).withQuarter(this.quarter).withDescription("This is our description 2") .withModifiedOn(LocalDateTime.MAX).withState(State.DRAFT).withCreatedOn(LocalDateTime.MAX).build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(objective)); - String[] exceptionParts = exception.getReason().split("\\."); - String[] errorArray = new String[errors.size()]; - - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - } - - for (int i = 0; i < exceptionParts.length; i++) { - assertThat(errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -242,26 +239,19 @@ void validateOnUpdateShouldThrowExceptionWhenIdHasChanged() { @ParameterizedTest @MethodSource("nameValidationArguments") - void validateOnUpdateShouldThrowExceptionWhenTitleIsInvalid(String title, List errors) { + void validateOnUpdateShouldThrowExceptionWhenTitleIsInvalid(String title, List expectedErrors) { Objective objective = Objective.Builder.builder().withId(3L).withTitle(title).withCreatedBy(this.user) .withTeam(this.team).withQuarter(this.quarter).withDescription("This is our description 2") .withModifiedOn(LocalDateTime.MAX).withState(State.DRAFT).withModifiedBy(this.user) .withCreatedOn(LocalDateTime.MAX).build(); when(objectivePersistenceService.findById(objective.getId())).thenReturn(objective); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(3L, objective)); - String[] exceptionParts = exception.getReason().split("\\."); - - String[] errorArray = new String[errors.size()]; - for (int i = 0; i < errors.size(); i++) { - errorArray[i] = exceptionParts[i].strip(); - } - - for (int i = 0; i < exceptionParts.length; i++) { - assertThat(errors.contains(errorArray[i])); - } + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test @@ -291,8 +281,11 @@ void validateOnUpdateShouldThrowExceptionWhenAttrModifiedByIsNotSet() { OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnUpdate(1L, objectiveInvalid)); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, exception.getStatus()); - assertEquals(String.format("Something went wrong. ModifiedBy %s is not set.", null), exception.getReason()); + List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NOT_SET", List.of("modifiedBy"))); + + assertEquals(INTERNAL_SERVER_ERROR, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index 277fe68737..6429ed44da 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -38,10 +38,10 @@ "ATTRIBUTE_NOT_VALID": "Das Attribut {0} auf dem Objekt {1} ist ungültig", "ATTRIBUTE_SIZE_BETWEEN": "Das Attribut {0} auf dem Objekt {1} muss folgende länge haben: {2}-{3}", "ATTRIBUTE_SET_FORBIDDEN": "Das Attribut {0} darf nicht während dem erstellen gesetzt sein ", - "ATTRIBUTE_NOT_SET": "Ein Fehler ist aufgetreten.\n Das Attribut {0} ist nicht gesetzt", + "ATTRIBUTE_NOT_SET": "Das Attribut {0} ist nicht gesetzt", "ATTRIBUTE_CANNOT_CHANGE": "Das Attribut {0} auf dem Objekt {1} kann nicht geändert werden", - "ATTRIBUTE_MIN_VALUE": "", - "ATTRIBUTE_MAX_VALUE":"", + "ATTRIBUTE_MIN_VALUE": "Das Attribut {0} auf dem Objekt muss mindestens einen Wert von {1} haben", + "ATTRIBUTE_MAX_VALUE": "Das Attribut {0} auf dem Objekt darf maximal einen Wert von {1} haben", "NOT_AUTHORIZED_TO_READ": "Du bist nicht autorisiert um dieses {0} anzuzeigen", "NOT_AUTHORIZED_TO_WRITE":"Du bist nicht autorisiert um dieses {0} zu bearbeiten", "NOT_AUTHORIZED_TO_DELETE": "Du bist nicht autorisiert um dieses {0} zu löschen", From 8479bda3d23b70f429708dfa44b9c20e621279b6 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Fri, 24 Nov 2023 16:12:08 +0100 Subject: [PATCH 33/38] fixx all backend tests --- .../okr/service/persistence/TeamPersistenceServiceIT.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java index 52034d8bdf..fca77d9a87 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java @@ -124,7 +124,8 @@ void shouldDeleteTeam() { OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> teamPersistenceService.findById(createdTeam.getId())); - List expectedErrors = List.of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("Team", "200"))); + List expectedErrors = List + .of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("Team", createdTeam.getId()))); assertEquals(NOT_FOUND, exception.getStatus()); assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); From 8540f39d0058d7b4c12aa323aa33fa6126029ec0 Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Fri, 24 Nov 2023 16:25:34 +0100 Subject: [PATCH 34/38] Fix frontend test --- .../src/app/shared/interceptors/error.interceptor.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/app/shared/interceptors/error.interceptor.spec.ts b/frontend/src/app/shared/interceptors/error.interceptor.spec.ts index 64d91bb2ba..6629e13e4b 100644 --- a/frontend/src/app/shared/interceptors/error.interceptor.spec.ts +++ b/frontend/src/app/shared/interceptors/error.interceptor.spec.ts @@ -1,6 +1,7 @@ import { TestBed } from '@angular/core/testing'; import { ErrorInterceptor } from './error-interceptor.service'; import { ToasterService } from '../services/toaster.service'; +import { TranslateService } from '@ngx-translate/core'; describe('ErrorInterceptor', () => { beforeEach(() => @@ -11,6 +12,10 @@ describe('ErrorInterceptor', () => { provide: ToasterService, useValue: {}, }, + { + provide: TranslateService, + useValue: {}, + }, ], }), ); From 05c99c3bed7d774a7ce07c97dd66284713f754aa Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Fri, 24 Nov 2023 16:36:20 +0100 Subject: [PATCH 35/38] Fix backend tests --- .../puzzle/okr/controller/TeamController.java | 7 +++--- .../ObjectiveValidationServiceTest.java | 2 +- .../validation/TeamValidationServiceTest.java | 25 ------------------- .../validation/UserValidationServiceTest.java | 8 ++++-- 4 files changed, 10 insertions(+), 32 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/controller/TeamController.java b/backend/src/main/java/ch/puzzle/okr/controller/TeamController.java index a465d59e24..38401b72cc 100644 --- a/backend/src/main/java/ch/puzzle/okr/controller/TeamController.java +++ b/backend/src/main/java/ch/puzzle/okr/controller/TeamController.java @@ -29,10 +29,9 @@ public TeamController(TeamAuthorizationService teamAuthorizationService, TeamMap this.teamMapper = teamMapper; } - @Operation(summary = "Get Teams", description = "Get all Teams from db as well as all active objectives from chosen quarter") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Returned all Teams with active objective in quarter", content = { - @Content(mediaType = "application/json", schema = @Schema(implementation = TeamDto.class)) }), }) + @Operation(summary = "Get Teams", description = "Get all Teams from db") + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Returned all Teams", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = TeamDto.class)) }), }) @GetMapping public List getAllTeams(@RequestParam(value = "quarterId", required = false) Long quarterId) { return teamAuthorizationService.getAllTeams().stream().map(team -> teamMapper.toDto(team, quarterId)).toList(); diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java index 2e53f7e4c6..62fd83239e 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java @@ -179,7 +179,7 @@ void validateOnCreateShouldThrowExceptionWhenAttrModifiedByIsSet() { OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(objectiveInvalid)); List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_SET_FORBIDDEN", List.of("ModifiedBy", - "User{id=1, username='bkaufmann', firstname='Bob', lastname='Kaufmann', email='kaufmann@puzzle.ch'}"))); + "User{id=1, version=0, username='bkaufmann', firstname='Bob', lastname='Kaufmann', email='kaufmann@puzzle.ch', writeable=false}"))); assertEquals(BAD_REQUEST, exception.getStatus()); assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/TeamValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/TeamValidationServiceTest.java index 7d9d8441c3..d4620e7f7b 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/TeamValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/TeamValidationServiceTest.java @@ -96,31 +96,6 @@ void validateOnDeleteShouldThrowExceptionIfTeamIdIsNull() { assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } - @Test - void validateOnGetActiveObjectivesShouldBeSuccessfulWhenValidTeamId() { - validator.validateOnGetActiveObjectives(team1); - - verify(validator, times(1)).validateOnGetActiveObjectives(team1); - verify(validator, times(1)).throwExceptionWhenIdIsNull(1L); - verify(validator, times(1)).throwExceptionWhenModelIsNull(team1); - verify(validator, times(1)).throwExceptionWhenIdIsNull(team1.getId()); - verify(validator, times(1)).doesEntityExist(team1.getId()); - } - - @Test - void validateOnGetActiveObjectivesShouldThrowExceptionWhenIdIsNull() { - OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, - () -> validator.validateOnGetActiveObjectives(teamWithIdNull)); - - verify(validator, times(1)).throwExceptionWhenModelIsNull(teamWithIdNull); - verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Team"))); - - assertEquals(BAD_REQUEST, exception.getStatus()); - assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); - assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); - } - @Test void validateOnCreateShouldThrowExceptionWhenIdIsNotNull() { OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java index aae2d2cb57..2b5e69955e 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java @@ -163,11 +163,15 @@ void validateOnGetOrCreateShouldBeSuccessful() { @Test void validateOnGetOrCreateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGetOrCreate(null)); verify(validator, times(1)).throwExceptionWhenModelIsNull(null); - assertEquals("Given model User is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_NULL", List.of("User"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test From 8e596ceb80fcea1d5bdb2d2d1c3cdca66ab8af3b Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Fri, 24 Nov 2023 16:36:20 +0100 Subject: [PATCH 36/38] Fix backend tests --- .../puzzle/okr/controller/TeamController.java | 7 +++--- .../ActionPersistenceServiceIT.java | 11 ++++++-- .../persistence/TeamPersistenceServiceIT.java | 7 ++++-- .../ObjectiveValidationServiceTest.java | 2 +- .../validation/TeamValidationServiceTest.java | 25 ------------------- .../validation/UserValidationServiceTest.java | 8 ++++-- 6 files changed, 24 insertions(+), 36 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/controller/TeamController.java b/backend/src/main/java/ch/puzzle/okr/controller/TeamController.java index a465d59e24..38401b72cc 100644 --- a/backend/src/main/java/ch/puzzle/okr/controller/TeamController.java +++ b/backend/src/main/java/ch/puzzle/okr/controller/TeamController.java @@ -29,10 +29,9 @@ public TeamController(TeamAuthorizationService teamAuthorizationService, TeamMap this.teamMapper = teamMapper; } - @Operation(summary = "Get Teams", description = "Get all Teams from db as well as all active objectives from chosen quarter") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Returned all Teams with active objective in quarter", content = { - @Content(mediaType = "application/json", schema = @Schema(implementation = TeamDto.class)) }), }) + @Operation(summary = "Get Teams", description = "Get all Teams from db") + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Returned all Teams", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = TeamDto.class)) }), }) @GetMapping public List getAllTeams(@RequestParam(value = "quarterId", required = false) Long quarterId) { return teamAuthorizationService.getAllTeams().stream().map(team -> teamMapper.toDto(team, quarterId)).toList(); diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/ActionPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/ActionPersistenceServiceIT.java index 881516f55a..48c438377c 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/ActionPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/ActionPersistenceServiceIT.java @@ -1,7 +1,10 @@ package ch.puzzle.okr.service.persistence; +import ch.puzzle.okr.TestHelper; +import ch.puzzle.okr.dto.ErrorDto; import ch.puzzle.okr.models.Action; import ch.puzzle.okr.models.Objective; +import ch.puzzle.okr.models.OkrResponseStatusException; import ch.puzzle.okr.models.keyresult.KeyResultMetric; import ch.puzzle.okr.test.SpringIntegrationTest; import org.junit.jupiter.api.AfterEach; @@ -11,6 +14,7 @@ import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; @@ -79,10 +83,13 @@ void updateActionShouldThrowExceptionWhenAlreadyUpdated() { Action changedAction = createAction(createdAction.getId(), 0); changedAction.setAction("Updated Action"); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> actionPersistenceService.save(changedAction)); + List expectedErrors = List.of(new ErrorDto("DATA_HAS_BEEN_UPDATED", List.of("Action"))); + assertEquals(UNPROCESSABLE_ENTITY, exception.getStatus()); - assertTrue(exception.getReason().contains("updated or deleted by another user")); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java index fca77d9a87..9fccf3ad63 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java @@ -109,10 +109,13 @@ void updateTeamShouldThrowExceptionWhenAlreadyUpdated() { Team changedTeam = Team.Builder.builder().withId(createdTeam.getId()).withVersion(0).withName("Changed Team") .build(); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> teamPersistenceService.save(changedTeam)); + List expectedErrors = List.of(new ErrorDto("DATA_HAS_BEEN_UPDATED", List.of("Team"))); + assertEquals(UNPROCESSABLE_ENTITY, exception.getStatus()); - assertTrue(exception.getReason().contains("updated or deleted by another user")); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java index 2e53f7e4c6..62fd83239e 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java @@ -179,7 +179,7 @@ void validateOnCreateShouldThrowExceptionWhenAttrModifiedByIsSet() { OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnCreate(objectiveInvalid)); List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_SET_FORBIDDEN", List.of("ModifiedBy", - "User{id=1, username='bkaufmann', firstname='Bob', lastname='Kaufmann', email='kaufmann@puzzle.ch'}"))); + "User{id=1, version=0, username='bkaufmann', firstname='Bob', lastname='Kaufmann', email='kaufmann@puzzle.ch', writeable=false}"))); assertEquals(BAD_REQUEST, exception.getStatus()); assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/TeamValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/TeamValidationServiceTest.java index 7d9d8441c3..d4620e7f7b 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/TeamValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/TeamValidationServiceTest.java @@ -96,31 +96,6 @@ void validateOnDeleteShouldThrowExceptionIfTeamIdIsNull() { assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } - @Test - void validateOnGetActiveObjectivesShouldBeSuccessfulWhenValidTeamId() { - validator.validateOnGetActiveObjectives(team1); - - verify(validator, times(1)).validateOnGetActiveObjectives(team1); - verify(validator, times(1)).throwExceptionWhenIdIsNull(1L); - verify(validator, times(1)).throwExceptionWhenModelIsNull(team1); - verify(validator, times(1)).throwExceptionWhenIdIsNull(team1.getId()); - verify(validator, times(1)).doesEntityExist(team1.getId()); - } - - @Test - void validateOnGetActiveObjectivesShouldThrowExceptionWhenIdIsNull() { - OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, - () -> validator.validateOnGetActiveObjectives(teamWithIdNull)); - - verify(validator, times(1)).throwExceptionWhenModelIsNull(teamWithIdNull); - verify(validator, times(1)).throwExceptionWhenIdIsNull(null); - List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Team"))); - - assertEquals(BAD_REQUEST, exception.getStatus()); - assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); - assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); - } - @Test void validateOnCreateShouldThrowExceptionWhenIdIsNotNull() { OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java index aae2d2cb57..2b5e69955e 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/UserValidationServiceTest.java @@ -163,11 +163,15 @@ void validateOnGetOrCreateShouldBeSuccessful() { @Test void validateOnGetOrCreateShouldThrowExceptionWhenModelIsNull() { - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class, () -> validator.validateOnGetOrCreate(null)); verify(validator, times(1)).throwExceptionWhenModelIsNull(null); - assertEquals("Given model User is null", exception.getReason()); + List expectedErrors = List.of(new ErrorDto("MODEL_NULL", List.of("User"))); + + assertEquals(BAD_REQUEST, exception.getStatus()); + assertThat(expectedErrors).hasSameElementsAs(exception.getErrors()); + assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason())); } @Test From b0c359a0de4517a12d888c8bcfb4281fd38205cb Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Fri, 24 Nov 2023 16:58:45 +0100 Subject: [PATCH 37/38] show only one toaster message on chekin create --- .../app/shared/interceptors/error-interceptor.service.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/shared/interceptors/error-interceptor.service.ts b/frontend/src/app/shared/interceptors/error-interceptor.service.ts index 2d6077deba..35973fe540 100644 --- a/frontend/src/app/shared/interceptors/error-interceptor.service.ts +++ b/frontend/src/app/shared/interceptors/error-interceptor.service.ts @@ -8,7 +8,8 @@ import { TranslateService } from '@ngx-translate/core'; @Injectable() export class ErrorInterceptor implements HttpInterceptor { - NO_ERROR_TOASTER_ROUTES = ['/token']; + NO_TOASTER_ROUTES = ['/token']; + NO_TOASTER_SUCCESS_ROUTES = ['/action']; constructor( private router: Router, @@ -31,7 +32,7 @@ export class ErrorInterceptor implements HttpInterceptor { } handleErrorToaster(response: any) { - if (this.NO_ERROR_TOASTER_ROUTES.some((route) => response.url.includes(route))) { + if (this.NO_TOASTER_ROUTES.some((route) => response.url.includes(route))) { return; } @@ -54,7 +55,8 @@ export class ErrorInterceptor implements HttpInterceptor { } handleSuccess(response: any, method: string) { - if (this.NO_ERROR_TOASTER_ROUTES.some((route) => response.url.includes(route))) { + const NO_TOASTER = this.NO_TOASTER_ROUTES.concat(this.NO_TOASTER_SUCCESS_ROUTES); + if (NO_TOASTER.some((route) => response.url.includes(route))) { return; } switch (method) { From 21ac8304ac97c299c678df9ef6eedabc82beebb8 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Fri, 24 Nov 2023 17:04:14 +0100 Subject: [PATCH 38/38] display 226 properly --- .../shared/interceptors/error-interceptor.service.ts | 11 ++++++----- frontend/src/assets/i18n/de.json | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/shared/interceptors/error-interceptor.service.ts b/frontend/src/app/shared/interceptors/error-interceptor.service.ts index 35973fe540..1aa9f9ab0a 100644 --- a/frontend/src/app/shared/interceptors/error-interceptor.service.ts +++ b/frontend/src/app/shared/interceptors/error-interceptor.service.ts @@ -40,11 +40,6 @@ export class ErrorInterceptor implements HttpInterceptor { this.translate.instant('ERRORS.' + error.errorKey).format(error.params), ); - if (response.status == 226) { - errors.forEach((error: string) => this.toasterService.showWarn(error)); - return; - } - errors.forEach((error: string) => this.toasterService.showError(error)); } @@ -59,6 +54,12 @@ export class ErrorInterceptor implements HttpInterceptor { if (NO_TOASTER.some((route) => response.url.includes(route))) { return; } + + if (response.status == 226) { + this.toasterService.showWarn(this.translate.instant('ERRORS.ILLEGAL_CHANGE_OBJECTIVE_QUARTER')); + return; + } + switch (method) { case 'POST': { this.toasterService.showSuccess('Element wurde erfolgreich erstellt'); diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index 6429ed44da..a9fef76509 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -45,6 +45,7 @@ "NOT_AUTHORIZED_TO_READ": "Du bist nicht autorisiert um dieses {0} anzuzeigen", "NOT_AUTHORIZED_TO_WRITE":"Du bist nicht autorisiert um dieses {0} zu bearbeiten", "NOT_AUTHORIZED_TO_DELETE": "Du bist nicht autorisiert um dieses {0} zu löschen", - "TOKEN_NULL": "Das erhaltene Token ist null" + "TOKEN_NULL": "Das erhaltene Token ist null", + "ILLEGAL_CHANGE_OBJECTIVE_QUARTER":"Element kann nicht in ein anderes Quartal verlegt werden" } }