refactor: move logic to application level

This commit is contained in:
xushanchuan 2021-03-16 17:16:20 +08:00
parent 49cac729f1
commit 36e33e7730
No known key found for this signature in database
GPG Key ID: 44D23C44E00838D6
40 changed files with 1310 additions and 1114 deletions

View File

@ -0,0 +1,7 @@
package io.spring;
public class Util {
public static boolean isEmpty(String value) {
return value == null || value.isEmpty();
}
}

View File

@ -1,15 +1,17 @@
package io.spring.api; package io.spring.api;
import com.fasterxml.jackson.annotation.JsonRootName;
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.ArticleData;
import io.spring.application.ArticleQueryService; import io.spring.application.ArticleQueryService;
import io.spring.application.article.ArticleCommandService;
import io.spring.application.article.UpdateArticleParam;
import io.spring.application.data.ArticleData;
import io.spring.core.article.ArticleRepository; import io.spring.core.article.ArticleRepository;
import io.spring.core.service.AuthorizationService;
import io.spring.core.user.User; import io.spring.core.user.User;
import lombok.Getter; import java.util.HashMap;
import lombok.NoArgsConstructor; import java.util.Map;
import javax.validation.Valid;
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;
@ -21,71 +23,72 @@ import org.springframework.web.bind.annotation.RequestBody;
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;
@RestController @RestController
@RequestMapping(path = "/articles/{slug}") @RequestMapping(path = "/articles/{slug}")
public class ArticleApi { public class ArticleApi {
private ArticleQueryService articleQueryService; private ArticleQueryService articleQueryService;
private ArticleRepository articleRepository; private ArticleRepository articleRepository;
private ArticleCommandService articleCommandService;
@Autowired @Autowired
public ArticleApi(ArticleQueryService articleQueryService, ArticleRepository articleRepository) { public ArticleApi(
this.articleQueryService = articleQueryService; ArticleQueryService articleQueryService,
this.articleRepository = articleRepository; ArticleRepository articleRepository,
} ArticleCommandService articleCommandService) {
this.articleQueryService = articleQueryService;
this.articleRepository = articleRepository;
this.articleCommandService = articleCommandService;
}
@GetMapping @GetMapping
public ResponseEntity<?> article(@PathVariable("slug") String slug, public ResponseEntity<?> article(
@AuthenticationPrincipal User user) { @PathVariable("slug") String slug, @AuthenticationPrincipal User user) {
return articleQueryService.findBySlug(slug, user) return articleQueryService
.map(articleData -> ResponseEntity.ok(articleResponse(articleData))) .findBySlug(slug, user)
.orElseThrow(ResourceNotFoundException::new); .map(articleData -> ResponseEntity.ok(articleResponse(articleData)))
} .orElseThrow(ResourceNotFoundException::new);
}
@PutMapping @PutMapping
public ResponseEntity<?> updateArticle(@PathVariable("slug") String slug, public ResponseEntity<?> updateArticle(
@AuthenticationPrincipal User user, @PathVariable("slug") String slug,
@Valid @RequestBody UpdateArticleParam updateArticleParam) { @AuthenticationPrincipal User user,
return articleRepository.findBySlug(slug).map(article -> { @Valid @RequestBody UpdateArticleParam updateArticleParam) {
if (!AuthorizationService.canWriteArticle(user, article)) { return articleRepository
.findBySlug(slug)
.map(
article -> {
if (!AuthorizationService.canWriteArticle(user, article)) {
throw new NoAuthorizationException(); throw new NoAuthorizationException();
} }
article.update( articleCommandService.updateArticle(article, updateArticleParam);
updateArticleParam.getTitle(), return ResponseEntity.ok(
updateArticleParam.getDescription(), articleResponse(articleQueryService.findBySlug(slug, user).get()));
updateArticleParam.getBody()); })
articleRepository.save(article); .orElseThrow(ResourceNotFoundException::new);
return ResponseEntity.ok(articleResponse(articleQueryService.findBySlug(slug, user).get())); }
}).orElseThrow(ResourceNotFoundException::new);
}
@DeleteMapping @DeleteMapping
public ResponseEntity deleteArticle(@PathVariable("slug") String slug, public ResponseEntity deleteArticle(
@AuthenticationPrincipal User user) { @PathVariable("slug") String slug, @AuthenticationPrincipal User user) {
return articleRepository.findBySlug(slug).map(article -> { return articleRepository
if (!AuthorizationService.canWriteArticle(user, article)) { .findBySlug(slug)
.map(
article -> {
if (!AuthorizationService.canWriteArticle(user, article)) {
throw new NoAuthorizationException(); throw new NoAuthorizationException();
} }
articleRepository.remove(article); articleRepository.remove(article);
return ResponseEntity.noContent().build(); return ResponseEntity.noContent().build();
}).orElseThrow(ResourceNotFoundException::new); })
} .orElseThrow(ResourceNotFoundException::new);
}
private Map<String, Object> articleResponse(ArticleData articleData) { private Map<String, Object> articleResponse(ArticleData articleData) {
return new HashMap<String, Object>() {{ return new HashMap<String, Object>() {
put("article", articleData); {
}}; put("article", articleData);
} }
} };
}
@Getter
@NoArgsConstructor
@JsonRootName("article")
class UpdateArticleParam {
private String title = "";
private String body = "";
private String description = "";
} }

View File

@ -1,13 +1,14 @@
package io.spring.api; package io.spring.api;
import io.spring.api.exception.ResourceNotFoundException; import io.spring.api.exception.ResourceNotFoundException;
import io.spring.application.data.ArticleData;
import io.spring.application.ArticleQueryService; import io.spring.application.ArticleQueryService;
import io.spring.application.data.ArticleData;
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.favorite.ArticleFavorite; import io.spring.core.favorite.ArticleFavorite;
import io.spring.core.favorite.ArticleFavoriteRepository; import io.spring.core.favorite.ArticleFavoriteRepository;
import io.spring.core.user.User; import io.spring.core.user.User;
import java.util.HashMap;
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;
@ -17,51 +18,54 @@ import org.springframework.web.bind.annotation.PostMapping;
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 java.util.HashMap;
@RestController @RestController
@RequestMapping(path = "articles/{slug}/favorite") @RequestMapping(path = "articles/{slug}/favorite")
public class ArticleFavoriteApi { public class ArticleFavoriteApi {
private ArticleFavoriteRepository articleFavoriteRepository; private ArticleFavoriteRepository articleFavoriteRepository;
private ArticleRepository articleRepository; private ArticleRepository articleRepository;
private ArticleQueryService articleQueryService; private ArticleQueryService articleQueryService;
@Autowired @Autowired
public ArticleFavoriteApi(ArticleFavoriteRepository articleFavoriteRepository, public ArticleFavoriteApi(
ArticleRepository articleRepository, ArticleFavoriteRepository articleFavoriteRepository,
ArticleQueryService articleQueryService) { ArticleRepository articleRepository,
this.articleFavoriteRepository = articleFavoriteRepository; ArticleQueryService articleQueryService) {
this.articleRepository = articleRepository; this.articleFavoriteRepository = articleFavoriteRepository;
this.articleQueryService = articleQueryService; this.articleRepository = articleRepository;
} this.articleQueryService = articleQueryService;
}
@PostMapping @PostMapping
public ResponseEntity favoriteArticle(@PathVariable("slug") String slug, public ResponseEntity favoriteArticle(
@AuthenticationPrincipal User user) { @PathVariable("slug") String slug, @AuthenticationPrincipal User user) {
Article article = getArticle(slug); Article article =
ArticleFavorite articleFavorite = new ArticleFavorite(article.getId(), user.getId()); articleRepository.findBySlug(slug).orElseThrow(ResourceNotFoundException::new);
articleFavoriteRepository.save(articleFavorite); ArticleFavorite articleFavorite = new ArticleFavorite(article.getId(), user.getId());
return responseArticleData(articleQueryService.findBySlug(slug, user).get()); articleFavoriteRepository.save(articleFavorite);
} return responseArticleData(articleQueryService.findBySlug(slug, user).get());
}
@DeleteMapping @DeleteMapping
public ResponseEntity unfavoriteArticle(@PathVariable("slug") String slug, public ResponseEntity unfavoriteArticle(
@AuthenticationPrincipal User user) { @PathVariable("slug") String slug, @AuthenticationPrincipal User user) {
Article article = getArticle(slug); Article article =
articleFavoriteRepository.find(article.getId(), user.getId()).ifPresent(favorite -> { articleRepository.findBySlug(slug).orElseThrow(ResourceNotFoundException::new);
articleFavoriteRepository.remove(favorite); articleFavoriteRepository
}); .find(article.getId(), user.getId())
return responseArticleData(articleQueryService.findBySlug(slug, user).get()); .ifPresent(
} favorite -> {
articleFavoriteRepository.remove(favorite);
});
return responseArticleData(articleQueryService.findBySlug(slug, user).get());
}
private ResponseEntity<HashMap<String, Object>> responseArticleData(final ArticleData articleData) { private ResponseEntity<HashMap<String, Object>> responseArticleData(
return ResponseEntity.ok(new HashMap<String, Object>() {{ final ArticleData articleData) {
return ResponseEntity.ok(
new HashMap<String, Object>() {
{
put("article", articleData); put("article", articleData);
}}); }
} });
}
private Article getArticle(String slug) {
return articleRepository.findBySlug(slug).map(article -> article)
.orElseThrow(ResourceNotFoundException::new);
}
} }

View File

@ -1,25 +1,13 @@
package io.spring.api; package io.spring.api;
import com.fasterxml.jackson.annotation.JsonRootName;
import io.spring.application.ArticleQueryService; import io.spring.application.ArticleQueryService;
import io.spring.application.Page; import io.spring.application.Page;
import io.spring.application.article.ArticleCommandService;
import io.spring.application.article.NewArticleParam;
import io.spring.core.article.Article; import io.spring.core.article.Article;
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 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.Valid;
import javax.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.NoArgsConstructor;
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;
@ -33,26 +21,20 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping(path = "/articles") @RequestMapping(path = "/articles")
public class ArticlesApi { public class ArticlesApi {
private ArticleRepository articleRepository; private ArticleCommandService articleCommandService;
private ArticleQueryService articleQueryService; private ArticleQueryService articleQueryService;
@Autowired @Autowired
public ArticlesApi(ArticleRepository articleRepository, ArticleQueryService articleQueryService) { public ArticlesApi(
this.articleRepository = articleRepository; ArticleCommandService articleCommandService, ArticleQueryService articleQueryService) {
this.articleCommandService = articleCommandService;
this.articleQueryService = articleQueryService; this.articleQueryService = articleQueryService;
} }
@PostMapping @PostMapping
public ResponseEntity createArticle( public ResponseEntity createArticle(
@Valid @RequestBody NewArticleParam newArticleParam, @AuthenticationPrincipal User user) { @Valid @RequestBody NewArticleParam newArticleParam, @AuthenticationPrincipal User user) {
Article article = Article article = articleCommandService.createArticle(newArticleParam, user);
new Article(
newArticleParam.getTitle(),
newArticleParam.getDescription(),
newArticleParam.getBody(),
newArticleParam.getTagList(),
user.getId());
articleRepository.save(article);
return ResponseEntity.ok( return ResponseEntity.ok(
new HashMap<String, Object>() { new HashMap<String, Object>() {
{ {
@ -82,43 +64,3 @@ public class ArticlesApi {
tag, author, favoritedBy, new Page(offset, limit), user)); tag, author, favoritedBy, new Page(offset, limit), user));
} }
} }
@Getter
@JsonRootName("article")
@NoArgsConstructor
class NewArticleParam {
@NotBlank(message = "can't be empty")
@DuplicatedArticleConstraint
private String title;
@NotBlank(message = "can't be empty")
private String description;
@NotBlank(message = "can't be empty")
private String body;
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

@ -51,7 +51,8 @@ public class CommentsApi {
@PathVariable("slug") String slug, @PathVariable("slug") String slug,
@AuthenticationPrincipal User user, @AuthenticationPrincipal User user,
@Valid @RequestBody NewCommentParam newCommentParam) { @Valid @RequestBody NewCommentParam newCommentParam) {
Article article = findArticle(slug); Article article =
articleRepository.findBySlug(slug).orElseThrow(ResourceNotFoundException::new);
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) return ResponseEntity.status(201)
@ -61,7 +62,8 @@ public class CommentsApi {
@GetMapping @GetMapping
public ResponseEntity getComments( public ResponseEntity getComments(
@PathVariable("slug") String slug, @AuthenticationPrincipal User user) { @PathVariable("slug") String slug, @AuthenticationPrincipal User user) {
Article article = findArticle(slug); Article article =
articleRepository.findBySlug(slug).orElseThrow(ResourceNotFoundException::new);
List<CommentData> comments = commentQueryService.findByArticleId(article.getId(), user); List<CommentData> comments = commentQueryService.findByArticleId(article.getId(), user);
return ResponseEntity.ok( return ResponseEntity.ok(
new HashMap<String, Object>() { new HashMap<String, Object>() {
@ -76,7 +78,8 @@ public class CommentsApi {
@PathVariable("slug") String slug, @PathVariable("slug") String slug,
@PathVariable("id") String commentId, @PathVariable("id") String commentId,
@AuthenticationPrincipal User user) { @AuthenticationPrincipal User user) {
Article article = findArticle(slug); Article article =
articleRepository.findBySlug(slug).orElseThrow(ResourceNotFoundException::new);
return commentRepository return commentRepository
.findById(article.getId(), commentId) .findById(article.getId(), commentId)
.map( .map(
@ -90,13 +93,6 @@ public class CommentsApi {
.orElseThrow(ResourceNotFoundException::new); .orElseThrow(ResourceNotFoundException::new);
} }
private Article findArticle(String slug) {
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>() {
{ {

View File

@ -1,28 +1,18 @@
package io.spring.api; package io.spring.api;
import com.fasterxml.jackson.annotation.JsonRootName;
import io.spring.application.UserQueryService; import io.spring.application.UserQueryService;
import io.spring.application.data.UserData; import io.spring.application.data.UserData;
import io.spring.application.data.UserWithToken; import io.spring.application.data.UserWithToken;
import io.spring.application.user.UpdateUserCommand;
import io.spring.application.user.UpdateUserParam;
import io.spring.application.user.UserService;
import io.spring.core.user.User; import io.spring.core.user.User;
import io.spring.core.user.UserRepository;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.Email;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
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.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;
@ -71,99 +61,3 @@ public class CurrentUserApi {
}; };
} }
} }
@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.getUsername(),
updateUserParam.getPassword(),
updateUserParam.getBio(),
updateUserParam.getImage());
userRepository.save(user);
}
}
@Getter
@AllArgsConstructor
@UpdateUserConstraint
class UpdateUserCommand {
private User targetUser;
private UpdateUserParam param;
}
@Constraint(validatedBy = UpdateUserValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@interface UpdateUserConstraint {
String message() default "invalid update param";
Class[] groups() default {};
Class[] payload() default {};
}
class UpdateUserValidator implements ConstraintValidator<UpdateUserConstraint, UpdateUserCommand> {
@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;
}
}
}
@Getter
@JsonRootName("user")
@NoArgsConstructor
class UpdateUserParam {
@Email(message = "should be an email")
private String email = "";
private String password = "";
private String username = "";
private String bio = "";
private String image = "";
}

View File

@ -7,26 +7,21 @@ import io.spring.api.exception.InvalidAuthenticationException;
import io.spring.application.UserQueryService; import io.spring.application.UserQueryService;
import io.spring.application.data.UserData; import io.spring.application.data.UserData;
import io.spring.application.data.UserWithToken; import io.spring.application.data.UserWithToken;
import io.spring.application.user.RegisterParam;
import io.spring.application.user.UserService;
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 java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
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 javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.Email; import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -36,34 +31,27 @@ import org.springframework.web.bind.annotation.RestController;
public class UsersApi { public class UsersApi {
private UserRepository userRepository; private UserRepository userRepository;
private UserQueryService userQueryService; private UserQueryService userQueryService;
private String defaultImage;
private EncryptService encryptService; private EncryptService encryptService;
private JwtService jwtService; private JwtService jwtService;
private UserService userService;
@Autowired @Autowired
public UsersApi( public UsersApi(
UserRepository userRepository, UserRepository userRepository,
UserQueryService userQueryService, UserQueryService userQueryService,
EncryptService encryptService, EncryptService encryptService,
@Value("${image.default}") String defaultImage, JwtService jwtService,
JwtService jwtService) { UserService userService) {
this.userRepository = userRepository; this.userRepository = userRepository;
this.userQueryService = userQueryService; this.userQueryService = userQueryService;
this.encryptService = encryptService; this.encryptService = encryptService;
this.defaultImage = defaultImage;
this.jwtService = jwtService; this.jwtService = jwtService;
this.userService = userService;
} }
@RequestMapping(path = "/users", method = POST) @RequestMapping(path = "/users", method = POST)
public ResponseEntity createUser(@Valid @RequestBody RegisterParam registerParam) { public ResponseEntity createUser(@Valid @RequestBody RegisterParam registerParam) {
User user = User user = userService.createUser(registerParam);
new User(
registerParam.getEmail(),
registerParam.getUsername(),
encryptService.encrypt(registerParam.getPassword()),
"",
defaultImage);
userRepository.save(user);
UserData userData = userQueryService.findById(user.getId()).get(); UserData userData = userQueryService.findById(user.getId()).get();
return ResponseEntity.status(201) return ResponseEntity.status(201)
.body(userResponse(new UserWithToken(userData, jwtService.toToken(user)))); .body(userResponse(new UserWithToken(userData, jwtService.toToken(user))));
@ -91,47 +79,6 @@ public class UsersApi {
} }
} }
@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();
}
}
@Getter @Getter
@JsonRootName("user") @JsonRootName("user")
@NoArgsConstructor @NoArgsConstructor
@ -143,20 +90,3 @@ class LoginParam {
@NotBlank(message = "can't be empty") @NotBlank(message = "can't be empty")
private String password; private String password;
} }
@Getter
@JsonRootName("user")
@NoArgsConstructor
class RegisterParam {
@NotBlank(message = "can't be empty")
@Email(message = "should be an email")
@DuplicatedEmailConstraint
private String email;
@NotBlank(message = "can't be empty")
@DuplicatedUsernameConstraint
private String username;
@NotBlank(message = "can't be empty")
private String password;
}

View File

@ -1,14 +0,0 @@
package io.spring.api.security;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CORSConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}
}

View File

@ -1,5 +1,7 @@
package io.spring.api.security; package io.spring.api.security;
import static java.util.Arrays.asList;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -15,8 +17,6 @@ import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import static java.util.Arrays.asList;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@ -32,11 +32,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
if (h2ConsoleEnabled) if (h2ConsoleEnabled) {
http.authorizeRequests() http.authorizeRequests()
.antMatchers("/h2-console", "/h2-console/**").permitAll() .antMatchers("/h2-console", "/h2-console/**").permitAll()
.and() .and()
.headers().frameOptions().sameOrigin(); .headers().frameOptions().sameOrigin();
}
http.csrf().disable() http.csrf().disable()
.cors() .cors()
@ -46,6 +47,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests() .authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll() .antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/graphiql").permitAll()
.antMatchers("/graphql").permitAll()
.antMatchers(HttpMethod.GET, "/articles/feed").authenticated() .antMatchers(HttpMethod.GET, "/articles/feed").authenticated()
.antMatchers(HttpMethod.POST, "/users", "/users/login").permitAll() .antMatchers(HttpMethod.POST, "/users", "/users/login").permitAll()
.antMatchers(HttpMethod.GET, "/articles/**", "/profiles/**", "/tags").permitAll() .antMatchers(HttpMethod.GET, "/articles/**", "/profiles/**", "/tags").permitAll()
@ -62,7 +65,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
"GET", "POST", "PUT", "DELETE", "PATCH")); "GET", "POST", "PUT", "DELETE", "PATCH"));
// setAllowCredentials(true) is important, otherwise: // setAllowCredentials(true) is important, otherwise:
// The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. // The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
configuration.setAllowCredentials(true); configuration.setAllowCredentials(false);
// setAllowedHeaders is important! Without it, OPTIONS preflight request // setAllowedHeaders is important! Without it, OPTIONS preflight request
// will fail with 403 Invalid CORS request // will fail with 403 Invalid CORS request
configuration.setAllowedHeaders(asList("Authorization", "Cache-Control", "Content-Type")); configuration.setAllowedHeaders(asList("Authorization", "Cache-Control", "Content-Type"));

View File

@ -2,37 +2,40 @@ package io.spring.application;
import io.spring.application.data.ProfileData; import io.spring.application.data.ProfileData;
import io.spring.application.data.UserData; import io.spring.application.data.UserData;
import io.spring.infrastructure.mybatis.readservice.UserReadService;
import io.spring.core.user.User; import io.spring.core.user.User;
import io.spring.infrastructure.mybatis.readservice.UserReadService;
import io.spring.infrastructure.mybatis.readservice.UserRelationshipQueryService; import io.spring.infrastructure.mybatis.readservice.UserRelationshipQueryService;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Optional;
@Component @Component
public class ProfileQueryService { public class ProfileQueryService {
private UserReadService userReadService; private UserReadService userReadService;
private UserRelationshipQueryService userRelationshipQueryService; private UserRelationshipQueryService userRelationshipQueryService;
@Autowired @Autowired
public ProfileQueryService(UserReadService userReadService, UserRelationshipQueryService userRelationshipQueryService) { public ProfileQueryService(
this.userReadService = userReadService; UserReadService userReadService, UserRelationshipQueryService userRelationshipQueryService) {
this.userRelationshipQueryService = userRelationshipQueryService; this.userReadService = userReadService;
} this.userRelationshipQueryService = userRelationshipQueryService;
}
public Optional<ProfileData> findByUsername(String username, User currentUser) { public Optional<ProfileData> findByUsername(String username, User currentUser) {
UserData userData = userReadService.findByUsername(username); UserData userData = userReadService.findByUsername(username);
if (userData == null) { if (userData == null) {
return Optional.empty(); return Optional.empty();
} else { } else {
ProfileData profileData = new ProfileData( ProfileData profileData =
userData.getId(), new ProfileData(
userData.getUsername(), userData.getId(),
userData.getBio(), userData.getUsername(),
userData.getImage(), userData.getBio(),
userRelationshipQueryService.isUserFollowing(currentUser.getId(), userData.getId())); userData.getImage(),
return Optional.of(profileData); currentUser != null
} && userRelationshipQueryService.isUserFollowing(
currentUser.getId(), userData.getId()));
return Optional.of(profileData);
} }
}
} }

View File

@ -0,0 +1,42 @@
package io.spring.application.article;
import io.spring.core.article.Article;
import io.spring.core.article.ArticleRepository;
import io.spring.core.user.User;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@Service
@Validated
public class ArticleCommandService {
private ArticleRepository articleRepository;
@Autowired
public ArticleCommandService(ArticleRepository articleRepository) {
this.articleRepository = articleRepository;
}
public Article createArticle(@Valid NewArticleParam newArticleParam, User creator) {
Article article =
new Article(
newArticleParam.getTitle(),
newArticleParam.getDescription(),
newArticleParam.getBody(),
newArticleParam.getTagList(),
creator.getId());
articleRepository.save(article);
return article;
}
public Article updateArticle(Article article, @Valid UpdateArticleParam updateArticleParam) {
article.update(
updateArticleParam.getTitle(),
updateArticleParam.getDescription(),
updateArticleParam.getBody());
articleRepository.save(article);
return article;
}
}

View File

@ -0,0 +1,21 @@
package io.spring.application.article;
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 javax.validation.Constraint;
import javax.validation.Payload;
@Documented
@Constraint(validatedBy = DuplicatedArticleValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DuplicatedArticleConstraint {
String message() default "article name exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,18 @@
package io.spring.application.article;
import io.spring.application.ArticleQueryService;
import io.spring.core.article.Article;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
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

@ -0,0 +1,28 @@
package io.spring.application.article;
import com.fasterxml.jackson.annotation.JsonRootName;
import java.util.List;
import javax.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@JsonRootName("article")
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class NewArticleParam {
@NotBlank(message = "can't be empty")
@DuplicatedArticleConstraint
private String title;
@NotBlank(message = "can't be empty")
private String description;
@NotBlank(message = "can't be empty")
private String body;
private List<String> tagList;
}

View File

@ -0,0 +1,16 @@
package io.spring.application.article;
import com.fasterxml.jackson.annotation.JsonRootName;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@JsonRootName("article")
public class UpdateArticleParam {
private String title = "";
private String body = "";
private String description = "";
}

View File

@ -0,0 +1,16 @@
package io.spring.application.user;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.validation.Constraint;
import javax.validation.Payload;
@Constraint(validatedBy = DuplicatedEmailValidator.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface DuplicatedEmailConstraint {
String message() default "duplicated email";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,17 @@
package io.spring.application.user;
import io.spring.core.user.UserRepository;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
public 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();
}
}

View File

@ -0,0 +1,16 @@
package io.spring.application.user;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.validation.Constraint;
import javax.validation.Payload;
@Constraint(validatedBy = DuplicatedUsernameValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@interface DuplicatedUsernameConstraint {
String message() default "duplicated username";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,17 @@
package io.spring.application.user;
import io.spring.core.user.UserRepository;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
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();
}
}

View File

@ -0,0 +1,26 @@
package io.spring.application.user;
import com.fasterxml.jackson.annotation.JsonRootName;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@JsonRootName("user")
@AllArgsConstructor
@NoArgsConstructor
public class RegisterParam {
@NotBlank(message = "can't be empty")
@Email(message = "should be an email")
@DuplicatedEmailConstraint
private String email;
@NotBlank(message = "can't be empty")
@DuplicatedUsernameConstraint
private String username;
@NotBlank(message = "can't be empty")
private String password;
}

View File

@ -0,0 +1,14 @@
package io.spring.application.user;
import io.spring.core.user.User;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
@UpdateUserConstraint
public class UpdateUserCommand {
private User targetUser;
private UpdateUserParam param;
}

View File

@ -0,0 +1,25 @@
package io.spring.application.user;
import com.fasterxml.jackson.annotation.JsonRootName;
import javax.validation.constraints.Email;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@JsonRootName("user")
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UpdateUserParam {
@Builder.Default
@Email(message = "should be an email")
private String email = "";
@Builder.Default private String password = "";
@Builder.Default private String username = "";
@Builder.Default private String bio = "";
@Builder.Default private String image = "";
}

View File

@ -0,0 +1,106 @@
package io.spring.application.user;
import io.spring.core.user.EncryptService;
import io.spring.core.user.User;
import io.spring.core.user.UserRepository;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@Service
@Validated
public class UserService {
private UserRepository userRepository;
private String defaultImage;
private EncryptService encryptService;
@Autowired
public UserService(
UserRepository userRepository,
@Value("${image.default}") String defaultImage,
EncryptService encryptService) {
this.userRepository = userRepository;
this.defaultImage = defaultImage;
this.encryptService = encryptService;
}
public User createUser(@Valid RegisterParam registerParam) {
User user =
new User(
registerParam.getEmail(),
registerParam.getUsername(),
encryptService.encrypt(registerParam.getPassword()),
"",
defaultImage);
userRepository.save(user);
return user;
}
public void updateUser(@Valid UpdateUserCommand command) {
User user = command.getTargetUser();
UpdateUserParam updateUserParam = command.getParam();
user.update(
updateUserParam.getEmail(),
updateUserParam.getUsername(),
updateUserParam.getPassword(),
updateUserParam.getBio(),
updateUserParam.getImage());
userRepository.save(user);
}
}
@Constraint(validatedBy = UpdateUserValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@interface UpdateUserConstraint {
String message() default "invalid update param";
Class[] groups() default {};
Class[] payload() default {};
}
class UpdateUserValidator implements ConstraintValidator<UpdateUserConstraint, UpdateUserCommand> {
@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;
}
}
}

View File

@ -1,62 +1,70 @@
package io.spring.core.article; package io.spring.core.article;
import static java.util.stream.Collectors.toList;
import io.spring.Util;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
@Getter @Getter
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode(of = {"id"}) @EqualsAndHashCode(of = {"id"})
public class Article { public class Article {
private String userId; private String userId;
private String id; private String id;
private String slug; private String slug;
private String title; private String title;
private String description; private String description;
private String body; private String body;
private List<Tag> tags; private List<Tag> tags;
private DateTime createdAt; private DateTime createdAt;
private DateTime updatedAt; private DateTime updatedAt;
public Article(String title, String description, String body, String[] tagList, String userId) { public Article(
this(title, description, body, tagList, userId, new DateTime()); String title, String description, String body, List<String> tagList, String userId) {
} this(title, description, body, tagList, userId, new DateTime());
}
public Article(String title, String description, String body, String[] tagList, String userId, DateTime createdAt) { public Article(
this.id = UUID.randomUUID().toString(); String title,
this.slug = toSlug(title); String description,
this.title = title; String body,
this.description = description; List<String> tagList,
this.body = body; String userId,
this.tags = Arrays.stream(tagList).collect(toSet()).stream().map(Tag::new).collect(toList()); DateTime createdAt) {
this.userId = userId; this.id = UUID.randomUUID().toString();
this.createdAt = createdAt; this.slug = toSlug(title);
this.updatedAt = createdAt; this.title = title;
} this.description = description;
this.body = body;
this.tags = new HashSet<>(tagList).stream().map(Tag::new).collect(toList());
this.userId = userId;
this.createdAt = createdAt;
this.updatedAt = createdAt;
}
public void update(String title, String description, String body) { public void update(String title, String description, String body) {
if (!"".equals(title)) { if (!Util.isEmpty(title)) {
this.title = title; this.title = title;
this.slug = toSlug(title); this.slug = toSlug(title);
} this.updatedAt = new DateTime();
if (!"".equals(description)) {
this.description = description;
}
if (!"".equals(body)) {
this.body = body;
}
this.updatedAt = new DateTime();
} }
if (!Util.isEmpty(description)) {
this.description = description;
this.updatedAt = new DateTime();
}
if (!Util.isEmpty(body)) {
this.body = body;
this.updatedAt = new DateTime();
}
}
public static String toSlug(String title) { public static String toSlug(String title) {
return title.toLowerCase().replaceAll("[\\&|[\\uFE30-\\uFFA0]|\\|\\”|\\s\\?\\,\\.]+", "-"); return title.toLowerCase().replaceAll("[\\&|[\\uFE30-\\uFFA0]|\\|\\”|\\s\\?\\,\\.]+", "-");
} }
} }

View File

@ -1,50 +1,50 @@
package io.spring.core.user; package io.spring.core.user;
import io.spring.Util;
import java.util.UUID;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.UUID;
@Getter @Getter
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode(of = {"id"}) @EqualsAndHashCode(of = {"id"})
public class User { public class User {
private String id; private String id;
private String email; private String email;
private String username; private String username;
private String password; private String password;
private String bio; private String bio;
private String image; private String image;
public User(String email, String username, String password, String bio, String image) { public User(String email, String username, String password, String bio, String image) {
this.id = UUID.randomUUID().toString(); this.id = UUID.randomUUID().toString();
this.email = email; this.email = email;
this.username = username; this.username = username;
this.password = password; this.password = password;
this.bio = bio; this.bio = bio;
this.image = image; this.image = image;
}
public void update(String email, String username, String password, String bio, String image) {
if (!Util.isEmpty(email)) {
this.email = email;
} }
public void update(String email, String username, String password, String bio, String image) { if (!Util.isEmpty(username)) {
if (!"".equals(email)) { this.username = username;
this.email = email;
}
if (!"".equals(username)) {
this.username = username;
}
if (!"".equals(password)) {
this.password = password;
}
if (!"".equals(bio)) {
this.bio = bio;
}
if (!"".equals(image)) {
this.image = image;
}
} }
if (!Util.isEmpty(password)) {
this.password = password;
}
if (!Util.isEmpty(bio)) {
this.bio = bio;
}
if (!Util.isEmpty(image)) {
this.image = image;
}
}
} }

View File

@ -6,6 +6,7 @@
C.id commentId, C.id commentId,
C.body commentBody, C.body commentBody,
C.created_at commentCreatedAt, C.created_at commentCreatedAt,
C.article_id commentArticleId,
<include refid="io.spring.infrastructure.mybatis.readservice.ArticleReadService.profileColumns"/> <include refid="io.spring.infrastructure.mybatis.readservice.ArticleReadService.profileColumns"/>
from comments C from comments C
left join users U left join users U

View File

@ -34,6 +34,7 @@
<result column="commentBody" property="body"/> <result column="commentBody" property="body"/>
<result column="commentCreatedAt" property="createdAt"/> <result column="commentCreatedAt" property="createdAt"/>
<result column="commentCreatedAt" property="updatedAt"/> <result column="commentCreatedAt" property="updatedAt"/>
<result column="commentArticleId" property="articleId"/>
<association property="profileData" resultMap="profileData"/> <association property="profileData" resultMap="profileData"/>
</resultMap> </resultMap>
</mapper> </mapper>

View File

@ -1,15 +1,28 @@
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.anyString;
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.TestHelper; import io.spring.TestHelper;
import io.spring.api.security.WebSecurityConfig; import io.spring.api.security.WebSecurityConfig;
import io.spring.application.ArticleQueryService; import io.spring.application.ArticleQueryService;
import io.spring.application.article.ArticleCommandService;
import io.spring.application.data.ArticleData; 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 io.spring.core.user.User; import io.spring.core.user.User;
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.joda.time.format.ISODateTimeFormat; import org.joda.time.format.ISODateTimeFormat;
import org.junit.Before; import org.junit.Before;
@ -20,104 +33,98 @@ 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.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@WebMvcTest({ArticleApi.class}) @WebMvcTest({ArticleApi.class})
@Import({WebSecurityConfig.class, JacksonCustomizations.class}) @Import({WebSecurityConfig.class, JacksonCustomizations.class})
public class ArticleApiTest extends TestWithCurrentUser { public class ArticleApiTest extends TestWithCurrentUser {
@Autowired @Autowired private MockMvc mvc;
private MockMvc mvc;
@MockBean @MockBean private ArticleQueryService articleQueryService;
private ArticleQueryService articleQueryService;
@MockBean @MockBean private ArticleRepository articleRepository;
private ArticleRepository articleRepository;
@Override @MockBean ArticleCommandService articleCommandService;
@Before
public void setUp() throws Exception {
super.setUp();
RestAssuredMockMvc.mockMvc(mvc);
}
@Test @Override
public void should_read_article_success() throws Exception { @Before
String slug = "test-new-article"; public void setUp() throws Exception {
DateTime time = new DateTime(); super.setUp();
Article article = new Article("Test New Article", "Desc", "Body", new String[]{"java", "spring", "jpg"}, user.getId(), time); RestAssuredMockMvc.mockMvc(mvc);
ArticleData articleData = TestHelper.getArticleDataFromArticleAndUser(article, user); }
when(articleQueryService.findBySlug(eq(slug), eq(null))).thenReturn(Optional.of(articleData)); @Test
public void should_read_article_success() throws Exception {
String slug = "test-new-article";
DateTime time = new DateTime();
Article article =
new Article(
"Test New Article",
"Desc",
"Body",
Arrays.asList("java", "spring", "jpg"),
user.getId(),
time);
ArticleData articleData = TestHelper.getArticleDataFromArticleAndUser(article, user);
RestAssuredMockMvc.when() when(articleQueryService.findBySlug(eq(slug), eq(null))).thenReturn(Optional.of(articleData));
.get("/articles/{slug}", slug)
.then()
.statusCode(200)
.body("article.slug", equalTo(slug))
.body("article.body", equalTo(articleData.getBody()))
.body("article.createdAt", equalTo(ISODateTimeFormat.dateTime().withZoneUTC().print(time)));
} RestAssuredMockMvc.when()
.get("/articles/{slug}", slug)
.then()
.statusCode(200)
.body("article.slug", equalTo(slug))
.body("article.body", equalTo(articleData.getBody()))
.body("article.createdAt", equalTo(ISODateTimeFormat.dateTime().withZoneUTC().print(time)));
}
@Test @Test
public void should_404_if_article_not_found() throws Exception { public void should_404_if_article_not_found() throws Exception {
when(articleQueryService.findBySlug(anyString(), any())).thenReturn(Optional.empty()); when(articleQueryService.findBySlug(anyString(), any())).thenReturn(Optional.empty());
RestAssuredMockMvc.when() RestAssuredMockMvc.when().get("/articles/not-exists").then().statusCode(404);
.get("/articles/not-exists") }
.then()
.statusCode(404);
}
@Test @Test
public void should_update_article_content_success() throws Exception { public void should_update_article_content_success() throws Exception {
String title = "new-title"; String title = "new-title";
String body = "new body"; String body = "new body";
String description = "new description"; String description = "new description";
Map<String, Object> updateParam = prepareUpdateParam(title, body, description); Map<String, Object> updateParam = prepareUpdateParam(title, body, description);
Article article = new Article(title, description, body, new String[]{"java", "spring", "jpg"}, user.getId()); Article article =
new Article(title, description, body, Arrays.asList("java", "spring", "jpg"), user.getId());
ArticleData articleData = TestHelper.getArticleDataFromArticleAndUser(article, user); ArticleData articleData = TestHelper.getArticleDataFromArticleAndUser(article, user);
when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article)); when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article));
when(articleQueryService.findBySlug(eq(article.getSlug()), eq(user))).thenReturn(Optional.of(articleData)); when(articleQueryService.findBySlug(eq(article.getSlug()), eq(user)))
.thenReturn(Optional.of(articleData));
given() given()
.contentType("application/json") .contentType("application/json")
.header("Authorization", "Token " + token) .header("Authorization", "Token " + token)
.body(updateParam) .body(updateParam)
.when() .when()
.put("/articles/{slug}", article.getSlug()) .put("/articles/{slug}", article.getSlug())
.then() .then()
.statusCode(200) .statusCode(200)
.body("article.slug", equalTo(articleData.getSlug())); .body("article.slug", equalTo(articleData.getSlug()));
} }
@Test @Test
public void should_get_403_if_not_author_to_update_article() throws Exception { public void should_get_403_if_not_author_to_update_article() throws Exception {
String title = "new-title"; String title = "new-title";
String body = "new body"; String body = "new body";
String description = "new description"; String description = "new description";
Map<String, Object> updateParam = prepareUpdateParam(title, body, description); Map<String, Object> updateParam = prepareUpdateParam(title, body, description);
User anotherUser = new User("test@test.com", "test", "123123", "", ""); User anotherUser = new User("test@test.com", "test", "123123", "", "");
Article article = new Article(title, description, body, new String[]{"java", "spring", "jpg"}, anotherUser.getId()); Article article =
new Article(
title, description, body, Arrays.asList("java", "spring", "jpg"), anotherUser.getId());
DateTime time = new DateTime(); DateTime time = new DateTime();
ArticleData articleData = new ArticleData( ArticleData articleData =
new ArticleData(
article.getId(), article.getId(),
article.getSlug(), article.getSlug(),
article.getTitle(), article.getTitle(),
@ -128,66 +135,82 @@ public class ArticleApiTest extends TestWithCurrentUser {
time, time,
time, time,
Arrays.asList("joda"), Arrays.asList("joda"),
new ProfileData(anotherUser.getId(), anotherUser.getUsername(), anotherUser.getBio(), anotherUser.getImage(), false)); new ProfileData(
anotherUser.getId(),
anotherUser.getUsername(),
anotherUser.getBio(),
anotherUser.getImage(),
false));
when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article)); when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article));
when(articleQueryService.findBySlug(eq(article.getSlug()), eq(user))).thenReturn(Optional.of(articleData)); when(articleQueryService.findBySlug(eq(article.getSlug()), eq(user)))
.thenReturn(Optional.of(articleData));
given() given()
.contentType("application/json") .contentType("application/json")
.header("Authorization", "Token " + token) .header("Authorization", "Token " + token)
.body(updateParam) .body(updateParam)
.when() .when()
.put("/articles/{slug}", article.getSlug()) .put("/articles/{slug}", article.getSlug())
.then() .then()
.statusCode(403); .statusCode(403);
} }
@Test @Test
public void should_delete_article_success() throws Exception { public void should_delete_article_success() throws Exception {
String title = "title"; String title = "title";
String body = "body"; String body = "body";
String description = "description"; String description = "description";
Article article = new Article(title, description, body, new String[]{"java", "spring", "jpg"}, user.getId()); Article article =
when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article)); new Article(title, description, body, Arrays.asList("java", "spring", "jpg"), user.getId());
when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article));
given() given()
.header("Authorization", "Token " + token) .header("Authorization", "Token " + token)
.when() .when()
.delete("/articles/{slug}", article.getSlug()) .delete("/articles/{slug}", article.getSlug())
.then() .then()
.statusCode(204); .statusCode(204);
verify(articleRepository).remove(eq(article)); verify(articleRepository).remove(eq(article));
} }
@Test @Test
public void should_403_if_not_author_delete_article() throws Exception { public void should_403_if_not_author_delete_article() throws Exception {
String title = "new-title"; String title = "new-title";
String body = "new body"; String body = "new body";
String description = "new description"; String description = "new description";
User anotherUser = new User("test@test.com", "test", "123123", "", ""); User anotherUser = new User("test@test.com", "test", "123123", "", "");
Article article = new Article(title, description, body, new String[]{"java", "spring", "jpg"}, anotherUser.getId()); Article article =
new Article(
title, description, body, Arrays.asList("java", "spring", "jpg"), anotherUser.getId());
when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article)); when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article));
given() given()
.header("Authorization", "Token " + token) .header("Authorization", "Token " + token)
.when() .when()
.delete("/articles/{slug}", article.getSlug()) .delete("/articles/{slug}", article.getSlug())
.then() .then()
.statusCode(403); .statusCode(403);
} }
private HashMap<String, Object> prepareUpdateParam(final String title, final String body, final String description) { private HashMap<String, Object> prepareUpdateParam(
return new HashMap<String, Object>() {{ final String title, final String body, final String description) {
put("article", new HashMap<String, Object>() {{ return new HashMap<String, Object>() {
{
put(
"article",
new HashMap<String, Object>() {
{
put("title", title); put("title", title);
put("body", body); put("body", body);
put("description", description); put("description", description);
}}); }
}}; });
} }
};
}
} }

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;
@ -12,6 +19,9 @@ import io.spring.core.article.Tag;
import io.spring.core.favorite.ArticleFavorite; import io.spring.core.favorite.ArticleFavorite;
import io.spring.core.favorite.ArticleFavoriteRepository; import io.spring.core.favorite.ArticleFavoriteRepository;
import io.spring.core.user.User; import io.spring.core.user.User;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -20,41 +30,28 @@ 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.Optional;
import java.util.stream.Collectors;
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;
@WebMvcTest(ArticleFavoriteApi.class) @WebMvcTest(ArticleFavoriteApi.class)
@Import({WebSecurityConfig.class, JacksonCustomizations.class}) @Import({WebSecurityConfig.class, JacksonCustomizations.class})
public class ArticleFavoriteApiTest extends TestWithCurrentUser { public class ArticleFavoriteApiTest extends TestWithCurrentUser {
@Autowired @Autowired private MockMvc mvc;
private MockMvc mvc;
@MockBean @MockBean private ArticleFavoriteRepository articleFavoriteRepository;
private ArticleFavoriteRepository articleFavoriteRepository;
@MockBean @MockBean private ArticleRepository articleRepository;
private ArticleRepository articleRepository;
@MockBean @MockBean private ArticleQueryService articleQueryService;
private ArticleQueryService articleQueryService;
private Article article; private Article article;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
RestAssuredMockMvc.mockMvc(mvc); RestAssuredMockMvc.mockMvc(mvc);
User anotherUser = new User("other@test.com", "other", "123", "", ""); User anotherUser = new User("other@test.com", "other", "123", "", "");
article = new Article("title", "desc", "body", new String[]{"java"}, anotherUser.getId()); article = new Article("title", "desc", "body", Arrays.asList("java"), anotherUser.getId());
when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article)); when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article));
ArticleData articleData = new ArticleData( ArticleData articleData =
new ArticleData(
article.getId(), article.getId(),
article.getSlug(), article.getSlug(),
article.getTitle(), article.getTitle(),
@ -70,36 +67,37 @@ public class ArticleFavoriteApiTest extends TestWithCurrentUser {
anotherUser.getUsername(), anotherUser.getUsername(),
anotherUser.getBio(), anotherUser.getBio(),
anotherUser.getImage(), anotherUser.getImage(),
false false));
)); when(articleQueryService.findBySlug(eq(articleData.getSlug()), eq(user)))
when(articleQueryService.findBySlug(eq(articleData.getSlug()), eq(user))).thenReturn(Optional.of(articleData)); .thenReturn(Optional.of(articleData));
} }
@Test @Test
public void should_favorite_an_article_success() throws Exception { public void should_favorite_an_article_success() throws Exception {
given() given()
.header("Authorization", "Token " + token) .header("Authorization", "Token " + token)
.when() .when()
.post("/articles/{slug}/favorite", article.getSlug()) .post("/articles/{slug}/favorite", article.getSlug())
.prettyPeek() .prettyPeek()
.then() .then()
.statusCode(200) .statusCode(200)
.body("article.id", equalTo(article.getId())); .body("article.id", equalTo(article.getId()));
verify(articleFavoriteRepository).save(any()); verify(articleFavoriteRepository).save(any());
} }
@Test @Test
public void should_unfavorite_an_article_success() throws Exception { public void should_unfavorite_an_article_success() throws Exception {
when(articleFavoriteRepository.find(eq(article.getId()), eq(user.getId()))).thenReturn(Optional.of(new ArticleFavorite(article.getId(), user.getId()))); when(articleFavoriteRepository.find(eq(article.getId()), eq(user.getId())))
given() .thenReturn(Optional.of(new ArticleFavorite(article.getId(), user.getId())));
.header("Authorization", "Token " + token) given()
.when() .header("Authorization", "Token " + token)
.delete("/articles/{slug}/favorite", article.getSlug()) .when()
.prettyPeek() .delete("/articles/{slug}/favorite", article.getSlug())
.then() .prettyPeek()
.statusCode(200) .then()
.body("article.id", equalTo(article.getId())); .statusCode(200)
verify(articleFavoriteRepository).remove(new ArticleFavorite(article.getId(), user.getId())); .body("article.id", equalTo(article.getId()));
} verify(articleFavoriteRepository).remove(new ArticleFavorite(article.getId(), user.getId()));
}
} }

View File

@ -1,6 +1,7 @@
package io.spring.api; package io.spring.api;
import static io.restassured.module.mockmvc.RestAssuredMockMvc.given; import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
import static java.util.Arrays.asList;
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;
@ -11,12 +12,12 @@ 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;
import io.spring.application.ArticleQueryService; import io.spring.application.ArticleQueryService;
import io.spring.application.article.ArticleCommandService;
import io.spring.application.data.ArticleData; 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 java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@ -33,10 +34,10 @@ import org.springframework.test.web.servlet.MockMvc;
public class ArticlesApiTest extends TestWithCurrentUser { public class ArticlesApiTest extends TestWithCurrentUser {
@Autowired private MockMvc mvc; @Autowired private MockMvc mvc;
@MockBean private ArticleRepository articleRepository;
@MockBean private ArticleQueryService articleQueryService; @MockBean private ArticleQueryService articleQueryService;
@MockBean private ArticleCommandService articleCommandService;
@Override @Override
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@ -50,7 +51,7 @@ public class ArticlesApiTest extends TestWithCurrentUser {
String slug = "how-to-train-your-dragon"; String slug = "how-to-train-your-dragon";
String description = "Ever wonder how?"; String description = "Ever wonder how?";
String body = "You have to believe"; String body = "You have to believe";
String[] tagList = {"reactjs", "angularjs", "dragons"}; List<String> tagList = asList("reactjs", "angularjs", "dragons");
Map<String, Object> param = prepareParam(title, description, body, tagList); Map<String, Object> param = prepareParam(title, description, body, tagList);
ArticleData articleData = ArticleData articleData =
@ -64,9 +65,12 @@ public class ArticlesApiTest extends TestWithCurrentUser {
0, 0,
new DateTime(), new DateTime(),
new DateTime(), new DateTime(),
Arrays.asList(tagList), tagList,
new ProfileData("userid", user.getUsername(), user.getBio(), user.getImage(), false)); new ProfileData("userid", user.getUsername(), user.getBio(), user.getImage(), false));
when(articleCommandService.createArticle(any(), any()))
.thenReturn(new Article(title, description, body, tagList, user.getId()));
when(articleQueryService.findBySlug(eq(Article.toSlug(title)), any())) when(articleQueryService.findBySlug(eq(Article.toSlug(title)), any()))
.thenReturn(Optional.empty()); .thenReturn(Optional.empty());
@ -87,7 +91,7 @@ public class ArticlesApiTest extends TestWithCurrentUser {
.body("article.author.username", equalTo(user.getUsername())) .body("article.author.username", equalTo(user.getUsername()))
.body("article.author.id", equalTo(null)); .body("article.author.id", equalTo(null));
verify(articleRepository).save(any()); verify(articleCommandService).createArticle(any(), any());
} }
@Test @Test
@ -96,7 +100,7 @@ public class ArticlesApiTest extends TestWithCurrentUser {
String description = "Ever wonder how?"; String description = "Ever wonder how?";
String body = ""; String body = "";
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, asList(tagList));
given() given()
.contentType("application/json") .contentType("application/json")
@ -117,7 +121,7 @@ public class ArticlesApiTest extends TestWithCurrentUser {
String description = "Ever wonder how?"; String description = "Ever wonder how?";
String body = "You have to believe"; String body = "You have to believe";
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, asList(tagList));
ArticleData articleData = ArticleData articleData =
new ArticleData( new ArticleData(
@ -130,7 +134,7 @@ public class ArticlesApiTest extends TestWithCurrentUser {
0, 0,
new DateTime(), new DateTime(),
new DateTime(), new DateTime(),
Arrays.asList(tagList), 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())) when(articleQueryService.findBySlug(eq(Article.toSlug(title)), any()))
@ -150,7 +154,7 @@ public class ArticlesApiTest extends TestWithCurrentUser {
} }
private HashMap<String, Object> prepareParam( private HashMap<String, Object> prepareParam(
final String title, final String description, final String body, final String[] tagList) { final String title, final String description, final String body, final List<String> tagList) {
return new HashMap<String, Object>() { return new HashMap<String, Object>() {
{ {
put( put(

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.anyString;
import static org.mockito.ArgumentMatchers.eq;
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;
@ -11,6 +18,10 @@ 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.user.User; import io.spring.core.user.User;
import java.util.Arrays;
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.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -19,132 +30,136 @@ 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.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@WebMvcTest(CommentsApi.class) @WebMvcTest(CommentsApi.class)
@Import({WebSecurityConfig.class, JacksonCustomizations.class}) @Import({WebSecurityConfig.class, JacksonCustomizations.class})
public class CommentsApiTest extends TestWithCurrentUser { public class CommentsApiTest extends TestWithCurrentUser {
@MockBean @MockBean private ArticleRepository articleRepository;
private ArticleRepository articleRepository;
@MockBean @MockBean private CommentRepository commentRepository;
private CommentRepository commentRepository; @MockBean private CommentQueryService commentQueryService;
@MockBean
private CommentQueryService commentQueryService;
private Article article; private Article article;
private CommentData commentData; private CommentData commentData;
private Comment comment; private Comment comment;
@Autowired @Autowired private MockMvc mvc;
private MockMvc mvc;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
RestAssuredMockMvc.mockMvc(mvc); RestAssuredMockMvc.mockMvc(mvc);
super.setUp(); super.setUp();
article = new Article("title", "desc", "body", new String[]{"test", "java"}, user.getId()); article = new Article("title", "desc", "body", Arrays.asList("test", "java"), user.getId());
when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article)); when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article));
comment = new Comment("comment", user.getId(), article.getId()); comment = new Comment("comment", user.getId(), article.getId());
commentData = new CommentData( commentData =
new CommentData(
comment.getId(), comment.getId(),
comment.getBody(), comment.getBody(),
comment.getArticleId(), comment.getArticleId(),
comment.getCreatedAt(), comment.getCreatedAt(),
comment.getCreatedAt(), comment.getCreatedAt(),
new ProfileData(user.getId(), user.getUsername(), user.getBio(), user.getImage(), false)); new ProfileData(
} user.getId(), user.getUsername(), user.getBio(), user.getImage(), false));
}
@Test @Test
public void should_create_comment_success() throws Exception { public void should_create_comment_success() throws Exception {
Map<String, Object> param = new HashMap<String, Object>() {{ Map<String, Object> param =
put("comment", new HashMap<String, Object>() {{ new HashMap<String, Object>() {
put("body", "comment content"); {
}}); put(
}}; "comment",
new HashMap<String, Object>() {
{
put("body", "comment content");
}
});
}
};
when(commentQueryService.findById(anyString(), eq(user))).thenReturn(Optional.of(commentData)); when(commentQueryService.findById(anyString(), eq(user))).thenReturn(Optional.of(commentData));
given() given()
.contentType("application/json") .contentType("application/json")
.header("Authorization", "Token " + token) .header("Authorization", "Token " + token)
.body(param) .body(param)
.when() .when()
.post("/articles/{slug}/comments", article.getSlug()) .post("/articles/{slug}/comments", article.getSlug())
.then() .then()
.statusCode(201) .statusCode(201)
.body("comment.body", equalTo(commentData.getBody())); .body("comment.body", equalTo(commentData.getBody()));
} }
@Test @Test
public void should_get_422_with_empty_body() throws Exception { public void should_get_422_with_empty_body() throws Exception {
Map<String, Object> param = new HashMap<String, Object>() {{ Map<String, Object> param =
put("comment", new HashMap<String, Object>() {{ new HashMap<String, Object>() {
put("body", ""); {
}}); put(
}}; "comment",
new HashMap<String, Object>() {
{
put("body", "");
}
});
}
};
given() given()
.contentType("application/json") .contentType("application/json")
.header("Authorization", "Token " + token) .header("Authorization", "Token " + token)
.body(param) .body(param)
.when() .when()
.post("/articles/{slug}/comments", article.getSlug()) .post("/articles/{slug}/comments", article.getSlug())
.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
public void should_get_comments_of_article_success() throws Exception {
when(commentQueryService.findByArticleId(anyString(), eq(null)))
.thenReturn(Arrays.asList(commentData));
RestAssuredMockMvc.when()
.get("/articles/{slug}/comments", article.getSlug())
.prettyPeek()
.then()
.statusCode(200)
.body("comments[0].id", equalTo(commentData.getId()));
}
@Test @Test
public void should_get_comments_of_article_success() throws Exception { public void should_delete_comment_success() throws Exception {
when(commentQueryService.findByArticleId(anyString(), eq(null))).thenReturn(Arrays.asList(commentData)); when(commentRepository.findById(eq(article.getId()), eq(comment.getId())))
RestAssuredMockMvc.when() .thenReturn(Optional.of(comment));
.get("/articles/{slug}/comments", article.getSlug())
.prettyPeek()
.then()
.statusCode(200)
.body("comments[0].id", equalTo(commentData.getId()));
}
@Test given()
public void should_delete_comment_success() throws Exception { .header("Authorization", "Token " + token)
when(commentRepository.findById(eq(article.getId()), eq(comment.getId()))).thenReturn(Optional.of(comment)); .when()
.delete("/articles/{slug}/comments/{id}", article.getSlug(), comment.getId())
.then()
.statusCode(204);
}
given() @Test
.header("Authorization", "Token " + token) public void should_get_403_if_not_author_of_article_or_author_of_comment_when_delete_comment()
.when() throws Exception {
.delete("/articles/{slug}/comments/{id}", article.getSlug(), comment.getId()) User anotherUser = new User("other@example.com", "other", "123", "", "");
.then() when(userRepository.findByUsername(eq(anotherUser.getUsername())))
.statusCode(204); .thenReturn(Optional.of(anotherUser));
} when(jwtService.getSubFromToken(any())).thenReturn(Optional.of(anotherUser.getId()));
when(userRepository.findById(eq(anotherUser.getId())))
.thenReturn(Optional.ofNullable(anotherUser));
@Test when(commentRepository.findById(eq(article.getId()), eq(comment.getId())))
public void should_get_403_if_not_author_of_article_or_author_of_comment_when_delete_comment() throws Exception { .thenReturn(Optional.of(comment));
User anotherUser = new User("other@example.com", "other", "123", "", ""); String token = jwtService.toToken(anotherUser);
when(userRepository.findByUsername(eq(anotherUser.getUsername()))).thenReturn(Optional.of(anotherUser)); when(userRepository.findById(eq(anotherUser.getId()))).thenReturn(Optional.of(anotherUser));
when(jwtService.getSubFromToken(any())).thenReturn(Optional.of(anotherUser.getId())); given()
when(userRepository.findById(eq(anotherUser.getId()))).thenReturn(Optional.ofNullable(anotherUser)); .header("Authorization", "Token " + token)
.when()
when(commentRepository.findById(eq(article.getId()), eq(comment.getId()))).thenReturn(Optional.of(comment)); .delete("/articles/{slug}/comments/{id}", article.getSlug(), comment.getId())
String token = jwtService.toToken(anotherUser); .then()
when(userRepository.findById(eq(anotherUser.getId()))).thenReturn(Optional.of(anotherUser)); .statusCode(403);
given() }
.header("Authorization", "Token " + token)
.when()
.delete("/articles/{slug}/comments/{id}", article.getSlug(), comment.getId())
.then()
.statusCode(403);
}
} }

View File

@ -10,7 +10,9 @@ 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;
import io.spring.application.UserQueryService; import io.spring.application.UserQueryService;
import io.spring.application.user.UserService;
import io.spring.core.user.User; import io.spring.core.user.User;
import io.spring.infrastructure.service.NaiveEncryptService;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -28,7 +30,8 @@ import org.springframework.test.web.servlet.MockMvc;
WebSecurityConfig.class, WebSecurityConfig.class,
JacksonCustomizations.class, JacksonCustomizations.class,
UserService.class, UserService.class,
ValidationAutoConfiguration.class ValidationAutoConfiguration.class,
NaiveEncryptService.class
}) })
public class CurrentUserApiTest extends TestWithCurrentUser { public class CurrentUserApiTest extends TestWithCurrentUser {

View File

@ -1,10 +1,17 @@
package io.spring.api; package io.spring.api;
import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
import static io.spring.TestHelper.articleDataFixture;
import static java.util.Arrays.asList;
import static org.mockito.ArgumentMatchers.eq;
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;
import io.spring.application.ArticleQueryService; import io.spring.application.ArticleQueryService;
import io.spring.application.Page; import io.spring.application.Page;
import io.spring.application.article.ArticleCommandService;
import io.spring.application.data.ArticleDataList; import io.spring.application.data.ArticleDataList;
import io.spring.core.article.ArticleRepository; import io.spring.core.article.ArticleRepository;
import org.junit.Before; import org.junit.Before;
@ -15,64 +22,54 @@ 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 static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
import static io.spring.TestHelper.articleDataFixture;
import static java.util.Arrays.asList;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@WebMvcTest(ArticlesApi.class) @WebMvcTest(ArticlesApi.class)
@Import({WebSecurityConfig.class, JacksonCustomizations.class}) @Import({WebSecurityConfig.class, JacksonCustomizations.class})
public class ListArticleApiTest extends TestWithCurrentUser { public class ListArticleApiTest extends TestWithCurrentUser {
@MockBean @MockBean private ArticleRepository articleRepository;
private ArticleRepository articleRepository;
@MockBean @MockBean private ArticleQueryService articleQueryService;
private ArticleQueryService articleQueryService;
@Autowired @MockBean private ArticleCommandService articleCommandService;
private MockMvc mvc;
@Override @Autowired private MockMvc mvc;
@Before
public void setUp() throws Exception {
super.setUp();
RestAssuredMockMvc.mockMvc(mvc);
}
@Test @Override
public void should_get_default_article_list() throws Exception { @Before
ArticleDataList articleDataList = new ArticleDataList( public void setUp() throws Exception {
super.setUp();
RestAssuredMockMvc.mockMvc(mvc);
}
@Test
public void should_get_default_article_list() throws Exception {
ArticleDataList articleDataList =
new ArticleDataList(
asList(articleDataFixture("1", user), articleDataFixture("2", user)), 2); asList(articleDataFixture("1", user), articleDataFixture("2", user)), 2);
when(articleQueryService.findRecentArticles(eq(null), eq(null), eq(null), eq(new Page(0, 20)), eq(null))).thenReturn(articleDataList); when(articleQueryService.findRecentArticles(
RestAssuredMockMvc.when() eq(null), eq(null), eq(null), eq(new Page(0, 20)), eq(null)))
.get("/articles") .thenReturn(articleDataList);
.prettyPeek() RestAssuredMockMvc.when().get("/articles").prettyPeek().then().statusCode(200);
.then() }
.statusCode(200);
}
@Test @Test
public void should_get_feeds_401_without_login() throws Exception { public void should_get_feeds_401_without_login() throws Exception {
RestAssuredMockMvc.when() RestAssuredMockMvc.when().get("/articles/feed").prettyPeek().then().statusCode(401);
.get("/articles/feed") }
.prettyPeek()
.then()
.statusCode(401);
}
@Test @Test
public void should_get_feeds_success() throws Exception { public void should_get_feeds_success() throws Exception {
ArticleDataList articleDataList = new ArticleDataList( ArticleDataList articleDataList =
new ArticleDataList(
asList(articleDataFixture("1", user), articleDataFixture("2", user)), 2); asList(articleDataFixture("1", user), articleDataFixture("2", user)), 2);
when(articleQueryService.findUserFeed(eq(user), eq(new Page(0, 20)))).thenReturn(articleDataList); when(articleQueryService.findUserFeed(eq(user), eq(new Page(0, 20))))
.thenReturn(articleDataList);
given() given()
.header("Authorization", "Token " + token) .header("Authorization", "Token " + token)
.when() .when()
.get("/articles/feed") .get("/articles/feed")
.prettyPeek() .prettyPeek()
.then() .then()
.statusCode(200); .statusCode(200);
} }
} }

View File

@ -12,6 +12,7 @@ import io.spring.JacksonCustomizations;
import io.spring.api.security.WebSecurityConfig; import io.spring.api.security.WebSecurityConfig;
import io.spring.application.UserQueryService; import io.spring.application.UserQueryService;
import io.spring.application.data.UserData; import io.spring.application.data.UserData;
import io.spring.application.user.UserService;
import io.spring.core.service.JwtService; import io.spring.core.service.JwtService;
import io.spring.core.user.User; import io.spring.core.user.User;
import io.spring.core.user.UserRepository; import io.spring.core.user.UserRepository;
@ -46,6 +47,9 @@ public class UsersApiTest {
@MockBean private JwtService jwtService; @MockBean private JwtService jwtService;
@MockBean private UserReadService userReadService; @MockBean private UserReadService userReadService;
@MockBean private UserService userService;
private String defaultAvatar; private String defaultAvatar;
@Before @Before
@ -64,6 +68,8 @@ public class UsersApiTest {
UserData userData = new UserData(user.getId(), email, username, "", defaultAvatar); UserData userData = new UserData(user.getId(), email, username, "", defaultAvatar);
when(userReadService.findById(any())).thenReturn(userData); when(userReadService.findById(any())).thenReturn(userData);
when(userService.createUser(any())).thenReturn(user);
when(userRepository.findByUsername(eq(username))).thenReturn(Optional.empty()); when(userRepository.findByUsername(eq(username))).thenReturn(Optional.empty());
when(userRepository.findByEmail(eq(email))).thenReturn(Optional.empty()); when(userRepository.findByEmail(eq(email))).thenReturn(Optional.empty());
@ -82,7 +88,7 @@ public class UsersApiTest {
.body("user.image", equalTo(defaultAvatar)) .body("user.image", equalTo(defaultAvatar))
.body("user.token", equalTo("123")); .body("user.token", equalTo("123"));
verify(userRepository).save(any()); verify(userService).createUser(any());
} }
@Test @Test

View File

@ -1,5 +1,10 @@
package io.spring.application.article; package io.spring.application.article;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import io.spring.application.ArticleQueryService; import io.spring.application.ArticleQueryService;
import io.spring.application.Page; import io.spring.application.Page;
import io.spring.application.data.ArticleData; import io.spring.application.data.ArticleData;
@ -14,6 +19,8 @@ import io.spring.core.user.UserRepository;
import io.spring.infrastructure.repository.MyBatisArticleFavoriteRepository; import io.spring.infrastructure.repository.MyBatisArticleFavoriteRepository;
import io.spring.infrastructure.repository.MyBatisArticleRepository; import io.spring.infrastructure.repository.MyBatisArticleRepository;
import io.spring.infrastructure.repository.MyBatisUserRepository; import io.spring.infrastructure.repository.MyBatisUserRepository;
import java.util.Arrays;
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;
@ -23,161 +30,170 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import java.util.Optional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@MybatisTest @MybatisTest
@Import({ @Import({
ArticleQueryService.class, ArticleQueryService.class,
MyBatisUserRepository.class, MyBatisUserRepository.class,
MyBatisArticleRepository.class, MyBatisArticleRepository.class,
MyBatisArticleFavoriteRepository.class}) MyBatisArticleFavoriteRepository.class
})
public class ArticleQueryServiceTest { public class ArticleQueryServiceTest {
@Autowired @Autowired private ArticleQueryService queryService;
private ArticleQueryService queryService;
@Autowired @Autowired private ArticleRepository articleRepository;
private ArticleRepository articleRepository;
@Autowired @Autowired private UserRepository userRepository;
private UserRepository userRepository;
@Autowired @Autowired private ArticleFavoriteRepository articleFavoriteRepository;
private ArticleFavoriteRepository articleFavoriteRepository;
private User user; private User user;
private Article article; private Article article;
@Before @Before
public void setUp() { public void setUp() {
user = new User("aisensiy@gmail.com", "aisensiy", "123", "", ""); user = new User("aisensiy@gmail.com", "aisensiy", "123", "", "");
userRepository.save(user); userRepository.save(user);
article = new Article("test", "desc", "body", new String[]{"java", "spring"}, user.getId(), new DateTime()); article =
articleRepository.save(article); new Article(
} "test", "desc", "body", Arrays.asList("java", "spring"), user.getId(), new DateTime());
articleRepository.save(article);
}
@Test @Test
public void should_fetch_article_success() { public void should_fetch_article_success() {
Optional<ArticleData> optional = queryService.findById(article.getId(), user); Optional<ArticleData> optional = queryService.findById(article.getId(), user);
assertTrue(optional.isPresent()); assertTrue(optional.isPresent());
ArticleData fetched = optional.get(); ArticleData fetched = optional.get();
assertEquals(fetched.getFavoritesCount(),0); assertEquals(fetched.getFavoritesCount(), 0);
assertFalse(fetched.isFavorited()); assertFalse(fetched.isFavorited());
assertNotNull(fetched.getCreatedAt()); assertNotNull(fetched.getCreatedAt());
assertNotNull(fetched.getUpdatedAt()); assertNotNull(fetched.getUpdatedAt());
assertTrue(fetched.getTagList().contains("java")); assertTrue(fetched.getTagList().contains("java"));
} }
@Test @Test
public void should_get_article_with_right_favorite_and_favorite_count() { public void should_get_article_with_right_favorite_and_favorite_count() {
User anotherUser = new User("other@test.com", "other", "123", "", ""); User anotherUser = new User("other@test.com", "other", "123", "", "");
userRepository.save(anotherUser); userRepository.save(anotherUser);
articleFavoriteRepository.save(new ArticleFavorite(article.getId(), anotherUser.getId())); articleFavoriteRepository.save(new ArticleFavorite(article.getId(), anotherUser.getId()));
Optional<ArticleData> optional = queryService.findById(article.getId(), anotherUser); Optional<ArticleData> optional = queryService.findById(article.getId(), anotherUser);
assertTrue(optional.isPresent()); assertTrue(optional.isPresent());
ArticleData articleData = optional.get(); ArticleData articleData = optional.get();
assertEquals(articleData.getFavoritesCount(), 1); assertEquals(articleData.getFavoritesCount(), 1);
assertTrue(articleData.isFavorited()); assertTrue(articleData.isFavorited());
} }
@Test @Test
public void should_get_default_article_list() { public void should_get_default_article_list() {
Article anotherArticle = new Article("new article", "desc", "body", new String[]{"test"}, user.getId(), new DateTime().minusHours(1)); Article anotherArticle =
articleRepository.save(anotherArticle); new Article(
"new article",
"desc",
"body",
Arrays.asList("test"),
user.getId(),
new DateTime().minusHours(1));
articleRepository.save(anotherArticle);
ArticleDataList recentArticles = queryService.findRecentArticles(null, null, null, new Page(), user); ArticleDataList recentArticles =
assertEquals(recentArticles.getCount(), 2); queryService.findRecentArticles(null, null, null, new Page(), user);
assertEquals(recentArticles.getArticleDatas().size(), 2); assertEquals(recentArticles.getCount(), 2);
assertEquals(recentArticles.getArticleDatas().get(0).getId(), article.getId()); assertEquals(recentArticles.getArticleDatas().size(), 2);
assertEquals(recentArticles.getArticleDatas().get(0).getId(), article.getId());
ArticleDataList nodata = queryService.findRecentArticles(null, null, null, new Page(2, 10), user); ArticleDataList nodata =
assertEquals(nodata.getCount(),2); queryService.findRecentArticles(null, null, null, new Page(2, 10), user);
assertEquals(nodata.getArticleDatas().size(), 0); assertEquals(nodata.getCount(), 2);
} assertEquals(nodata.getArticleDatas().size(), 0);
}
@Test @Test
public void should_query_article_by_author() { public void should_query_article_by_author() {
User anotherUser = new User("other@email.com", "other", "123", "", ""); User anotherUser = new User("other@email.com", "other", "123", "", "");
userRepository.save(anotherUser); userRepository.save(anotherUser);
Article anotherArticle = new Article("new article", "desc", "body", new String[]{"test"}, anotherUser.getId()); Article anotherArticle =
articleRepository.save(anotherArticle); new Article("new article", "desc", "body", Arrays.asList("test"), anotherUser.getId());
articleRepository.save(anotherArticle);
ArticleDataList recentArticles = queryService.findRecentArticles(null, user.getUsername(), null, new Page(), user); ArticleDataList recentArticles =
assertEquals(recentArticles.getArticleDatas().size(), 1); queryService.findRecentArticles(null, user.getUsername(), null, new Page(), user);
assertEquals(recentArticles.getCount(), 1); assertEquals(recentArticles.getArticleDatas().size(), 1);
} assertEquals(recentArticles.getCount(), 1);
}
@Test @Test
public void should_query_article_by_favorite() { public void should_query_article_by_favorite() {
User anotherUser = new User("other@email.com", "other", "123", "", ""); User anotherUser = new User("other@email.com", "other", "123", "", "");
userRepository.save(anotherUser); userRepository.save(anotherUser);
Article anotherArticle = new Article("new article", "desc", "body", new String[]{"test"}, anotherUser.getId()); Article anotherArticle =
articleRepository.save(anotherArticle); new Article("new article", "desc", "body", Arrays.asList("test"), anotherUser.getId());
articleRepository.save(anotherArticle);
ArticleFavorite articleFavorite = new ArticleFavorite(article.getId(), anotherUser.getId()); ArticleFavorite articleFavorite = new ArticleFavorite(article.getId(), anotherUser.getId());
articleFavoriteRepository.save(articleFavorite); articleFavoriteRepository.save(articleFavorite);
ArticleDataList recentArticles = queryService.findRecentArticles(null, null, anotherUser.getUsername(), new Page(), anotherUser); ArticleDataList recentArticles =
assertEquals(recentArticles.getArticleDatas().size(), 1); queryService.findRecentArticles(
assertEquals(recentArticles.getCount(), 1); null, null, anotherUser.getUsername(), new Page(), anotherUser);
ArticleData articleData = recentArticles.getArticleDatas().get(0); assertEquals(recentArticles.getArticleDatas().size(), 1);
assertEquals(articleData.getId(), article.getId()); assertEquals(recentArticles.getCount(), 1);
assertEquals(articleData.getFavoritesCount(),1); ArticleData articleData = recentArticles.getArticleDatas().get(0);
assertTrue(articleData.isFavorited()); assertEquals(articleData.getId(), article.getId());
} assertEquals(articleData.getFavoritesCount(), 1);
assertTrue(articleData.isFavorited());
}
@Test @Test
public void should_query_article_by_tag() { public void should_query_article_by_tag() {
Article anotherArticle = new Article("new article", "desc", "body", new String[]{"test"}, user.getId()); Article anotherArticle =
articleRepository.save(anotherArticle); new Article("new article", "desc", "body", Arrays.asList("test"), user.getId());
articleRepository.save(anotherArticle);
ArticleDataList recentArticles = queryService.findRecentArticles("spring", null, null, new Page(), user); ArticleDataList recentArticles =
assertEquals(recentArticles.getArticleDatas().size(), 1); queryService.findRecentArticles("spring", null, null, new Page(), user);
assertEquals(recentArticles.getCount(), 1); assertEquals(recentArticles.getArticleDatas().size(), 1);
assertEquals(recentArticles.getArticleDatas().get(0).getId(), article.getId()); assertEquals(recentArticles.getCount(), 1);
assertEquals(recentArticles.getArticleDatas().get(0).getId(), article.getId());
ArticleDataList notag = queryService.findRecentArticles("notag", null, null, new Page(), user); ArticleDataList notag = queryService.findRecentArticles("notag", null, null, new Page(), user);
assertEquals(notag.getCount(), 0); assertEquals(notag.getCount(), 0);
} }
@Test @Test
public void should_show_following_if_user_followed_author() { public void should_show_following_if_user_followed_author() {
User anotherUser = new User("other@email.com", "other", "123", "", ""); User anotherUser = new User("other@email.com", "other", "123", "", "");
userRepository.save(anotherUser); userRepository.save(anotherUser);
FollowRelation followRelation = new FollowRelation(anotherUser.getId(), user.getId()); FollowRelation followRelation = new FollowRelation(anotherUser.getId(), user.getId());
userRepository.saveRelation(followRelation); userRepository.saveRelation(followRelation);
ArticleDataList recentArticles = queryService.findRecentArticles(null, null, null, new Page(), anotherUser); ArticleDataList recentArticles =
assertEquals(recentArticles.getCount(), 1); queryService.findRecentArticles(null, null, null, new Page(), anotherUser);
ArticleData articleData = recentArticles.getArticleDatas().get(0); assertEquals(recentArticles.getCount(), 1);
assertTrue(articleData.getProfileData().isFollowing()); ArticleData articleData = recentArticles.getArticleDatas().get(0);
} assertTrue(articleData.getProfileData().isFollowing());
}
@Test @Test
public void should_get_user_feed() { public void should_get_user_feed() {
User anotherUser = new User("other@email.com", "other", "123", "", ""); User anotherUser = new User("other@email.com", "other", "123", "", "");
userRepository.save(anotherUser); userRepository.save(anotherUser);
FollowRelation followRelation = new FollowRelation(anotherUser.getId(), user.getId()); FollowRelation followRelation = new FollowRelation(anotherUser.getId(), user.getId());
userRepository.saveRelation(followRelation); userRepository.saveRelation(followRelation);
ArticleDataList userFeed = queryService.findUserFeed(user, new Page()); ArticleDataList userFeed = queryService.findUserFeed(user, new Page());
assertEquals(userFeed.getCount(), 0); assertEquals(userFeed.getCount(), 0);
ArticleDataList anotherUserFeed = queryService.findUserFeed(anotherUser, new Page()); ArticleDataList anotherUserFeed = queryService.findUserFeed(anotherUser, new Page());
assertEquals(anotherUserFeed.getCount(), 1); assertEquals(anotherUserFeed.getCount(), 1);
ArticleData articleData = anotherUserFeed.getArticleDatas().get(0); ArticleData articleData = anotherUserFeed.getArticleDatas().get(0);
assertTrue(articleData.getProfileData().isFollowing()); assertTrue(articleData.getProfileData().isFollowing());
} }
} }

View File

@ -1,5 +1,8 @@
package io.spring.application.comment; package io.spring.application.comment;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import io.spring.application.CommentQueryService; import io.spring.application.CommentQueryService;
import io.spring.application.data.CommentData; import io.spring.application.data.CommentData;
import io.spring.core.article.Article; import io.spring.core.article.Article;
@ -12,6 +15,9 @@ import io.spring.core.user.UserRepository;
import io.spring.infrastructure.repository.MyBatisArticleRepository; import io.spring.infrastructure.repository.MyBatisArticleRepository;
import io.spring.infrastructure.repository.MyBatisCommentRepository; import io.spring.infrastructure.repository.MyBatisCommentRepository;
import io.spring.infrastructure.repository.MyBatisUserRepository; import io.spring.infrastructure.repository.MyBatisUserRepository;
import java.util.Arrays;
import java.util.List;
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,63 +26,57 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import java.util.Optional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@MybatisTest @MybatisTest
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@Import({MyBatisCommentRepository.class, MyBatisUserRepository.class, CommentQueryService.class, MyBatisArticleRepository.class}) @Import({
MyBatisCommentRepository.class,
MyBatisUserRepository.class,
CommentQueryService.class,
MyBatisArticleRepository.class
})
public class CommentQueryServiceTest { public class CommentQueryServiceTest {
@Autowired @Autowired private CommentRepository commentRepository;
private CommentRepository commentRepository;
@Autowired @Autowired private UserRepository userRepository;
private UserRepository userRepository;
@Autowired @Autowired private CommentQueryService commentQueryService;
private CommentQueryService commentQueryService;
@Autowired @Autowired private ArticleRepository articleRepository;
private ArticleRepository articleRepository;
private User user; private User user;
@Before @Before
public void setUp() { public void setUp() {
user = new User("aisensiy@test.com", "aisensiy", "123", "", ""); user = new User("aisensiy@test.com", "aisensiy", "123", "", "");
userRepository.save(user); userRepository.save(user);
} }
@Test @Test
public void should_read_comment_success() { public void should_read_comment_success() {
Comment comment = new Comment("content", user.getId(), "123"); Comment comment = new Comment("content", user.getId(), "123");
commentRepository.save(comment); commentRepository.save(comment);
Optional<CommentData> optional = commentQueryService.findById(comment.getId(), user); Optional<CommentData> optional = commentQueryService.findById(comment.getId(), user);
assertTrue(optional.isPresent()); assertTrue(optional.isPresent());
CommentData commentData = optional.get(); CommentData commentData = optional.get();
assertEquals(commentData.getProfileData().getUsername(), user.getUsername()); assertEquals(commentData.getProfileData().getUsername(), user.getUsername());
} }
@Test @Test
public void should_read_comments_of_article() { public void should_read_comments_of_article() {
Article article = new Article("title", "desc", "body", new String[]{"java"}, user.getId()); Article article = new Article("title", "desc", "body", Arrays.asList("java"), user.getId());
articleRepository.save(article); articleRepository.save(article);
User user2 = new User("user2@email.com", "user2", "123", "", ""); User user2 = new User("user2@email.com", "user2", "123", "", "");
userRepository.save(user2); userRepository.save(user2);
userRepository.saveRelation(new FollowRelation(user.getId(), user2.getId())); userRepository.saveRelation(new FollowRelation(user.getId(), user2.getId()));
Comment comment1 = new Comment("content1", user.getId(), article.getId()); Comment comment1 = new Comment("content1", user.getId(), article.getId());
commentRepository.save(comment1); commentRepository.save(comment1);
Comment comment2 = new Comment("content2", user2.getId(), article.getId()); Comment comment2 = new Comment("content2", user2.getId(), article.getId());
commentRepository.save(comment2); commentRepository.save(comment2);
List<CommentData> comments = commentQueryService.findByArticleId(article.getId(), user); List<CommentData> comments = commentQueryService.findByArticleId(article.getId(), user);
assertEquals(comments.size(), 2); assertEquals(comments.size(), 2);
}
}
} }

View File

@ -1,9 +1,12 @@
package io.spring.application.tag; package io.spring.application.tag;
import static org.junit.Assert.assertTrue;
import io.spring.application.TagsQueryService; import io.spring.application.TagsQueryService;
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.infrastructure.repository.MyBatisArticleRepository; import io.spring.infrastructure.repository.MyBatisArticleRepository;
import java.util.Arrays;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest; import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
@ -11,21 +14,17 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.assertTrue;
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@MybatisTest @MybatisTest
@Import({TagsQueryService.class, MyBatisArticleRepository.class}) @Import({TagsQueryService.class, MyBatisArticleRepository.class})
public class TagsQueryServiceTest { public class TagsQueryServiceTest {
@Autowired @Autowired private TagsQueryService tagsQueryService;
private TagsQueryService tagsQueryService;
@Autowired @Autowired private ArticleRepository articleRepository;
private ArticleRepository articleRepository;
@Test @Test
public void should_get_all_tags() { public void should_get_all_tags() {
articleRepository.save(new Article("test", "test", "test", new String[]{"java"}, "123")); articleRepository.save(new Article("test", "test", "test", Arrays.asList("java"), "123"));
assertTrue(tagsQueryService.allTags().contains("java")); assertTrue(tagsQueryService.allTags().contains("java"));
} }
} }

View File

@ -1,39 +1,40 @@
package io.spring.core.article; package io.spring.core.article;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import java.util.Arrays;
import org.junit.Test;
public class ArticleTest { public class ArticleTest {
@Test @Test
public void should_get_right_slug() { public void should_get_right_slug() {
Article article = new Article("a new title", "desc", "body", new String[]{"java"}, "123"); Article article = new Article("a new title", "desc", "body", Arrays.asList("java"), "123");
assertThat(article.getSlug(), is("a-new-title")); assertThat(article.getSlug(), is("a-new-title"));
} }
@Test @Test
public void should_get_right_slug_with_number_in_title() { public void should_get_right_slug_with_number_in_title() {
Article article = new Article("a new title 2", "desc", "body", new String[]{"java"}, "123"); Article article = new Article("a new title 2", "desc", "body", Arrays.asList("java"), "123");
assertThat(article.getSlug(), is("a-new-title-2")); assertThat(article.getSlug(), is("a-new-title-2"));
} }
@Test @Test
public void should_get_lower_case_slug() { public void should_get_lower_case_slug() {
Article article = new Article("A NEW TITLE", "desc", "body", new String[]{"java"}, "123"); Article article = new Article("A NEW TITLE", "desc", "body", Arrays.asList("java"), "123");
assertThat(article.getSlug(), is("a-new-title")); assertThat(article.getSlug(), is("a-new-title"));
} }
@Test @Test
public void should_handle_other_language() { public void should_handle_other_language() {
Article article = new Article("中文:标题", "desc", "body", new String[]{"java"}, "123"); Article article = new Article("中文:标题", "desc", "body", Arrays.asList("java"), "123");
assertThat(article.getSlug(), is("中文-标题")); assertThat(article.getSlug(), is("中文-标题"));
} }
@Test @Test
public void should_handle_commas() { public void should_handle_commas() {
Article article = new Article("what?the.hell,w", "desc", "body", new String[]{"java"}, "123"); Article article = new Article("what?the.hell,w", "desc", "body", Arrays.asList("java"), "123");
assertThat(article.getSlug(), is("what-the-hell-w")); assertThat(article.getSlug(), is("what-the-hell-w"));
} }
} }

View File

@ -1,10 +1,13 @@
package io.spring.infrastructure.article; package io.spring.infrastructure.article;
import static org.junit.Assert.assertNull;
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 io.spring.core.user.UserRepository; import io.spring.core.user.UserRepository;
import io.spring.infrastructure.mybatis.mapper.ArticleMapper; import io.spring.infrastructure.mybatis.mapper.ArticleMapper;
import java.util.Arrays;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -12,35 +15,29 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabas
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest @SpringBootTest
@AutoConfigureTestDatabase @AutoConfigureTestDatabase
public class ArticleRepositoryTransactionTest { public class ArticleRepositoryTransactionTest {
@Autowired @Autowired private ArticleRepository articleRepository;
private ArticleRepository articleRepository;
@Autowired @Autowired private UserRepository userRepository;
private UserRepository userRepository;
@Autowired @Autowired private ArticleMapper articleMapper;
private ArticleMapper articleMapper;
@Test
@Test public void transactional_test() {
public void transactional_test() { User user = new User("aisensiy@gmail.com", "aisensiy", "123", "bio", "default");
User user = new User("aisensiy@gmail.com", "aisensiy", "123", "bio", "default"); userRepository.save(user);
userRepository.save(user); Article article =
Article article = new Article("test", "desc", "body", new String[]{"java", "spring"}, user.getId()); new Article("test", "desc", "body", Arrays.asList("java", "spring"), user.getId());
articleRepository.save(article); articleRepository.save(article);
Article anotherArticle = new Article("test", "desc", "body", new String[]{"java", "spring", "other"}, user.getId()); Article anotherArticle =
try { new Article("test", "desc", "body", Arrays.asList("java", "spring", "other"), user.getId());
articleRepository.save(anotherArticle); try {
} catch (Exception e) { articleRepository.save(anotherArticle);
assertNull(articleMapper.findTag("other")); } catch (Exception e) {
} assertNull(articleMapper.findTag("other"));
} }
}
} }

View File

@ -1,5 +1,10 @@
package io.spring.infrastructure.article; package io.spring.infrastructure.article;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
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.article.Tag; import io.spring.core.article.Tag;
@ -7,6 +12,8 @@ import io.spring.core.user.User;
import io.spring.core.user.UserRepository; import io.spring.core.user.UserRepository;
import io.spring.infrastructure.repository.MyBatisArticleRepository; import io.spring.infrastructure.repository.MyBatisArticleRepository;
import io.spring.infrastructure.repository.MyBatisUserRepository; import io.spring.infrastructure.repository.MyBatisUserRepository;
import java.util.Arrays;
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;
@ -15,63 +22,53 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import java.util.Optional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
@MybatisTest @MybatisTest
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@Import({MyBatisArticleRepository.class, MyBatisUserRepository.class}) @Import({MyBatisArticleRepository.class, MyBatisUserRepository.class})
public class MyBatisArticleRepositoryTest { public class MyBatisArticleRepositoryTest {
@Autowired @Autowired private ArticleRepository articleRepository;
private ArticleRepository articleRepository;
@Autowired @Autowired private UserRepository userRepository;
private UserRepository userRepository;
private Article article; private Article article;
@Before
public void setUp() {
User user = new User("aisensiy@gmail.com", "aisensiy", "123", "bio", "default");
userRepository.save(user);
article = new Article("test", "desc", "body", Arrays.asList("java", "spring"), user.getId());
}
@Before @Test
public void setUp() { public void should_create_and_fetch_article_success() {
User user = new User("aisensiy@gmail.com", "aisensiy", "123", "bio", "default"); articleRepository.save(article);
userRepository.save(user); Optional<Article> optional = articleRepository.findById(article.getId());
article = new Article("test", "desc", "body", new String[]{"java", "spring"}, user.getId()); assertTrue(optional.isPresent());
} assertEquals(optional.get(), article);
assertTrue(optional.get().getTags().contains(new Tag("java")));
assertTrue(optional.get().getTags().contains(new Tag("spring")));
}
@Test @Test
public void should_create_and_fetch_article_success() { public void should_update_and_fetch_article_success() {
articleRepository.save(article); articleRepository.save(article);
Optional<Article> optional = articleRepository.findById(article.getId());
assertTrue(optional.isPresent());
assertEquals(optional.get(), article);
assertTrue(optional.get().getTags().contains(new Tag("java")));
assertTrue(optional.get().getTags().contains(new Tag("spring")));
}
@Test String newTitle = "new test 2";
public void should_update_and_fetch_article_success() { article.update(newTitle, "", "");
articleRepository.save(article); articleRepository.save(article);
System.out.println(article.getSlug());
Optional<Article> optional = articleRepository.findBySlug(article.getSlug());
assertTrue(optional.isPresent());
Article fetched = optional.get();
assertEquals(fetched.getTitle(), newTitle);
assertNotEquals(fetched.getBody(), "");
}
String newTitle = "new test 2"; @Test
article.update(newTitle, "", ""); public void should_delete_article() {
articleRepository.save(article); articleRepository.save(article);
System.out.println(article.getSlug());
Optional<Article> optional = articleRepository.findBySlug(article.getSlug());
assertTrue(optional.isPresent());
Article fetched = optional.get();
assertEquals(fetched.getTitle(), newTitle);
assertNotEquals(fetched.getBody(), "");
}
@Test articleRepository.remove(article);
public void should_delete_article() { assertFalse(articleRepository.findById(article.getId()).isPresent());
articleRepository.save(article); }
articleRepository.remove(article);
assertFalse(articleRepository.findById(article.getId()).isPresent());
}
} }