diff --git a/.gitignore b/.gitignore index a8660af..39420aa 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ build/ ### images ### **/src/main/resources/static/ -**/src/test/resources/images/ ### STS ### .apt_generated diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/AuthService.java b/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/AuthService.java index fce0a64..ae90b73 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/AuthService.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/AuthService.java @@ -7,6 +7,8 @@ import com.inq.wishhair.wesharewishhair.auth.application.dto.response.LoginResponse; import com.inq.wishhair.wesharewishhair.auth.domain.AuthToken; import com.inq.wishhair.wesharewishhair.auth.domain.AuthTokenManager; +import com.inq.wishhair.wesharewishhair.auth.domain.TokenRepository; +import com.inq.wishhair.wesharewishhair.auth.domain.entity.Token; import com.inq.wishhair.wesharewishhair.global.exception.ErrorCode; import com.inq.wishhair.wesharewishhair.global.exception.WishHairException; import com.inq.wishhair.wesharewishhair.user.domain.entity.Email; @@ -20,26 +22,39 @@ @Transactional(readOnly = true) public class AuthService { - private final TokenManager tokenManager; private final UserRepository userRepository; + private final TokenRepository tokenRepository; private final PasswordEncoder passwordEncoder; private final AuthTokenManager authTokenManager; @Transactional - public LoginResponse login(String email, String pw) { + public LoginResponse login( + final String email, + final String pw + ) { User user = userRepository.findByEmail(new Email(email)) .filter(findUser -> passwordEncoder.matches(pw, findUser.getPasswordValue())) .orElseThrow(() -> new WishHairException(ErrorCode.LOGIN_FAIL)); AuthToken authToken = authTokenManager.generate(user.getId()); - - tokenManager.synchronizeRefreshToken(user.getId(), authToken.refreshToken()); + synchronizeRefreshToken(user, authToken); return new LoginResponse(authToken.accessToken(), authToken.refreshToken()); } @Transactional - public void logout(Long userId) { - tokenManager.deleteToken(userId); + public void logout(final Long userId) { + tokenRepository.deleteByUserId(userId); + } + + private void synchronizeRefreshToken( + final User user, + final AuthToken authToken + ) { + tokenRepository.findByUserId(user.getId()) + .ifPresentOrElse( + token -> token.updateRefreshToken(authToken.refreshToken()), + () -> tokenRepository.save(Token.issue(user.getId(), authToken.refreshToken())) + ); } } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/MailAuthService.java b/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/MailAuthService.java index 93a613a..1af88ad 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/MailAuthService.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/MailAuthService.java @@ -38,12 +38,10 @@ public void requestMailAuthorization(final String email) { public void checkAuthCode( final String email, - final String authCode + final String code ) { authCodeRepository.findByEmail(email) - .filter(actualAuthCode -> actualAuthCode.getCode().equals(authCode)) + .filter(authCode -> authCode.getCode().equals(code)) .orElseThrow(() -> new WishHairException(AUTH_INVALID_AUTH_CODE)); - - authCodeRepository.deleteByEmail(email); } } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/TokenManager.java b/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/TokenManager.java deleted file mode 100644 index 093c844..0000000 --- a/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/TokenManager.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.inq.wishhair.wesharewishhair.auth.application; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.inq.wishhair.wesharewishhair.auth.domain.entity.Token; -import com.inq.wishhair.wesharewishhair.auth.domain.TokenRepository; - -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class TokenManager { - - private final TokenRepository tokenRepository; - - @Transactional - public void synchronizeRefreshToken(Long userId, String refreshToken) { - tokenRepository.findByUserId(userId) - .ifPresentOrElse( - token -> token.updateRefreshToken(refreshToken), - () -> tokenRepository.save(Token.issue(userId, refreshToken)) - ); - } - - public boolean existByUserIdAndRefreshToken(Long userId, String refreshToken) { - return tokenRepository - .findByUserIdAndRefreshToken(userId, refreshToken) - .isPresent(); - } - - @Transactional - public void deleteToken(Long userId) { - tokenRepository.deleteByUserId(userId); - } - - @Transactional - public void updateRefreshToken(Long userId, String refreshToken) { - tokenRepository.updateRefreshTokenByUserId(userId, refreshToken); - } -} diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/TokenReissueService.java b/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/TokenReissueService.java index b5d2c33..d50ad65 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/TokenReissueService.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/auth/application/TokenReissueService.java @@ -6,6 +6,7 @@ import com.inq.wishhair.wesharewishhair.auth.application.dto.response.TokenResponse; import com.inq.wishhair.wesharewishhair.auth.domain.AuthToken; import com.inq.wishhair.wesharewishhair.auth.domain.AuthTokenManager; +import com.inq.wishhair.wesharewishhair.auth.domain.TokenRepository; import com.inq.wishhair.wesharewishhair.global.exception.ErrorCode; import com.inq.wishhair.wesharewishhair.global.exception.WishHairException; @@ -15,20 +16,20 @@ @RequiredArgsConstructor public class TokenReissueService { - private final TokenManager tokenManager; + private final TokenRepository tokenRepository; private final AuthTokenManager authTokenManager; @Transactional public TokenResponse reissueToken(Long userId, String refreshToken) { //사용하지 않은 RTR 토큰인지, 존재하는지 확인 - if (!tokenManager.existByUserIdAndRefreshToken(userId, refreshToken)) { + if (!tokenRepository.existsByUserIdAndRefreshToken(userId, refreshToken)) { throw new WishHairException(ErrorCode.AUTH_INVALID_TOKEN); } AuthToken authToken = authTokenManager.generate(userId); - tokenManager.updateRefreshToken(userId, authToken.refreshToken()); + tokenRepository.updateRefreshTokenByUserId(userId, authToken.refreshToken()); return new TokenResponse(authToken.accessToken(), authToken.refreshToken()); } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/auth/domain/AuthCodeRepository.java b/src/main/java/com/inq/wishhair/wesharewishhair/auth/domain/AuthCodeRepository.java index 1e15f84..6a09b29 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/auth/domain/AuthCodeRepository.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/auth/domain/AuthCodeRepository.java @@ -8,7 +8,5 @@ public interface AuthCodeRepository { AuthCode save(AuthCode authCode); - void deleteByEmail(String email); - Optional findByEmail(String email); } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/auth/domain/TokenRepository.java b/src/main/java/com/inq/wishhair/wesharewishhair/auth/domain/TokenRepository.java index 6fe8ea4..5e6355e 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/auth/domain/TokenRepository.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/auth/domain/TokenRepository.java @@ -2,15 +2,15 @@ import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; - import com.inq.wishhair.wesharewishhair.auth.domain.entity.Token; -public interface TokenRepository extends JpaRepository { +public interface TokenRepository { + + Token save(Token token); Optional findByUserId(Long userId); - Optional findByUserIdAndRefreshToken(Long userId, String refreshToken); + boolean existsByUserIdAndRefreshToken(Long userId, String refreshToken); void deleteByUserId(Long userId); diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/AuthCodeJpaRepository.java b/src/main/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/AuthCodeJpaRepository.java index 82437cd..6b05b9c 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/AuthCodeJpaRepository.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/AuthCodeJpaRepository.java @@ -9,8 +9,7 @@ public interface AuthCodeJpaRepository extends AuthCodeRepository, JpaRepository { - @Override - void deleteByEmail(String email); + void deleteById(Long id); @Override Optional findByEmail(String email); diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/TokenJpaRepository.java b/src/main/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/TokenJpaRepository.java index ea40ffa..6727684 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/TokenJpaRepository.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/TokenJpaRepository.java @@ -14,11 +14,7 @@ public interface TokenJpaRepository extends TokenRepository, JpaRepository findByUserId(Long userId); - @Query("select t from Token t where t.userId = :userId " + - "and t.refreshToken = :refreshToken") - Optional findByUserIdAndRefreshToken( - @Param("userId") Long userId, - @Param("refreshToken") String refreshToken); + boolean existsByUserIdAndRefreshToken(Long userId, String refreshToken); @Modifying @Query("delete from Token t where t.userId = :userId") diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/jwt/JwtTokenProvider.java b/src/main/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/jwt/JwtTokenProvider.java index c801243..d986682 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/jwt/JwtTokenProvider.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/jwt/JwtTokenProvider.java @@ -38,12 +38,12 @@ public JwtTokenProvider( this.refreshTokenValidity = refreshTokenValidity; } - public String createAccessToken(final Long memberId) { - return createToken(memberId, accessTokenValidity); + public String createAccessToken(final Long userId) { + return createToken(userId, accessTokenValidity); } - public String createRefreshToken(final Long memberId) { - return createToken(memberId, refreshTokenValidity); + public String createRefreshToken(final Long userId) { + return createToken(userId, refreshTokenValidity); } public Long getPayload(final String token) { diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/global/config/SecurityConfig.java b/src/main/java/com/inq/wishhair/wesharewishhair/global/config/SecurityConfig.java index 395a94a..7248635 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/global/config/SecurityConfig.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/global/config/SecurityConfig.java @@ -2,8 +2,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; @Configuration public class SecurityConfig { @@ -12,4 +15,9 @@ public class SecurityConfig { public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http.csrf(AbstractHttpConfigurer::disable).build(); + } } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/global/config/WebConfig.java b/src/main/java/com/inq/wishhair/wesharewishhair/global/config/WebConfig.java index 518f446..59080f5 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/global/config/WebConfig.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/global/config/WebConfig.java @@ -4,7 +4,9 @@ import java.util.List; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.config.annotation.CorsRegistry; @@ -53,4 +55,9 @@ public void addInterceptors(final InterceptorRegistry registry) { public void addArgumentResolvers(final List resolvers) { resolvers.add(new AuthInfoArgumentResolver(authTokenManager)); } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/PathMatcherContainer.java b/src/main/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/PathMatcherContainer.java index 52711fb..be5d251 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/PathMatcherContainer.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/PathMatcherContainer.java @@ -3,6 +3,7 @@ import java.util.HashSet; import java.util.Set; +import org.springframework.http.HttpMethod; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; @@ -18,7 +19,7 @@ public PathMatcherContainer() { excludePath = new HashSet<>(); } - public boolean isInterceptorRequired(final String path, final String method) { + public boolean isInterceptorRequired(final String path, final HttpMethod method) { boolean isInclude = includePath.stream() .anyMatch(include -> matches(include, path, method)); @@ -28,27 +29,27 @@ public boolean isInterceptorRequired(final String path, final String method) { return isNotExclude && isInclude; } - public void includePathPattern(final String pathPattern, final String method) { + public void includePathPattern(final String pathPattern, final HttpMethod method) { includePath.add(new PathInfo(pathPattern, method)); } - public void excludePathPattern(final String pathPattern, final String method) { + public void excludePathPattern(final String pathPattern, final HttpMethod method) { excludePath.add(new PathInfo(pathPattern, method)); } private boolean matches( final PathInfo pathInfo, final String targetPath, - final String targetMethod + final HttpMethod targetMethod ) { - boolean match = pathMatcher.match(pathInfo.pathPattern, targetPath); - boolean equals = pathInfo.method.equals(targetMethod); - return match && equals; + boolean pathMatch = pathMatcher.match(pathInfo.pathPattern, targetPath); + boolean methodMath = pathInfo.method.equals(targetMethod); + return pathMatch && methodMath; } private record PathInfo( String pathPattern, - String method + HttpMethod method ) { } } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/PathMatcherInterceptor.java b/src/main/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/PathMatcherInterceptor.java index 553f5c8..bbdec28 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/PathMatcherInterceptor.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/PathMatcherInterceptor.java @@ -24,35 +24,38 @@ public boolean preHandle( final HttpServletResponse response, final Object handler ) throws Exception { + boolean interceptorRequired = pathMatcherContainer.isInterceptorRequired( + request.getRequestURI(), HttpMethod.valueOf(request.getMethod()) + ); - if (pathMatcherContainer.isInterceptorRequired(request.getRequestURI(), request.getMethod())) { + if (interceptorRequired) { return target.preHandle(request, response, handler); } return true; } public PathMatcherInterceptor addIncludePathPattern(final String pathPattern, final HttpMethod method) { - pathMatcherContainer.includePathPattern(pathPattern, method.toString()); + pathMatcherContainer.includePathPattern(pathPattern, method); return this; } public PathMatcherInterceptor addIncludePathPattern(final String pathPattern) { Arrays.stream(HttpMethod.values()) .forEach(method -> - pathMatcherContainer.includePathPattern(pathPattern, method.toString())); + pathMatcherContainer.includePathPattern(pathPattern, method)); return this; } public PathMatcherInterceptor addExcludePathPattern(final String pathPattern, final HttpMethod method) { - pathMatcherContainer.excludePathPattern(pathPattern, method.toString()); + pathMatcherContainer.excludePathPattern(pathPattern, method); return this; } public PathMatcherInterceptor addExcludePathPattern(final String pathPattern) { Arrays.stream(HttpMethod.values()) .forEach(method -> - pathMatcherContainer.excludePathPattern(pathPattern, method.toString())); + pathMatcherContainer.excludePathPattern(pathPattern, method)); return this; } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/hairstyle/application/HairStyleSearchService.java b/src/main/java/com/inq/wishhair/wesharewishhair/hairstyle/application/HairStyleSearchService.java index c87a0b0..0c7160a 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/hairstyle/application/HairStyleSearchService.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/hairstyle/application/HairStyleSearchService.java @@ -40,7 +40,7 @@ public class HairStyleSearchService { //메인 추천 로직 public ResponseWrapper recommendHair(List tags, Long userId) { - User user = userFindService.findByUserId(userId); + User user = userFindService.getById(userId); validateUserHasFaceShapeTag(user); @@ -52,7 +52,7 @@ public ResponseWrapper recommendHair(List tags, Long use //홈 화면 사용자 맞춤 추천 로직 public ResponseWrapper recommendHairByFaceShape(Long userId) { - User user = userFindService.findByUserId(userId); + User user = userFindService.getById(userId); HairRecommendCondition condition = subRecommend(user.getFaceShape(), user.getSex()); List hairStyles = hairStyleQueryRepository.findByFaceShape(condition, getDefaultPageable()); diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/point/application/PointService.java b/src/main/java/com/inq/wishhair/wesharewishhair/point/application/PointService.java index 144a338..3f6d41b 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/point/application/PointService.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/point/application/PointService.java @@ -25,7 +25,7 @@ public class PointService { @Transactional public void usePoint(final PointUseRequest request, final Long userId) { - User user = userFindService.findByUserId(userId); + User user = userFindService.getById(userId); insertPointHistory(PointType.USE, request.dealAmount(), user); eventPublisher.publishEvent(request.toRefundMailEvent(user.getName())); @@ -33,7 +33,7 @@ public void usePoint(final PointUseRequest request, final Long userId) { @Transactional public void chargePoint(int dealAmount, Long userId) { - User user = userFindService.findByUserId(userId); + User user = userFindService.getById(userId); insertPointHistory(PointType.CHARGE, dealAmount, user); } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/review/application/ReviewService.java b/src/main/java/com/inq/wishhair/wesharewishhair/review/application/ReviewService.java index c28240e..22c3e8e 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/review/application/ReviewService.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/review/application/ReviewService.java @@ -41,7 +41,7 @@ public class ReviewService { public Long createReview(ReviewCreateRequest request, Long userId) { List photoUrls = photoService.uploadPhotos(request.getFiles()); - User user = userFindService.findByUserId(userId); + User user = userFindService.getById(userId); HairStyle hairStyle = hairStyleFindService.findById(request.getHairStyleId()); Review review = generateReview(request, photoUrls, user, hairStyle); diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/application/UserFindService.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/application/UserFindService.java index 6490576..cae8242 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/application/UserFindService.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/application/UserFindService.java @@ -18,12 +18,12 @@ public class UserFindService { private final UserRepository userRepository; - public User findByUserId(Long userId) { + public User getById(Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new WishHairException(ErrorCode.NOT_EXIST_KEY)); } - public User findByEmail(Email email) { + public User getByEmail(Email email) { return userRepository.findByEmail(email) .orElseThrow(() -> new WishHairException(ErrorCode.USER_NOT_FOUND_BY_EMAIL)); } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/application/UserInfoService.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/application/UserInfoService.java index 9b2d06b..baf8855 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/application/UserInfoService.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/application/UserInfoService.java @@ -34,7 +34,7 @@ public MyPageResponse getMyPageInfo(Long userId) { Pageable pageable = PageableGenerator.generateDateDescPageable(3); List reviewResponses = reviewSearchService.findLikingReviews(userId, pageable).getResult(); - User user = userFindService.findByUserId(userId); + User user = userFindService.getById(userId); int point = pointLogRepository.findByUserOrderByCreatedDateDesc(user) .map(PointLog::getPoint) @@ -44,10 +44,10 @@ public MyPageResponse getMyPageInfo(Long userId) { } public UserInformation getUserInformation(Long userId) { - return toUserInformation(userFindService.findByUserId(userId)); + return toUserInformation(userFindService.getById(userId)); } public UserInfo getUserInfo(Long userId) { - return new UserInfo(userFindService.findByUserId(userId)); + return new UserInfo(userFindService.getById(userId)); } } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/application/UserService.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/application/UserService.java index 2d008ed..be91b17 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/application/UserService.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/application/UserService.java @@ -1,47 +1,40 @@ package com.inq.wishhair.wesharewishhair.user.application; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import com.inq.wishhair.wesharewishhair.auth.domain.TokenRepository; import com.inq.wishhair.wesharewishhair.global.dto.response.SimpleResponseWrapper; -import com.inq.wishhair.wesharewishhair.global.exception.ErrorCode; -import com.inq.wishhair.wesharewishhair.global.exception.WishHairException; import com.inq.wishhair.wesharewishhair.hairstyle.domain.hashtag.Tag; +import com.inq.wishhair.wesharewishhair.point.domain.PointLogRepository; import com.inq.wishhair.wesharewishhair.review.application.ReviewService; +import com.inq.wishhair.wesharewishhair.user.application.utils.UserValidator; +import com.inq.wishhair.wesharewishhair.user.domain.AiConnector; +import com.inq.wishhair.wesharewishhair.user.domain.UserRepository; +import com.inq.wishhair.wesharewishhair.user.domain.entity.Email; +import com.inq.wishhair.wesharewishhair.user.domain.entity.Nickname; +import com.inq.wishhair.wesharewishhair.user.domain.entity.User; import com.inq.wishhair.wesharewishhair.user.presentation.dto.request.PasswordRefreshRequest; import com.inq.wishhair.wesharewishhair.user.presentation.dto.request.PasswordUpdateRequest; import com.inq.wishhair.wesharewishhair.user.presentation.dto.request.SignUpRequest; import com.inq.wishhair.wesharewishhair.user.presentation.dto.request.UserUpdateRequest; -import com.inq.wishhair.wesharewishhair.user.domain.entity.Email; -import com.inq.wishhair.wesharewishhair.user.domain.entity.FaceShape; -import com.inq.wishhair.wesharewishhair.user.domain.entity.Nickname; -import com.inq.wishhair.wesharewishhair.user.domain.entity.Password; -import com.inq.wishhair.wesharewishhair.user.domain.entity.User; -import com.inq.wishhair.wesharewishhair.user.domain.UserRepository; -import com.inq.wishhair.wesharewishhair.point.domain.PointLogRepository; -import com.inq.wishhair.wesharewishhair.user.application.utils.UserValidator; -import com.inq.wishhair.wesharewishhair.user.domain.AiConnector; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor -@Transactional(readOnly = true) +@Transactional public class UserService { private final UserRepository userRepository; private final UserFindService userFindService; private final UserValidator userValidator; - private final PasswordEncoder passwordEncoder; private final ReviewService reviewService; private final TokenRepository tokenRepository; private final AiConnector connector; private final PointLogRepository pointLogRepository; - @Transactional public Long createUser(SignUpRequest request) { User user = generateUser(request); @@ -52,7 +45,6 @@ public Long createUser(SignUpRequest request) { return saveUser.getId(); } - @Transactional public void deleteUser(Long userId) { tokenRepository.deleteByUserId(userId); reviewService.deleteReviewByWriter(userId); @@ -60,53 +52,42 @@ public void deleteUser(Long userId) { userRepository.deleteById(userId); } - @Transactional public void refreshPassword(PasswordRefreshRequest request) { - User user = userFindService.findByEmail(new Email(request.getEmail())); + User user = userFindService.getByEmail(new Email(request.email())); - user.updatePassword(Password.encrypt(request.getNewPassword(), passwordEncoder)); + user.updatePassword(request.newPassword()); } - @Transactional public void updateUser(Long userId, UserUpdateRequest request) { - User user = userFindService.findByUserId(userId); - Nickname newNickname = new Nickname(request.getNickname()); + User user = userFindService.getById(userId); - userValidator.validateNicknameIsNotDuplicated(newNickname); + userValidator.validateNicknameIsNotDuplicated(new Nickname(request.nickname())); - user.updateNickname(newNickname); - user.updateSex(request.getSex()); + user.updateNickname(request.nickname()); + user.updateSex(request.sex()); } - @Transactional public SimpleResponseWrapper updateFaceShape(Long userId, MultipartFile file) { - User user = userFindService.findByUserId(userId); + User user = userFindService.getById(userId); Tag faceShapeTag = connector.detectFaceShape(file); - user.updateFaceShape(new FaceShape(faceShapeTag)); + user.updateFaceShape(faceShapeTag); return new SimpleResponseWrapper<>(user.getFaceShapeTag().getDescription()); } - @Transactional public void updatePassword(Long userId, PasswordUpdateRequest request) { - User user = userFindService.findByUserId(userId); - confirmPassword(user, request.getOldPassword()); + User user = userFindService.getById(userId); + user.confirmPassword(request.oldPassword()); - user.updatePassword(Password.encrypt(request.getNewPassword(), passwordEncoder)); + user.updatePassword(request.newPassword()); } private User generateUser(SignUpRequest request) { - return User.createUser( - request.getEmail(), - Password.encrypt(request.getPw(), passwordEncoder), - request.getName(), - request.getNickname(), - request.getSex()); - } - - private void confirmPassword(User user, String password) { - if (!passwordEncoder.matches(password, user.getPasswordValue())) { - throw new WishHairException(ErrorCode.USER_WRONG_PASSWORD); - } + return User.of( + request.email(), + request.pw(), + request.name(), + request.nickname(), + request.sex()); } } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/domain/entity/Email.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/domain/entity/Email.java index bed1bd5..f7d8ff4 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/domain/entity/Email.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/domain/entity/Email.java @@ -17,6 +17,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Embeddable public class Email { + private static final String EMAIL_PATTERN = "^[_a-z0-9-]+(.[_a-z0-9-]+)*@(?:\\w+\\.)+\\w+$"; private static final Pattern EMAIL_MATCHER = Pattern.compile(EMAIL_PATTERN); @@ -28,13 +29,13 @@ public Email(String email) { this.value = email; } - private static void validateEmailPattern(String email) { + private void validateEmailPattern(String email) { if (isNotValidPattern(email)) { throw new WishHairException(ErrorCode.USER_INVALID_EMAIL); } } - private static boolean isNotValidPattern(String email) { + private boolean isNotValidPattern(String email) { return !EMAIL_MATCHER.matcher(email).matches(); } } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/domain/entity/Password.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/domain/entity/Password.java index f46b852..37c406c 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/domain/entity/Password.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/domain/entity/Password.java @@ -2,6 +2,7 @@ import java.util.regex.Pattern; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import com.inq.wishhair.wesharewishhair.global.exception.ErrorCode; @@ -23,26 +24,29 @@ public class Password { private static final String PASSWORD_PATTERN = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*\\W).{8,20}$"; private static final Pattern PASSWORD_MATCHER = Pattern.compile(PASSWORD_PATTERN); + private static final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + @Column(name = "pw", nullable = false) private String value; - private Password(String pw) { - this.value = pw; + public Password(String pw) { + validatePasswordPattern(pw); + this.value = passwordEncoder.encode(pw); } - //암호화 - public static Password encrypt(String pw, PasswordEncoder encoder) { - validatePasswordPattern(pw); - return new Password(encoder.encode(pw)); + public void confirmPassword(String password) { + if (!passwordEncoder.matches(password, value)) { + throw new WishHairException(ErrorCode.USER_WRONG_PASSWORD); + } } - private static void validatePasswordPattern(String pw) { + private void validatePasswordPattern(String pw) { if (isNotValidPattern(pw)) { throw new WishHairException(ErrorCode.USER_INVALID_PASSWORD); } } - private static boolean isNotValidPattern(String pw) { + private boolean isNotValidPattern(String pw) { return !PASSWORD_MATCHER.matcher(pw).matches(); } } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/domain/entity/User.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/domain/entity/User.java index 0046658..1b2d89a 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/domain/entity/User.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/domain/entity/User.java @@ -43,23 +43,23 @@ public class User { private FaceShape faceShape; private User(final Email email, - final Password password, + final String password, final String name, final Nickname nickname, final Sex sex ) { this.email = email; - this.password = password; + this.password = new Password(password); this.name = name; this.nickname = nickname; this.sex = sex; this.faceShape = null; } - //=Factory method==// - public static User createUser( + //==Factory method==// + public static User of( final String email, - final Password password, + final String password, final String name, final String nickname, final Sex sex @@ -105,16 +105,16 @@ public boolean existFaceShape() { return faceShape != null; } - public void updateFaceShape(FaceShape faceShape) { - this.faceShape = faceShape; + public void updateFaceShape(Tag tag) { + this.faceShape = new FaceShape(tag); } - public void updatePassword(Password password) { - this.password = password; + public void updatePassword(String password) { + this.password = new Password(password); } - public void updateNickname(Nickname nickname) { - this.nickname = nickname; + public void updateNickname(String nickname) { + this.nickname = new Nickname(nickname); } public void updateSex(Sex sex) { @@ -128,4 +128,8 @@ public String getPasswordValue() { public String getNicknameValue() { return nickname.getValue(); } + + public void confirmPassword(String password) { + this.password.confirmPassword(password); + } } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/infrastructure/FlaskConnector.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/infrastructure/FlaskConnector.java index 3089be4..668fda9 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/infrastructure/FlaskConnector.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/infrastructure/FlaskConnector.java @@ -27,45 +27,38 @@ public class FlaskConnector implements AiConnector { private final String requestUri; private final RestTemplate restTemplate; - public FlaskConnector(@Value("${flask.domain}") String domain) { + public FlaskConnector( + @Value("${flask.domain}") String domain, + RestTemplate restTemplate + ) { this.requestUri = domain + URL; - this.restTemplate = new RestTemplate(); + this.restTemplate = restTemplate; } @Override public Tag detectFaceShape(MultipartFile file) { validateFileExist(file); - HttpHeaders headers = generateHeaders(); - MultiValueMap body = generateBody(file); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add(FILES, file.getResource()); + HttpEntity> request = new HttpEntity<>(body, headers); - String response = fetchFlackResponse(request); - return toTag(response); + ResponseEntity response = postRequest(request); + validateResponseStatusIsOk(response.getStatusCode()); + + return Tag.valueOf(response.getBody()); } - private String fetchFlackResponse(HttpEntity> request) { - ResponseEntity response; + private ResponseEntity postRequest(HttpEntity> request) { try { - response = restTemplate.postForEntity(requestUri, request, String.class); + return restTemplate.postForEntity(requestUri, request, String.class); } catch (RestClientException e) { throw new WishHairException(ErrorCode.FLASK_SERVER_EXCEPTION); } - - validateResponseStatusIsOk(response.getStatusCode()); - return response.getBody(); - } - - private HttpHeaders generateHeaders() { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); - return headers; - } - - private MultiValueMap generateBody(MultipartFile file) { - LinkedMultiValueMap body = new LinkedMultiValueMap<>(); - body.add(FILES, file.getResource()); - return body; } private void validateResponseStatusIsOk(HttpStatusCode status) { @@ -79,12 +72,4 @@ private void validateFileExist(MultipartFile file) { throw new WishHairException(ErrorCode.EMPTY_FILE_EX); } } - - private Tag toTag(String response) { - try { - return Tag.valueOf(response); - } catch (IllegalArgumentException e) { - throw new WishHairException(ErrorCode.FLASK_RESPONSE_ERROR); - } - } } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/infrastructure/UserJpaRepository.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/infrastructure/UserJpaRepository.java index b664b5b..d9e340f 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/infrastructure/UserJpaRepository.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/infrastructure/UserJpaRepository.java @@ -11,8 +11,6 @@ public interface UserJpaRepository extends UserRepository, JpaRepository { - Optional findById(Long id); - Optional findByEmail(Email email); boolean existsByNickname(Nickname nickname); diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/UserController.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/UserController.java index 7d06599..57eb20b 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/UserController.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/UserController.java @@ -41,14 +41,16 @@ public ResponseEntity signUp(@RequestBody SignUpRequest createRequest) } @DeleteMapping - public ResponseEntity deleteUser(final @FetchAuthInfo AuthInfo authInfo) { + public ResponseEntity deleteUser(@FetchAuthInfo AuthInfo authInfo) { userService.deleteUser(authInfo.userId()); return ResponseEntity.ok(new Success()); } @PatchMapping("/refresh/password") - public ResponseEntity refreshPassword(@RequestBody PasswordRefreshRequest request) { + public ResponseEntity refreshPassword( + @RequestBody PasswordRefreshRequest request + ) { userService.refreshPassword(request); @@ -56,8 +58,9 @@ public ResponseEntity refreshPassword(@RequestBody PasswordRefreshReque } @PatchMapping - public ResponseEntity updateUser(@RequestBody UserUpdateRequest request, - final @FetchAuthInfo AuthInfo authInfo + public ResponseEntity updateUser( + @RequestBody UserUpdateRequest request, + @FetchAuthInfo AuthInfo authInfo ) { userService.updateUser(authInfo.userId(), request); @@ -67,8 +70,8 @@ public ResponseEntity updateUser(@RequestBody UserUpdateRequest request @PatchMapping("/password") public ResponseEntity updatePassword( - final @RequestBody PasswordUpdateRequest request, - final @FetchAuthInfo AuthInfo authInfo + @RequestBody PasswordUpdateRequest request, + @FetchAuthInfo AuthInfo authInfo ) { userService.updatePassword(authInfo.userId(), request); @@ -77,11 +80,11 @@ public ResponseEntity updatePassword( @PatchMapping("/face_shape") public ResponseEntity> updateFaceShape( - final @ModelAttribute FaceShapeUpdateRequest request, - final @FetchAuthInfo AuthInfo authInfo + @ModelAttribute FaceShapeUpdateRequest request, + @FetchAuthInfo AuthInfo authInfo ) { - SimpleResponseWrapper result = userService.updateFaceShape(authInfo.userId(), request.getFile()); + SimpleResponseWrapper result = userService.updateFaceShape(authInfo.userId(), request.file()); return ResponseEntity.ok(result); } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/UserInfoController.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/UserInfoController.java index 0c06a99..e5e46ff 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/UserInfoController.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/UserInfoController.java @@ -22,7 +22,7 @@ public class UserInfoController { private final UserInfoService userInfoService; @GetMapping("/my_page") - public ResponseEntity getMyPageInfo(final @FetchAuthInfo AuthInfo authInfo) { + public ResponseEntity getMyPageInfo(@FetchAuthInfo AuthInfo authInfo) { MyPageResponse result = userInfoService.getMyPageInfo(authInfo.userId()); @@ -31,7 +31,7 @@ public ResponseEntity getMyPageInfo(final @FetchAuthInfo AuthInf @GetMapping("/info") public ResponseEntity getUserInformation( - final @FetchAuthInfo AuthInfo authInfo + @FetchAuthInfo AuthInfo authInfo ) { UserInformation result = userInfoService.getUserInformation(authInfo.userId()); @@ -40,7 +40,7 @@ public ResponseEntity getUserInformation( } @GetMapping("/home_info") - public ResponseEntity getUserInfo(final @FetchAuthInfo AuthInfo authInfo) { + public ResponseEntity getUserInfo(@FetchAuthInfo AuthInfo authInfo) { UserInfo result = userInfoService.getUserInfo(authInfo.userId()); diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/FaceShapeUpdateRequest.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/FaceShapeUpdateRequest.java index fdcc807..899907c 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/FaceShapeUpdateRequest.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/FaceShapeUpdateRequest.java @@ -2,15 +2,7 @@ import org.springframework.web.multipart.MultipartFile; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class FaceShapeUpdateRequest { - - private MultipartFile file; +public record FaceShapeUpdateRequest( + MultipartFile file +) { } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/PasswordRefreshRequest.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/PasswordRefreshRequest.java index e497a4c..1c41e70 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/PasswordRefreshRequest.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/PasswordRefreshRequest.java @@ -1,15 +1,7 @@ package com.inq.wishhair.wesharewishhair.user.presentation.dto.request; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class PasswordRefreshRequest { - - private String email; - private String newPassword; +public record PasswordRefreshRequest( + String email, + String newPassword +) { } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/PasswordUpdateRequest.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/PasswordUpdateRequest.java index 6b406cc..a780dda 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/PasswordUpdateRequest.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/PasswordUpdateRequest.java @@ -1,16 +1,7 @@ package com.inq.wishhair.wesharewishhair.user.presentation.dto.request; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class PasswordUpdateRequest { - - private String oldPassword; - - private String newPassword; +public record PasswordUpdateRequest( + String oldPassword, + String newPassword +) { } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/SignUpRequest.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/SignUpRequest.java index 02dede2..9cb3223 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/SignUpRequest.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/SignUpRequest.java @@ -3,28 +3,17 @@ import com.inq.wishhair.wesharewishhair.user.domain.entity.Sex; import jakarta.validation.constraints.NotNull; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class SignUpRequest { +public record SignUpRequest( @NotNull - private String email; - + String email, @NotNull - private String pw; - + String pw, @NotNull - private String name; - + String name, @NotNull - private String nickname; - + String nickname, @NotNull - private Sex sex; + Sex sex +) { } diff --git a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/UserUpdateRequest.java b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/UserUpdateRequest.java index 15ceebc..073eb67 100644 --- a/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/UserUpdateRequest.java +++ b/src/main/java/com/inq/wishhair/wesharewishhair/user/presentation/dto/request/UserUpdateRequest.java @@ -2,17 +2,8 @@ import com.inq.wishhair.wesharewishhair.user.domain.entity.Sex; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class UserUpdateRequest { - - private String nickname; - - private Sex sex; +public record UserUpdateRequest( + String nickname, + Sex sex +) { } diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/application/AuthServiceTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/application/AuthServiceTest.java new file mode 100644 index 0000000..a334fa0 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/application/AuthServiceTest.java @@ -0,0 +1,158 @@ +package com.inq.wishhair.wesharewishhair.auth.application; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.ThrowableAssert.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.Mockito; +import org.springframework.security.crypto.password.PasswordEncoder; + +import com.inq.wishhair.wesharewishhair.auth.application.dto.response.LoginResponse; +import com.inq.wishhair.wesharewishhair.auth.domain.AuthToken; +import com.inq.wishhair.wesharewishhair.auth.domain.AuthTokenManager; +import com.inq.wishhair.wesharewishhair.auth.domain.TokenRepository; +import com.inq.wishhair.wesharewishhair.auth.domain.entity.Token; +import com.inq.wishhair.wesharewishhair.auth.fixture.TokenFixture; +import com.inq.wishhair.wesharewishhair.auth.stub.AuthTokenMangerStub; +import com.inq.wishhair.wesharewishhair.global.exception.ErrorCode; +import com.inq.wishhair.wesharewishhair.global.exception.WishHairException; +import com.inq.wishhair.wesharewishhair.user.domain.UserRepository; +import com.inq.wishhair.wesharewishhair.user.domain.entity.Email; +import com.inq.wishhair.wesharewishhair.user.domain.entity.User; +import com.inq.wishhair.wesharewishhair.user.fixture.UserFixture; + +@DisplayName("[AuthService Test] - Application Layer") +class AuthServiceTest { + + private static final String EMAIL = "hello@naver.com"; + private static final String PW = "hello1234@"; + private static final User USER = UserFixture.getFixedManUser(); + + private final AuthService authService; + private final UserRepository userRepository; + private final TokenRepository tokenRepository; + private final PasswordEncoder passwordEncoder; + private final AuthTokenManager authTokenManager; + + public AuthServiceTest() { + this.userRepository = Mockito.mock(UserRepository.class); + this.tokenRepository = Mockito.mock(TokenRepository.class); + this.passwordEncoder = Mockito.mock(PasswordEncoder.class); + this.authTokenManager = new AuthTokenMangerStub(); + this.authService = new AuthService( + userRepository, + tokenRepository, + passwordEncoder, + authTokenManager + ); + } + + @Nested + @DisplayName("로그인을 한다") + class login { + + @Nested + @DisplayName("[로그인에 성공한다]") + class loginSuccess { + + public loginSuccess() { + given(userRepository.findByEmail(new Email(EMAIL))) + .willReturn(Optional.of(USER)); + + given(passwordEncoder.matches(PW, USER.getPasswordValue())) + .willReturn(true); + } + + @Test + @DisplayName("[토큰을 가지고있는 유저로 기존 토큰의 RefreshToken 을 업데이트한다]") + void updateRefreshToken() { + //given + Token token = TokenFixture.getFixedToken(); + given(tokenRepository.findByUserId(null)) + .willReturn(Optional.of(token)); + + //when + LoginResponse actual = authService.login(EMAIL, PW); + + //then + assertLoginResponse(actual); + assertThat(token.getRefreshToken()).isEqualTo(actual.refreshToken()); + } + + @Test + @DisplayName("[토큰이 없는 유저로 새로운 토큰을 생성한다]") + void createNewToken() { + //given + given(tokenRepository.findByUserId(null)) + .willReturn(Optional.empty()); + + //when + LoginResponse actual = authService.login(EMAIL, PW); + + //then + assertLoginResponse(actual); + verify(tokenRepository, times(1)).save(any(Token.class)); + } + + private void assertLoginResponse(LoginResponse actual) { + AuthToken expected = authTokenManager.generate(1L); + assertAll( + () -> assertThat(actual.accessToken()).isEqualTo(expected.accessToken()), + () -> assertThat(actual.refreshToken()).isEqualTo(expected.refreshToken()) + ); + } + } + + @Test + @DisplayName("[잘못된 이메일로 로그인에 실패한다]") + void failByInvalidEmail() { + //given + given(userRepository.findByEmail(new Email(EMAIL))) + .willReturn(Optional.empty()); + + //when + ThrowingCallable when = () -> authService.login(EMAIL, PW); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.LOGIN_FAIL.getMessage()); + } + + @Test + @DisplayName("[잘못된 비밀번호로 로그인에 실패한다]") + void failByInvalidPw() { + //given + given(userRepository.findByEmail(new Email(EMAIL))) + .willReturn(Optional.of(USER)); + + given(passwordEncoder.matches(PW, USER.getPasswordValue())) + .willReturn(false); + + //when + ThrowingCallable when = () -> authService.login(EMAIL, PW); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.LOGIN_FAIL.getMessage()); + } + } + + @Test + @DisplayName("[로그아웃을 한다]") + void logout() { + //when + Executable when = () -> authService.logout(1L); + + //then + assertDoesNotThrow(when); + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/application/MailAuthServiceTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/application/MailAuthServiceTest.java new file mode 100644 index 0000000..b1b1d45 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/application/MailAuthServiceTest.java @@ -0,0 +1,115 @@ +package com.inq.wishhair.wesharewishhair.auth.application; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.Optional; + +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.function.Executable; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; + +import com.inq.wishhair.wesharewishhair.auth.application.utils.RandomGenerator; +import com.inq.wishhair.wesharewishhair.auth.domain.AuthCodeRepository; +import com.inq.wishhair.wesharewishhair.auth.domain.entity.AuthCode; +import com.inq.wishhair.wesharewishhair.global.exception.ErrorCode; +import com.inq.wishhair.wesharewishhair.global.exception.WishHairException; + +@ExtendWith(MockitoExtension.class) +@DisplayName("[AuthService Test] - Application Layer") +class MailAuthServiceTest { + + private static final String CODE = "1234"; + private static final String EMAIL = "hello@naver.com"; + + private final MailAuthService mailAuthService; + private final AuthCodeRepository authCodeRepository; + + public MailAuthServiceTest() { + this.authCodeRepository = Mockito.mock(AuthCodeRepository.class); + ApplicationEventPublisher eventPublisher = Mockito.mock(ApplicationEventPublisher.class); + RandomGenerator randomGenerator = Mockito.mock(RandomGenerator.class); + + this.mailAuthService = new MailAuthService( + eventPublisher, authCodeRepository, randomGenerator + ); + + given(randomGenerator.generateString()).willReturn(CODE); + } + + @Nested + @DisplayName("[인증 메일을 요청한다]") + class requestMailAuthorization { + + @Test + @DisplayName("[인증 메일을 요청하고 인증코드를 새로 생성한다]") + void createNewAuthCode() { + //given + given(authCodeRepository.findByEmail(EMAIL)) + .willReturn(Optional.empty()); + + //when + mailAuthService.requestMailAuthorization(EMAIL); + + //then + verify(authCodeRepository, times(1)).save(any(AuthCode.class)); + } + + @Test + @DisplayName("[인증 메일을 요청하고 기존의 인증코드가 존재해서 기존 인증코드를 업데이트한다]") + void updateAuthCode() { + //given + AuthCode authCode = new AuthCode(EMAIL, "code"); + given(authCodeRepository.findByEmail(EMAIL)) + .willReturn(Optional.of(authCode)); + + //when + mailAuthService.requestMailAuthorization(EMAIL); + + //then + assertThat(authCode.getCode()).isEqualTo(CODE); + } + } + + @Nested + @DisplayName("[인증코드가 올바른지 확인한다]") + class checkAuthCode { + + private static final String CODE = "code"; + + public checkAuthCode() { + //given + given(authCodeRepository.findByEmail(EMAIL)) + .willReturn(Optional.of(new AuthCode(EMAIL, CODE))); + } + + @Test + @DisplayName("[검사에 성공한다]") + void success() { + //when + Executable when = () -> mailAuthService.checkAuthCode(EMAIL, CODE); + + //then + assertDoesNotThrow(when); + } + + @Test + @DisplayName("잘못된 code 로 검사에 실패한다") + void failByInvalidCode() { + //when + ThrowingCallable when = () -> mailAuthService.checkAuthCode(EMAIL, "hello"); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.AUTH_INVALID_AUTH_CODE.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/application/TokenReissueServiceTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/application/TokenReissueServiceTest.java new file mode 100644 index 0000000..d329b26 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/application/TokenReissueServiceTest.java @@ -0,0 +1,78 @@ +package com.inq.wishhair.wesharewishhair.auth.application; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.ThrowableAssert.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.inq.wishhair.wesharewishhair.auth.application.dto.response.TokenResponse; +import com.inq.wishhair.wesharewishhair.auth.domain.AuthToken; +import com.inq.wishhair.wesharewishhair.auth.domain.AuthTokenManager; +import com.inq.wishhair.wesharewishhair.auth.domain.TokenRepository; +import com.inq.wishhair.wesharewishhair.auth.stub.AuthTokenMangerStub; +import com.inq.wishhair.wesharewishhair.global.exception.ErrorCode; +import com.inq.wishhair.wesharewishhair.global.exception.WishHairException; + +@ExtendWith(MockitoExtension.class) +@DisplayName("[TokenReissueService 테스트] - Application Layer") +class TokenReissueServiceTest { + + private final TokenReissueService tokenReissueService; + private final TokenRepository tokenRepository; + private final AuthTokenManager authTokenManager; + + public TokenReissueServiceTest() { + this.tokenRepository = Mockito.mock(TokenRepository.class); + this.authTokenManager = new AuthTokenMangerStub(); + this.tokenReissueService = new TokenReissueService(tokenRepository, authTokenManager); + } + + @Nested + @DisplayName("토큰을 재발급한다") + class reissueToken { + + private static final Long USER_ID = 1L; + private static final String REFRESH_TOKEN = "TOKEN"; + + @Test + @DisplayName("[재발급에 성공한다]") + void success() { + //given + given(tokenRepository.existsByUserIdAndRefreshToken(USER_ID, REFRESH_TOKEN)) + .willReturn(true); + + //when + TokenResponse actual = tokenReissueService.reissueToken(USER_ID, REFRESH_TOKEN); + + //then + AuthToken expected = authTokenManager.generate(1L); + assertAll( + () -> assertThat(actual.accessToken()).isEqualTo(expected.accessToken()), + () -> assertThat(actual.refreshToken()).isEqualTo(expected.refreshToken()) + ); + } + + @Test + @DisplayName("[이미 재발급에 사용된 토큰으로 실패한다]") + void failByInvalidRefreshToken() { + //given + given(tokenRepository.existsByUserIdAndRefreshToken(USER_ID, REFRESH_TOKEN)) + .willReturn(false); + + //when + ThrowingCallable when = () -> tokenReissueService.reissueToken(USER_ID, REFRESH_TOKEN); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.AUTH_INVALID_TOKEN.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/application/utils/AuthRandomGeneratorTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/application/utils/AuthRandomGeneratorTest.java new file mode 100644 index 0000000..c834eeb --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/application/utils/AuthRandomGeneratorTest.java @@ -0,0 +1,24 @@ +package com.inq.wishhair.wesharewishhair.auth.application.utils; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.inq.wishhair.wesharewishhair.auth.infrastructure.utils.AuthRandomGenerator; + +@DisplayName("[RandomGenerator Test] - Infrastructure Layer") +class AuthRandomGeneratorTest { + + private final RandomGenerator randomGenerator = new AuthRandomGenerator(); + + @Test + @DisplayName("[1000~9999 사이의 랜덤한 숫자 문자열을 생성한다]") + void generateString() { + //when + String actual = randomGenerator.generateString(); + + //then + assertThat(Integer.parseInt(actual)).isBetween(1000, 9999); + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/domain/AuthTokenManagerTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/domain/AuthTokenManagerTest.java new file mode 100644 index 0000000..575be65 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/domain/AuthTokenManagerTest.java @@ -0,0 +1,160 @@ +package com.inq.wishhair.wesharewishhair.auth.domain; + +import static com.inq.wishhair.wesharewishhair.global.exception.ErrorCode.*; +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.ThrowableAssert.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import com.inq.wishhair.wesharewishhair.auth.infrastructure.jwt.AuthAuthTokenManagerAdaptor; +import com.inq.wishhair.wesharewishhair.auth.infrastructure.jwt.JwtTokenProvider; +import com.inq.wishhair.wesharewishhair.global.exception.WishHairException; + +@DisplayName("[AuthTokenManager Test] - Infrastructure Layer") +class AuthTokenManagerTest { + + private final AuthTokenManager authTokenManager; + private final String secretKey; + private final String differentSecretKey; + + public AuthTokenManagerTest() { + secretKey = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + differentSecretKey = secretKey.replaceAll("a", "b"); + JwtTokenProvider jwtTokenProvider = new JwtTokenProvider(secretKey, 10000, 10000); + authTokenManager = new AuthAuthTokenManagerAdaptor(jwtTokenProvider); + } + + @Test + @DisplayName("[인증 토큰을 발행한다]") + void generate() { + //when + AuthToken actual = authTokenManager.generate(1L); + + //then + assertDoesNotThrow(() -> authTokenManager.validateToken(actual.accessToken())); + assertDoesNotThrow(() -> authTokenManager.validateToken(actual.refreshToken())); + } + + @Nested + @DisplayName("[토큰에서 id 를 추출한다]") + class getId { + + @Test + @DisplayName("[성공적으로 id 를 추출한다]") + void success() { + long userId = 1L; + AuthToken authToken = authTokenManager.generate(userId); + String token = authToken.accessToken(); + + //when + Long actual = authTokenManager.getId(token); + + //then + assertThat(actual).isEqualTo(userId); + } + + @Test + @DisplayName("[유효하지 않은 토큰으로 추출에 실패한다]") + void failByInvalidToken() { + //given + AuthTokenManager differentKeyTokenManager = new AuthAuthTokenManagerAdaptor( + new JwtTokenProvider(differentSecretKey, 100, 100)); + + String token1 = authTokenManager.generate(1L).accessToken() + "e"; + String token2 = differentKeyTokenManager.generate(1L).accessToken(); + + //when + ThrowingCallable when1 = () -> authTokenManager.getId(token1); + ThrowingCallable when2 = () -> authTokenManager.getId(token2); + + //then + assertThrownByInvalidToken(when1, when2); + } + + @Test + @DisplayName("[유효기간이 만료된 토큰으로 추출에 실패한다]") + void failByExpired() { + //given + AuthTokenManager timeOutTokenManger = new AuthAuthTokenManagerAdaptor( + new JwtTokenProvider(secretKey, 0, 0)); + + String token = timeOutTokenManger.generate(1L).accessToken(); + + //when + ThrowingCallable when = () -> authTokenManager.getId(token); + + //then + assertThrownByExpiredToken(when); + } + } + + @Nested + @DisplayName("[토큰을 검증한다]") + class validateToken { + + @Test + @DisplayName("[검증에 성공한다]") + void success() { + //given + String token = authTokenManager.generate(1L).accessToken(); + + //when + Executable when = () -> authTokenManager.validateToken(token); + + //then + assertDoesNotThrow(when); + } + + @Test + @DisplayName("[유효하지 않은 토큰으로 검증에 실패한다]") + void failByInvalidToken() { + //given + AuthTokenManager differentKeyTokenManager = new AuthAuthTokenManagerAdaptor( + new JwtTokenProvider(differentSecretKey, 100, 100)); + + String token1 = authTokenManager.generate(1L).accessToken() + "e"; + String token2 = differentKeyTokenManager.generate(1L).accessToken(); + + //when + ThrowingCallable when1 = () -> authTokenManager.validateToken(token1); + ThrowingCallable when2 = () -> authTokenManager.validateToken(token2); + + //then + assertThrownByInvalidToken(when1, when2); + } + + @Test + @DisplayName("[유효기간이 만료된 토큰으로 검증에 실패한다]") + void failByExpired() { + //given + AuthTokenManager timeOutTokenManger = new AuthAuthTokenManagerAdaptor( + new JwtTokenProvider(secretKey, 0, 0)); + + String token = timeOutTokenManger.generate(1L).accessToken(); + + //when + ThrowingCallable when = () -> authTokenManager.getId(token); + + //then + assertThrownByExpiredToken(when); + } + } + + private void assertThrownByExpiredToken(ThrowingCallable when) { + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(AUTH_EXPIRED_TOKEN.getMessage()); + } + + private void assertThrownByInvalidToken(ThrowingCallable... whens) { + for (ThrowingCallable when : whens) { + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(AUTH_INVALID_TOKEN.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/domain/TokenRepositoryTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/domain/TokenRepositoryTest.java new file mode 100644 index 0000000..fd45266 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/domain/TokenRepositoryTest.java @@ -0,0 +1,94 @@ +package com.inq.wishhair.wesharewishhair.auth.domain; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.inq.wishhair.wesharewishhair.auth.domain.entity.Token; +import com.inq.wishhair.wesharewishhair.common.support.RepositoryTestSupport; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +@DisplayName("[TokenRepository Test] - Domain Layer") +class TokenRepositoryTest extends RepositoryTestSupport { + + @PersistenceContext + private EntityManager entityManager; + + @Autowired + private TokenRepository tokenRepository; + + @Test + @DisplayName("[해당 유저아이디를 가진 토큰을 조회한다]") + void findByUserId() { + //given + final long userId = 1L; + Token token = Token.issue(userId, "token"); + tokenRepository.save(token); + + //when + Optional actual = tokenRepository.findByUserId(userId); + + //then + assertThat(actual).contains(token); + } + + @Test + @DisplayName("[해당 유저 아이디와 리프래쉬 토큰을 가진 토큰의 존재여부를 조회한다]") + void findByUserIdAndRefreshToken() { + //given + final String refreshToken = "token"; + final long userId = 1L; + + Token token = Token.issue(userId, refreshToken); + tokenRepository.save(token); + + //when + boolean actual = tokenRepository.existsByUserIdAndRefreshToken(userId, refreshToken); + + //then + assertThat(actual).isTrue(); + } + + @Test + @DisplayName("[해당 유저 아이디를 가진 토큰을 삭제한다]") + void deleteByUserId() { + //given + final long userId = 1L; + Token token = Token.issue(userId, "token"); + tokenRepository.save(token); + + //when + tokenRepository.deleteByUserId(userId); + + //then + Optional actual = tokenRepository.findByUserId(userId); + assertThat(actual).isNotPresent(); + } + + @Test + @DisplayName("[해당 유저 아이디를 가진 토큰의 리프래쉬 토큰을 업데이트한다]") + void updateRefreshTokenByUserId() { + //given + final long userId = 1L; + Token token = Token.issue(userId, "token"); + tokenRepository.save(token); + + final String newRefreshToken = "refreshToken"; + + //when + tokenRepository.updateRefreshTokenByUserId(userId, newRefreshToken); + entityManager.flush(); + entityManager.clear(); + + //then + Optional actual = tokenRepository.findByUserId(userId); + assertThat(actual).isPresent(); + assertThat(actual.get().getRefreshToken()).isEqualTo(newRefreshToken); + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/domain/entity/AuthCodeTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/domain/entity/AuthCodeTest.java new file mode 100644 index 0000000..16107b1 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/domain/entity/AuthCodeTest.java @@ -0,0 +1,24 @@ +package com.inq.wishhair.wesharewishhair.auth.domain.entity; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("[AuthCode Test] - Domain Layer") +class AuthCodeTest { + + @Test + @DisplayName("[인증 코드를 변경한다]") + void updateCode() { + //given + AuthCode authCode = new AuthCode("email", "code"); + final String newCode = "newCode"; + + //when + authCode.updateCode(newCode); + + //then + assertThat(authCode.getCode()).isEqualTo(newCode); + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/domain/entity/TokenTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/domain/entity/TokenTest.java new file mode 100644 index 0000000..93193b7 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/domain/entity/TokenTest.java @@ -0,0 +1,42 @@ +package com.inq.wishhair.wesharewishhair.auth.domain.entity; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("[Token Test] - Domain Layer") +class TokenTest { + + @Test + @DisplayName("[토큰을 생성한다]") + void issueTest() { + //given + final Long userId = 1L; + final String token = "token"; + + //when + Token actual = Token.issue(userId, token); + + //then + assertAll( + () -> assertThat(actual.getUserId()).isEqualTo(userId), + () -> assertThat(actual.getRefreshToken()).isEqualTo(token) + ); + } + + @Test + @DisplayName("[토큰을 변경한다]") + void updateRefreshTokenTest() { + //given + Token token = Token.issue(1L, "token"); + final String newToken = "newToken"; + + //when + token.updateRefreshToken(newToken); + + //then + assertThat(token.getRefreshToken()).isEqualTo(newToken); + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/fixture/TokenFixture.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/fixture/TokenFixture.java new file mode 100644 index 0000000..bc20be3 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/fixture/TokenFixture.java @@ -0,0 +1,17 @@ +package com.inq.wishhair.wesharewishhair.auth.fixture; + +import com.inq.wishhair.wesharewishhair.auth.domain.entity.Token; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class TokenFixture { + + private static final String TOKEN = "token"; + private static final Long USER_ID = 1L; + + public static Token getFixedToken() { + return Token.issue(USER_ID, TOKEN); + } +} diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/jwt/JwtTokenProviderTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/jwt/JwtTokenProviderTest.java new file mode 100644 index 0000000..95a15fa --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/infrastructure/jwt/JwtTokenProviderTest.java @@ -0,0 +1,115 @@ +package com.inq.wishhair.wesharewishhair.auth.infrastructure.jwt; + +import static com.inq.wishhair.wesharewishhair.global.exception.ErrorCode.*; +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.ThrowableAssert.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import com.inq.wishhair.wesharewishhair.global.exception.WishHairException; + +@DisplayName("[JwtTokenProvider Test] - Infrastructure Layer") +class JwtTokenProviderTest { + + private final String secretKey; + private final JwtTokenProvider jwtTokenProvider; + + public JwtTokenProviderTest() { + secretKey = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + this.jwtTokenProvider = new JwtTokenProvider(secretKey, 10000, 10000); + } + + @Test + void createAccessToken() { + //given + final Long userId =1L; + + //when + String actual = jwtTokenProvider.createAccessToken(userId); + + //then + Long expected = jwtTokenProvider.getPayload(actual); + assertThat(userId).isEqualTo(expected); + } + + @Test + void createRefreshToken() { + //given + final Long userId =1L; + + //when + String actual = jwtTokenProvider.createRefreshToken(userId); + + //then + Long expected = jwtTokenProvider.getPayload(actual); + assertThat(userId).isEqualTo(expected); + } + + @Test + void getPayload() { + //given + final Long userId =1L; + String token = jwtTokenProvider.createAccessToken(userId); + + //when + Long actual = jwtTokenProvider.getPayload(token); + + //then + assertThat(actual).isEqualTo(userId); + } + + @Nested + @DisplayName("[토큰을 검증한다]") + class validateToken { + + @Test + @DisplayName("[검증을 통과한다]") + void pass() { + //given + String token = jwtTokenProvider.createAccessToken(1L); + + //when + Executable when = () -> jwtTokenProvider.validateToken(token); + + //then + assertDoesNotThrow(when); + } + + @Test + @DisplayName("[유효기간이 만료되어 검증에 실패한다]") + void failByExpire() { + //given + JwtTokenProvider provider = new JwtTokenProvider(secretKey, 0, 0); + String token = provider.createRefreshToken(1L); + + //when + ThrowingCallable when = () -> provider.validateToken(token); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(AUTH_EXPIRED_TOKEN.getMessage()); + } + + @Test + @DisplayName("[잘못된 시크릿키로 인코딩된 토큰여서 실패한다]") + void failBySecretKey() { + //given + final String newSecretKey = secretKey + "b"; + JwtTokenProvider provider = new JwtTokenProvider(newSecretKey, 10000, 10000); + String token = provider.createRefreshToken(1L); + + //when + ThrowingCallable when = () -> jwtTokenProvider.validateToken(token); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(AUTH_INVALID_TOKEN.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/presentation/AuthControllerTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/presentation/AuthControllerTest.java new file mode 100644 index 0000000..5bf94e4 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/presentation/AuthControllerTest.java @@ -0,0 +1,72 @@ +package com.inq.wishhair.wesharewishhair.auth.presentation; + +import static com.inq.wishhair.wesharewishhair.common.fixture.AuthFixture.*; +import static org.mockito.BDDMockito.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import com.inq.wishhair.wesharewishhair.auth.application.AuthService; +import com.inq.wishhair.wesharewishhair.auth.application.dto.response.LoginResponse; +import com.inq.wishhair.wesharewishhair.auth.presentation.dto.request.LoginRequest; +import com.inq.wishhair.wesharewishhair.common.support.ApiTestSupport; +import com.inq.wishhair.wesharewishhair.global.config.SecurityConfig; + +@WebMvcTest(value = {AuthController.class, SecurityConfig.class}) +@DisplayName("[AuthController 테스트] - API") +class AuthControllerTest extends ApiTestSupport { + + private static final String LOGIN_URL = "/api/auth/login"; + private static final String LOGOUT_URL = "/api/auth/logout"; + + @MockBean + private AuthService authService; + + @Test + @DisplayName("[로그인 API 를 호출한다]") + void login() throws Exception { + //given + String email = "email"; + String pw = "pw"; + LoginRequest loginRequest = new LoginRequest(email, pw); + + LoginResponse loginResponse = new LoginResponse("token", "token"); + given(authService.login(email, pw)) + .willReturn(loginResponse); + + //when + ResultActions result = mockMvc.perform( + MockMvcRequestBuilders + .post(LOGIN_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(toJson(loginRequest)) + + ); + + //then + result.andExpectAll( + jsonPath("$.accessToken").value(loginResponse.accessToken()), + jsonPath("$.refreshToken").value(loginResponse.refreshToken()) + ); + } + + @Test + @DisplayName("[로그아웃 API 를 호출한다]") + void logout() throws Exception { + //when + ResultActions result = mockMvc.perform( + MockMvcRequestBuilders + .post(LOGOUT_URL) + .header(AUTHORIZATION, ACCESS_TOKEN) + ); + + //then + result.andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/presentation/MailAuthControllerTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/presentation/MailAuthControllerTest.java new file mode 100644 index 0000000..a121670 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/presentation/MailAuthControllerTest.java @@ -0,0 +1,87 @@ +package com.inq.wishhair.wesharewishhair.auth.presentation; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import com.inq.wishhair.wesharewishhair.auth.application.MailAuthService; +import com.inq.wishhair.wesharewishhair.auth.presentation.dto.request.AuthKeyRequest; +import com.inq.wishhair.wesharewishhair.auth.presentation.dto.request.MailRequest; +import com.inq.wishhair.wesharewishhair.common.support.ApiTestSupport; +import com.inq.wishhair.wesharewishhair.global.config.SecurityConfig; +import com.inq.wishhair.wesharewishhair.user.application.utils.UserValidator; + +@WebMvcTest(value = {MailAuthController.class, SecurityConfig.class}) +@DisplayName("[MailAuthController 테스트] - API") +class MailAuthControllerTest extends ApiTestSupport { + + private static final String CHECK_DUPLICATED_EMAIL_URL = "/api/email/check"; + private static final String SEND_AUTHORIZATION_MAIL_URL = "/api/email/send"; + private static final String AUTHORIZE_KEY_URL = "/api/email/validate"; + private static final String EMAIL = "hello@naver.com"; + + @MockBean + private UserValidator userValidator; + @MockBean + private MailAuthService mailAuthService; + + @Test + @DisplayName("[이메일 중복검사 API 를 호출한다]") + void checkDuplicateEmail() throws Exception { + //given + MailRequest mailRequest = new MailRequest(EMAIL); + + //when + ResultActions result = mockMvc.perform( + MockMvcRequestBuilders + .post(CHECK_DUPLICATED_EMAIL_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(toJson(mailRequest)) + ); + + //then + result.andExpect(status().isOk()); + } + + @Test + @DisplayName("[인증 메일 발송 API 를 호출한다]") + void sendAuthorizationMail() throws Exception { + //given + MailRequest mailRequest = new MailRequest(EMAIL); + + //when + ResultActions result = mockMvc.perform( + MockMvcRequestBuilders + .post(SEND_AUTHORIZATION_MAIL_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(toJson(mailRequest)) + ); + + //then + result.andExpect(status().isOk()); + } + + @Test + @DisplayName("[인증코드 확인 API 를 호출한다]") + void authorizeKey() throws Exception { + //given + AuthKeyRequest authKeyRequest = new AuthKeyRequest(EMAIL, "authcode"); + + //when + ResultActions result = mockMvc.perform( + MockMvcRequestBuilders + .post(AUTHORIZE_KEY_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(toJson(authKeyRequest)) + ); + + //then + result.andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/presentation/TokenReissueControllerTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/presentation/TokenReissueControllerTest.java new file mode 100644 index 0000000..fce4a19 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/presentation/TokenReissueControllerTest.java @@ -0,0 +1,50 @@ +package com.inq.wishhair.wesharewishhair.auth.presentation; + +import static com.inq.wishhair.wesharewishhair.common.fixture.AuthFixture.*; +import static org.mockito.BDDMockito.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import com.inq.wishhair.wesharewishhair.auth.application.TokenReissueService; +import com.inq.wishhair.wesharewishhair.auth.application.dto.response.TokenResponse; +import com.inq.wishhair.wesharewishhair.common.support.ApiTestSupport; +import com.inq.wishhair.wesharewishhair.global.config.SecurityConfig; + +@WebMvcTest(value = {TokenReissueController.class, SecurityConfig.class}) +@DisplayName("[TokenReissueController 테스트] - API") +class TokenReissueControllerTest extends ApiTestSupport { + + private static final String REISSUE_TOKEN_URL = "/api/token/reissue"; + + @MockBean + private TokenReissueService tokenReissueService; + + @Test + @DisplayName("[토큰 재발급 API 를 호출한다]") + void reissueToken() throws Exception { + //given + TokenResponse tokenResponse = new TokenResponse("accessToken", "refreshToken"); + given(tokenReissueService.reissueToken(1L, TOKEN)) + .willReturn(tokenResponse); + + //when + ResultActions result = mockMvc.perform( + MockMvcRequestBuilders + .post(REISSUE_TOKEN_URL) + .header(AUTHORIZATION, ACCESS_TOKEN) + ); + + //then + result.andExpectAll( + status().isOk(), + jsonPath("$.accessToken").value(tokenResponse.accessToken()), + jsonPath("$.refreshToken").value(tokenResponse.refreshToken()) + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/auth/stub/AuthTokenMangerStub.java b/src/test/java/com/inq/wishhair/wesharewishhair/auth/stub/AuthTokenMangerStub.java new file mode 100644 index 0000000..f46c2b8 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/auth/stub/AuthTokenMangerStub.java @@ -0,0 +1,21 @@ +package com.inq.wishhair.wesharewishhair.auth.stub; + +import com.inq.wishhair.wesharewishhair.auth.domain.AuthToken; +import com.inq.wishhair.wesharewishhair.auth.domain.AuthTokenManager; + +public class AuthTokenMangerStub implements AuthTokenManager { + + @Override + public AuthToken generate(Long userId) { + return new AuthToken("accessToken", "refreshToken"); + } + + @Override + public Long getId(String token) { + return 1L; + } + + @Override + public void validateToken(String token) { + } +} diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/common/fixture/AuthFixture.java b/src/test/java/com/inq/wishhair/wesharewishhair/common/fixture/AuthFixture.java new file mode 100644 index 0000000..dbf431c --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/common/fixture/AuthFixture.java @@ -0,0 +1,13 @@ +package com.inq.wishhair.wesharewishhair.common.fixture; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class AuthFixture { + + public static final String TOKEN = "token"; + public static final String BEARER = "Bearer "; + public static final String AUTHORIZATION = "Authorization"; + public static final String ACCESS_TOKEN = BEARER + TOKEN; +} diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/common/stub/PasswordEncoderStub.java b/src/test/java/com/inq/wishhair/wesharewishhair/common/stub/PasswordEncoderStub.java new file mode 100644 index 0000000..ece942e --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/common/stub/PasswordEncoderStub.java @@ -0,0 +1,18 @@ +package com.inq.wishhair.wesharewishhair.common.stub; + +import org.springframework.security.crypto.password.PasswordEncoder; + +public class PasswordEncoderStub implements PasswordEncoder { + + private static final String ENCODED_PW = "encoded_password"; + + @Override + public String encode(CharSequence rawPassword) { + return ENCODED_PW; + } + + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + return true; + } +} diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/common/support/ApiTestSupport.java b/src/test/java/com/inq/wishhair/wesharewishhair/common/support/ApiTestSupport.java new file mode 100644 index 0000000..a4b1132 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/common/support/ApiTestSupport.java @@ -0,0 +1,35 @@ +package com.inq.wishhair.wesharewishhair.common.support; + +import static com.inq.wishhair.wesharewishhair.common.fixture.AuthFixture.*; +import static org.mockito.BDDMockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.inq.wishhair.wesharewishhair.auth.domain.AuthToken; +import com.inq.wishhair.wesharewishhair.auth.domain.AuthTokenManager; + +public abstract class ApiTestSupport { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Autowired + protected MockMvc mockMvc; + + @MockBean + protected AuthTokenManager authTokenManager; + + @BeforeEach + public void setAuthorization() { + given(authTokenManager.generate(any(Long.class))).willReturn(new AuthToken(TOKEN, TOKEN)); + given(authTokenManager.getId(anyString())).willReturn(1L); + } + + public String toJson(Object object) throws JsonProcessingException { + return objectMapper.writeValueAsString(object); + } +} diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/common/support/RepositoryTestSupport.java b/src/test/java/com/inq/wishhair/wesharewishhair/common/support/RepositoryTestSupport.java new file mode 100644 index 0000000..61f60cc --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/common/support/RepositoryTestSupport.java @@ -0,0 +1,11 @@ +package com.inq.wishhair.wesharewishhair.common.support; + +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import com.inq.wishhair.wesharewishhair.global.config.QueryDslConfig; + +@Import(QueryDslConfig.class) +@DataJpaTest +public abstract class RepositoryTestSupport { +} diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/common/utils/FileMockingUtils.java b/src/test/java/com/inq/wishhair/wesharewishhair/common/utils/FileMockingUtils.java new file mode 100644 index 0000000..2ff3a2f --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/common/utils/FileMockingUtils.java @@ -0,0 +1,43 @@ +package com.inq.wishhair.wesharewishhair.common.utils; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class FileMockingUtils { + + private static final String FILE_PATH = "src/test/resources/images/"; + private static final String FILE_META_NAME = "files"; + private static final String CONTENT_TYPE = "image/bmp"; + + public static MultipartFile createMockMultipartFile(String fileName) throws IOException { + try (FileInputStream stream = new FileInputStream(FILE_PATH + fileName)) { + return new MockMultipartFile(FILE_META_NAME, fileName, CONTENT_TYPE, stream); + } + } + + public static List createMockMultipartFiles() throws IOException { + List files = new ArrayList<>(); + for (int i = 1; i <= 2; i++) { + files.add(createMockMultipartFile(String.format("hello%s.jpg", i))); + } + return files; + } + + public static MultipartFile createEmptyFile() { + return new MockMultipartFile( + "file", + "hello.png", + "image/png", + new byte[] {} + ); + } +} diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/AuthenticationInterceptorTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/AuthenticationInterceptorTest.java new file mode 100644 index 0000000..de8c6aa --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/AuthenticationInterceptorTest.java @@ -0,0 +1,90 @@ +package com.inq.wishhair.wesharewishhair.global.interceptor.interceptor; + +import static com.inq.wishhair.wesharewishhair.global.exception.ErrorCode.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import com.inq.wishhair.wesharewishhair.auth.stub.AuthTokenMangerStub; +import com.inq.wishhair.wesharewishhair.global.exception.WishHairException; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@DisplayName("[AuthenticationInterceptor 테스트] - Global") +class AuthenticationInterceptorTest { + + private static final String AUTHORIZATION = "Authorization"; + + private final AuthenticationInterceptor interceptor; + private final HttpServletRequest mockRequest; + private final HttpServletResponse mockResponse; + + public AuthenticationInterceptorTest() { + mockRequest = Mockito.mock(HttpServletRequest.class); + mockResponse = Mockito.mock(HttpServletResponse.class); + this.interceptor = new AuthenticationInterceptor(new AuthTokenMangerStub()); + } + + @Nested + @DisplayName("[요청에 대해서 인증을 수행한다]") + class preHandle { + + @Test + @DisplayName("[인증에 성공한다]") + void success() { + //given + given(mockRequest.getHeader(AUTHORIZATION)).willReturn("Bearer token"); + + //when + boolean actual = interceptor.preHandle(mockRequest, mockResponse, new Object()); + + //then + assertThat(actual).isTrue(); + } + + @Nested + @DisplayName("[인증에 실패한다]") + class fail { + + @Test + @DisplayName("[인증헤더 값이 존재하지 않아 실패한다]") + void failByNoHeader() { + //given + given(mockRequest.getHeader(AUTHORIZATION)).willReturn(""); + + //when + ThrowingCallable when = () -> interceptor.preHandle( + mockRequest, mockResponse, new Object() + ); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(AUTH_REQUIRED_LOGIN.getMessage()); + } + + @Test + @DisplayName("[인증헤더 값의 형식이 올바르지 않아 실패한다]") + void failByHeaderFormat() { + //given + given(mockRequest.getHeader(AUTHORIZATION)).willReturn("token"); + + //when + ThrowingCallable when = () -> interceptor.preHandle( + mockRequest, mockResponse, new Object() + ); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(AUTH_INVALID_AUTHORIZATION_HEADER.getMessage()); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/PathMatcherContainerTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/PathMatcherContainerTest.java new file mode 100644 index 0000000..8b20468 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/global/interceptor/interceptor/PathMatcherContainerTest.java @@ -0,0 +1,81 @@ +package com.inq.wishhair.wesharewishhair.global.interceptor.interceptor; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.http.HttpMethod.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("[PathMatcherContainer 테스트] - Global") +class PathMatcherContainerTest { + + private final PathMatcherContainer pathMatcherContainer; + + public PathMatcherContainerTest() { + //given + this.pathMatcherContainer = new PathMatcherContainer(); + pathMatcherContainer.includePathPattern("/include/**", GET); + pathMatcherContainer.includePathPattern("/path/include", GET); + + pathMatcherContainer.excludePathPattern("/exclude/**", GET); + pathMatcherContainer.excludePathPattern("/path/exclude", GET); + + pathMatcherContainer.includePathPattern("/exclude/path", GET); + } + + @Nested + @DisplayName("[인터셉터가 적용되어아햐는 요청인지 확인한다]") + class isInterceptorRequired { + + @Test + @DisplayName("[적용되어아야하는 요청으로 true 를 반환한다]") + void returnTrue() { + //when + boolean actual1 = pathMatcherContainer.isInterceptorRequired("/include/path", GET); + boolean actual2 = pathMatcherContainer.isInterceptorRequired("/path/include", GET); + + //then + assertAll( + () -> assertThat(actual1).isTrue(), + () -> assertThat(actual2).isTrue() + ); + } + + @Nested + @DisplayName("[적용되지않아야하는 요청으로 false 를 반환한다]") + class returnFalse { + + @Test + @DisplayName("[include 패턴에 포함되지만 exclude 패턴에도 포함되어서 false 를 반환한다]") + void returnFalse1() { + //when + boolean actual = pathMatcherContainer.isInterceptorRequired("/exclude/path", GET); + + //then + assertThat(actual).isFalse(); + } + + @Test + @DisplayName("[path 가 include 패턴에 포함되지 않아 false 를 반환한다]") + void returnFalse2() { + //when + boolean actual = pathMatcherContainer.isInterceptorRequired("/hello/path", GET); + + //then + assertThat(actual).isFalse(); + } + + @Test + @DisplayName("[메소드가 맞지 않아 false 를 반환한다]") + void returnFalse3() { + //when + boolean actual = pathMatcherContainer.isInterceptorRequired("/include/path", POST); + + //then + assertThat(actual).isFalse(); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/user/application/UserFindServiceTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/user/application/UserFindServiceTest.java new file mode 100644 index 0000000..4dd2858 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/user/application/UserFindServiceTest.java @@ -0,0 +1,108 @@ +package com.inq.wishhair.wesharewishhair.user.application; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.ThrowableAssert.*; +import static org.mockito.BDDMockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import com.inq.wishhair.wesharewishhair.global.exception.ErrorCode; +import com.inq.wishhair.wesharewishhair.global.exception.WishHairException; +import com.inq.wishhair.wesharewishhair.user.domain.UserRepository; +import com.inq.wishhair.wesharewishhair.user.domain.entity.Email; +import com.inq.wishhair.wesharewishhair.user.domain.entity.User; +import com.inq.wishhair.wesharewishhair.user.fixture.UserFixture; + +@DisplayName("[UserFindService 테스트] - Application") +class UserFindServiceTest { + + private final UserFindService userFindService; + private final UserRepository userRepository; + + public UserFindServiceTest() { + this.userRepository = Mockito.mock(UserRepository.class); + this.userFindService = new UserFindService(userRepository); + } + + @Nested + @DisplayName("[id 로 유저를 조회한다]") + class getById { + + private final Long userId = 1L; + + @Test + @DisplayName("[조회에 성공한다]") + void success() { + //given + User user = UserFixture.getFixedManUser(); + given(userRepository.findById(userId)) + .willReturn(Optional.of(user)); + + //when + User actual = userFindService.getById(userId); + + //then + assertThat(actual).isEqualTo(user); + } + + @Test + @DisplayName("[id 에 맞는 유저가 존재하지 않아 실패한다]") + void fail() { + //given + given(userRepository.findById(userId)) + .willReturn(Optional.empty()); + + //when + ThrowingCallable when = () -> userFindService.getById(userId); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.NOT_EXIST_KEY.getMessage()); + } + } + + @Nested + @DisplayName("[email 로 유저를 조회한다]") + class getByEmail { + + private final Email email = new Email("hello@naver.com"); + + @Test + @DisplayName("[조회에 성공한다]") + void success() { + //given + User user = UserFixture.getFixedManUser(); + given(userRepository.findByEmail(email)) + .willReturn(Optional.of(user)); + + //when + User actual = userFindService.getByEmail(email); + + //then + assertThat(actual).isEqualTo(user); + } + + @Test + @DisplayName("[email 에 맞는 유저가 존재하지 않아 실패한다]") + void fail() { + //given + User user = UserFixture.getFixedManUser(); + given(userRepository.findByEmail(email)) + .willReturn(Optional.empty()); + + //when + ThrowingCallable when = () -> userFindService.getByEmail(email); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.USER_NOT_FOUND_BY_EMAIL.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/user/application/UserInfoServiceTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/user/application/UserInfoServiceTest.java new file mode 100644 index 0000000..53ea67c --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/user/application/UserInfoServiceTest.java @@ -0,0 +1,136 @@ +package com.inq.wishhair.wesharewishhair.user.application; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.ArrayList; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Pageable; + +import com.inq.wishhair.wesharewishhair.global.dto.response.PagedResponse; +import com.inq.wishhair.wesharewishhair.hairstyle.domain.hashtag.Tag; +import com.inq.wishhair.wesharewishhair.point.domain.PointLog; +import com.inq.wishhair.wesharewishhair.point.domain.PointLogRepository; +import com.inq.wishhair.wesharewishhair.point.domain.PointType; +import com.inq.wishhair.wesharewishhair.review.application.ReviewSearchService; +import com.inq.wishhair.wesharewishhair.user.application.dto.response.MyPageResponse; +import com.inq.wishhair.wesharewishhair.user.application.dto.response.UserInfo; +import com.inq.wishhair.wesharewishhair.user.application.dto.response.UserInformation; +import com.inq.wishhair.wesharewishhair.user.domain.entity.User; +import com.inq.wishhair.wesharewishhair.user.fixture.UserFixture; + +@ExtendWith(MockitoExtension.class) +@DisplayName("[UserInfoService 테스트] - Application") +class UserInfoServiceTest { + + @InjectMocks + private UserInfoService userInfoService; + @Mock + private UserFindService userFindService; + @Mock + private ReviewSearchService reviewSearchService; + @Mock + private PointLogRepository pointLogRepository; + + @Nested + @DisplayName("[마이페이지 정보를 조회한다]") + class getMyPageInfo { + + @Test + @DisplayName("[포인트 기록이 존재해 잔여 포인트까지 조회한다]") + void hasPointLog() { + //given + User user = UserFixture.getFixedManUser(); + given(userFindService.getById(1L)).willReturn(user); + + PointLog pointLog = PointLog.addPointLog(user, PointType.CHARGE, 1000, 500); + given(pointLogRepository.findByUserOrderByCreatedDateDesc(user)) + .willReturn(Optional.of(pointLog)); + + given(reviewSearchService.findLikingReviews(eq(1L), any(Pageable.class))) + .willReturn(new PagedResponse<>(new ArrayList<>(), null)); + + //when + MyPageResponse actual = userInfoService.getMyPageInfo(1L); + + //then + assertAll( + () -> assertThat(actual.nickname()).isEqualTo(user.getNicknameValue()), + () -> assertThat(actual.point()).isEqualTo(pointLog.getPoint()), + () -> assertThat(actual.reviews()).isEmpty(), + () -> assertThat(actual.sex()).isEqualTo(user.getSex()) + ); + } + + @Test + @DisplayName("[포인트 기록이 존재하지 않아 0 포인트를 조회한다]") + void noPoint() { + //given + User user = UserFixture.getFixedManUser(); + given(userFindService.getById(1L)).willReturn(user); + + given(pointLogRepository.findByUserOrderByCreatedDateDesc(user)) + .willReturn(Optional.empty()); + + given(reviewSearchService.findLikingReviews(eq(1L), any(Pageable.class))) + .willReturn(new PagedResponse<>(new ArrayList<>(), null)); + + //when + MyPageResponse actual = userInfoService.getMyPageInfo(1L); + + //then + assertAll( + () -> assertThat(actual.nickname()).isEqualTo(user.getNicknameValue()), + () -> assertThat(actual.point()).isZero(), + () -> assertThat(actual.reviews()).isEmpty(), + () -> assertThat(actual.sex()).isEqualTo(user.getSex()) + ); + } + } + + @Test + @DisplayName("[얼굴형 정보가 포함된 유저의 간단한 정보를 조회한다]") + void getUserInfo() { + //given + User user = UserFixture.getFixedManUser(); + user.updateFaceShape(Tag.ROUND); + given(userFindService.getById(1L)).willReturn(user); + + //when + UserInfo actual = userInfoService.getUserInfo(1L); + + //then + assertAll( + () -> assertThat(actual.hasFaceShape()).isTrue(), + () -> assertThat(actual.faceShapeTag()).isEqualTo(Tag.ROUND.getDescription()) + ); + } + + @Test + @DisplayName("[유저 상세정보를 조회한다]") + void getUserInformation() { + //given + User user = UserFixture.getFixedManUser(); + given(userFindService.getById(1L)).willReturn(user); + + //when + UserInformation actual = userInfoService.getUserInformation(1L); + + //then + assertAll( + () -> assertThat(actual.email()).isEqualTo(user.getEmailValue()), + () -> assertThat(actual.name()).isEqualTo(user.getName()), + () -> assertThat(actual.nickname()).isEqualTo(user.getNicknameValue()), + () -> assertThat(actual.sex()).isEqualTo(user.getSex()) + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/user/application/UserServiceTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/user/application/UserServiceTest.java new file mode 100644 index 0000000..9723ff3 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/user/application/UserServiceTest.java @@ -0,0 +1,152 @@ +package com.inq.wishhair.wesharewishhair.user.application; + +import static com.inq.wishhair.wesharewishhair.user.fixture.UserFixture.*; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.io.IOException; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.function.Executable; +import org.mockito.BDDMockito; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.multipart.MultipartFile; + +import com.inq.wishhair.wesharewishhair.auth.domain.TokenRepository; +import com.inq.wishhair.wesharewishhair.common.utils.FileMockingUtils; +import com.inq.wishhair.wesharewishhair.global.dto.response.SimpleResponseWrapper; +import com.inq.wishhair.wesharewishhair.hairstyle.domain.hashtag.Tag; +import com.inq.wishhair.wesharewishhair.point.domain.PointLogRepository; +import com.inq.wishhair.wesharewishhair.review.application.ReviewService; +import com.inq.wishhair.wesharewishhair.user.application.utils.UserValidator; +import com.inq.wishhair.wesharewishhair.user.domain.AiConnector; +import com.inq.wishhair.wesharewishhair.user.domain.UserRepository; +import com.inq.wishhair.wesharewishhair.user.domain.entity.Email; +import com.inq.wishhair.wesharewishhair.user.domain.entity.User; +import com.inq.wishhair.wesharewishhair.user.fixture.UserFixture; +import com.inq.wishhair.wesharewishhair.user.presentation.dto.request.UserUpdateRequest; + +@ExtendWith(MockitoExtension.class) +@DisplayName("[UserService 테스트] - Application") +class UserServiceTest { + + @InjectMocks + private UserService userService; + @Mock + private UserRepository userRepository; + @Mock + private UserFindService userFindService; + @Mock + private UserValidator userValidator; + @Mock + private ReviewService reviewService; + @Mock + private TokenRepository tokenRepository; + @Mock + private AiConnector connector; + @Mock + private PointLogRepository pointLogRepository; + + @Test + @DisplayName("[회원가입을 한다]") + void createUser() { + //given + User user = getFixedManUser(); + ReflectionTestUtils.setField(user, "id", 1L); + + given(userRepository.save(any(User.class))) + .willReturn(user); + + //when + Long actual = userService.createUser(getSignUpRequest()); + + //then + assertThat(actual).isEqualTo(1L); + } + + @Test + @DisplayName("[회원탈퇴를 한다]") + void deleteUser() { + //when + Executable when = () -> userService.deleteUser(1L); + + //then + assertDoesNotThrow(when); + } + + @Test + @DisplayName("[비밀번호를 갱신한다(비밀번호 찾기)]") + void refreshPassword() { + //given + User user = getFixedManUser(); + given(userFindService.getByEmail(any(Email.class))) + .willReturn(user); + + //when + Executable when = () -> userService.refreshPassword(getPasswordRefreshRequest()); + + //then + assertDoesNotThrow(when); + } + + @Test + @DisplayName("[회원정보를 변경한다]") + void updateUser() { + //given + Long userId = 1L; + UserUpdateRequest request = getUserUpdateRequest(); + User user = getFixedManUser(); + + given(userFindService.getById(userId)) + .willReturn(user); + + //when + Executable when = () -> userService.updateUser(userId, request); + + //then + assertDoesNotThrow(when); + } + + @Test + @DisplayName("[얼굴형 정보를 업데이트한다]") + void updateFaceShape() throws IOException { + //given + MultipartFile file = FileMockingUtils.createMockMultipartFile("hello2.jpg"); + User user = getFixedManUser(); + Tag tag = Tag.OBLONG; + + given(userFindService.getById(1L)) + .willReturn(user); + given(connector.detectFaceShape(file)) + .willReturn(tag); + + //when + SimpleResponseWrapper actual = userService.updateFaceShape(1L, file); + + //then + assertThat(actual.getResult()).isEqualTo(tag.getDescription()); + } + + @Test + @DisplayName("[비밀번호를 변경한다(비밀번호 변경)]") + void updatePassword() { + //given + User user = getFixedManUser(); + + given(userFindService.getById(1L)) + .willReturn(user); + + //when + Executable when = () -> userService.updatePassword(1L, getPasswordUpdateRequest()); + + //then + assertDoesNotThrow(when); + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/user/application/utils/UserValidatorTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/user/application/utils/UserValidatorTest.java new file mode 100644 index 0000000..b5a25d6 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/user/application/utils/UserValidatorTest.java @@ -0,0 +1,104 @@ +package com.inq.wishhair.wesharewishhair.user.application.utils; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.Mockito; + +import com.inq.wishhair.wesharewishhair.global.exception.ErrorCode; +import com.inq.wishhair.wesharewishhair.global.exception.WishHairException; +import com.inq.wishhair.wesharewishhair.user.domain.UserRepository; +import com.inq.wishhair.wesharewishhair.user.domain.entity.Email; +import com.inq.wishhair.wesharewishhair.user.domain.entity.Nickname; + +@DisplayName("[UserValidator 테스트] - Application") +class UserValidatorTest { + + private final UserValidator userValidator; + private final UserRepository userRepository; + + public UserValidatorTest() { + this.userRepository = Mockito.mock(UserRepository.class); + this.userValidator = new UserValidator(userRepository); + } + + @Nested + @DisplayName("[닉네임 중복 검증을 한다]") + class validateNicknameIsNotDuplicated { + + @Test + @DisplayName("[중복이 아니어서 검증에 통과한다]") + void success() { + //given + Nickname nickname = new Nickname("nickname"); + given(userRepository.existsByNickname(any(Nickname.class))) + .willReturn(false); + + //when + Executable when = () -> userValidator.validateNicknameIsNotDuplicated(nickname); + + //then + assertDoesNotThrow(when); + } + + @Test + @DisplayName("[중복이어서 실패한다]") + void fail() { + //given + Nickname nickname = new Nickname("nickname"); + given(userRepository.existsByNickname(any(Nickname.class))) + .willReturn(true); + + //when + ThrowingCallable when = () -> userValidator.validateNicknameIsNotDuplicated(nickname); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.USER_DUPLICATED_NICKNAME.getMessage()); + } + } + + @Nested + @DisplayName("[이메일 중복 검증을 한다]") + class validateEmailIsNotDuplicated { + + @Test + @DisplayName("[중복이 아니어서 검증에 통과한다]") + void success() { + //given + Email email = new Email("hello@naver.com"); + given(userRepository.existsByEmail(any(Email.class))) + .willReturn(false); + + //when + Executable when = () -> userValidator.validateEmailIsNotDuplicated(email); + + //then + assertDoesNotThrow(when); + } + + @Test + @DisplayName("[중복이어서 실패한다]") + void fail() { + //given + Email email = new Email("hello@naver.com"); + given(userRepository.existsByEmail(any(Email.class))) + .willReturn(true); + + //when + ThrowingCallable when = () -> userValidator.validateEmailIsNotDuplicated(email); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.USER_DUPLICATED_EMAIL.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/user/domain/AiConnectorTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/user/domain/AiConnectorTest.java new file mode 100644 index 0000000..fad423c --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/user/domain/AiConnectorTest.java @@ -0,0 +1,97 @@ +package com.inq.wishhair.wesharewishhair.user.domain; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.ThrowableAssert.*; +import static org.mockito.BDDMockito.*; + +import java.io.IOException; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.multipart.MultipartFile; + +import com.inq.wishhair.wesharewishhair.common.utils.FileMockingUtils; +import com.inq.wishhair.wesharewishhair.global.exception.ErrorCode; +import com.inq.wishhair.wesharewishhair.global.exception.WishHairException; +import com.inq.wishhair.wesharewishhair.hairstyle.domain.hashtag.Tag; +import com.inq.wishhair.wesharewishhair.user.infrastructure.FlaskConnector; + +@DisplayName("[AiConnector 테스트] - Domain") +class AiConnectorTest { + + private static final String DOMAIN = "https://hello_domain/"; + private static final String URL = "/fileupload"; + + private final AiConnector aiConnector; + private final RestTemplate restTemplate; + + public AiConnectorTest() { + this.restTemplate = Mockito.mock(RestTemplate.class); + this.aiConnector = new FlaskConnector(DOMAIN, restTemplate); + } + + @Nested + @DisplayName("[AI 서버를 통해 얼굴형을 탐지한다]") + class detectFaceShape { + + @Test + @DisplayName("[얼굴형 탐지에 성공한다]") + void success() throws IOException { + //given + MultipartFile file = FileMockingUtils.createMockMultipartFile("hello1.jpg"); + Tag tag = Tag.ROUND; + given(restTemplate.postForEntity(eq(DOMAIN + URL), any(), eq(String.class))) + .willReturn(ResponseEntity.ok(tag.name())); + + //when + Tag actual = aiConnector.detectFaceShape(file); + + //then + assertThat(actual).isEqualTo(tag); + } + + @Nested + @DisplayName("[얼굴형 탐지에 실패한다]") + class fail { + + @Test + @DisplayName("[AI 서버와의 통신 오류로 실패한다]") + void failByConnect() throws IOException { + //given + MultipartFile file = FileMockingUtils.createMockMultipartFile("hello1.jpg"); + given(restTemplate.postForEntity(eq(DOMAIN + URL), any(), eq(String.class))) + .willThrow(new RestClientException("msg")); + + //when + ThrowingCallable when = () -> aiConnector.detectFaceShape(file); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.FLASK_SERVER_EXCEPTION.getMessage()); + } + + @Test + @DisplayName("[AI 서버의 실패 응답으로 실패한다]") + void failByFailResponse() throws IOException { + //given + MultipartFile file = FileMockingUtils.createMockMultipartFile("hello1.jpg"); + given(restTemplate.postForEntity(eq(DOMAIN + URL), any(), eq(String.class))) + .willReturn(ResponseEntity.badRequest().build()); + + //when + ThrowingCallable when = () -> aiConnector.detectFaceShape(file); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.FLASK_RESPONSE_ERROR.getMessage()); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/user/domain/entity/UserTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/user/domain/entity/UserTest.java new file mode 100644 index 0000000..ace77ae --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/user/domain/entity/UserTest.java @@ -0,0 +1,126 @@ +package com.inq.wishhair.wesharewishhair.user.domain.entity; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.inq.wishhair.wesharewishhair.global.exception.ErrorCode; +import com.inq.wishhair.wesharewishhair.global.exception.WishHairException; +import com.inq.wishhair.wesharewishhair.hairstyle.domain.hashtag.Tag; +import com.inq.wishhair.wesharewishhair.user.fixture.UserFixture; + +@DisplayName("[User 테스트] - Domain") +class UserTest { + + @Nested + @DisplayName("[User 를 생성한다]") + class of { + + @Test + @DisplayName("[생성에 성공한다]") + void success() { + //given + String email = "hello@naver.com"; + String password = "hello1234@"; + String name = "name"; + String nickname = "hello"; + Sex sex = Sex.WOMAN; + + //when + User user = User.of( + email, + password, + name, + nickname, + sex + ); + + //then + assertAll( + () -> assertThat(user.getName()).isEqualTo(name), + () -> assertThat(user.getNicknameValue()).isEqualTo(nickname), + () -> assertThat(user.getSex()).isEqualTo(sex), + () -> assertThat(user.getFaceShape()).isNull() + ); + } + + @ParameterizedTest(name = "{0}") + @ValueSource(strings = {"hellonaver.com", "hello@navercom", "hello#naver.com"}) + @DisplayName("[이메일 형식이 맞지않아 생성에 실패한다]") + void failByEmail(String email) { + //when + ThrowingCallable when = () -> new Email(email); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.USER_INVALID_EMAIL.getMessage()); + } + + @ParameterizedTest(name = "{0}") + @ValueSource(strings = {"hello", "hello1234"}) + @DisplayName("[비밀번호 형식에 맞지않아 생성에 실패한다]") + void failByPassword(String password) { + //when + ThrowingCallable when = () -> new Password(password); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.USER_INVALID_PASSWORD.getMessage()); + } + + @ParameterizedTest(name = "{0}") + @ValueSource(strings = {"hello man", "hello_man", "h"}) + @DisplayName("[닉네임 형식에 맞지않아 생성에 실패한다]") + void failByNickname(String nickname) { + //when + ThrowingCallable when = () -> new Nickname(nickname); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.USER_INVALID_NICKNAME.getMessage()); + } + } + + @Nested + @DisplayName("[FaceShape 변경한다]") + class updateFaceShape { + + @Test + @DisplayName("[생성에 성공한다]") + void success() { + //given + User user = UserFixture.getFixedManUser(); + + //when + user.updateFaceShape(Tag.ROUND); + + //then + assertThat(user.getFaceShapeTag()).isEqualTo(Tag.ROUND); + } + + @Test + @DisplayName("[얼굴형 태그가 아니어서 실패한다]") + void fail() { + //given + User user = UserFixture.getFixedManUser(); + Tag tag = Tag.BANGS; + + //when + ThrowingCallable when = () -> user.updateFaceShape(tag); + + //then + assertThatThrownBy(when) + .isInstanceOf(WishHairException.class) + .hasMessageContaining(ErrorCode.USER_TAG_MISMATCH.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/user/fixture/UserFixture.java b/src/test/java/com/inq/wishhair/wesharewishhair/user/fixture/UserFixture.java new file mode 100644 index 0000000..22ff69b --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/user/fixture/UserFixture.java @@ -0,0 +1,101 @@ +package com.inq.wishhair.wesharewishhair.user.fixture; + +import java.util.ArrayList; + +import com.inq.wishhair.wesharewishhair.hairstyle.domain.hashtag.Tag; +import com.inq.wishhair.wesharewishhair.user.application.dto.response.MyPageResponse; +import com.inq.wishhair.wesharewishhair.user.application.dto.response.UserInfo; +import com.inq.wishhair.wesharewishhair.user.application.dto.response.UserInformation; +import com.inq.wishhair.wesharewishhair.user.domain.entity.Sex; +import com.inq.wishhair.wesharewishhair.user.domain.entity.User; +import com.inq.wishhair.wesharewishhair.user.presentation.dto.request.PasswordRefreshRequest; +import com.inq.wishhair.wesharewishhair.user.presentation.dto.request.PasswordUpdateRequest; +import com.inq.wishhair.wesharewishhair.user.presentation.dto.request.SignUpRequest; +import com.inq.wishhair.wesharewishhair.user.presentation.dto.request.UserUpdateRequest; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class UserFixture { + + private static final String EMAIL = "hello@naver.com"; + private static final String PW = "hello1234@"; + private static final String NAME = "hello"; + private static final String NICKNAME = "hello"; + + public static User getFixedManUser() { + return User.of( + EMAIL, + PW, + NAME, + NICKNAME, + Sex.MAN + ); + } + + public static User getFixedWomanUser() { + return User.of( + EMAIL, + PW, + NAME, + NICKNAME, + Sex.WOMAN + ); + } + + public static SignUpRequest getSignUpRequest() { + return new SignUpRequest( + EMAIL, + PW, + NAME, + NICKNAME, + Sex.MAN + ); + } + + public static PasswordRefreshRequest getPasswordRefreshRequest() { + return new PasswordRefreshRequest( + EMAIL, + "newPassword1234!" + ); + } + + public static UserUpdateRequest getUserUpdateRequest() { + return new UserUpdateRequest( + "newNick", + Sex.WOMAN + ); + } + + public static PasswordUpdateRequest getPasswordUpdateRequest() { + return new PasswordUpdateRequest( + PW, + "newPassword1234!" + ); + } + + public static MyPageResponse getMyPageResponse() { + return new MyPageResponse( + getFixedManUser(), + new ArrayList<>(), + 1000 + ); + } + + public static UserInformation getUserInformation() { + return new UserInformation( + EMAIL, + NAME, + NICKNAME, + Sex.MAN + ); + } + + public static UserInfo getUserInfo() { + User user = getFixedManUser(); + user.updateFaceShape(Tag.ROUND); + + return new UserInfo(user); + } +} diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/user/presentation/UserControllerTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/user/presentation/UserControllerTest.java new file mode 100644 index 0000000..f68acc6 --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/user/presentation/UserControllerTest.java @@ -0,0 +1,146 @@ +package com.inq.wishhair.wesharewishhair.user.presentation; + +import static com.inq.wishhair.wesharewishhair.common.fixture.AuthFixture.*; +import static org.mockito.BDDMockito.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.web.multipart.MultipartFile; + +import com.inq.wishhair.wesharewishhair.common.support.ApiTestSupport; +import com.inq.wishhair.wesharewishhair.common.utils.FileMockingUtils; +import com.inq.wishhair.wesharewishhair.global.config.SecurityConfig; +import com.inq.wishhair.wesharewishhair.user.application.UserService; +import com.inq.wishhair.wesharewishhair.user.fixture.UserFixture; +import com.inq.wishhair.wesharewishhair.user.presentation.dto.request.PasswordRefreshRequest; +import com.inq.wishhair.wesharewishhair.user.presentation.dto.request.PasswordUpdateRequest; +import com.inq.wishhair.wesharewishhair.user.presentation.dto.request.SignUpRequest; +import com.inq.wishhair.wesharewishhair.user.presentation.dto.request.UserUpdateRequest; + +@WebMvcTest(value = {UserController.class, SecurityConfig.class}) +@DisplayName("[UserController 테스트] - API") +class UserControllerTest extends ApiTestSupport { + + private static final String BASE_URL = "/api/users"; + private static final String PASSWORD_REFRESH_URL = BASE_URL + "/refresh/password"; + private static final String PASSWORD_UPDATE_URL = BASE_URL + "/password"; + private static final String UPDATE_FACE_SHAPE_URL = BASE_URL + "/face_shape"; + + @MockBean + private UserService userService; + + @Test + @DisplayName("[회원가입 API 를 호출한다]") + void signUp() throws Exception { + //given + SignUpRequest request = UserFixture.getSignUpRequest(); + given(userService.createUser(request)).willReturn(1L); + + //then + ResultActions actual = mockMvc.perform( + MockMvcRequestBuilders + .post(BASE_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(toJson(request)) + ); + + //then + actual.andExpect(status().isCreated()); + } + + @Test + @DisplayName("[회원탈퇴 API 를 호출한다]") + void deleteUser() throws Exception { + //then + ResultActions actual = mockMvc.perform( + MockMvcRequestBuilders + .delete(BASE_URL) + .header(AUTHORIZATION, ACCESS_TOKEN) + ); + + //then + actual.andExpect(status().isOk()); + } + + @Test + @DisplayName("[비밀번호 갱신(비밀번호 찾기) API 를 호출한다]") + void refreshPassword() throws Exception { + //given + PasswordRefreshRequest request = UserFixture.getPasswordRefreshRequest(); + + //when + ResultActions actual = mockMvc.perform( + MockMvcRequestBuilders + .patch(PASSWORD_REFRESH_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(toJson(request)) + ); + + //then + actual.andExpect(status().isOk()); + } + + @Test + @DisplayName("[회원정보 수정 API 를 호출한다]") + void updateUser() throws Exception { + //given + UserUpdateRequest request = UserFixture.getUserUpdateRequest(); + + //when + ResultActions actual = mockMvc.perform( + MockMvcRequestBuilders + .patch(BASE_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(toJson(request)) + .header(AUTHORIZATION, ACCESS_TOKEN) + ); + + //then + actual.andExpect(status().isOk()); + } + + @Test + @DisplayName("[비밀번호 변경 API 를 호출한다]") + void updatePassword() throws Exception { + //given + PasswordUpdateRequest request = UserFixture.getPasswordUpdateRequest(); + + //when + ResultActions actual = mockMvc.perform( + MockMvcRequestBuilders + .patch(PASSWORD_UPDATE_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(toJson(request)) + .header(AUTHORIZATION, ACCESS_TOKEN) + ); + + //then + actual.andExpect(status().isOk()); + } + + @Test + @DisplayName("[얼굴형 정보 업데이트 API 를 호출한다]") + void updateFaceShape() throws Exception { + //given + MultipartFile file = FileMockingUtils.createMockMultipartFile("hello1.jpg"); + + //when + ResultActions actual = mockMvc.perform( + MockMvcRequestBuilders + .multipart(HttpMethod.PATCH, UPDATE_FACE_SHAPE_URL) + .file((MockMultipartFile)file) + .header(AUTHORIZATION, ACCESS_TOKEN) + ); + + //then + actual.andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/src/test/java/com/inq/wishhair/wesharewishhair/user/presentation/UserInfoControllerTest.java b/src/test/java/com/inq/wishhair/wesharewishhair/user/presentation/UserInfoControllerTest.java new file mode 100644 index 0000000..90c47cb --- /dev/null +++ b/src/test/java/com/inq/wishhair/wesharewishhair/user/presentation/UserInfoControllerTest.java @@ -0,0 +1,107 @@ +package com.inq.wishhair.wesharewishhair.user.presentation; + +import static com.inq.wishhair.wesharewishhair.common.fixture.AuthFixture.*; +import static org.mockito.BDDMockito.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import com.inq.wishhair.wesharewishhair.common.support.ApiTestSupport; +import com.inq.wishhair.wesharewishhair.global.config.SecurityConfig; +import com.inq.wishhair.wesharewishhair.user.application.UserInfoService; +import com.inq.wishhair.wesharewishhair.user.application.dto.response.MyPageResponse; +import com.inq.wishhair.wesharewishhair.user.application.dto.response.UserInfo; +import com.inq.wishhair.wesharewishhair.user.application.dto.response.UserInformation; +import com.inq.wishhair.wesharewishhair.user.fixture.UserFixture; + +@WebMvcTest(value = {UserInfoController.class, SecurityConfig.class}) +@DisplayName("[UserInfoController 테스트] - API") +class UserInfoControllerTest extends ApiTestSupport { + + private static final String BASE_URL = "/api/users"; + private static final String MY_PAGE_URL = BASE_URL + "/my_page"; + private static final String USER_INFORMATION = BASE_URL + "/info"; + private static final String USER_HOME_INFO = BASE_URL + "/home_info"; + + @MockBean + private UserInfoService userInfoService; + + @Test + @DisplayName("[마이페이지 정보 조회 API 를 호출한다]") + void getMyPageInfo() throws Exception { + //given + MyPageResponse response = UserFixture.getMyPageResponse(); + given(userInfoService.getMyPageInfo(1L)) + .willReturn(response); + + //when + ResultActions actual = mockMvc.perform( + MockMvcRequestBuilders + .get(MY_PAGE_URL) + .header(AUTHORIZATION, ACCESS_TOKEN) + ); + + //then + actual.andExpectAll( + status().isOk(), + jsonPath("$.nickname").value(response.nickname()), + jsonPath("$.sex").value(response.sex().name()), + jsonPath("$.point").value(response.point()), + jsonPath("$.reviews.size()").value(0) + ); + } + + @Test + @DisplayName("[회원정보 조회 API 를 호출한다]") + void getUserInformation() throws Exception { + //given + UserInformation response = UserFixture.getUserInformation(); + given(userInfoService.getUserInformation(1L)) + .willReturn(response); + + //when + ResultActions actual = mockMvc.perform( + MockMvcRequestBuilders + .get(USER_INFORMATION) + .header(AUTHORIZATION, ACCESS_TOKEN) + ); + + //then + actual.andExpectAll( + status().isOk(), + jsonPath("$.email").value(response.email()), + jsonPath("$.name").value(response.name()), + jsonPath("$.nickname").value(response.nickname()), + jsonPath("$.sex").value(response.sex().name()) + ); + } + + @Test + @DisplayName("[홈페이지 회원정보 조회 API 를 호출한다]") + void getUserInfo() throws Exception { + //given + UserInfo response = UserFixture.getUserInfo(); + given(userInfoService.getUserInfo(1L)) + .willReturn(response); + + //when + ResultActions actual = mockMvc.perform( + MockMvcRequestBuilders + .get(USER_HOME_INFO) + .header(AUTHORIZATION, ACCESS_TOKEN) + ); + + //then + actual.andExpectAll( + status().isOk(), + jsonPath("$.nickname").value(response.nickname()), + jsonPath("$.faceShapeTag").value(response.faceShapeTag()), + jsonPath("$.hasFaceShape").value(response.hasFaceShape()) + ); + } +} \ No newline at end of file diff --git a/src/test/resources/images/hello1.jpg b/src/test/resources/images/hello1.jpg new file mode 100644 index 0000000..4a5dc1f Binary files /dev/null and b/src/test/resources/images/hello1.jpg differ diff --git a/src/test/resources/images/hello2.jpg b/src/test/resources/images/hello2.jpg new file mode 100644 index 0000000..8a9c1ab Binary files /dev/null and b/src/test/resources/images/hello2.jpg differ diff --git a/src/test/resources/images/hello3.png b/src/test/resources/images/hello3.png new file mode 100644 index 0000000..3bbcf7f Binary files /dev/null and b/src/test/resources/images/hello3.png differ