Update bean validation

This commit is contained in:
xushanchuan 2021-03-03 15:26:48 +08:00
parent 1d6e4af94a
commit 3602ebdc81
No known key found for this signature in database
GPG Key ID: 44D23C44E00838D6
9 changed files with 964 additions and 706 deletions

View File

@ -1,19 +1,28 @@
package io.spring.api; package io.spring.api;
import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.annotation.JsonRootName;
import io.spring.api.exception.InvalidRequestException;
import io.spring.application.Page;
import io.spring.application.ArticleQueryService; import io.spring.application.ArticleQueryService;
import io.spring.application.Page;
import io.spring.core.article.Article; import io.spring.core.article.Article;
import io.spring.core.article.ArticleRepository; import io.spring.core.article.ArticleRepository;
import io.spring.core.user.User; import io.spring.core.user.User;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.HashMap;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@ -21,9 +30,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.HashMap;
@RestController @RestController
@RequestMapping(path = "/articles") @RequestMapping(path = "/articles")
public class ArticlesApi { public class ArticlesApi {
@ -37,45 +43,43 @@ public class ArticlesApi {
} }
@PostMapping @PostMapping
public ResponseEntity createArticle(@Valid @RequestBody NewArticleParam newArticleParam, public ResponseEntity createArticle(
BindingResult bindingResult, @Valid @RequestBody NewArticleParam newArticleParam, @AuthenticationPrincipal User user) {
@AuthenticationPrincipal User user) { Article article =
if (bindingResult.hasErrors()) { new Article(
throw new InvalidRequestException(bindingResult);
}
if (articleQueryService.findBySlug(Article.toSlug(newArticleParam.getTitle()), null).isPresent()) {
bindingResult.rejectValue("title", "DUPLICATED", "article name exists");
throw new InvalidRequestException(bindingResult);
}
Article article = new Article(
newArticleParam.getTitle(), newArticleParam.getTitle(),
newArticleParam.getDescription(), newArticleParam.getDescription(),
newArticleParam.getBody(), newArticleParam.getBody(),
newArticleParam.getTagList(), newArticleParam.getTagList(),
user.getId()); user.getId());
articleRepository.save(article); articleRepository.save(article);
return ResponseEntity.ok(new HashMap<String, Object>() {{ return ResponseEntity.ok(
new HashMap<String, Object>() {
{
put("article", articleQueryService.findById(article.getId(), user).get()); put("article", articleQueryService.findById(article.getId(), user).get());
}}); }
});
} }
@GetMapping(path = "feed") @GetMapping(path = "feed")
public ResponseEntity getFeed(@RequestParam(value = "offset", defaultValue = "0") int offset, public ResponseEntity getFeed(
@RequestParam(value = "offset", defaultValue = "0") int offset,
@RequestParam(value = "limit", defaultValue = "20") int limit, @RequestParam(value = "limit", defaultValue = "20") int limit,
@AuthenticationPrincipal User user) { @AuthenticationPrincipal User user) {
return ResponseEntity.ok(articleQueryService.findUserFeed(user, new Page(offset, limit))); return ResponseEntity.ok(articleQueryService.findUserFeed(user, new Page(offset, limit)));
} }
@GetMapping @GetMapping
public ResponseEntity getArticles(@RequestParam(value = "offset", defaultValue = "0") int offset, public ResponseEntity getArticles(
@RequestParam(value = "offset", defaultValue = "0") int offset,
@RequestParam(value = "limit", defaultValue = "20") int limit, @RequestParam(value = "limit", defaultValue = "20") int limit,
@RequestParam(value = "tag", required = false) String tag, @RequestParam(value = "tag", required = false) String tag,
@RequestParam(value = "favorited", required = false) String favoritedBy, @RequestParam(value = "favorited", required = false) String favoritedBy,
@RequestParam(value = "author", required = false) String author, @RequestParam(value = "author", required = false) String author,
@AuthenticationPrincipal User user) { @AuthenticationPrincipal User user) {
return ResponseEntity.ok(articleQueryService.findRecentArticles(tag, author, favoritedBy, new Page(offset, limit), user)); return ResponseEntity.ok(
articleQueryService.findRecentArticles(
tag, author, favoritedBy, new Page(offset, limit), user));
} }
} }
@ -84,10 +88,37 @@ public class ArticlesApi {
@NoArgsConstructor @NoArgsConstructor
class NewArticleParam { class NewArticleParam {
@NotBlank(message = "can't be empty") @NotBlank(message = "can't be empty")
@DuplicatedArticleConstraint
private String title; private String title;
@NotBlank(message = "can't be empty") @NotBlank(message = "can't be empty")
private String description; private String description;
@NotBlank(message = "can't be empty") @NotBlank(message = "can't be empty")
private String body; private String body;
private String[] tagList; private String[] tagList;
} }
@Documented
@Constraint(validatedBy = DuplicatedArticleValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@interface DuplicatedArticleConstraint {
String message() default "article name exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
class DuplicatedArticleValidator
implements ConstraintValidator<DuplicatedArticleConstraint, String> {
@Autowired private ArticleQueryService articleQueryService;
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return !articleQueryService.findBySlug(Article.toSlug(value), null).isPresent();
}
}

View File

@ -1,24 +1,26 @@
package io.spring.api; package io.spring.api;
import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.annotation.JsonRootName;
import io.spring.api.exception.InvalidRequestException;
import io.spring.api.exception.NoAuthorizationException; import io.spring.api.exception.NoAuthorizationException;
import io.spring.api.exception.ResourceNotFoundException; import io.spring.api.exception.ResourceNotFoundException;
import io.spring.core.service.AuthorizationService;
import io.spring.application.data.CommentData;
import io.spring.application.CommentQueryService; import io.spring.application.CommentQueryService;
import io.spring.application.data.CommentData;
import io.spring.core.article.Article; import io.spring.core.article.Article;
import io.spring.core.article.ArticleRepository; import io.spring.core.article.ArticleRepository;
import io.spring.core.comment.Comment; import io.spring.core.comment.Comment;
import io.spring.core.comment.CommentRepository; import io.spring.core.comment.CommentRepository;
import io.spring.core.service.AuthorizationService;
import io.spring.core.user.User; import io.spring.core.user.User;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -27,11 +29,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController @RestController
@RequestMapping(path = "/articles/{slug}/comments") @RequestMapping(path = "/articles/{slug}/comments")
public class CommentsApi { public class CommentsApi {
@ -40,7 +37,8 @@ public class CommentsApi {
private CommentQueryService commentQueryService; private CommentQueryService commentQueryService;
@Autowired @Autowired
public CommentsApi(ArticleRepository articleRepository, public CommentsApi(
ArticleRepository articleRepository,
CommentRepository commentRepository, CommentRepository commentRepository,
CommentQueryService commentQueryService) { CommentQueryService commentQueryService) {
this.articleRepository = articleRepository; this.articleRepository = articleRepository;
@ -49,51 +47,62 @@ public class CommentsApi {
} }
@PostMapping @PostMapping
public ResponseEntity<?> createComment(@PathVariable("slug") String slug, public ResponseEntity<?> createComment(
@PathVariable("slug") String slug,
@AuthenticationPrincipal User user, @AuthenticationPrincipal User user,
@Valid @RequestBody NewCommentParam newCommentParam, @Valid @RequestBody NewCommentParam newCommentParam) {
BindingResult bindingResult) {
Article article = findArticle(slug); Article article = findArticle(slug);
if (bindingResult.hasErrors()) {
throw new InvalidRequestException(bindingResult);
}
Comment comment = new Comment(newCommentParam.getBody(), user.getId(), article.getId()); Comment comment = new Comment(newCommentParam.getBody(), user.getId(), article.getId());
commentRepository.save(comment); commentRepository.save(comment);
return ResponseEntity.status(201).body(commentResponse(commentQueryService.findById(comment.getId(), user).get())); return ResponseEntity.status(201)
.body(commentResponse(commentQueryService.findById(comment.getId(), user).get()));
} }
@GetMapping @GetMapping
public ResponseEntity getComments(@PathVariable("slug") String slug, public ResponseEntity getComments(
@AuthenticationPrincipal User user) { @PathVariable("slug") String slug, @AuthenticationPrincipal User user) {
Article article = findArticle(slug); Article article = findArticle(slug);
List<CommentData> comments = commentQueryService.findByArticleId(article.getId(), user); List<CommentData> comments = commentQueryService.findByArticleId(article.getId(), user);
return ResponseEntity.ok(new HashMap<String, Object>() {{ return ResponseEntity.ok(
new HashMap<String, Object>() {
{
put("comments", comments); put("comments", comments);
}}); }
});
} }
@RequestMapping(path = "{id}", method = RequestMethod.DELETE) @RequestMapping(path = "{id}", method = RequestMethod.DELETE)
public ResponseEntity deleteComment(@PathVariable("slug") String slug, public ResponseEntity deleteComment(
@PathVariable("slug") String slug,
@PathVariable("id") String commentId, @PathVariable("id") String commentId,
@AuthenticationPrincipal User user) { @AuthenticationPrincipal User user) {
Article article = findArticle(slug); Article article = findArticle(slug);
return commentRepository.findById(article.getId(), commentId).map(comment -> { return commentRepository
.findById(article.getId(), commentId)
.map(
comment -> {
if (!AuthorizationService.canWriteComment(user, article, comment)) { if (!AuthorizationService.canWriteComment(user, article, comment)) {
throw new NoAuthorizationException(); throw new NoAuthorizationException();
} }
commentRepository.remove(comment); commentRepository.remove(comment);
return ResponseEntity.noContent().build(); return ResponseEntity.noContent().build();
}).orElseThrow(ResourceNotFoundException::new); })
.orElseThrow(ResourceNotFoundException::new);
} }
private Article findArticle(String slug) { private Article findArticle(String slug) {
return articleRepository.findBySlug(slug).map(article -> article).orElseThrow(ResourceNotFoundException::new); return articleRepository
.findBySlug(slug)
.map(article -> article)
.orElseThrow(ResourceNotFoundException::new);
} }
private Map<String, Object> commentResponse(CommentData commentData) { private Map<String, Object> commentResponse(CommentData commentData) {
return new HashMap<String, Object>() {{ return new HashMap<String, Object>() {
{
put("comment", commentData); put("comment", commentData);
}}; }
};
} }
} }

View File

@ -1,19 +1,28 @@
package io.spring.api; package io.spring.api;
import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.annotation.JsonRootName;
import io.spring.api.exception.InvalidRequestException;
import io.spring.application.UserQueryService; import io.spring.application.UserQueryService;
import io.spring.application.data.UserWithToken;
import io.spring.application.data.UserData; import io.spring.application.data.UserData;
import io.spring.application.data.UserWithToken;
import io.spring.core.user.User; import io.spring.core.user.User;
import io.spring.core.user.UserRepository; import io.spring.core.user.UserRepository;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.Map;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Valid;
import javax.validation.constraints.Email;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import javax.validation.constraints.Email;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.BindingResult; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@ -21,79 +30,127 @@ import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@RestController @RestController
@RequestMapping(path = "/user") @RequestMapping(path = "/user")
public class CurrentUserApi { public class CurrentUserApi {
private UserQueryService userQueryService; private UserQueryService userQueryService;
private UserRepository userRepository; private UserService userService;
@Autowired @Autowired
public CurrentUserApi(UserQueryService userQueryService, UserRepository userRepository) { public CurrentUserApi(UserQueryService userQueryService, UserService userService) {
this.userQueryService = userQueryService; this.userQueryService = userQueryService;
this.userRepository = userRepository; this.userService = userService;
} }
@GetMapping @GetMapping
public ResponseEntity currentUser(@AuthenticationPrincipal User currentUser, public ResponseEntity currentUser(
@AuthenticationPrincipal User currentUser,
@RequestHeader(value = "Authorization") String authorization) { @RequestHeader(value = "Authorization") String authorization) {
UserData userData = userQueryService.findById(currentUser.getId()).get(); UserData userData = userQueryService.findById(currentUser.getId()).get();
return ResponseEntity.ok(userResponse( return ResponseEntity.ok(
new UserWithToken(userData, authorization.split(" ")[1]) userResponse(new UserWithToken(userData, authorization.split(" ")[1])));
));
} }
@PutMapping @PutMapping
public ResponseEntity updateProfile(@AuthenticationPrincipal User currentUser, public ResponseEntity updateProfile(
@AuthenticationPrincipal User currentUser,
@RequestHeader("Authorization") String token, @RequestHeader("Authorization") String token,
@Valid @RequestBody UpdateUserParam updateUserParam, @Valid @RequestBody UpdateUserParam updateUserParam) {
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
throw new InvalidRequestException(bindingResult);
}
checkUniquenessOfUsernameAndEmail(currentUser, updateUserParam, bindingResult);
currentUser.update( userService.updateUser(new UpdateUserCommand(currentUser, updateUserParam));
UserData userData = userQueryService.findById(currentUser.getId()).get();
return ResponseEntity.ok(userResponse(new UserWithToken(userData, token.split(" ")[1])));
}
private Map<String, Object> userResponse(UserWithToken userWithToken) {
return new HashMap<String, Object>() {
{
put("user", userWithToken);
}
};
}
}
@Validated
@Service
class UserService {
private UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void updateUser(@Valid UpdateUserCommand command) {
User user = command.getTargetUser();
UpdateUserParam updateUserParam = command.getParam();
user.update(
updateUserParam.getEmail(), updateUserParam.getEmail(),
updateUserParam.getUsername(), updateUserParam.getUsername(),
updateUserParam.getPassword(), updateUserParam.getPassword(),
updateUserParam.getBio(), updateUserParam.getBio(),
updateUserParam.getImage()); updateUserParam.getImage());
userRepository.save(currentUser); userRepository.save(user);
UserData userData = userQueryService.findById(currentUser.getId()).get();
return ResponseEntity.ok(userResponse(
new UserWithToken(userData, token.split(" ")[1])
));
}
private void checkUniquenessOfUsernameAndEmail(User currentUser, UpdateUserParam updateUserParam, BindingResult bindingResult) {
if (!"".equals(updateUserParam.getUsername())) {
Optional<User> byUsername = userRepository.findByUsername(updateUserParam.getUsername());
if (byUsername.isPresent() && !byUsername.get().equals(currentUser)) {
bindingResult.rejectValue("username", "DUPLICATED", "username already exist");
} }
} }
if (!"".equals(updateUserParam.getEmail())) { @Getter
Optional<User> byEmail = userRepository.findByEmail(updateUserParam.getEmail()); @AllArgsConstructor
if (byEmail.isPresent() && !byEmail.get().equals(currentUser)) { @UpdateUserConstraint
bindingResult.rejectValue("email", "DUPLICATED", "email already exist"); class UpdateUserCommand {
}
private User targetUser;
private UpdateUserParam param;
} }
if (bindingResult.hasErrors()) { @Constraint(validatedBy = UpdateUserValidator.class)
throw new InvalidRequestException(bindingResult); @Retention(RetentionPolicy.RUNTIME)
} @interface UpdateUserConstraint {
String message() default "invalid update param";
Class[] groups() default {};
Class[] payload() default {};
} }
private Map<String, Object> userResponse(UserWithToken userWithToken) { class UpdateUserValidator implements ConstraintValidator<UpdateUserConstraint, UpdateUserCommand> {
return new HashMap<String, Object>() {{
put("user", userWithToken); @Autowired private UserRepository userRepository;
}};
@Override
public boolean isValid(UpdateUserCommand value, ConstraintValidatorContext context) {
String inputEmail = value.getParam().getEmail();
String inputUsername = value.getParam().getUsername();
final User targetUser = value.getTargetUser();
boolean isEmailValid =
userRepository.findByEmail(inputEmail).map(user -> user.equals(targetUser)).orElse(true);
boolean isUsernameValid =
userRepository
.findByUsername(inputUsername)
.map(user -> user.equals(targetUser))
.orElse(true);
if (isEmailValid && isUsernameValid) {
return true;
} else {
context.disableDefaultConstraintViolation();
if (!isEmailValid) {
context
.buildConstraintViolationWithTemplate("email already exist")
.addPropertyNode("email")
.addConstraintViolation();
}
if (!isUsernameValid) {
context
.buildConstraintViolationWithTemplate("username already exist")
.addPropertyNode("username")
.addConstraintViolation();
}
return false;
}
} }
} }
@ -101,8 +158,10 @@ public class CurrentUserApi {
@JsonRootName("user") @JsonRootName("user")
@NoArgsConstructor @NoArgsConstructor
class UpdateUserParam { class UpdateUserParam {
@Email(message = "should be an email") @Email(message = "should be an email")
private String email = ""; private String email = "";
private String password = ""; private String password = "";
private String username = ""; private String username = "";
private String bio = ""; private String bio = "";

View File

@ -1,32 +1,36 @@
package io.spring.api; package io.spring.api;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.annotation.JsonRootName;
import io.spring.api.exception.InvalidRequestException; import io.spring.api.exception.InvalidAuthenticationException;
import io.spring.application.UserQueryService; import io.spring.application.UserQueryService;
import io.spring.application.data.UserWithToken;
import io.spring.application.data.UserData; import io.spring.application.data.UserData;
import io.spring.application.data.UserWithToken;
import io.spring.core.service.JwtService; import io.spring.core.service.JwtService;
import io.spring.core.user.EncryptService; import io.spring.core.user.EncryptService;
import io.spring.core.user.User; import io.spring.core.user.User;
import io.spring.core.user.UserRepository; import io.spring.core.user.UserRepository;
import lombok.Getter; import java.lang.annotation.Retention;
import lombok.NoArgsConstructor; import java.lang.annotation.RetentionPolicy;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import javax.validation.Constraint;
import static org.springframework.web.bind.annotation.RequestMethod.POST; import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class UsersApi { public class UsersApi {
@ -37,7 +41,8 @@ public class UsersApi {
private JwtService jwtService; private JwtService jwtService;
@Autowired @Autowired
public UsersApi(UserRepository userRepository, public UsersApi(
UserRepository userRepository,
UserQueryService userQueryService, UserQueryService userQueryService,
EncryptService encryptService, EncryptService encryptService,
@Value("${image.default}") String defaultImage, @Value("${image.default}") String defaultImage,
@ -50,10 +55,9 @@ public class UsersApi {
} }
@RequestMapping(path = "/users", method = POST) @RequestMapping(path = "/users", method = POST)
public ResponseEntity createUser(@Valid @RequestBody RegisterParam registerParam, BindingResult bindingResult) { public ResponseEntity createUser(@Valid @RequestBody RegisterParam registerParam) {
checkInput(registerParam, bindingResult); User user =
new User(
User user = new User(
registerParam.getEmail(), registerParam.getEmail(),
registerParam.getUsername(), registerParam.getUsername(),
encryptService.encrypt(registerParam.getPassword()), encryptService.encrypt(registerParam.getPassword()),
@ -61,42 +65,70 @@ public class UsersApi {
defaultImage); defaultImage);
userRepository.save(user); userRepository.save(user);
UserData userData = userQueryService.findById(user.getId()).get(); UserData userData = userQueryService.findById(user.getId()).get();
return ResponseEntity.status(201).body(userResponse(new UserWithToken(userData, jwtService.toToken(user)))); return ResponseEntity.status(201)
} .body(userResponse(new UserWithToken(userData, jwtService.toToken(user))));
private void checkInput(@Valid @RequestBody RegisterParam registerParam, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
throw new InvalidRequestException(bindingResult);
}
if (userRepository.findByUsername(registerParam.getUsername()).isPresent()) {
bindingResult.rejectValue("username", "DUPLICATED", "duplicated username");
}
if (userRepository.findByEmail(registerParam.getEmail()).isPresent()) {
bindingResult.rejectValue("email", "DUPLICATED", "duplicated email");
}
if (bindingResult.hasErrors()) {
throw new InvalidRequestException(bindingResult);
}
} }
@RequestMapping(path = "/users/login", method = POST) @RequestMapping(path = "/users/login", method = POST)
public ResponseEntity userLogin(@Valid @RequestBody LoginParam loginParam, BindingResult bindingResult) { public ResponseEntity userLogin(@Valid @RequestBody LoginParam loginParam) {
Optional<User> optional = userRepository.findByEmail(loginParam.getEmail()); Optional<User> optional = userRepository.findByEmail(loginParam.getEmail());
if (optional.isPresent() && encryptService.check(loginParam.getPassword(), optional.get().getPassword())) { if (optional.isPresent()
&& encryptService.check(loginParam.getPassword(), optional.get().getPassword())) {
UserData userData = userQueryService.findById(optional.get().getId()).get(); UserData userData = userQueryService.findById(optional.get().getId()).get();
return ResponseEntity.ok(userResponse(new UserWithToken(userData, jwtService.toToken(optional.get())))); return ResponseEntity.ok(
userResponse(new UserWithToken(userData, jwtService.toToken(optional.get()))));
} else { } else {
bindingResult.rejectValue("password", "INVALID", "invalid email or password"); throw new InvalidAuthenticationException();
throw new InvalidRequestException(bindingResult);
} }
} }
private Map<String, Object> userResponse(UserWithToken userWithToken) { private Map<String, Object> userResponse(UserWithToken userWithToken) {
return new HashMap<String, Object>() {{ return new HashMap<String, Object>() {
{
put("user", userWithToken); put("user", userWithToken);
}}; }
};
}
}
@Constraint(validatedBy = DuplicatedEmailValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@interface DuplicatedEmailConstraint {
String message() default "duplicated email";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
class DuplicatedEmailValidator implements ConstraintValidator<DuplicatedEmailConstraint, String> {
@Autowired private UserRepository userRepository;
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return (value == null || value.isEmpty()) || !userRepository.findByEmail(value).isPresent();
}
}
@Constraint(validatedBy = DuplicatedUsernameValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@interface DuplicatedUsernameConstraint {
String message() default "duplicated username";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
class DuplicatedUsernameValidator
implements ConstraintValidator<DuplicatedUsernameConstraint, String> {
@Autowired private UserRepository userRepository;
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return (value == null || value.isEmpty()) || !userRepository.findByUsername(value).isPresent();
} }
} }
@ -107,6 +139,7 @@ class LoginParam {
@NotBlank(message = "can't be empty") @NotBlank(message = "can't be empty")
@Email(message = "should be an email") @Email(message = "should be an email")
private String email; private String email;
@NotBlank(message = "can't be empty") @NotBlank(message = "can't be empty")
private String password; private String password;
} }
@ -117,9 +150,13 @@ class LoginParam {
class RegisterParam { class RegisterParam {
@NotBlank(message = "can't be empty") @NotBlank(message = "can't be empty")
@Email(message = "should be an email") @Email(message = "should be an email")
@DuplicatedEmailConstraint
private String email; private String email;
@NotBlank(message = "can't be empty") @NotBlank(message = "can't be empty")
@DuplicatedUsernameConstraint
private String username; private String username;
@NotBlank(message = "can't be empty") @NotBlank(message = "can't be empty")
private String password; private String password;
} }

View File

@ -1,18 +1,26 @@
package io.spring.api.exception; package io.spring.api.exception;
import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.util.List;
import java.util.stream.Collectors;
import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY;
@RestControllerAdvice @RestControllerAdvice
public class CustomizeExceptionHandler extends ResponseEntityExceptionHandler { public class CustomizeExceptionHandler extends ResponseEntityExceptionHandler {
@ -20,12 +28,16 @@ public class CustomizeExceptionHandler extends ResponseEntityExceptionHandler {
public ResponseEntity<Object> handleInvalidRequest(RuntimeException e, WebRequest request) { public ResponseEntity<Object> handleInvalidRequest(RuntimeException e, WebRequest request) {
InvalidRequestException ire = (InvalidRequestException) e; InvalidRequestException ire = (InvalidRequestException) e;
List<FieldErrorResource> errorResources = ire.getErrors().getFieldErrors().stream().map(fieldError -> List<FieldErrorResource> errorResources =
ire.getErrors().getFieldErrors().stream()
.map(
fieldError ->
new FieldErrorResource( new FieldErrorResource(
fieldError.getObjectName(), fieldError.getObjectName(),
fieldError.getField(), fieldError.getField(),
fieldError.getCode(), fieldError.getCode(),
fieldError.getDefaultMessage())).collect(Collectors.toList()); fieldError.getDefaultMessage()))
.collect(Collectors.toList());
ErrorResource error = new ErrorResource(errorResources); ErrorResource error = new ErrorResource(errorResources);
@ -34,4 +46,64 @@ public class CustomizeExceptionHandler extends ResponseEntityExceptionHandler {
return handleExceptionInternal(e, error, headers, UNPROCESSABLE_ENTITY, request); return handleExceptionInternal(e, error, headers, UNPROCESSABLE_ENTITY, request);
} }
@ExceptionHandler(InvalidAuthenticationException.class)
public ResponseEntity<Object> handleInvalidAuthentication(
InvalidAuthenticationException e, WebRequest request) {
return ResponseEntity.status(UNPROCESSABLE_ENTITY)
.body(
new HashMap<String, Object>() {
{
put("message", e.getMessage());
}
});
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException e,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
List<FieldErrorResource> errorResources =
e.getBindingResult().getFieldErrors().stream()
.map(
fieldError ->
new FieldErrorResource(
fieldError.getObjectName(),
fieldError.getField(),
fieldError.getCode(),
fieldError.getDefaultMessage()))
.collect(Collectors.toList());
return ResponseEntity.status(UNPROCESSABLE_ENTITY).body(new ErrorResource(errorResources));
}
@ExceptionHandler({ConstraintViolationException.class})
@ResponseStatus(UNPROCESSABLE_ENTITY)
@ResponseBody
public ErrorResource handleConstraintViolation(
ConstraintViolationException ex, WebRequest request) {
List<FieldErrorResource> errors = new ArrayList<>();
for (ConstraintViolation<?> violation : ex.getConstraintViolations()) {
FieldErrorResource fieldErrorResource =
new FieldErrorResource(
violation.getRootBeanClass().getName(),
getParam(violation.getPropertyPath().toString()),
violation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName(),
violation.getMessage());
errors.add(fieldErrorResource);
}
return new ErrorResource(errors);
}
private String getParam(String s) {
String[] splits = s.split("\\.");
if (splits.length == 1) {
return s;
} else {
return String.join(".", Arrays.copyOfRange(splits, 2, splits.length));
}
}
} }

View File

@ -0,0 +1,8 @@
package io.spring.api.exception;
public class InvalidAuthenticationException extends RuntimeException {
public InvalidAuthenticationException() {
super("invalid email or password");
}
}

View File

@ -1,5 +1,12 @@
package io.spring.api; package io.spring.api;
import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import io.restassured.module.mockmvc.RestAssuredMockMvc; import io.restassured.module.mockmvc.RestAssuredMockMvc;
import io.spring.JacksonCustomizations; import io.spring.JacksonCustomizations;
import io.spring.api.security.WebSecurityConfig; import io.spring.api.security.WebSecurityConfig;
@ -8,6 +15,10 @@ import io.spring.application.data.ArticleData;
import io.spring.application.data.ProfileData; import io.spring.application.data.ProfileData;
import io.spring.core.article.Article; import io.spring.core.article.Article;
import io.spring.core.article.ArticleRepository; import io.spring.core.article.ArticleRepository;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -17,30 +28,14 @@ import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@WebMvcTest({ArticlesApi.class}) @WebMvcTest({ArticlesApi.class})
@Import({WebSecurityConfig.class, JacksonCustomizations.class}) @Import({WebSecurityConfig.class, JacksonCustomizations.class})
public class ArticlesApiTest extends TestWithCurrentUser { public class ArticlesApiTest extends TestWithCurrentUser {
@Autowired @Autowired private MockMvc mvc;
private MockMvc mvc;
@MockBean @MockBean private ArticleRepository articleRepository;
private ArticleRepository articleRepository;
@MockBean @MockBean private ArticleQueryService articleQueryService;
private ArticleQueryService articleQueryService;
@Override @Override
@Before @Before
@ -58,7 +53,8 @@ public class ArticlesApiTest extends TestWithCurrentUser {
String[] tagList = {"reactjs", "angularjs", "dragons"}; String[] tagList = {"reactjs", "angularjs", "dragons"};
Map<String, Object> param = prepareParam(title, description, body, tagList); Map<String, Object> param = prepareParam(title, description, body, tagList);
ArticleData articleData = new ArticleData( ArticleData articleData =
new ArticleData(
"123", "123",
slug, slug,
title, title,
@ -71,7 +67,8 @@ public class ArticlesApiTest extends TestWithCurrentUser {
Arrays.asList(tagList), Arrays.asList(tagList),
new ProfileData("userid", user.getUsername(), user.getBio(), user.getImage(), false)); new ProfileData("userid", user.getUsername(), user.getBio(), user.getImage(), false));
when(articleQueryService.findBySlug(eq(Article.toSlug(title)), any())).thenReturn(Optional.empty()); when(articleQueryService.findBySlug(eq(Article.toSlug(title)), any()))
.thenReturn(Optional.empty());
when(articleQueryService.findById(any(), any())).thenReturn(Optional.of(articleData)); when(articleQueryService.findById(any(), any())).thenReturn(Optional.of(articleData));
@ -111,7 +108,6 @@ public class ArticlesApiTest extends TestWithCurrentUser {
.then() .then()
.statusCode(422) .statusCode(422)
.body("errors.body[0]", equalTo("can't be empty")); .body("errors.body[0]", equalTo("can't be empty"));
} }
@Test @Test
@ -123,7 +119,8 @@ public class ArticlesApiTest extends TestWithCurrentUser {
String[] tagList = {"reactjs", "angularjs", "dragons"}; String[] tagList = {"reactjs", "angularjs", "dragons"};
Map<String, Object> param = prepareParam(title, description, body, tagList); Map<String, Object> param = prepareParam(title, description, body, tagList);
ArticleData articleData = new ArticleData( ArticleData articleData =
new ArticleData(
"123", "123",
slug, slug,
title, title,
@ -136,7 +133,8 @@ public class ArticlesApiTest extends TestWithCurrentUser {
Arrays.asList(tagList), Arrays.asList(tagList),
new ProfileData("userid", user.getUsername(), user.getBio(), user.getImage(), false)); new ProfileData("userid", user.getUsername(), user.getBio(), user.getImage(), false));
when(articleQueryService.findBySlug(eq(Article.toSlug(title)), any())).thenReturn(Optional.of(articleData)); when(articleQueryService.findBySlug(eq(Article.toSlug(title)), any()))
.thenReturn(Optional.of(articleData));
when(articleQueryService.findById(any(), any())).thenReturn(Optional.of(articleData)); when(articleQueryService.findById(any(), any())).thenReturn(Optional.of(articleData));
@ -146,19 +144,26 @@ public class ArticlesApiTest extends TestWithCurrentUser {
.body(param) .body(param)
.when() .when()
.post("/articles") .post("/articles")
.prettyPeek()
.then() .then()
.statusCode(422); .statusCode(422);
} }
private HashMap<String, Object> prepareParam(final String title, final String description, final String body, final String[] tagList) { private HashMap<String, Object> prepareParam(
return new HashMap<String, Object>() {{ final String title, final String description, final String body, final String[] tagList) {
put("article", new HashMap<String, Object>() {{ return new HashMap<String, Object>() {
{
put(
"article",
new HashMap<String, Object>() {
{
put("title", title); put("title", title);
put("description", description); put("description", description);
put("body", body); put("body", body);
put("tagList", tagList); put("tagList", tagList);
}}); }
}}; });
}
};
} }
} }

View File

@ -1,37 +1,40 @@
package io.spring.api; package io.spring.api;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import io.spring.JacksonCustomizations;
import io.spring.api.security.WebSecurityConfig;
import io.spring.application.UserQueryService;
import io.spring.core.user.User;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static io.restassured.module.mockmvc.RestAssuredMockMvc.given; import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
import static org.hamcrest.core.IsEqual.equalTo; import static org.hamcrest.core.IsEqual.equalTo;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import io.spring.JacksonCustomizations;
import io.spring.api.security.WebSecurityConfig;
import io.spring.application.UserQueryService;
import io.spring.core.user.User;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(CurrentUserApi.class) @WebMvcTest(CurrentUserApi.class)
@Import({WebSecurityConfig.class, JacksonCustomizations.class}) @Import({
WebSecurityConfig.class,
JacksonCustomizations.class,
UserService.class,
ValidationAutoConfiguration.class
})
public class CurrentUserApiTest extends TestWithCurrentUser { public class CurrentUserApiTest extends TestWithCurrentUser {
@Autowired @Autowired private MockMvc mvc;
private MockMvc mvc;
@MockBean @MockBean private UserQueryService userQueryService;
private UserQueryService userQueryService;
@Override @Override
@Before @Before
@ -60,13 +63,7 @@ public class CurrentUserApiTest extends TestWithCurrentUser {
@Test @Test
public void should_get_401_without_token() throws Exception { public void should_get_401_without_token() throws Exception {
given() given().contentType("application/json").when().get("/user").then().statusCode(401);
.contentType("application/json")
.when()
.get("/user")
.then()
.statusCode(401);
} }
@Test @Test
@ -88,13 +85,20 @@ public class CurrentUserApiTest extends TestWithCurrentUser {
String newBio = "updated"; String newBio = "updated";
String newUsername = "newusernamee"; String newUsername = "newusernamee";
Map<String, Object> param = new HashMap<String, Object>() {{ Map<String, Object> param =
put("user", new HashMap<String, Object>() {{ new HashMap<String, Object>() {
{
put(
"user",
new HashMap<String, Object>() {
{
put("email", newEmail); put("email", newEmail);
put("bio", newBio); put("bio", newBio);
put("username", newUsername); put("username", newUsername);
}}); }
}}; });
}
};
when(userRepository.findByUsername(eq(newUsername))).thenReturn(Optional.empty()); when(userRepository.findByUsername(eq(newUsername))).thenReturn(Optional.empty());
when(userRepository.findByEmail(eq(newEmail))).thenReturn(Optional.empty()); when(userRepository.findByEmail(eq(newEmail))).thenReturn(Optional.empty());
@ -119,7 +123,8 @@ public class CurrentUserApiTest extends TestWithCurrentUser {
Map<String, Object> param = prepareUpdateParam(newEmail, newBio, newUsername); Map<String, Object> param = prepareUpdateParam(newEmail, newBio, newUsername);
when(userRepository.findByEmail(eq(newEmail))).thenReturn(Optional.of(new User(newEmail, "username", "123", "", ""))); when(userRepository.findByEmail(eq(newEmail)))
.thenReturn(Optional.of(new User(newEmail, "username", "123", "", "")));
when(userRepository.findByUsername(eq(newUsername))).thenReturn(Optional.empty()); when(userRepository.findByUsername(eq(newUsername))).thenReturn(Optional.empty());
when(userQueryService.findById(eq(user.getId()))).thenReturn(Optional.of(userData)); when(userQueryService.findById(eq(user.getId()))).thenReturn(Optional.of(userData));
@ -134,28 +139,38 @@ public class CurrentUserApiTest extends TestWithCurrentUser {
.then() .then()
.statusCode(422) .statusCode(422)
.body("errors.email[0]", equalTo("email already exist")); .body("errors.email[0]", equalTo("email already exist"));
} }
private HashMap<String, Object> prepareUpdateParam(final String newEmail, final String newBio, final String newUsername) { private HashMap<String, Object> prepareUpdateParam(
return new HashMap<String, Object>() {{ final String newEmail, final String newBio, final String newUsername) {
put("user", new HashMap<String, Object>() {{ return new HashMap<String, Object>() {
{
put(
"user",
new HashMap<String, Object>() {
{
put("email", newEmail); put("email", newEmail);
put("bio", newBio); put("bio", newBio);
put("username", newUsername); put("username", newUsername);
}}); }
}}; });
}
};
} }
@Test @Test
public void should_get_401_if_not_login() throws Exception { public void should_get_401_if_not_login() throws Exception {
given() given()
.contentType("application/json") .contentType("application/json")
.body(new HashMap<String, Object>() {{ .body(
new HashMap<String, Object>() {
{
put("user", new HashMap<String, Object>()); put("user", new HashMap<String, Object>());
}}) }
})
.when() .when()
.put("/user") .put("/user")
.then().statusCode(401); .then()
.statusCode(401);
} }
} }

View File

@ -1,5 +1,12 @@
package io.spring.api; package io.spring.api;
import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import io.restassured.module.mockmvc.RestAssuredMockMvc; import io.restassured.module.mockmvc.RestAssuredMockMvc;
import io.spring.JacksonCustomizations; import io.spring.JacksonCustomizations;
import io.spring.api.security.WebSecurityConfig; import io.spring.api.security.WebSecurityConfig;
@ -10,6 +17,9 @@ import io.spring.core.user.User;
import io.spring.core.user.UserRepository; import io.spring.core.user.UserRepository;
import io.spring.infrastructure.mybatis.readservice.UserReadService; import io.spring.infrastructure.mybatis.readservice.UserReadService;
import io.spring.infrastructure.service.NaiveEncryptService; import io.spring.infrastructure.service.NaiveEncryptService;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -20,35 +30,24 @@ import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@WebMvcTest(UsersApi.class) @WebMvcTest(UsersApi.class)
@Import({WebSecurityConfig.class, UserQueryService.class, NaiveEncryptService.class, JacksonCustomizations.class}) @Import({
WebSecurityConfig.class,
UserQueryService.class,
NaiveEncryptService.class,
JacksonCustomizations.class
})
public class UsersApiTest { public class UsersApiTest {
@Autowired @Autowired private MockMvc mvc;
private MockMvc mvc;
@MockBean @MockBean private UserRepository userRepository;
private UserRepository userRepository;
@MockBean @MockBean private JwtService jwtService;
private JwtService jwtService;
@MockBean @MockBean private UserReadService userReadService;
private UserReadService userReadService;
private String defaultAvatar; private String defaultAvatar;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
RestAssuredMockMvc.mockMvc(mvc); RestAssuredMockMvc.mockMvc(mvc);
@ -99,6 +98,7 @@ public class UsersApiTest {
.body(param) .body(param)
.when() .when()
.post("/users") .post("/users")
.prettyPeek()
.then() .then()
.statusCode(422) .statusCode(422)
.body("errors.username[0]", equalTo("can't be empty")); .body("errors.username[0]", equalTo("can't be empty"));
@ -116,10 +116,10 @@ public class UsersApiTest {
.body(param) .body(param)
.when() .when()
.post("/users") .post("/users")
.prettyPeek()
.then() .then()
.statusCode(422) .statusCode(422)
.body("errors.email[0]", equalTo("should be an email")); .body("errors.email[0]", equalTo("should be an email"));
} }
@Test @Test
@ -127,9 +127,8 @@ public class UsersApiTest {
String email = "john@jacob.com"; String email = "john@jacob.com";
String username = "johnjacob"; String username = "johnjacob";
when(userRepository.findByUsername(eq(username))).thenReturn(Optional.of(new User( when(userRepository.findByUsername(eq(username)))
email, username, "123", "bio", "" .thenReturn(Optional.of(new User(email, username, "123", "bio", "")));
)));
when(userRepository.findByEmail(any())).thenReturn(Optional.empty()); when(userRepository.findByEmail(any())).thenReturn(Optional.empty());
Map<String, Object> param = prepareRegisterParameter(email, username); Map<String, Object> param = prepareRegisterParameter(email, username);
@ -139,6 +138,7 @@ public class UsersApiTest {
.body(param) .body(param)
.when() .when()
.post("/users") .post("/users")
.prettyPeek()
.then() .then()
.statusCode(422) .statusCode(422)
.body("errors.username[0]", equalTo("duplicated username")); .body("errors.username[0]", equalTo("duplicated username"));
@ -149,9 +149,8 @@ public class UsersApiTest {
String email = "john@jacob.com"; String email = "john@jacob.com";
String username = "johnjacob2"; String username = "johnjacob2";
when(userRepository.findByEmail(eq(email))).thenReturn(Optional.of(new User( when(userRepository.findByEmail(eq(email)))
email, username, "123", "bio", "" .thenReturn(Optional.of(new User(email, username, "123", "bio", "")));
)));
when(userRepository.findByUsername(eq(username))).thenReturn(Optional.empty()); when(userRepository.findByUsername(eq(username))).thenReturn(Optional.empty());
@ -167,14 +166,21 @@ public class UsersApiTest {
.body("errors.email[0]", equalTo("duplicated email")); .body("errors.email[0]", equalTo("duplicated email"));
} }
private HashMap<String, Object> prepareRegisterParameter(final String email, final String username) { private HashMap<String, Object> prepareRegisterParameter(
return new HashMap<String, Object>() {{ final String email, final String username) {
put("user", new HashMap<String, Object>() {{ return new HashMap<String, Object>() {
{
put(
"user",
new HashMap<String, Object>() {
{
put("email", email); put("email", email);
put("password", "johnnyjacob"); put("password", "johnnyjacob");
put("username", username); put("username", username);
}}); }
}}; });
}
};
} }
@Test @Test
@ -191,12 +197,19 @@ public class UsersApiTest {
when(userReadService.findById(eq(user.getId()))).thenReturn(userData); when(userReadService.findById(eq(user.getId()))).thenReturn(userData);
when(jwtService.toToken(any())).thenReturn("123"); when(jwtService.toToken(any())).thenReturn("123");
Map<String, Object> param = new HashMap<String, Object>() {{ Map<String, Object> param =
put("user", new HashMap<String, Object>() {{ new HashMap<String, Object>() {
{
put(
"user",
new HashMap<String, Object>() {
{
put("email", email); put("email", email);
put("password", password); put("password", password);
}}); }
}}; });
}
};
given() given()
.contentType("application/json") .contentType("application/json")
@ -209,7 +222,8 @@ public class UsersApiTest {
.body("user.username", equalTo(username)) .body("user.username", equalTo(username))
.body("user.bio", equalTo("")) .body("user.bio", equalTo(""))
.body("user.image", equalTo(defaultAvatar)) .body("user.image", equalTo(defaultAvatar))
.body("user.token", equalTo("123"));; .body("user.token", equalTo("123"));
;
} }
@Test @Test
@ -224,20 +238,28 @@ public class UsersApiTest {
when(userRepository.findByEmail(eq(email))).thenReturn(Optional.of(user)); when(userRepository.findByEmail(eq(email))).thenReturn(Optional.of(user));
when(userReadService.findByUsername(eq(username))).thenReturn(userData); when(userReadService.findByUsername(eq(username))).thenReturn(userData);
Map<String, Object> param = new HashMap<String, Object>() {{ Map<String, Object> param =
put("user", new HashMap<String, Object>() {{ new HashMap<String, Object>() {
{
put(
"user",
new HashMap<String, Object>() {
{
put("email", email); put("email", email);
put("password", "123123"); put("password", "123123");
}}); }
}}; });
}
};
given() given()
.contentType("application/json") .contentType("application/json")
.body(param) .body(param)
.when() .when()
.post("/users/login") .post("/users/login")
.prettyPeek()
.then() .then()
.statusCode(422) .statusCode(422)
.body("errors.password[0]", equalTo("invalid email or password")); .body("message", equalTo("invalid email or password"));
} }
} }