diff --git a/build.gradle b/build.gradle index 0aeecfa8..ec75f2ce 100644 --- a/build.gradle +++ b/build.gradle @@ -69,6 +69,7 @@ dependencies { // Redis 설정 implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.data:spring-data-redis' // JWT 설정 implementation 'io.jsonwebtoken:jjwt:0.9.1' @@ -89,7 +90,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-aop' // redisson 설정 - implementation 'org.redisson:redisson-spring-boot-starter:3.17.0' + implementation 'org.redisson:redisson:3.16.2' // cool sms 설정 implementation 'net.nurigo:sdk:4.3.0' diff --git a/scripts/after-install.sh b/scripts/after-install.sh index 0bd07079..be6c2605 100644 --- a/scripts/after-install.sh +++ b/scripts/after-install.sh @@ -22,4 +22,4 @@ if [ -f "$JAR_FILE" ]; then else echo "JAR file not found: $JAR_FILE" exit 1 -fi +fi \ No newline at end of file diff --git a/src/main/java/com/softeer/backend/fo_domain/comment/controller/CommentController.java b/src/main/java/com/softeer/backend/fo_domain/comment/controller/CommentController.java index 7c776317..cd91a1a7 100644 --- a/src/main/java/com/softeer/backend/fo_domain/comment/controller/CommentController.java +++ b/src/main/java/com/softeer/backend/fo_domain/comment/controller/CommentController.java @@ -1,6 +1,7 @@ package com.softeer.backend.fo_domain.comment.controller; import com.softeer.backend.fo_domain.comment.dto.CommentsResponseDto; +import com.softeer.backend.fo_domain.comment.dto.CommentsResponsePageDto; import com.softeer.backend.fo_domain.comment.exception.CommentException; import com.softeer.backend.fo_domain.comment.service.CommentService; import com.softeer.backend.global.annotation.AuthInfo; @@ -39,6 +40,14 @@ ResponseDto getComment(@RequestParam(name = "cursor", requi return ResponseDto.onSuccess(commentsResponseDto); } + @GetMapping("/comment/page") + ResponseDto getCommentByPage(@RequestParam(name = "page", defaultValue = "0") Integer page) { + + CommentsResponsePageDto commentsResponsePageDto = commentService.getCommentsByPage(page); + + return ResponseDto.onSuccess(commentsResponsePageDto); + } + /** * 기대평을 등록하는 메서드 */ @@ -57,4 +66,13 @@ ResponseDto saveComment(@RequestParam(name = "commentType") Integer commen return ResponseDto.onSuccess(); } + + @PostMapping("/comment/test") + ResponseDto saveCommentTest(@RequestParam(name = "num") Integer num) { + + commentService.saveCommentTest(num); + + return ResponseDto.onSuccess(); + + } } diff --git a/src/main/java/com/softeer/backend/fo_domain/comment/dto/CommentsResponsePageDto.java b/src/main/java/com/softeer/backend/fo_domain/comment/dto/CommentsResponsePageDto.java new file mode 100644 index 00000000..955925dd --- /dev/null +++ b/src/main/java/com/softeer/backend/fo_domain/comment/dto/CommentsResponsePageDto.java @@ -0,0 +1,48 @@ +package com.softeer.backend.fo_domain.comment.dto; + +import com.softeer.backend.fo_domain.comment.domain.Comment; +import lombok.*; + +import java.util.List; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PUBLIC) +@Builder +@Getter +public class CommentsResponsePageDto { + + + private int nextPage; + + private int totalComments; + + private List comments; + + @Getter + @AllArgsConstructor + @Builder + public static class CommentPageResponse { + + private Boolean isMine; + + private String nickName; + + private int commentType; + } + + public static CommentsResponsePageDto of(List comments, int nextPage) { + List commentPageResponseList = comments.stream() + .map((comment) -> CommentPageResponse.builder() + .isMine(false) + .nickName(comment.getNickname()) + .commentType(comment.getCommentType()) + .build()) + .toList(); + + return CommentsResponsePageDto.builder() + .nextPage(nextPage) + .totalComments(comments.size()) + .comments(commentPageResponseList) + .build(); + } +} diff --git a/src/main/java/com/softeer/backend/fo_domain/comment/repository/CommentRepository.java b/src/main/java/com/softeer/backend/fo_domain/comment/repository/CommentRepository.java index 941c7334..2d761014 100644 --- a/src/main/java/com/softeer/backend/fo_domain/comment/repository/CommentRepository.java +++ b/src/main/java/com/softeer/backend/fo_domain/comment/repository/CommentRepository.java @@ -12,5 +12,7 @@ @Repository public interface CommentRepository extends JpaRepository { - Page findAllByIdLessThanOrderByIdDesc(Integer id, Pageable pageable); + Page findAllByIdLessThanEqualOrderByIdDesc(Integer id, Pageable pageable); + + Page findAllByOrderByIdDesc(Pageable pageable); } diff --git a/src/main/java/com/softeer/backend/fo_domain/comment/service/CommentService.java b/src/main/java/com/softeer/backend/fo_domain/comment/service/CommentService.java index d97674e5..cad95bfc 100644 --- a/src/main/java/com/softeer/backend/fo_domain/comment/service/CommentService.java +++ b/src/main/java/com/softeer/backend/fo_domain/comment/service/CommentService.java @@ -3,15 +3,18 @@ import com.softeer.backend.fo_domain.comment.constant.CommentNickname; import com.softeer.backend.fo_domain.comment.domain.Comment; import com.softeer.backend.fo_domain.comment.dto.CommentsResponseDto; +import com.softeer.backend.fo_domain.comment.dto.CommentsResponsePageDto; import com.softeer.backend.fo_domain.comment.repository.CommentRepository; import com.softeer.backend.fo_domain.comment.util.ScrollPaginationUtil; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Random; /** * 기대평 요청을 처리하는 클래스 @@ -20,9 +23,11 @@ @RequiredArgsConstructor public class CommentService { private static final int SCROLL_SIZE = 30; + public static final int LAST_PAGE = -1; private final CommentRepository commentRepository; + /** * SCROLL_SIZE 만큼의 기대평을 반환하는 메서드 *

@@ -40,6 +45,17 @@ public CommentsResponseDto getComments(Integer userId, Integer cursor) { return CommentsResponseDto.of(commentCursor, userId); } + @Transactional(readOnly = true) + public CommentsResponsePageDto getCommentsByPage(int page) { + + Pageable pageable = PageRequest.of(page, SCROLL_SIZE); + Page commentPage = commentRepository.findAllByOrderByIdDesc(pageable); + + List comments = commentPage.getContent(); + + return CommentsResponsePageDto.of(comments, commentPage.hasNext() ? commentPage.getNumber() + 1 : LAST_PAGE); + } + /** * 기대평을 저장하는 메서드 *

@@ -59,4 +75,21 @@ public void saveComment(Integer userId, int commentType) { .build() ); } + + @Transactional + public void saveCommentTest(int num){ + + int i=0; + while(i getFcfsTutorialPage() { return ResponseDto.onSuccess(fcfsPageResponseDto); } + @PostMapping("/insert") + public ResponseDto insertFcfsCode(@RequestParam("num") Integer num, @RequestParam("round") Integer round){ + fcfsService.insertFcfsCode(num, round); + + return ResponseDto.onSuccess(); + } + /** * 선착순 등록을 처리하는 메서드 */ @PostMapping public ResponseDto handleFcfs(@Parameter(hidden = true) HttpServletRequest request, @Parameter(hidden = true) @AuthInfo Integer userId, - @RequestBody FcfsRequestDto fcfsRequestDto) { + @RequestBody FcfsRequestDto fcfsRequestDto) throws JsonProcessingException { int round = (Integer) request.getAttribute("round"); - FcfsResultResponseDto fcfsResultResponseDto = fcfsService.handleFcfsEvent(userId, round, fcfsRequestDto); + FcfsResultResponseDto fcfsResultResponseDto = fcfsService.handleFcfs(userId, round, fcfsRequestDto); return ResponseDto.onSuccess(fcfsResultResponseDto); } diff --git a/src/main/java/com/softeer/backend/fo_domain/fcfs/domain/Fcfs.java b/src/main/java/com/softeer/backend/fo_domain/fcfs/domain/Fcfs.java index f11e8b38..21a8acb2 100644 --- a/src/main/java/com/softeer/backend/fo_domain/fcfs/domain/Fcfs.java +++ b/src/main/java/com/softeer/backend/fo_domain/fcfs/domain/Fcfs.java @@ -20,7 +20,6 @@ @AllArgsConstructor @Getter @Builder -@EntityListeners(AuditingEntityListener.class) @Table(name = "fcfs") public class Fcfs { diff --git a/src/main/java/com/softeer/backend/fo_domain/fcfs/interceptor/FcfsTimeCheckInterceptor.java b/src/main/java/com/softeer/backend/fo_domain/fcfs/interceptor/FcfsTimeCheckInterceptor.java index 332f268d..6fb69615 100644 --- a/src/main/java/com/softeer/backend/fo_domain/fcfs/interceptor/FcfsTimeCheckInterceptor.java +++ b/src/main/java/com/softeer/backend/fo_domain/fcfs/interceptor/FcfsTimeCheckInterceptor.java @@ -3,7 +3,6 @@ import com.softeer.backend.fo_domain.fcfs.exception.FcfsException; import com.softeer.backend.fo_domain.fcfs.service.FcfsSettingManager; import com.softeer.backend.global.common.code.status.ErrorStatus; -import io.micrometer.core.ipc.http.HttpSender; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/softeer/backend/fo_domain/fcfs/repository/FcfsRepository.java b/src/main/java/com/softeer/backend/fo_domain/fcfs/repository/FcfsRepository.java index 94ed7d7e..e349ae34 100644 --- a/src/main/java/com/softeer/backend/fo_domain/fcfs/repository/FcfsRepository.java +++ b/src/main/java/com/softeer/backend/fo_domain/fcfs/repository/FcfsRepository.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Optional; /** * 선착순 등록 정보 entity repository 클래스 @@ -24,4 +25,8 @@ public interface FcfsRepository extends JpaRepository { List findByUserIdOrderByWinningDateAsc(Integer userId); + Optional findByUserIdAndRound(Integer userId, int round); + + Optional findByCode(String code); + } diff --git a/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsHandler/FcfsHandler.java b/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsHandler/FcfsHandler.java new file mode 100644 index 00000000..07abd9c1 --- /dev/null +++ b/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsHandler/FcfsHandler.java @@ -0,0 +1,11 @@ +package com.softeer.backend.fo_domain.fcfs.service.FcfsHandler; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.softeer.backend.fo_domain.fcfs.dto.FcfsRequestDto; +import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsResultResponseDto; + +public interface FcfsHandler { + + public FcfsResultResponseDto handleFcfsEvent(int userId, int round, FcfsRequestDto fcfsRequestDto) throws JsonProcessingException; + +} diff --git a/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsHandler/FcfsHandlerByLuaScript.java b/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsHandler/FcfsHandlerByLuaScript.java new file mode 100644 index 00000000..a8d6d9a0 --- /dev/null +++ b/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsHandler/FcfsHandlerByLuaScript.java @@ -0,0 +1,219 @@ +package com.softeer.backend.fo_domain.fcfs.service.FcfsHandler; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.softeer.backend.fo_domain.draw.service.DrawSettingManager; +import com.softeer.backend.fo_domain.fcfs.domain.Fcfs; +import com.softeer.backend.fo_domain.fcfs.dto.FcfsRequestDto; +import com.softeer.backend.fo_domain.fcfs.dto.FcfsSettingDto; +import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsFailResult; +import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsResultResponseDto; +import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsSuccessResult; +import com.softeer.backend.fo_domain.fcfs.exception.FcfsException; +import com.softeer.backend.fo_domain.fcfs.repository.FcfsRepository; +import com.softeer.backend.fo_domain.fcfs.service.FcfsSettingManager; +import com.softeer.backend.fo_domain.fcfs.service.QuizManager; +import com.softeer.backend.fo_domain.fcfs.service.test.FcfsCount; +import com.softeer.backend.fo_domain.fcfs.service.test.FcfsCountRepository; +import com.softeer.backend.fo_domain.fcfs.service.test.LuaRedisUtil; +import com.softeer.backend.fo_domain.user.domain.User; +import com.softeer.backend.fo_domain.user.repository.UserRepository; +import com.softeer.backend.global.common.code.status.ErrorStatus; +import com.softeer.backend.global.common.constant.RedisKeyPrefix; +import com.softeer.backend.global.staticresources.constant.S3FileName; +import com.softeer.backend.global.staticresources.constant.StaticTextName; +import com.softeer.backend.global.staticresources.util.StaticResourceUtil; +import com.softeer.backend.global.util.FcfsRedisUtil; +import com.softeer.backend.global.util.RandomCodeUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@Component +@RequiredArgsConstructor +public class FcfsHandlerByLuaScript implements FcfsHandler { + + private final LuaRedisUtil luaRedisUtil; + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("M월 d일"); + private final ObjectProvider fcfsHandlerProvider; + + private final FcfsSettingManager fcfsSettingManager; + private final DrawSettingManager drawSettingManager; + private final QuizManager quizManager; + private final FcfsRedisUtil fcfsRedisUtil; + private final RandomCodeUtil randomCodeUtil; + private final StaticResourceUtil staticResourceUtil; + + + /** + * 선착순 등록을 처리하고 결과 모달 정보를 반환하는 메서드 + *

+ * 1. 선착순 등록 요청 dto에서 전달된 퀴즈 정답이 유효한지 확인한다. + * 1-1. 유효하지 않다면 예외가 발생한다. + * 2. 선착순 설정 매니저 클래스의 fcfsClosed 변수값을 확인한다.(선착순 당첨자가 다 나왔는지 여부를 의미) + * 2-1. 값이 true라면 선착순 이벤트 참여자 수에 1을 더하고 실패 모달 정보를 반환한다. + * 2-2. 값이 false라면 선착순 등록을 처리하는 메서드를 호출한다. + */ + @Override + @Transactional(readOnly = true) + public FcfsResultResponseDto handleFcfsEvent(int userId, int round, FcfsRequestDto fcfsRequestDto) throws JsonProcessingException { + + // 퀴즈 정답이 유효한지 확인하고 유효하지 않다면 예외 발생 + if (!fcfsRequestDto.getAnswer().equals(quizManager.getQuiz(round).getAnswerWord())) { + log.error("fcfs quiz answer is not match, correct answer: {}, wrong anwer: {}", + quizManager.getQuiz(round).getAnswerWord(), fcfsRequestDto.getAnswer()); + throw new FcfsException(ErrorStatus._BAD_REQUEST); + } + + // 선착순 당첨자가 다 나왔다면 선착순 이벤트 참여자 수에 1을 더하는 메서드를 호출하고 실패 모달 정보를 반환 + if (fcfsSettingManager.isFcfsClosed()) { + countFcfsParticipant(round); + + return getFcfsResult(false, false, null); + } + + // 선착순 등록을 처리하는 메서드 호출 + FcfsHandlerByLuaScript fcfsHandlerByLuaScript = fcfsHandlerProvider.getObject(); + return fcfsHandlerByLuaScript.saveFcfsWinners(userId, round); + } + + + public FcfsResultResponseDto saveFcfsWinners(int userId, int round) throws JsonProcessingException { + + int maxWinners = fcfsSettingManager.getFcfsWinnerNum(); + + // Define the Redis keys + String userIdSetKey = RedisKeyPrefix.FCFS_USERID_PREFIX.getPrefix() + round; + String codeSetKey = RedisKeyPrefix.FCFS_CODE_PREFIX.getPrefix() + round; + String codeUserIdHashKey = RedisKeyPrefix.FCFS_CODE_USERID_PREFIX.getPrefix() + round; + String participantCountKey = RedisKeyPrefix.FCFS_PARTICIPANT_COUNT_PREFIX.getPrefix() + round; + + String result = luaRedisUtil.executeFcfsScript( + userIdSetKey, + codeSetKey, + codeUserIdHashKey, + participantCountKey, + userId, + maxWinners); + + switch (result) { + case "FCFS_CLOSED": + case "FCFS_CODE_EMPTY": + + return getFcfsResult(false, false, null); + + case "DUPLICATED": + + return getFcfsResult(false, true, null); + + default: + String code = result; + if (result.contains("_CLOSED")) { + fcfsSettingManager.setFcfsClosed(true); + code = result.replace("_CLOSED", ""); + } + + return getFcfsResult(true, false, code); + } + + } + + + /** + * 선착순 이벤트 코드를 반환하는 메서드 + *

+ * round값에 따라 코드의 앞부분을 특정 문자로 고정한다. + */ + private String makeFcfsCode(int round) { + return (char) ('A' + round - 1) + randomCodeUtil.generateRandomCode(5); + } + + /** + * redis에 저장된 선착순 이벤트 참여자 수를 1만큼 늘리는 메서드 + */ + private void countFcfsParticipant(int round) { + fcfsRedisUtil.incrementValue(RedisKeyPrefix.FCFS_PARTICIPANT_COUNT_PREFIX.getPrefix() + round); + } + + /** + * 선착순 결과 모달 응답 Dto를 만들어서 반환하는 메서드 + */ + public FcfsResultResponseDto getFcfsResult(boolean fcfsWin, boolean isDuplicated, String fcfsCode) { + + FcfsSettingDto firstFcfsSetting = fcfsSettingManager.getFcfsSettingByRound(1); + + FcfsHandlerByLuaScript fcfsHandlerByLuaScript = fcfsHandlerProvider.getObject(); + + if (fcfsWin) { + FcfsSuccessResult fcfsSuccessResult = fcfsHandlerByLuaScript.getFcfsSuccessResult(firstFcfsSetting); + fcfsSuccessResult.setFcfsCode(fcfsCode); + + return FcfsResultResponseDto.builder() + .fcfsWinner(fcfsWin) + .fcfsResult(fcfsSuccessResult) + .build(); + } + + FcfsFailResult fcfsFailResult = fcfsHandlerByLuaScript.getFcfsFailResult(isDuplicated); + + return FcfsResultResponseDto.builder() + .fcfsWinner(fcfsWin) + .fcfsResult(fcfsFailResult) + .build(); + } + + /** + * 선착순 당첨 모달 정보 중, 정적 정보를 반환하는 메서드 + */ + @Cacheable(value = "staticResources", key = "'fcfsSuccess'") + public FcfsSuccessResult getFcfsSuccessResult(FcfsSettingDto firstFcfsSetting) { + + Map textContentMap = staticResourceUtil.getTextContentMap(); + Map s3ContentMap = staticResourceUtil.getS3ContentMap(); + + return FcfsSuccessResult.builder() + .title(staticResourceUtil.format(textContentMap.get(StaticTextName.FCFS_WINNER_TITLE.name()), + fcfsSettingManager.getFcfsWinnerNum())) + .subTitle(textContentMap.get(StaticTextName.FCFS_WINNER_SUBTITLE.name())) + .qrCode(s3ContentMap.get(S3FileName.BARCODE_IMAGE.name())) + .codeWord(textContentMap.get(StaticTextName.FCFS_WINNER_CODE_WORD.name())) + .expirationDate(staticResourceUtil.format(textContentMap.get(StaticTextName.FCFS_WINNER_EXPIRY_DATE.name()), + firstFcfsSetting.getStartTime().getYear(), + firstFcfsSetting.getStartTime().format(dateFormatter), + drawSettingManager.getEndDate().plusDays(14).format(dateFormatter))) + .caution(textContentMap.get(StaticTextName.FCFS_WINNER_CAUTION.name())) + .build(); + } + + /** + * 선착순 실패 모달 정보 중, 정적 정보를 반환하는 메서드 + */ + @Cacheable(value = "staticResources", key = "'fcfsFail_' + #isDuplicated") + public FcfsFailResult getFcfsFailResult(boolean isDuplicated) { + Map textContentMap = staticResourceUtil.getTextContentMap(); + + if (isDuplicated) { + return FcfsFailResult.builder() + .title(textContentMap.get(StaticTextName.FCFS_DUPLICATED_TITLE.name())) + .subTitle(textContentMap.get(StaticTextName.FCFS_DUPLICATED_SUBTITLE.name())) + .caution(textContentMap.get(StaticTextName.FCFS_LOSER_CAUTION.name())) + .build(); + } + return FcfsFailResult.builder() + .title(textContentMap.get(StaticTextName.FCFS_LOSER_TITLE.name())) + .subTitle(textContentMap.get(StaticTextName.FCFS_LOSER_SUBTITLE.name())) + .caution(textContentMap.get(StaticTextName.FCFS_LOSER_CAUTION.name())) + .build(); + } +} + + diff --git a/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsHandler/FcfsHandlerByPessimisticLock.java b/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsHandler/FcfsHandlerByPessimisticLock.java new file mode 100644 index 00000000..439a4afe --- /dev/null +++ b/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsHandler/FcfsHandlerByPessimisticLock.java @@ -0,0 +1,216 @@ +package com.softeer.backend.fo_domain.fcfs.service.FcfsHandler; + +import com.softeer.backend.fo_domain.draw.service.DrawSettingManager; +import com.softeer.backend.fo_domain.fcfs.domain.Fcfs; +import com.softeer.backend.fo_domain.fcfs.dto.FcfsRequestDto; +import com.softeer.backend.fo_domain.fcfs.dto.FcfsSettingDto; +import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsFailResult; +import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsResultResponseDto; +import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsSuccessResult; +import com.softeer.backend.fo_domain.fcfs.exception.FcfsException; +import com.softeer.backend.fo_domain.fcfs.repository.FcfsRepository; +import com.softeer.backend.fo_domain.fcfs.service.FcfsSettingManager; +import com.softeer.backend.fo_domain.fcfs.service.QuizManager; +import com.softeer.backend.fo_domain.fcfs.service.test.FcfsCount; +import com.softeer.backend.fo_domain.fcfs.service.test.FcfsCountRepository; +import com.softeer.backend.fo_domain.user.domain.User; +import com.softeer.backend.fo_domain.user.repository.UserRepository; +import com.softeer.backend.global.common.code.status.ErrorStatus; +import com.softeer.backend.global.common.constant.RedisKeyPrefix; +import com.softeer.backend.global.staticresources.constant.S3FileName; +import com.softeer.backend.global.staticresources.constant.StaticTextName; +import com.softeer.backend.global.staticresources.util.StaticResourceUtil; +import com.softeer.backend.global.util.FcfsRedisUtil; +import com.softeer.backend.global.util.RandomCodeUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.time.format.DateTimeFormatter; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@Component +@RequiredArgsConstructor +@Primary +public class FcfsHandlerByPessimisticLock implements FcfsHandler { + + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("M월 d일"); + private final ObjectProvider fcfsHandlerProvider; + + private final FcfsSettingManager fcfsSettingManager; + private final DrawSettingManager drawSettingManager; + private final QuizManager quizManager; + private final FcfsRedisUtil fcfsRedisUtil; + private final RandomCodeUtil randomCodeUtil; + private final StaticResourceUtil staticResourceUtil; + private final FcfsCountRepository fcfsCountRepository; + private final FcfsRepository fcfsRepository; + private final UserRepository userRepository; + + + /** + * 선착순 등록을 처리하고 결과 모달 정보를 반환하는 메서드 + *

+ * 1. 선착순 등록 요청 dto에서 전달된 퀴즈 정답이 유효한지 확인한다. + * 1-1. 유효하지 않다면 예외가 발생한다. + * 2. 선착순 설정 매니저 클래스의 fcfsClosed 변수값을 확인한다.(선착순 당첨자가 다 나왔는지 여부를 의미) + * 2-1. 값이 true라면 선착순 이벤트 참여자 수에 1을 더하고 실패 모달 정보를 반환한다. + * 2-2. 값이 false라면 선착순 등록을 처리하는 메서드를 호출한다. + */ + @Override + @Transactional + public FcfsResultResponseDto handleFcfsEvent(int userId, int round, FcfsRequestDto fcfsRequestDto) { + + // 퀴즈 정답이 유효한지 확인하고 유효하지 않다면 예외 발생 + if (!fcfsRequestDto.getAnswer().equals(quizManager.getQuiz(round).getAnswerWord())) { + log.error("fcfs quiz answer is not match, correct answer: {}, wrong anwer: {}", + quizManager.getQuiz(round).getAnswerWord(), fcfsRequestDto.getAnswer()); + throw new FcfsException(ErrorStatus._BAD_REQUEST); + } + + // 선착순 당첨자가 다 나왔다면 선착순 이벤트 참여자 수에 1을 더하는 메서드를 호출하고 실패 모달 정보를 반환 + if (fcfsSettingManager.isFcfsClosed()) { + countFcfsParticipant(round); + + return getFcfsResult(false, false, null); + } + + // 선착순 등록을 처리하는 메서드 호출 + FcfsHandlerByPessimisticLock fcfsHandlerByPessimisticLock = fcfsHandlerProvider.getObject(); + return fcfsHandlerByPessimisticLock.saveFcfsWinners(userId, round); + } + + + public FcfsResultResponseDto saveFcfsWinners(int userId, int round) { + + FcfsCount fcfsCount = fcfsCountRepository.findByRound(round) + .orElseThrow(() -> new IllegalArgumentException("Round not found")); + + int fcfsNum = fcfsCount.getFcfsNum(); + + if(fcfsNum < fcfsSettingManager.getFcfsWinnerNum()){ + Optional fcfs = fcfsRepository.findByUserIdAndRound(userId, round); + if(fcfs.isPresent()){ + return getFcfsResult(false, true, null); + } + + User user = userRepository.findById(userId).orElseThrow(()-> new IllegalArgumentException("User not found")); + + String code = makeFcfsCode(round); + while (fcfsRepository.findByCode(code).isPresent()) { + code = makeFcfsCode(round); + } + + fcfsRepository.save(Fcfs.builder() + .user(user) + .round(round) + .code(code) + .winningDate(fcfsSettingManager.getFcfsSettingByRound(round).getStartTime().toLocalDate()) + .build()); + + fcfsCount.setFcfsNum(fcfsCount.getFcfsNum()+1); + + return getFcfsResult(true, false, code); + } + + return getFcfsResult(false, false, null); + + + } + + /** + * 선착순 이벤트 코드를 반환하는 메서드 + * + * round값에 따라 코드의 앞부분을 특정 문자로 고정한다. + */ + private String makeFcfsCode(int round) { + return (char) ('A' + round - 1) + randomCodeUtil.generateRandomCode(5); + } + + /** + * redis에 저장된 선착순 이벤트 참여자 수를 1만큼 늘리는 메서드 + */ + private void countFcfsParticipant(int round) { + fcfsRedisUtil.incrementValue(RedisKeyPrefix.FCFS_PARTICIPANT_COUNT_PREFIX.getPrefix() + round); + } + + /** + * 선착순 결과 모달 응답 Dto를 만들어서 반환하는 메서드 + */ + public FcfsResultResponseDto getFcfsResult(boolean fcfsWin, boolean isDuplicated, String fcfsCode) { + + FcfsSettingDto firstFcfsSetting = fcfsSettingManager.getFcfsSettingByRound(1); + + FcfsHandlerByPessimisticLock fcfsHandlerByPessimisticLock = fcfsHandlerProvider.getObject(); + + if (fcfsWin) { + FcfsSuccessResult fcfsSuccessResult = fcfsHandlerByPessimisticLock.getFcfsSuccessResult(firstFcfsSetting); + fcfsSuccessResult.setFcfsCode(fcfsCode); + + return FcfsResultResponseDto.builder() + .fcfsWinner(fcfsWin) + .fcfsResult(fcfsSuccessResult) + .build(); + } + + FcfsFailResult fcfsFailResult = fcfsHandlerByPessimisticLock.getFcfsFailResult(isDuplicated); + + return FcfsResultResponseDto.builder() + .fcfsWinner(fcfsWin) + .fcfsResult(fcfsFailResult) + .build(); + } + + /** + * 선착순 당첨 모달 정보 중, 정적 정보를 반환하는 메서드 + */ + @Cacheable(value = "staticResources", key = "'fcfsSuccess'") + public FcfsSuccessResult getFcfsSuccessResult(FcfsSettingDto firstFcfsSetting) { + + Map textContentMap = staticResourceUtil.getTextContentMap(); + Map s3ContentMap = staticResourceUtil.getS3ContentMap(); + + return FcfsSuccessResult.builder() + .title(staticResourceUtil.format(textContentMap.get(StaticTextName.FCFS_WINNER_TITLE.name()), + fcfsSettingManager.getFcfsWinnerNum())) + .subTitle(textContentMap.get(StaticTextName.FCFS_WINNER_SUBTITLE.name())) + .qrCode(s3ContentMap.get(S3FileName.BARCODE_IMAGE.name())) + .codeWord(textContentMap.get(StaticTextName.FCFS_WINNER_CODE_WORD.name())) + .expirationDate(staticResourceUtil.format(textContentMap.get(StaticTextName.FCFS_WINNER_EXPIRY_DATE.name()), + firstFcfsSetting.getStartTime().getYear(), + firstFcfsSetting.getStartTime().format(dateFormatter), + drawSettingManager.getEndDate().plusDays(14).format(dateFormatter))) + .caution(textContentMap.get(StaticTextName.FCFS_WINNER_CAUTION.name())) + .build(); + } + + /** + * 선착순 실패 모달 정보 중, 정적 정보를 반환하는 메서드 + */ + @Cacheable(value = "staticResources", key = "'fcfsFail_' + #isDuplicated") + public FcfsFailResult getFcfsFailResult(boolean isDuplicated) { + Map textContentMap = staticResourceUtil.getTextContentMap(); + + if(isDuplicated){ + return FcfsFailResult.builder() + .title(textContentMap.get(StaticTextName.FCFS_DUPLICATED_TITLE.name())) + .subTitle(textContentMap.get(StaticTextName.FCFS_DUPLICATED_SUBTITLE.name())) + .caution(textContentMap.get(StaticTextName.FCFS_LOSER_CAUTION.name())) + .build(); + } + return FcfsFailResult.builder() + .title(textContentMap.get(StaticTextName.FCFS_LOSER_TITLE.name())) + .subTitle(textContentMap.get(StaticTextName.FCFS_LOSER_SUBTITLE.name())) + .caution(textContentMap.get(StaticTextName.FCFS_LOSER_CAUTION.name())) + .build(); + } + + + +} diff --git a/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsHandler/FcfsHandlerByRedisson.java b/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsHandler/FcfsHandlerByRedisson.java new file mode 100644 index 00000000..8101dc5c --- /dev/null +++ b/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsHandler/FcfsHandlerByRedisson.java @@ -0,0 +1,215 @@ +package com.softeer.backend.fo_domain.fcfs.service.FcfsHandler; + +import com.softeer.backend.fo_domain.draw.service.DrawSettingManager; +import com.softeer.backend.fo_domain.fcfs.domain.Fcfs; +import com.softeer.backend.fo_domain.fcfs.dto.*; +import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsFailResult; +import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsResultResponseDto; +import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsSuccessResult; +import com.softeer.backend.fo_domain.fcfs.exception.FcfsException; +import com.softeer.backend.fo_domain.fcfs.repository.FcfsRepository; +import com.softeer.backend.fo_domain.fcfs.service.FcfsService; +import com.softeer.backend.fo_domain.fcfs.service.FcfsSettingManager; +import com.softeer.backend.fo_domain.fcfs.service.QuizManager; +import com.softeer.backend.global.annotation.EventLock; +import com.softeer.backend.global.common.code.status.ErrorStatus; +import com.softeer.backend.global.common.constant.RedisKeyPrefix; +import com.softeer.backend.global.staticresources.constant.S3FileName; +import com.softeer.backend.global.staticresources.constant.StaticTextName; +import com.softeer.backend.global.staticresources.util.StaticResourceUtil; +import com.softeer.backend.global.util.FcfsRedisUtil; +import com.softeer.backend.global.util.RandomCodeUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Slf4j +@Component +@RequiredArgsConstructor +public class FcfsHandlerByRedisson implements FcfsHandler { + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("M월 d일"); + private final ObjectProvider fcfsServiceProvider; + + private final FcfsSettingManager fcfsSettingManager; + private final DrawSettingManager drawSettingManager; + private final QuizManager quizManager; + private final FcfsRedisUtil fcfsRedisUtil; + private final RandomCodeUtil randomCodeUtil; + private final StaticResourceUtil staticResourceUtil; + + private final FcfsRepository fcfsRepository; + + /** + * 선착순 등록을 처리하고 결과 모달 정보를 반환하는 메서드 + *

+ * 1. 선착순 등록 요청 dto에서 전달된 퀴즈 정답이 유효한지 확인한다. + * 1-1. 유효하지 않다면 예외가 발생한다. + * 2. 선착순 설정 매니저 클래스의 fcfsClosed 변수값을 확인한다.(선착순 당첨자가 다 나왔는지 여부를 의미) + * 2-1. 값이 true라면 선착순 이벤트 참여자 수에 1을 더하고 실패 모달 정보를 반환한다. + * 2-2. 값이 false라면 선착순 등록을 처리하는 메서드를 호출한다. + */ + @Override + public FcfsResultResponseDto handleFcfsEvent(int userId, int round, FcfsRequestDto fcfsRequestDto) { + + // 퀴즈 정답이 유효한지 확인하고 유효하지 않다면 예외 발생 + if (!fcfsRequestDto.getAnswer().equals(quizManager.getQuiz(round).getAnswerWord())) { + log.error("fcfs quiz answer is not match, correct answer: {}, wrong anwer: {}", + quizManager.getQuiz(round).getAnswerWord(), fcfsRequestDto.getAnswer()); + throw new FcfsException(ErrorStatus._BAD_REQUEST); + } + + // 선착순 당첨자가 다 나왔다면 선착순 이벤트 참여자 수에 1을 더하는 메서드를 호출하고 실패 모달 정보를 반환 + if (fcfsSettingManager.isFcfsClosed()) { + countFcfsParticipant(round); + + return getFcfsResult(false, false, null); + } + + // 선착순 등록을 처리하는 메서드 호출 + FcfsHandlerByRedisson fcfsHandlerByRedisson = fcfsServiceProvider.getObject(); + return fcfsHandlerByRedisson.saveFcfsWinners(userId, round); + } + + /** + * 선착순 등록을 처리하는 메서드 + *

+ * 1. 선착순 당첨자 수가 남아있고 이미 선착순 이벤트에 당첨됐는지를 확인한다. + * 1-1. 당첨자가 모두 나왔거나 이미 선착순 이벤트에 당첨됐었다면, 선착순 실패 모달 정보를 반환한다. + * 2. redis에 선착순 등록 요청한 유저의 userId, 이벤트 코드를 저장하고 선착순 참가자 수에 1을 더한다. + * 3. 해당 유저를 마지막으로 선착순 당첨이 마감되면 FcfsSettingManager의 fcfsClose 변수값을 true로 설정한다. + * 4. 선착순 성공 모달 정보를 반환한다. + */ + + @EventLock(key = "FCFS_#{#round}") + @Transactional + public FcfsResultResponseDto saveFcfsWinners(int userId, int round) { + + long numOfWinners = fcfsRedisUtil.getIntegerSetSize(RedisKeyPrefix.FCFS_USERID_PREFIX.getPrefix() + round); + + if (numOfWinners < fcfsSettingManager.getFcfsWinnerNum() + && !fcfsRedisUtil.isValueInIntegerSet(RedisKeyPrefix.FCFS_USERID_PREFIX.getPrefix() + round, userId)) { + + // redis에 userId 등록 + fcfsRedisUtil.addToIntegerSet(RedisKeyPrefix.FCFS_USERID_PREFIX.getPrefix() + round, userId); + + String code = fcfsRedisUtil.popFromStringSet(RedisKeyPrefix.FCFS_CODE_PREFIX.getPrefix() + round); + + // redis에 code-userId 형태로 등록(hash) + fcfsRedisUtil.addToHash(RedisKeyPrefix.FCFS_CODE_USERID_PREFIX.getPrefix() + round, code, userId); + + // redis에 선착순 참가자 수 +1 + countFcfsParticipant(round); + + // 선착순 당첨이 마감되면 FcfsSettingManager의 fcfsClodes 변수값을 true로 설정 + if (numOfWinners + 1 == fcfsSettingManager.getFcfsWinnerNum()) { + fcfsSettingManager.setFcfsClosed(true); + } + + return getFcfsResult(true, false, code); + } else if (numOfWinners < fcfsSettingManager.getFcfsWinnerNum() + && fcfsRedisUtil.isValueInIntegerSet(RedisKeyPrefix.FCFS_USERID_PREFIX.getPrefix() + round, userId)) + return getFcfsResult(false, true, null); + + + return getFcfsResult(false, false, null); + + } + + /** + * 선착순 이벤트 코드를 반환하는 메서드 + *

+ * round값에 따라 코드의 앞부분을 특정 문자로 고정한다. + */ + private String makeFcfsCode(int round) { + return (char) ('A' + round - 1) + randomCodeUtil.generateRandomCode(5); + } + + /** + * redis에 저장된 선착순 이벤트 참여자 수를 1만큼 늘리는 메서드 + */ + private void countFcfsParticipant(int round) { + fcfsRedisUtil.incrementValue(RedisKeyPrefix.FCFS_PARTICIPANT_COUNT_PREFIX.getPrefix() + round); + } + + /** + * 선착순 결과 모달 응답 Dto를 만들어서 반환하는 메서드 + */ + public FcfsResultResponseDto getFcfsResult(boolean fcfsWin, boolean isDuplicated, String fcfsCode) { + + FcfsSettingDto firstFcfsSetting = fcfsSettingManager.getFcfsSettingByRound(1); + + FcfsHandlerByRedisson fcfsHandlerByRedisson = fcfsServiceProvider.getObject(); + + if (fcfsWin) { + FcfsSuccessResult fcfsSuccessResult = fcfsHandlerByRedisson.getFcfsSuccessResult(firstFcfsSetting); + fcfsSuccessResult.setFcfsCode(fcfsCode); + + return FcfsResultResponseDto.builder() + .fcfsWinner(fcfsWin) + .fcfsResult(fcfsSuccessResult) + .build(); + } + + FcfsFailResult fcfsFailResult = fcfsHandlerByRedisson.getFcfsFailResult(isDuplicated); + + return FcfsResultResponseDto.builder() + .fcfsWinner(fcfsWin) + .fcfsResult(fcfsFailResult) + .build(); + } + + /** + * 선착순 당첨 모달 정보 중, 정적 정보를 반환하는 메서드 + */ + @Cacheable(value = "staticResources", key = "'fcfsSuccess'") + public FcfsSuccessResult getFcfsSuccessResult(FcfsSettingDto firstFcfsSetting) { + + Map textContentMap = staticResourceUtil.getTextContentMap(); + Map s3ContentMap = staticResourceUtil.getS3ContentMap(); + + return FcfsSuccessResult.builder() + .title(staticResourceUtil.format(textContentMap.get(StaticTextName.FCFS_WINNER_TITLE.name()), + fcfsSettingManager.getFcfsWinnerNum())) + .subTitle(textContentMap.get(StaticTextName.FCFS_WINNER_SUBTITLE.name())) + .qrCode(s3ContentMap.get(S3FileName.BARCODE_IMAGE.name())) + .codeWord(textContentMap.get(StaticTextName.FCFS_WINNER_CODE_WORD.name())) + .expirationDate(staticResourceUtil.format(textContentMap.get(StaticTextName.FCFS_WINNER_EXPIRY_DATE.name()), + firstFcfsSetting.getStartTime().getYear(), + firstFcfsSetting.getStartTime().format(dateFormatter), + drawSettingManager.getEndDate().plusDays(14).format(dateFormatter))) + .caution(textContentMap.get(StaticTextName.FCFS_WINNER_CAUTION.name())) + .build(); + } + + /** + * 선착순 실패 모달 정보 중, 정적 정보를 반환하는 메서드 + */ + @Cacheable(value = "staticResources", key = "'fcfsFail_' + #isDuplicated") + public FcfsFailResult getFcfsFailResult(boolean isDuplicated) { + Map textContentMap = staticResourceUtil.getTextContentMap(); + + if (isDuplicated) { + return FcfsFailResult.builder() + .title(textContentMap.get(StaticTextName.FCFS_DUPLICATED_TITLE.name())) + .subTitle(textContentMap.get(StaticTextName.FCFS_DUPLICATED_SUBTITLE.name())) + .caution(textContentMap.get(StaticTextName.FCFS_LOSER_CAUTION.name())) + .build(); + } + return FcfsFailResult.builder() + .title(textContentMap.get(StaticTextName.FCFS_LOSER_TITLE.name())) + .subTitle(textContentMap.get(StaticTextName.FCFS_LOSER_SUBTITLE.name())) + .caution(textContentMap.get(StaticTextName.FCFS_LOSER_CAUTION.name())) + .build(); + } + +} diff --git a/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsService.java b/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsService.java index c426520b..a018271e 100644 --- a/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsService.java +++ b/src/main/java/com/softeer/backend/fo_domain/fcfs/service/FcfsService.java @@ -1,5 +1,6 @@ package com.softeer.backend.fo_domain.fcfs.service; +import com.fasterxml.jackson.core.JsonProcessingException; import com.softeer.backend.fo_domain.draw.service.DrawSettingManager; import com.softeer.backend.fo_domain.fcfs.domain.Fcfs; import com.softeer.backend.fo_domain.fcfs.dto.*; @@ -8,6 +9,7 @@ import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsSuccessResult; import com.softeer.backend.fo_domain.fcfs.exception.FcfsException; import com.softeer.backend.fo_domain.fcfs.repository.FcfsRepository; +import com.softeer.backend.fo_domain.fcfs.service.FcfsHandler.FcfsHandler; import com.softeer.backend.global.annotation.EventLock; import com.softeer.backend.global.common.code.status.ErrorStatus; import com.softeer.backend.global.common.constant.RedisKeyPrefix; @@ -19,15 +21,19 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.Map; +import static java.util.stream.Collectors.toList; + /** * 선착순 관련 이벤트를 처리하는 클래스 */ @@ -46,6 +52,7 @@ public class FcfsService { private final StaticResourceUtil staticResourceUtil; private final FcfsRepository fcfsRepository; + private final FcfsHandler fcfsHandler; /** * 선착순 페이지에 필요한 정보를 반환하는 메서드 @@ -83,174 +90,11 @@ public FcfsPageResponseDto getFcfsTutorialPage() { .build(); } - /** - * 선착순 등록을 처리하고 결과 모달 정보를 반환하는 메서드 - *

- * 1. 선착순 등록 요청 dto에서 전달된 퀴즈 정답이 유효한지 확인한다. - * 1-1. 유효하지 않다면 예외가 발생한다. - * 2. 선착순 설정 매니저 클래스의 fcfsClosed 변수값을 확인한다.(선착순 당첨자가 다 나왔는지 여부를 의미) - * 2-1. 값이 true라면 선착순 이벤트 참여자 수에 1을 더하고 실패 모달 정보를 반환한다. - * 2-2. 값이 false라면 선착순 등록을 처리하는 메서드를 호출한다. - */ - public FcfsResultResponseDto handleFcfsEvent(int userId, int round, FcfsRequestDto fcfsRequestDto) { - - // 퀴즈 정답이 유효한지 확인하고 유효하지 않다면 예외 발생 - if (!fcfsRequestDto.getAnswer().equals(quizManager.getQuiz(round).getAnswerWord())) { - log.error("fcfs quiz answer is not match, correct answer: {}, wrong anwer: {}", - quizManager.getQuiz(round).getAnswerWord(), fcfsRequestDto.getAnswer()); - throw new FcfsException(ErrorStatus._BAD_REQUEST); - } - - // 선착순 당첨자가 다 나왔다면 선착순 이벤트 참여자 수에 1을 더하는 메서드를 호출하고 실패 모달 정보를 반환 - if (fcfsSettingManager.isFcfsClosed()) { - countFcfsParticipant(round); - - return getFcfsResult(false, false, null); - } - - // 선착순 등록을 처리하는 메서드 호출 - FcfsService fcfsService = fcfsServiceProvider.getObject(); - return fcfsService.saveFcfsWinners(userId, round); - } - - /** - * 선착순 등록을 처리하는 메서드 - *

- * 1. 선착순 당첨자 수가 남아있고 이미 선착순 이벤트에 당첨됐는지를 확인한다. - * 1-1. 당첨자가 모두 나왔거나 이미 선착순 이벤트에 당첨됐었다면, 선착순 실패 모달 정보를 반환한다. - * 2. redis에 선착순 등록 요청한 유저의 userId, 이벤트 코드를 저장하고 선착순 참가자 수에 1을 더한다. - * 3. 해당 유저를 마지막으로 선착순 당첨이 마감되면 FcfsSettingManager의 fcfsClose 변수값을 true로 설정한다. - * 4. 선착순 성공 모달 정보를 반환한다. - */ - @EventLock(key = "FCFS_#{#round}") - public FcfsResultResponseDto saveFcfsWinners(int userId, int round) { - - long numOfWinners = fcfsRedisUtil.getIntegerSetSize(RedisKeyPrefix.FCFS_USERID_PREFIX.getPrefix() + round); - - if (numOfWinners < fcfsSettingManager.getFcfsWinnerNum() - && !fcfsRedisUtil.isValueInIntegerSet(RedisKeyPrefix.FCFS_USERID_PREFIX.getPrefix() + round, userId)) { - - // redis에 userId 등록 - fcfsRedisUtil.addToIntegerSet(RedisKeyPrefix.FCFS_USERID_PREFIX.getPrefix() + round, userId); - - // 중복되지 않는 code를 생성 - String code = makeFcfsCode(round); - while (fcfsRedisUtil.isValueInStringSet(RedisKeyPrefix.FCFS_CODE_PREFIX.getPrefix() + round, code)) { - code = makeFcfsCode(round); - } - - // redis에 선착순 code 등록 - fcfsRedisUtil.addToStringSet(RedisKeyPrefix.FCFS_CODE_PREFIX.getPrefix() + round, code); - - // redis에 code-userId 형태로 등록(hash) - fcfsRedisUtil.addToHash(RedisKeyPrefix.FCFS_CODE_USERID_PREFIX.getPrefix() + round, code, userId); - - // redis에 선착순 참가자 수 +1 - countFcfsParticipant(round); - - // 선착순 당첨이 마감되면 FcfsSettingManager의 fcfsClodes 변수값을 true로 설정 - if (numOfWinners + 1 == fcfsSettingManager.getFcfsWinnerNum()) { - fcfsSettingManager.setFcfsClosed(true); - } - - return getFcfsResult(true, false, code); - } - - if(numOfWinners < fcfsSettingManager.getFcfsWinnerNum() - && fcfsRedisUtil.isValueInIntegerSet(RedisKeyPrefix.FCFS_USERID_PREFIX.getPrefix() + round, userId)) - return getFcfsResult(false, true, null); - - - return getFcfsResult(false, false, null); - + public FcfsResultResponseDto handleFcfs(int userId, int round, FcfsRequestDto fcfsRequestDto) throws JsonProcessingException { + return fcfsHandler.handleFcfsEvent(userId, round, fcfsRequestDto); } - /** - * 선착순 이벤트 코드를 반환하는 메서드 - * - * round값에 따라 코드의 앞부분을 특정 문자로 고정한다. - */ - private String makeFcfsCode(int round) { - return (char) ('A' + round - 1) + randomCodeUtil.generateRandomCode(5); - } - /** - * redis에 저장된 선착순 이벤트 참여자 수를 1만큼 늘리는 메서드 - */ - private void countFcfsParticipant(int round) { - fcfsRedisUtil.incrementValue(RedisKeyPrefix.FCFS_PARTICIPANT_COUNT_PREFIX.getPrefix() + round); - } - - /** - * 선착순 결과 모달 응답 Dto를 만들어서 반환하는 메서드 - */ - public FcfsResultResponseDto getFcfsResult(boolean fcfsWin, boolean isDuplicated, String fcfsCode) { - - FcfsSettingDto firstFcfsSetting = fcfsSettingManager.getFcfsSettingByRound(1); - - FcfsService fcfsService = fcfsServiceProvider.getObject(); - - if (fcfsWin) { - FcfsSuccessResult fcfsSuccessResult = fcfsService.getFcfsSuccessResult(firstFcfsSetting); - fcfsSuccessResult.setFcfsCode(fcfsCode); - - return FcfsResultResponseDto.builder() - .fcfsWinner(fcfsWin) - .fcfsResult(fcfsSuccessResult) - .build(); - } - - FcfsFailResult fcfsFailResult = fcfsService.getFcfsFailResult(isDuplicated); - - return FcfsResultResponseDto.builder() - .fcfsWinner(fcfsWin) - .fcfsResult(fcfsFailResult) - .build(); - } - - /** - * 선착순 당첨 모달 정보 중, 정적 정보를 반환하는 메서드 - */ - @Cacheable(value = "staticResources", key = "'fcfsSuccess'") - public FcfsSuccessResult getFcfsSuccessResult(FcfsSettingDto firstFcfsSetting) { - - Map textContentMap = staticResourceUtil.getTextContentMap(); - Map s3ContentMap = staticResourceUtil.getS3ContentMap(); - - return FcfsSuccessResult.builder() - .title(staticResourceUtil.format(textContentMap.get(StaticTextName.FCFS_WINNER_TITLE.name()), - fcfsSettingManager.getFcfsWinnerNum())) - .subTitle(textContentMap.get(StaticTextName.FCFS_WINNER_SUBTITLE.name())) - .qrCode(s3ContentMap.get(S3FileName.BARCODE_IMAGE.name())) - .codeWord(textContentMap.get(StaticTextName.FCFS_WINNER_CODE_WORD.name())) - .expirationDate(staticResourceUtil.format(textContentMap.get(StaticTextName.FCFS_WINNER_EXPIRY_DATE.name()), - firstFcfsSetting.getStartTime().getYear(), - firstFcfsSetting.getStartTime().format(dateFormatter), - drawSettingManager.getEndDate().plusDays(14).format(dateFormatter))) - .caution(textContentMap.get(StaticTextName.FCFS_WINNER_CAUTION.name())) - .build(); - } - - /** - * 선착순 실패 모달 정보 중, 정적 정보를 반환하는 메서드 - */ - @Cacheable(value = "staticResources", key = "'fcfsFail_' + #isDuplicated") - public FcfsFailResult getFcfsFailResult(boolean isDuplicated) { - Map textContentMap = staticResourceUtil.getTextContentMap(); - - if(isDuplicated){ - return FcfsFailResult.builder() - .title(textContentMap.get(StaticTextName.FCFS_DUPLICATED_TITLE.name())) - .subTitle(textContentMap.get(StaticTextName.FCFS_DUPLICATED_SUBTITLE.name())) - .caution(textContentMap.get(StaticTextName.FCFS_LOSER_CAUTION.name())) - .build(); - } - return FcfsFailResult.builder() - .title(textContentMap.get(StaticTextName.FCFS_LOSER_TITLE.name())) - .subTitle(textContentMap.get(StaticTextName.FCFS_LOSER_SUBTITLE.name())) - .caution(textContentMap.get(StaticTextName.FCFS_LOSER_CAUTION.name())) - .build(); - } /** * 선착순 당첨 기록 응답을 반환하는 메서드 @@ -301,4 +145,18 @@ public FcfsHistoryResponseDto getFcfsHistory(int userId){ } + public void insertFcfsCode(int num, int round){ + fcfsRedisUtil.clearStringSet(RedisKeyPrefix.FCFS_CODE_PREFIX.getPrefix() + round); + + int i=0; + while(i { + + @Lock(LockModeType.PESSIMISTIC_WRITE) + Optional findByRound(int round); +} diff --git a/src/main/java/com/softeer/backend/fo_domain/fcfs/service/test/LuaRedisUtil.java b/src/main/java/com/softeer/backend/fo_domain/fcfs/service/test/LuaRedisUtil.java new file mode 100644 index 00000000..10cb18bb --- /dev/null +++ b/src/main/java/com/softeer/backend/fo_domain/fcfs/service/test/LuaRedisUtil.java @@ -0,0 +1,73 @@ +package com.softeer.backend.fo_domain.fcfs.service.test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class LuaRedisUtil { + + private final ObjectMapper objectMapper; + private final StringRedisTemplate stringRedisTemplate; + + public String executeFcfsScript(String userIdSetKey, String codeSetKey, String codeUserIdHashKey, String participantCountKey, Integer userId, int maxWinners) throws JsonProcessingException { + String script = """ + local userIdSetKey = KEYS[1] + local codeSetKey = KEYS[2] + local codeUserIdHashKey = KEYS[3] + local participantCountKey = KEYS[4] + + local userId = ARGV[1] + local maxWinners = tonumber(ARGV[2]) + + if redis.call('SISMEMBER', userIdSetKey, userId) == 1 then + return 'DUPLICATED' + end + + local numOfWinners = redis.call('SCARD', userIdSetKey) + if numOfWinners >= maxWinners then + return 'FCFS_CLOSED' + end + + redis.call('SADD', userIdSetKey, userId) + local code = redis.call('SPOP', codeSetKey) + if not code then + return 'FCFS_CODE_EMPTY' + end + + redis.call('HSET', codeUserIdHashKey, code, userId) + redis.call('INCR', participantCountKey) + + local updatedNumOfWinners = numOfWinners + 1 + local isClosed = (updatedNumOfWinners == maxWinners) and true or false + + if isClosed then + code = code .. "_CLOSED" + end + + return code + """; + + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(script); + redisScript.setResultType(String.class); + + List keys = Arrays.asList(userIdSetKey, codeSetKey, codeUserIdHashKey, participantCountKey); + + String result = stringRedisTemplate.execute(redisScript, keys, userId.toString(), String.valueOf(maxWinners)); + log.info("lua result: {}", result); + + return result; + } +} diff --git a/src/main/java/com/softeer/backend/fo_domain/mainpage/service/MainPageService.java b/src/main/java/com/softeer/backend/fo_domain/mainpage/service/MainPageService.java index 1419c8b5..3249b933 100644 --- a/src/main/java/com/softeer/backend/fo_domain/mainpage/service/MainPageService.java +++ b/src/main/java/com/softeer/backend/fo_domain/mainpage/service/MainPageService.java @@ -35,9 +35,9 @@ @Service @RequiredArgsConstructor public class MainPageService { - private final DateTimeFormatter eventDateFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd"); - private final DateTimeFormatter eventTimeFormatter = DateTimeFormatter.ofPattern("a h시", Locale.KOREAN); - private final DateTimeFormatter eventTimeMinFormatter = DateTimeFormatter.ofPattern("a h시 m분", Locale.KOREAN); + private final DateTimeFormatter eventTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd"); + private final DateTimeFormatter fcfsTimeFormatter = DateTimeFormatter.ofPattern("a h시", Locale.KOREAN); + private final DateTimeFormatter fcfsTimeMinFormatter = DateTimeFormatter.ofPattern("a h시 m분", Locale.KOREAN); private final DecimalFormat decimalFormat = new DecimalFormat("#,###"); private final EventLockRedisUtil eventLockRedisUtil; @@ -107,41 +107,25 @@ public MainPageEventInfoResponseDto getEventPageInfo() { String fcfsTime = ""; if(firstFcfsSetting.getStartTime().getMinute() != 0){ - fcfsTime = firstFcfsSetting.getStartTime().format(eventTimeMinFormatter); + fcfsTime = firstFcfsSetting.getStartTime().format(fcfsTimeMinFormatter); } else - fcfsTime = firstFcfsSetting.getStartTime().format(eventTimeFormatter); - - String drawStartTime = ""; - String drawEndTime = ""; - if(drawSettingManager.getStartTime().getMinute() != 0) - drawStartTime = drawSettingManager.getStartTime().format(eventTimeMinFormatter); - else - drawStartTime = drawSettingManager.getStartTime().format(eventTimeFormatter); - - if(drawSettingManager.getEndTime().getMinute() != 0) - drawEndTime = drawSettingManager.getEndTime().format(eventTimeMinFormatter); - else - drawEndTime = drawSettingManager.getEndTime().format(eventTimeFormatter); + fcfsTime = firstFcfsSetting.getStartTime().format(fcfsTimeFormatter); return MainPageEventInfoResponseDto.builder() - .startDate(drawSettingManager.getStartDate().format(eventDateFormatter)) - .endDate(drawSettingManager.getEndDate().format(eventDateFormatter)) + .startDate(drawSettingManager.getStartDate().format(eventTimeFormatter)) + .endDate(drawSettingManager.getEndDate().format(eventTimeFormatter)) .fcfsInfo(staticResourceUtil.format(textContentMap.get(StaticTextName.FCFS_INFO.name()), staticResourceUtil.getKoreanDayOfWeek(firstFcfsSetting.getStartTime().getDayOfWeek()), staticResourceUtil.getKoreanDayOfWeek(secondFcfsSetting.getStartTime().getDayOfWeek()), fcfsTime, firstFcfsSetting.getWinnerNum())) - .drawInfo(staticResourceUtil.format(textContentMap.get(StaticTextName.DRAW_INFO.name()), - drawStartTime, drawEndTime)) .totalDrawWinner(staticResourceUtil.format( textContentMap.get(StaticTextName.TOTAL_DRAW_WINNER.name()), decimalFormat.format(totalDrawWinner))) .remainDrawCount(staticResourceUtil.format( textContentMap.get(StaticTextName.REMAIN_DRAW_COUNT.name()), decimalFormat.format(remainDrawCount))) .fcfsHint(quizManager.getHint()) .fcfsStartTime(fcfsSettingManager.getNowOrNextFcfsTime(LocalDateTime.now())) - .drawStartTime(drawSettingManager.getStartTime()) - .drawEndTime(drawSettingManager.getEndTime()) .build(); } diff --git a/src/main/java/com/softeer/backend/global/annotation/EventLock.java b/src/main/java/com/softeer/backend/global/annotation/EventLock.java index 62e54638..f10e0fc6 100644 --- a/src/main/java/com/softeer/backend/global/annotation/EventLock.java +++ b/src/main/java/com/softeer/backend/global/annotation/EventLock.java @@ -23,10 +23,10 @@ TimeUnit timeUnit() default TimeUnit.MILLISECONDS; /** - * 락을 기다리는 시간 (default - 5000ms) + * 락을 기다리는 시간 (default - 500ms) * 락 획득을 위해 waitTime 만큼 대기한다 */ - long waitTime() default 5000L; + long waitTime() default 500L; /** * 락 임대 시간 (default - 100ms) diff --git a/src/main/java/com/softeer/backend/global/common/exception/ExceptionAdvice.java b/src/main/java/com/softeer/backend/global/common/exception/ExceptionAdvice.java index 37a35938..f25258d7 100644 --- a/src/main/java/com/softeer/backend/global/common/exception/ExceptionAdvice.java +++ b/src/main/java/com/softeer/backend/global/common/exception/ExceptionAdvice.java @@ -1,7 +1,5 @@ package com.softeer.backend.global.common.exception; -import com.softeer.backend.fo_domain.draw.dto.participate.DrawLoseModalResponseDto; -import com.softeer.backend.fo_domain.draw.util.DrawUtil; import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsFailResult; import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsResultResponseDto; import com.softeer.backend.fo_domain.fcfs.exception.FcfsException; @@ -35,7 +33,6 @@ public class ExceptionAdvice extends ResponseEntityExceptionHandler { private final StaticResourceUtil staticResourceUtil; - private final DrawUtil drawUtil; /** * GeneralException을 처리하는 메서드 @@ -172,15 +169,9 @@ private ResponseEntity handleEventLockExceptionInternal(EventLockExcepti .build()); } - if (redissonKeyName.contains("DRAW")) { - body = ResponseDto.onSuccess( - DrawLoseModalResponseDto.builder() - .isDrawWin(false) - .images(drawUtil.generateLoseImages()) - .shareUrl("https://softeer.site") - .build() - ); - } + + //TODO + // DRAW 관련 예외일 경우, body 구성하는 코드 필요 return super.handleExceptionInternal( e, @@ -199,13 +190,13 @@ private ResponseEntity handleFcfsExceptionInternal(FcfsException e, Http if (e.getCode() == ErrorStatus._FCFS_ALREADY_CLOSED) { body = ResponseDto.onSuccess(FcfsResultResponseDto.builder() - .fcfsWinner(false) - .fcfsResult(FcfsFailResult.builder() - .title(textContentMap.get(StaticTextName.FCFS_CLOSED_TITLE.name())) - .subTitle(textContentMap.get(StaticTextName.FCFS_CLOSED_SUBTITLE.name())) - .caution(textContentMap.get(StaticTextName.FCFS_LOSER_CAUTION.name())) - .build()) - .build()); + .fcfsWinner(false) + .fcfsResult(FcfsFailResult.builder() + .title(textContentMap.get(StaticTextName.FCFS_CLOSED_TITLE.name())) + .subTitle(textContentMap.get(StaticTextName.FCFS_CLOSED_SUBTITLE.name())) + .caution(textContentMap.get(StaticTextName.FCFS_LOSER_CAUTION.name())) + .build()) + .build()); } else { body = ResponseDto.onFailure(e.getCode()); } diff --git a/src/main/java/com/softeer/backend/global/config/redis/RedisConfig.java b/src/main/java/com/softeer/backend/global/config/redis/RedisConfig.java index 524bca26..07737c67 100644 --- a/src/main/java/com/softeer/backend/global/config/redis/RedisConfig.java +++ b/src/main/java/com/softeer/backend/global/config/redis/RedisConfig.java @@ -1,5 +1,8 @@ package com.softeer.backend.global.config.redis; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; import com.softeer.backend.global.config.properties.RedisProperties; import lombok.RequiredArgsConstructor; import org.redisson.Redisson; @@ -10,7 +13,9 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.GenericToStringSerializer; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** @@ -44,6 +49,19 @@ public RedisTemplate redisTemplateForInteger(RedisConnectionFac return template; } + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + template.setKeySerializer(new StringRedisSerializer()); + template.setHashKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericToStringSerializer<>(Object.class)); + template.setHashValueSerializer(new GenericToStringSerializer<>(Object.class)); + + return template; + } + /** * Redisson 설정 * diff --git a/src/main/java/com/softeer/backend/global/filter/JwtAuthenticationFilter.java b/src/main/java/com/softeer/backend/global/filter/JwtAuthenticationFilter.java index bcb22b2b..52298ec3 100644 --- a/src/main/java/com/softeer/backend/global/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/softeer/backend/global/filter/JwtAuthenticationFilter.java @@ -39,8 +39,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { "/login", "/main/event/static", "/main/event/info", "/main/car", "/admin/login", "/admin/signup", - "/share/**", - "/admin/fcfs/test","/admin/draw/test" + "/share/**", "/comment/page", "/fcfs/insert", "/comment/test" }; // Access Token이 header에 있으면 인증하고 없으면 인증하지 않는 url 설정 diff --git a/src/main/java/com/softeer/backend/global/scheduler/DbInsertScheduler.java b/src/main/java/com/softeer/backend/global/scheduler/DbInsertScheduler.java index 74abc264..ca683b98 100644 --- a/src/main/java/com/softeer/backend/global/scheduler/DbInsertScheduler.java +++ b/src/main/java/com/softeer/backend/global/scheduler/DbInsertScheduler.java @@ -16,6 +16,7 @@ import com.softeer.backend.global.util.DrawRedisUtil; import com.softeer.backend.global.util.EventLockRedisUtil; import com.softeer.backend.global.util.FcfsRedisUtil; +import com.softeer.backend.global.util.RandomCodeUtil; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -47,6 +48,7 @@ public class DbInsertScheduler { private final UserRepository userRepository; private final FcfsRepository fcfsRepository; private final DrawRepository drawRepository; + private final RandomCodeUtil randomCodeUtil; private ScheduledFuture scheduledFuture; @@ -160,6 +162,30 @@ protected void insertData() { .drawParticipantCount(drawParticipantCount) .eventDate(now.minusDays(1)) .build()); + + if(fcfsSettingManager.getRoundForFcfsCode(now) != -1){ + + int round = fcfsSettingManager.getRoundForFcfsCode(now); + + int i=0; + while(i < fcfsSettingManager.getFcfsWinnerNum()){ + + String code = makeFcfsCode(round); + while (fcfsRedisUtil.isValueInStringSet(RedisKeyPrefix.FCFS_CODE_PREFIX.getPrefix() + round, code)) { + code = makeFcfsCode(round); + } + + fcfsRedisUtil.addToStringSet(RedisKeyPrefix.FCFS_CODE_PREFIX.getPrefix() + round, code); + + i++; + } + + } + + } + + private String makeFcfsCode(int round) { + return (char) ('A' + round - 1) + randomCodeUtil.generateRandomCode(5); } /** diff --git a/src/main/java/com/softeer/backend/global/util/FcfsRedisUtil.java b/src/main/java/com/softeer/backend/global/util/FcfsRedisUtil.java index 47187f19..8f87ba0b 100644 --- a/src/main/java/com/softeer/backend/global/util/FcfsRedisUtil.java +++ b/src/main/java/com/softeer/backend/global/util/FcfsRedisUtil.java @@ -25,6 +25,10 @@ public void addToStringSet(String key, String value) { stringRedisTemplate.opsForSet().add(key, value); } + public Long getStringSetSize(String key) { + return stringRedisTemplate.opsForSet().size(key); + } + public void addToHash(String key, String field, Integer value) { integerRedisTemplate.opsForHash().put(key, field, value); } @@ -45,6 +49,11 @@ public boolean isValueInStringSet(String key, String value) { return Boolean.TRUE.equals(stringRedisTemplate.opsForSet().isMember(key, value)); } + public String popFromStringSet(String key) { + // SPOP 명령어를 실행하여 집합에서 임의의 요소를 제거하고 반환 + return stringRedisTemplate.opsForSet().pop(key); + } + public Map getHashEntries(String key) { Map entries = integerRedisTemplate.opsForHash().entries(key); Map result = new HashMap<>(); diff --git a/src/test/java/com/softeer/backend/bo_domain/admin/service/AdminLoginServiceTest.java b/src/test/java/com/softeer/backend/bo_domain/admin/service/AdminLoginServiceTest.java deleted file mode 100644 index d82afd9c..00000000 --- a/src/test/java/com/softeer/backend/bo_domain/admin/service/AdminLoginServiceTest.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.softeer.backend.bo_domain.admin.service; - -import com.softeer.backend.bo_domain.admin.domain.Admin; -import com.softeer.backend.bo_domain.admin.dto.login.AdminLoginRequestDto; -import com.softeer.backend.bo_domain.admin.dto.login.AdminSignUpRequestDto; -import com.softeer.backend.bo_domain.admin.exception.AdminException; -import com.softeer.backend.bo_domain.admin.repository.AdminRepository; -import com.softeer.backend.bo_domain.admin.service.AdminLoginService; -import com.softeer.backend.bo_domain.admin.util.PasswordEncoder; -import com.softeer.backend.global.common.code.status.ErrorStatus; -import com.softeer.backend.global.common.constant.RoleType; -import com.softeer.backend.global.common.dto.JwtClaimsDto; -import com.softeer.backend.global.common.dto.JwtTokenResponseDto; -import com.softeer.backend.global.util.JwtUtil; -import com.softeer.backend.global.util.StringRedisUtil; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -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.beans.factory.ObjectProvider; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.Assert.assertThrows; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -public class AdminLoginServiceTest { - - @Mock - private AdminRepository adminRepository; - - @Mock - private JwtUtil jwtUtil; - - @Mock - private StringRedisUtil stringRedisUtil; - - @Mock - private PasswordEncoder passwordEncoder; - - @InjectMocks - private AdminLoginService adminLoginService; - - private Admin admin; - private AdminLoginRequestDto loginRequestDto; - private AdminSignUpRequestDto signUpRequestDto; - private JwtTokenResponseDto jwtTokenResponseDto; - - @BeforeEach - void setUp() { - admin = Admin.builder() - .id(1) - .account("admin") - .password("encodedPassword") - .build(); - - loginRequestDto = AdminLoginRequestDto.builder() - .account("admin") - .password("plainPassword") - .build(); - - signUpRequestDto = AdminSignUpRequestDto.builder() - .account("newAdmin") - .password("newPassword") - .build(); - - jwtTokenResponseDto = JwtTokenResponseDto.builder() - .accessToken("accessToken") - .refreshToken("refreshToken") - .expiredTime(LocalDateTime.now().plusHours(1)) - .build(); - } - - @Test - @DisplayName("어드민 로그인 성공 시 JWT 토큰을 반환한다.") - void handleLoginSuccess() { - // Given - when(adminRepository.findByAccount("admin")).thenReturn(Optional.of(admin)); - when(passwordEncoder.matches("plainPassword", "encodedPassword")).thenReturn(true); - when(jwtUtil.createServiceToken(any())).thenReturn(jwtTokenResponseDto); - - // When - JwtTokenResponseDto response = adminLoginService.handleLogin(loginRequestDto); - - // Then - assertThat(response).isNotNull(); - assertThat(response.getAccessToken()).isEqualTo("accessToken"); - assertThat(response.getRefreshToken()).isEqualTo("refreshToken"); - assertThat(response.getExpiredTime()).isNotNull(); - } - - @Test - @DisplayName("어드민 로그인 시 비밀번호 불일치 시 예외가 발생한다.") - void handleLoginPasswordMismatch() { - // Given - when(adminRepository.findByAccount("admin")).thenReturn(Optional.of(admin)); - when(passwordEncoder.matches("plainPassword", "encodedPassword")).thenReturn(false); - - // When & Then - AdminException exception = assertThrows(AdminException.class, () -> adminLoginService.handleLogin(loginRequestDto)); - assertThat(exception.getCode()).isEqualTo(ErrorStatus._NOT_FOUND); - } - - @Test - @DisplayName("어드민 로그아웃 시 Redis에서 refresh token을 삭제한다.") - void handleLogout() { - // Given - int adminId = 1; - - // When - adminLoginService.handleLogout(adminId); - - // Then - verify(stringRedisUtil).deleteRefreshToken(argThat(jwtClaims -> - jwtClaims.getId() == adminId && - jwtClaims.getRoleType() == RoleType.ROLE_ADMIN - )); - } - - @Test - @DisplayName("어드민 회원가입 시 계정이 이미 존재하면 예외가 발생한다.") - void handleSignUpAccountAlreadyExists() { - // Given - when(adminRepository.existsByAccount("newAdmin")).thenReturn(true); - - // When & Then - AdminException exception = assertThrows(AdminException.class, () -> adminLoginService.handleSignUp(signUpRequestDto)); - assertThat(exception.getCode()).isEqualTo(ErrorStatus._BAD_REQUEST); - } -} \ No newline at end of file diff --git a/src/test/java/com/softeer/backend/bo_domain/admin/service/EventPageServiceTest.java b/src/test/java/com/softeer/backend/bo_domain/admin/service/EventPageServiceTest.java deleted file mode 100644 index 01f2e458..00000000 --- a/src/test/java/com/softeer/backend/bo_domain/admin/service/EventPageServiceTest.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.softeer.backend.bo_domain.admin.service; - -import com.softeer.backend.bo_domain.admin.dto.event.DrawEventTimeRequestDto; -import com.softeer.backend.bo_domain.admin.dto.event.EventPageResponseDto; -import com.softeer.backend.bo_domain.admin.dto.event.FcfsEventTimeRequestDto; -import com.softeer.backend.bo_domain.admin.service.EventPageService; -import com.softeer.backend.fo_domain.draw.domain.DrawSetting; -import com.softeer.backend.fo_domain.draw.repository.DrawSettingRepository; -import com.softeer.backend.fo_domain.fcfs.domain.FcfsSetting; -import com.softeer.backend.fo_domain.fcfs.repository.FcfsSettingRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -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.beans.factory.ObjectProvider; -import org.springframework.data.domain.Sort; - -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.temporal.TemporalAdjusters; -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.Assert.assertThrows; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -public class EventPageServiceTest { - @InjectMocks - private EventPageService eventPageService; - - @Mock - private FcfsSettingRepository fcfsSettingRepository; - - @Mock - private DrawSettingRepository drawSettingRepository; - - @Test - @DisplayName("선착순 이벤트 시간을 수정한다.") - void updateFcfsEventTime() { - // Given - FcfsSetting fcfsSetting1 = new FcfsSetting(); - FcfsSetting fcfsSetting2 = new FcfsSetting(); - FcfsSetting fcfsSetting3 = new FcfsSetting(); - FcfsSetting fcfsSetting4 = new FcfsSetting(); - List fcfsSettings = Arrays.asList(fcfsSetting1, fcfsSetting2, fcfsSetting3, fcfsSetting4); - when(fcfsSettingRepository.findAll(Sort.by(Sort.Order.asc("id")))).thenReturn(fcfsSettings); - - DrawSetting drawSetting = new DrawSetting(); - when(drawSettingRepository.findAll()).thenReturn(List.of(drawSetting)); - - LocalDate startDate = LocalDate.of(2024, 8, 1); - LocalDate endDate = LocalDate.of(2024, 8, 2); - LocalTime startTime = LocalTime.of(10, 0); - - FcfsEventTimeRequestDto requestDto = new FcfsEventTimeRequestDto(startDate, endDate, startTime); - - // When - eventPageService.updateFcfsEventTime(requestDto); - - // Then - assertThat(fcfsSetting1.getStartTime()).isEqualTo(LocalDateTime.of(startDate, startTime)); - assertThat(fcfsSetting1.getEndTime()).isEqualTo(LocalDateTime.of(startDate, startTime.plusHours(2))); - - assertThat(fcfsSetting2.getStartTime()).isEqualTo(LocalDateTime.of(endDate, startTime)); - assertThat(fcfsSetting2.getEndTime()).isEqualTo(LocalDateTime.of(endDate, startTime.plusHours(2))); - - assertThat(fcfsSetting3.getStartTime()).isEqualTo(LocalDateTime.of(startDate.plusWeeks(1), startTime)); - assertThat(fcfsSetting3.getEndTime()).isEqualTo(LocalDateTime.of(startDate.plusWeeks(1), startTime.plusHours(2))); - - assertThat(fcfsSetting4.getStartTime()).isEqualTo(LocalDateTime.of(endDate.plusWeeks(1), startTime)); - assertThat(fcfsSetting4.getEndTime()).isEqualTo(LocalDateTime.of(endDate.plusWeeks(1), startTime.plusHours(2))); - - verify(drawSettingRepository).findAll(); - assertThat(drawSetting.getStartDate()).isEqualTo(startDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))); - assertThat(drawSetting.getEndDate()).isEqualTo(endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)) - .with(TemporalAdjusters.next(DayOfWeek.SUNDAY))); - } - - @Test - @DisplayName("추첨 이벤트 시간을 수정한다.") - void updateDrawEventTime() { - // Given - DrawSetting drawSetting = new DrawSetting(); - when(drawSettingRepository.findAll()).thenReturn(List.of(drawSetting)); - - LocalTime startTime = LocalTime.of(9, 0); - LocalTime endTime = LocalTime.of(11, 0); - DrawEventTimeRequestDto requestDto = new DrawEventTimeRequestDto(startTime, endTime); - - // When - eventPageService.updateDrawEventTime(requestDto); - - // Then - assertThat(drawSetting.getStartTime()).isEqualTo(startTime); - assertThat(drawSetting.getEndTime()).isEqualTo(endTime); - } -} diff --git a/src/test/java/com/softeer/backend/bo_domain/admin/service/IndicatorPageServiceTest.java b/src/test/java/com/softeer/backend/bo_domain/admin/service/IndicatorPageServiceTest.java deleted file mode 100644 index df4a4aa0..00000000 --- a/src/test/java/com/softeer/backend/bo_domain/admin/service/IndicatorPageServiceTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.softeer.backend.bo_domain.admin.service; - -import com.softeer.backend.bo_domain.admin.dto.indicator.EventIndicatorResponseDto; -import com.softeer.backend.bo_domain.admin.service.IndicatorPageService; -import com.softeer.backend.bo_domain.eventparticipation.domain.EventParticipation; -import com.softeer.backend.bo_domain.eventparticipation.repository.EventParticipationRepository; -import com.softeer.backend.fo_domain.draw.domain.DrawSetting; -import com.softeer.backend.fo_domain.draw.repository.DrawSettingRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.MockitoAnnotations; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import java.time.LocalDate; -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class IndicatorPageServiceTest { - - @Mock - private EventParticipationRepository eventParticipationRepository; - - @Mock - private DrawSettingRepository drawSettingRepository; - - @InjectMocks - private IndicatorPageService indicatorPageService; - - private DrawSetting drawSetting; - private List eventParticipationList; - - @BeforeEach - void setUp() { - - // Mock data - drawSetting = DrawSetting.builder() - .startDate(LocalDate.of(2024, 8, 1)) - .endDate(LocalDate.of(2024, 8, 31)) - .build(); - - eventParticipationList = List.of( - EventParticipation.builder() - .eventDate(LocalDate.of(2024, 8, 1)) - .visitorCount(100) - .fcfsParticipantCount(50) - .drawParticipantCount(30) - .build(), - EventParticipation.builder() - .eventDate(LocalDate.of(2024, 8, 2)) - .visitorCount(150) - .fcfsParticipantCount(75) - .drawParticipantCount(45) - .build() - ); - } - - @Test - @DisplayName("이벤트 지표 데이터를 정상적으로 반환한다.") - void getEventIndicatorSuccess() { - // Given - when(drawSettingRepository.findAll()).thenReturn(List.of(drawSetting)); - when(eventParticipationRepository.findAllByEventDateBetween(drawSetting.getStartDate(), drawSetting.getEndDate())) - .thenReturn(eventParticipationList); - - // When - EventIndicatorResponseDto response = indicatorPageService.getEventIndicator(); - - // Then - assertThat(response).isNotNull(); - assertThat(response.getStartDate()).isEqualTo(drawSetting.getStartDate()); - assertThat(response.getEndDate()).isEqualTo(drawSetting.getEndDate()); - assertThat(response.getTotalVisitorCount()).isEqualTo(250); // 100 + 150 - assertThat(response.getTotalFcfsParticipantCount()).isEqualTo(125); // 50 + 75 - assertThat(response.getTotalDrawParticipantCount()).isEqualTo(75); // 30 + 45 - assertThat(response.getFcfsParticipantRate()).isEqualTo(125.0 / 250.0); // 125 / 250 - assertThat(response.getDrawParticipantRate()).isEqualTo(75.0 / 250.0); // 75 / 250 - assertThat(response.getVisitorNumList()).hasSize(2); - assertThat(response.getVisitorNumList().get(0).getVisitDate()).isEqualTo(LocalDate.of(2024, 8, 1)); - assertThat(response.getVisitorNumList().get(0).getVisitorNum()).isEqualTo(100); - assertThat(response.getVisitorNumList().get(1).getVisitDate()).isEqualTo(LocalDate.of(2024, 8, 2)); - assertThat(response.getVisitorNumList().get(1).getVisitorNum()).isEqualTo(150); - } -} diff --git a/src/test/java/com/softeer/backend/bo_domain/admin/service/WinnerPageServiceTest.java b/src/test/java/com/softeer/backend/bo_domain/admin/service/WinnerPageServiceTest.java deleted file mode 100644 index 20e8a085..00000000 --- a/src/test/java/com/softeer/backend/bo_domain/admin/service/WinnerPageServiceTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package com.softeer.backend.bo_domain.admin.service; - -import com.softeer.backend.bo_domain.admin.dto.winner.*; -import com.softeer.backend.bo_domain.admin.service.WinnerPageService; -import com.softeer.backend.fo_domain.draw.domain.Draw; -import com.softeer.backend.fo_domain.draw.domain.DrawSetting; -import com.softeer.backend.fo_domain.draw.repository.DrawRepository; -import com.softeer.backend.fo_domain.draw.repository.DrawSettingRepository; -import com.softeer.backend.fo_domain.fcfs.domain.Fcfs; -import com.softeer.backend.fo_domain.fcfs.domain.FcfsSetting; -import com.softeer.backend.fo_domain.fcfs.repository.FcfsRepository; -import com.softeer.backend.fo_domain.fcfs.repository.FcfsSettingRepository; -import com.softeer.backend.fo_domain.user.domain.User; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.MockitoAnnotations; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class WinnerPageServiceTest { - - @Mock - private FcfsRepository fcfsRepository; - - @Mock - private DrawRepository drawRepository; - - @Mock - private FcfsSettingRepository fcfsSettingRepository; - - @Mock - private DrawSettingRepository drawSettingRepository; - - @InjectMocks - private WinnerPageService winnerPageService; - - @Test - @DisplayName("당첨 관리 페이지 정보를 반환한다.") - void getWinnerPage() { - // Given - List fcfsSettings = Arrays.asList( - FcfsSetting.builder().round(1).startTime(LocalDateTime.of(LocalDate.now(), LocalTime.now())).winnerNum(10).build(), - FcfsSetting.builder().round(2).startTime(LocalDateTime.of(LocalDate.now(), LocalTime.now())).winnerNum(20).build() - ); - - DrawSetting drawSetting = DrawSetting.builder() - .winnerNum1(5) - .winnerNum2(10) - .winnerNum3(15) - .build(); - - when(fcfsSettingRepository.findAll()).thenReturn(fcfsSettings); - when(drawSettingRepository.findAll()).thenReturn(Collections.singletonList(drawSetting)); - - // When - WinnerPageResponseDto response = winnerPageService.getWinnerPage(); - - // Then - assertThat(response.getFcfsEventList()).hasSize(2); - assertThat(response.getDrawEventList()).hasSize(3); - assertThat(response.getDrawEventList().get(0).getWinnerNum()).isEqualTo(5); - assertThat(response.getDrawEventList().get(1).getWinnerNum()).isEqualTo(10); - assertThat(response.getDrawEventList().get(2).getWinnerNum()).isEqualTo(15); - - verify(fcfsSettingRepository).findAll(); - verify(drawSettingRepository).findAll(); - } - - @Test - @DisplayName("선착순 당첨자 목록을 반환한다.") - void getFcfsWinnerList() { - // Given - int round = 1; - List fcfsList = Arrays.asList( - Fcfs.builder().user(User.builder().name("Alice").phoneNumber("010-1234-5678").build()).build(), - Fcfs.builder().user(User.builder().name("Bob").phoneNumber("010-2345-6789").build()).build() - ); - - when(fcfsRepository.findFcfsWithUser(round)).thenReturn(fcfsList); - - // When - FcfsWinnerListResponseDto response = winnerPageService.getFcfsWinnerList(round); - - // Then - assertThat(response.getFcfsWinnerList()).hasSize(2); - assertThat(response.getFcfsWinnerList().get(0).getName()).isEqualTo("Alice"); - assertThat(response.getFcfsWinnerList().get(1).getName()).isEqualTo("Bob"); - - verify(fcfsRepository).findFcfsWithUser(round); - } - - @Test - @DisplayName("추첨 당첨자 목록을 반환한다.") - void getDrawWinnerList() { - // Given - int rank = 1; - List drawList = Arrays.asList( - Draw.builder().user(User.builder().name("Charlie").phoneNumber("010-3456-7890").build()).build(), - Draw.builder().user(User.builder().name("David").phoneNumber("010-4567-8901").build()).build() - ); - - when(drawRepository.findDrawWithUser(rank)).thenReturn(drawList); - - // When - DrawWinnerListResponseDto response = winnerPageService.getDrawWinnerList(rank); - - // Then - assertThat(response.getDrawWinnerList()).hasSize(2); - assertThat(response.getDrawWinnerList().get(0).getName()).isEqualTo("Charlie"); - assertThat(response.getDrawWinnerList().get(1).getName()).isEqualTo("David"); - - verify(drawRepository).findDrawWithUser(rank); - } - - @Test - @DisplayName("선착순 당첨자 수를 수정한다.") - void updateFcfsWinnerNum() { - // Given - FcfsWinnerUpdateRequestDto requestDto = new FcfsWinnerUpdateRequestDto(50); - List fcfsSettings = Arrays.asList( - FcfsSetting.builder().round(1).winnerNum(10).build(), - FcfsSetting.builder().round(2).winnerNum(20).build() - ); - - when(fcfsSettingRepository.findAll()).thenReturn(fcfsSettings); - - // When - winnerPageService.updateFcfsWinnerNum(requestDto); - - // Then - assertThat(fcfsSettings.get(0).getWinnerNum()).isEqualTo(50); - assertThat(fcfsSettings.get(1).getWinnerNum()).isEqualTo(50); - - verify(fcfsSettingRepository).findAll(); - } - - @Test - @DisplayName("추첨 당첨자 수를 수정한다.") - void updateDrawWinnerNum() { - // Given - DrawWinnerUpdateRequestDto requestDto = new DrawWinnerUpdateRequestDto(5, 10, 15); - DrawSetting drawSetting = DrawSetting.builder() - .winnerNum1(1) - .winnerNum2(2) - .winnerNum3(3) - .build(); - - when(drawSettingRepository.findAll()).thenReturn(Collections.singletonList(drawSetting)); - - // When - winnerPageService.updateDrawWinnerNum(requestDto); - - // Then - assertThat(drawSetting.getWinnerNum1()).isEqualTo(5); - assertThat(drawSetting.getWinnerNum2()).isEqualTo(10); - assertThat(drawSetting.getWinnerNum3()).isEqualTo(15); - - verify(drawSettingRepository).findAll(); - } -} \ No newline at end of file diff --git a/src/test/java/com/softeer/backend/fo_domain/comment/service/CommentServiceTest.java b/src/test/java/com/softeer/backend/fo_domain/comment/service/CommentServiceTest.java deleted file mode 100644 index b9a77618..00000000 --- a/src/test/java/com/softeer/backend/fo_domain/comment/service/CommentServiceTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.softeer.backend.fo_domain.comment.service; - -import com.softeer.backend.fo_domain.comment.constant.CommentNickname; -import com.softeer.backend.fo_domain.comment.domain.Comment; -import com.softeer.backend.fo_domain.comment.dto.CommentsResponseDto; -import com.softeer.backend.fo_domain.comment.repository.CommentRepository; -import org.junit.jupiter.api.DisplayName; -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.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.*; - -@Transactional -@ExtendWith(MockitoExtension.class) -class CommentServiceTest { - private static final int SCROLL_SIZE = 30; - @Mock - private CommentRepository commentRepository; - - @InjectMocks - private CommentService commentService; - - @Test - @DisplayName("getComments - 기대평을 페이징 처리하여 반환") - void testGetComments() { - // Given - Integer userId = 1; - Integer cursor = 10; - List comments = createComments(); - - Page page = new PageImpl<>(comments, PageRequest.of(0, SCROLL_SIZE + 1), comments.size()); - when(commentRepository.findAllByIdLessThanOrderByIdDesc(anyInt(), any(PageRequest.class))) - .thenReturn(page); - - // When - CommentsResponseDto response = commentService.getComments(userId, cursor); - - // Then - assertThat(response).isNotNull(); - assertThat(response.getComments()).hasSize(2); - - // 첫 번째 기대평 - CommentsResponseDto.CommentResponse firstCommentResponse = response.getComments().get(0); - assertThat(firstCommentResponse).isNotNull(); - assertThat(firstCommentResponse.getIsMine()).isTrue(); - assertThat(firstCommentResponse.getNickName()).isEqualTo("user1(나)"); - assertThat(firstCommentResponse.getCommentType()).isEqualTo(1); - - // 두 번째 기대평 - CommentsResponseDto.CommentResponse secondCommentResponse = response.getComments().get(1); - assertThat(secondCommentResponse).isNotNull(); - assertThat(secondCommentResponse.getIsMine()).isFalse(); - assertThat(secondCommentResponse.getNickName()).isEqualTo("user2"); - assertThat(secondCommentResponse.getCommentType()).isEqualTo(1); - - verify(commentRepository, times(1)) - .findAllByIdLessThanOrderByIdDesc(eq(cursor), any(PageRequest.class)); - } - - @Test - @DisplayName("saveComment - 기대평 저장 테스트") - void testSaveComment() { - // Given - Integer userId = 1; - int commentType = 1; - - // When - commentService.saveComment(userId, commentType); - - // Then - verify(commentRepository, times(1)).save(any(Comment.class)); - } - - private List createComments() { - return Arrays.asList( - Comment.builder() - .id(9) - .nickname("user1") - .commentType(1) - .userId(1) - .build(), - Comment.builder() - .id(8) - .nickname("user2") - .commentType(1) - .userId(2) - .build() - ); - } -} \ No newline at end of file diff --git a/src/test/java/com/softeer/backend/fo_domain/draw/service/DrawServiceTest.java b/src/test/java/com/softeer/backend/fo_domain/draw/service/DrawServiceTest.java deleted file mode 100644 index c03b8095..00000000 --- a/src/test/java/com/softeer/backend/fo_domain/draw/service/DrawServiceTest.java +++ /dev/null @@ -1,640 +0,0 @@ -package com.softeer.backend.fo_domain.draw.service; - -import com.softeer.backend.fo_domain.draw.domain.Draw; -import com.softeer.backend.fo_domain.draw.domain.DrawParticipationInfo; -import com.softeer.backend.fo_domain.draw.dto.history.DrawHistoryDto; -import com.softeer.backend.fo_domain.draw.dto.main.DrawMainFullAttendResponseDto; -import com.softeer.backend.fo_domain.draw.dto.main.DrawMainResponseDto; -import com.softeer.backend.fo_domain.draw.dto.modal.WinModal; -import com.softeer.backend.fo_domain.draw.dto.participate.DrawLoseModalResponseDto; -import com.softeer.backend.fo_domain.draw.dto.participate.DrawModalResponseDto; -import com.softeer.backend.fo_domain.draw.dto.participate.DrawWinModalResponseDto; -import com.softeer.backend.fo_domain.draw.dto.history.DrawHistoryLoserResponseDto; -import com.softeer.backend.fo_domain.draw.dto.history.DrawHistoryResponseDto; -import com.softeer.backend.fo_domain.draw.dto.history.DrawHistoryWinnerResponseDto; -import com.softeer.backend.fo_domain.draw.exception.DrawException; -import com.softeer.backend.fo_domain.draw.repository.DrawParticipationInfoRepository; -import com.softeer.backend.fo_domain.draw.repository.DrawRepository; -import com.softeer.backend.fo_domain.draw.util.*; -import com.softeer.backend.fo_domain.share.domain.ShareInfo; -import com.softeer.backend.fo_domain.share.repository.ShareInfoRepository; -import com.softeer.backend.global.util.DrawRedisUtil; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.transaction.annotation.Transactional; - -import java.time.Clock; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.Mockito.*; - - -@Transactional -@ExtendWith(MockitoExtension.class) -class DrawServiceTest { - @Mock - private DrawParticipationInfoRepository drawParticipationInfoRepository; - @Mock - private ShareInfoRepository shareInfoRepository; - @Mock - private DrawRedisUtil drawRedisUtil; - @Mock - private DrawUtil drawUtil; - @Mock - private DrawResponseGenerateUtil drawResponseGenerateUtil; - @Mock - private DrawModalGenerateUtil drawModalGenerateUtil; - @Mock - private DrawAttendanceCountUtil drawAttendanceCountUtil; - @Mock - private DrawRemainDrawCountUtil drawRemainDrawCountUtil; - @Mock - private DrawSettingManager drawSettingManager; - @Mock - DrawRepository drawRepository; - - @InjectMocks - private DrawService drawService; - - @BeforeEach - @DisplayName("getDrawMainPageInfo를 위한 초기화") - void setUpGetDrawMainPageInfo() { - WinModal fullAttendModal = WinModal.builder() - .title("7일 연속 출석하셨네요!") - .subtitle("등록된 번호로 스타벅스 기프티콘을 보내드려요.") - .img("https://d1wv99asbppzjv.cloudfront.net/draw-page/7th_complete_image.svg") - .description("본 이벤트는 The new IONIQ 5 출시 이벤트 기간 내 한 회선당 1회만 참여 가능합니다.\n" + - "이벤트 경품 수령을 위해 등록된 전화번호로 영업일 기준 3~5일 내 개별 안내가 진행될 예정입니다.\n" + - "이벤트 당첨 이후 개인정보 제공을 거부하거나 개별 안내를 거부하는 경우, 당첨이 취소될 수 있습니다.\n") - .build(); - - Mockito.lenient().when(drawModalGenerateUtil.generateWinModal(7)).thenReturn(fullAttendModal); - - DrawMainFullAttendResponseDto drawMainFullAttendResponseDto = DrawMainFullAttendResponseDto - .builder() - .invitedNum(3) - .remainDrawCount(1) - .drawAttendanceCount(7) - .fullAttendModal(fullAttendModal) - .build(); - - Mockito.lenient().when(drawResponseGenerateUtil.generateMainFullAttendResponse(3, 1, 7)).thenReturn(drawMainFullAttendResponseDto); - - DrawMainResponseDto drawMainNotAttendResponseDto = DrawMainResponseDto - .builder() - .invitedNum(3) - .remainDrawCount(1) - .drawAttendanceCount(1) - .build(); - - Mockito.lenient().when(drawResponseGenerateUtil.generateMainNotAttendResponse(3, 1, 1)).thenReturn(drawMainNotAttendResponseDto); - } - - @Test - @DisplayName("7일 연속출석자의 추첨 이벤트 페이지 접속") - void getDrawMainPageFullAttend() { - // given - Integer userId = 6; - - WinModal fullAttendModal = WinModal.builder() - .title("7일 연속 출석하셨네요!") - .subtitle("등록된 번호로 스타벅스 기프티콘을 보내드려요.") - .img("https://d1wv99asbppzjv.cloudfront.net/draw-page/7th_complete_image.svg") - .description("본 이벤트는 The new IONIQ 5 출시 이벤트 기간 내 한 회선당 1회만 참여 가능합니다.\n" + - "이벤트 경품 수령을 위해 등록된 전화번호로 영업일 기준 3~5일 내 개별 안내가 진행될 예정입니다.\n" + - "이벤트 당첨 이후 개인정보 제공을 거부하거나 개별 안내를 거부하는 경우, 당첨이 취소될 수 있습니다.\n") - .build(); - - DrawParticipationInfo drawParticipationInfo = DrawParticipationInfo.builder() - .userId(userId) - .drawWinningCount(10) - .drawLosingCount(10) - .drawAttendanceCount(7) - .lastAttendance(LocalDateTime.parse("2024-08-23 15:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) - .build(); - - Clock fixedClock = Clock.fixed(LocalDateTime.of(2024, 8, 23, 15, 30, 0) - .atZone(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault()); - LocalDateTime now = LocalDateTime.now(fixedClock); - - when(drawParticipationInfoRepository.findDrawParticipationInfoByUserId(userId)) - .thenReturn(Optional.ofNullable(drawParticipationInfo)); - - ShareInfo shareInfo = ShareInfo.builder() - .userId(userId) - .invitedNum(3) - .remainDrawCount(1) - .build(); - - when(shareInfoRepository.findShareInfoByUserId(userId)) - .thenReturn(Optional.ofNullable(shareInfo)); - - lenient().when(drawAttendanceCountUtil.handleAttendanceCount(userId, drawParticipationInfo)).thenReturn(7); - - when(drawRemainDrawCountUtil.handleRemainDrawCount(userId, 1, drawParticipationInfo)) - .thenReturn(3); - - DrawMainFullAttendResponseDto expectedResponse = DrawMainFullAttendResponseDto.builder() - .invitedNum(3) - .remainDrawCount(3) - .drawAttendanceCount(7) - .fullAttendModal(fullAttendModal) - .build(); - - when(drawResponseGenerateUtil.generateMainFullAttendResponse(3, 3, 7 % 8)) - .thenReturn(expectedResponse); - - // when - DrawMainResponseDto actualResponse = drawService.getDrawMainPageInfo(userId); - - // then - assertThat(actualResponse).isNotNull(); - assertThat(actualResponse.getInvitedNum()).isEqualTo(expectedResponse.getInvitedNum()); - assertThat(actualResponse.getRemainDrawCount()).isEqualTo(expectedResponse.getRemainDrawCount()); - assertThat(actualResponse.getDrawAttendanceCount()).isEqualTo(expectedResponse.getDrawAttendanceCount()); - - DrawMainFullAttendResponseDto actualFullAttendResponse = (DrawMainFullAttendResponseDto) actualResponse; - - assertThat(actualFullAttendResponse.getFullAttendModal().getTitle()).isEqualTo(expectedResponse.getFullAttendModal().getTitle()); - assertThat(actualFullAttendResponse.getFullAttendModal().getSubtitle()).isEqualTo(expectedResponse.getFullAttendModal().getSubtitle()); - assertThat(actualFullAttendResponse.getFullAttendModal().getImg()).isEqualTo(expectedResponse.getFullAttendModal().getImg()); - assertThat(actualFullAttendResponse.getFullAttendModal().getDescription()).isEqualTo(expectedResponse.getFullAttendModal().getDescription()); - } - - @Test - @DisplayName("1일 출석자의 추첨 페이지 접속") - void getDrawMainPageNotAttend() { - // given - Integer userId = 6; - - DrawParticipationInfo drawParticipationInfo = DrawParticipationInfo.builder() - .userId(6) - .drawWinningCount(10) - .drawLosingCount(10) - .drawAttendanceCount(1) - .build(); - - when(drawParticipationInfoRepository.findDrawParticipationInfoByUserId(userId)) - .thenReturn(Optional.ofNullable(drawParticipationInfo)); - - ShareInfo shareInfo = ShareInfo.builder() - .userId(userId) - .invitedNum(3) - .remainDrawCount(1) - .build(); - - when(shareInfoRepository.findShareInfoByUserId(userId)) - .thenReturn(Optional.ofNullable(shareInfo)); - - when(drawAttendanceCountUtil.handleAttendanceCount(userId, drawParticipationInfo)).thenReturn(1); - - when(drawRemainDrawCountUtil.handleRemainDrawCount(userId, shareInfo.getRemainDrawCount(), drawParticipationInfo)) - .thenReturn(1); - - DrawMainResponseDto expectedResponse = DrawMainResponseDto.builder() - .invitedNum(3) - .remainDrawCount(1) - .drawAttendanceCount(1) - .build(); - - when(drawResponseGenerateUtil.generateMainNotAttendResponse(3, 1, 1)).thenReturn(expectedResponse); - - // when - DrawMainResponseDto actualResponse = drawService.getDrawMainPageInfo(userId); - - // then - assertThat(actualResponse).isNotNull(); - assertThat(actualResponse.getInvitedNum()).isEqualTo(expectedResponse.getInvitedNum()); - assertThat(actualResponse.getRemainDrawCount()).isEqualTo(expectedResponse.getRemainDrawCount()); - assertThat(actualResponse.getDrawAttendanceCount()).isEqualTo(expectedResponse.getDrawAttendanceCount()); - } - - @Test - @DisplayName("남은 기회 0회인 사용자의 추첨 참여") - void participateDrawEventZero() { - // given - Integer userId = 6; - - ShareInfo shareInfo = ShareInfo.builder() - .userId(userId) - .invitedNum(3) - .remainDrawCount(0) - .build(); - - when(shareInfoRepository.findShareInfoByUserId(userId)).thenReturn(Optional.ofNullable(shareInfo)); - - // When & Then - assertThatThrownBy(() -> drawService.participateDrawEvent(userId)) - .isInstanceOf(DrawException.class); - } - - @Test - @DisplayName("이미 하루 중 당첨된 적이 있는 사용자의 추첨 참여") - void participateDrawEventTwice() { - // given - Integer userId = 6; - - ShareInfo shareInfo = ShareInfo.builder() - .userId(userId) - .invitedNum(3) - .remainDrawCount(2) - .build(); - - when(shareInfoRepository.findShareInfoByUserId(userId)).thenReturn(Optional.ofNullable(shareInfo)); - - ArrayList images = new ArrayList<>(); - images.add("left"); - images.add("left"); - images.add("right"); - - DrawLoseModalResponseDto drawLoseModalResponseDto = DrawLoseModalResponseDto.builder() - .isDrawWin(false) - .images(images) - .shareUrl("https://softeer.site/share/of8w") - .build(); - - when(drawResponseGenerateUtil.generateDrawLoserResponse(userId)).thenReturn(drawLoseModalResponseDto); - - when(drawRedisUtil.getRankingIfWinner(userId)).thenReturn(3); - - // when - DrawModalResponseDto actualResponse = drawService.participateDrawEvent(userId); - - // then - assertThat(actualResponse).isNotNull(); - assertThat(actualResponse.isDrawWin()).isEqualTo(false); - assertThat(((DrawLoseModalResponseDto) actualResponse).getShareUrl()).isEqualTo("https://softeer.site/share/of8w"); - assertThat(actualResponse.getImages().get(0)).isEqualTo("left"); - assertThat(actualResponse.getImages().get(1)).isEqualTo("left"); - assertThat(actualResponse.getImages().get(2)).isEqualTo("right"); - } - - @Test - @DisplayName("낙첨자의 응답 반환") - void participateDrawEventLoser() { - // given - Integer userId = 6; - - ShareInfo shareInfo = ShareInfo.builder() - .userId(userId) - .invitedNum(3) - .remainDrawCount(2) - .build(); - - when(shareInfoRepository.findShareInfoByUserId(userId)).thenReturn(Optional.ofNullable(shareInfo)); - - when(drawRedisUtil.getRankingIfWinner(userId)).thenReturn(0); - - when(drawUtil.isDrawWin()).thenReturn(false); - - ArrayList images = new ArrayList<>(); - images.add("left"); - images.add("left"); - images.add("right"); - - DrawLoseModalResponseDto drawLoseModalResponseDto = DrawLoseModalResponseDto.builder() - .isDrawWin(false) - .images(images) - .shareUrl("https://softeer.site/share/of8w") - .build(); - - when(drawResponseGenerateUtil.generateDrawLoserResponse(userId)).thenReturn(drawLoseModalResponseDto); - - // when - DrawModalResponseDto actualResponse = drawService.participateDrawEvent(userId); - - // then - assertThat(actualResponse).isNotNull(); - assertThat(actualResponse.isDrawWin()).isEqualTo(false); - assertThat(((DrawLoseModalResponseDto) actualResponse).getShareUrl()).isEqualTo("https://softeer.site/share/of8w"); - assertThat(actualResponse.getImages().get(0)).isEqualTo("left"); - assertThat(actualResponse.getImages().get(1)).isEqualTo("left"); - assertThat(actualResponse.getImages().get(2)).isEqualTo("right"); - } - - @Test - @DisplayName("추첨 1등 응답 반환") - void participateDrawEventFirst() { - // given - Integer userId = 6; - - ShareInfo shareInfo = ShareInfo.builder() - .userId(userId) - .invitedNum(3) - .remainDrawCount(2) - .build(); - - when(shareInfoRepository.findShareInfoByUserId(userId)).thenReturn(Optional.ofNullable(shareInfo)); - - when(drawRedisUtil.getRankingIfWinner(userId)).thenReturn(0); - - when(drawSettingManager.getWinnerNum1()).thenReturn(1); - when(drawSettingManager.getWinnerNum2()).thenReturn(10); - when(drawSettingManager.getWinnerNum3()).thenReturn(100); - - when(drawUtil.isDrawWin()).thenReturn(true); - when(drawUtil.getRanking()).thenReturn(1); - - when(drawRedisUtil.isWinner(userId, 1, 1)).thenReturn(true); - - ArrayList images = new ArrayList<>(); - images.add("up"); - images.add("up"); - images.add("up"); - - WinModal winModal = WinModal.builder() - .title("축하합니다!") - .subtitle("아이패드에 당첨됐어요!") - .img("https://d1wv99asbppzjv.cloudfront.net/main-page/draw_reward_image_1.svg") - .description("이벤트 경품 수령을 위해 등록된 전화번호로 영업일 기준 3~5일 내 개별 안내가 진행될 예정입니다.\n" + - "이벤트 당첨 이후 개인정보 제공을 거부하거나 개별 안내를 거부하는 경우, 당첨이 취소될 수 있습니다.") - .build(); - - DrawWinModalResponseDto drawWinModalResponseDto = DrawWinModalResponseDto.builder() - .isDrawWin(true) - .images(images) - .winModal(winModal) - .build(); - - lenient().when(drawResponseGenerateUtil.generateDrawWinnerResponse(1)).thenReturn(drawWinModalResponseDto); - - // when - DrawModalResponseDto actualResponse = drawService.participateDrawEvent(userId); - - // then - assertThat(actualResponse).isNotNull(); - assertThat(actualResponse.isDrawWin()).isEqualTo(true); - assertThat(actualResponse.getImages().get(0)).isEqualTo("up"); - assertThat(actualResponse.getImages().get(1)).isEqualTo("up"); - assertThat(actualResponse.getImages().get(2)).isEqualTo("up"); - assertThat(((DrawWinModalResponseDto) actualResponse).getWinModal().getTitle()).isEqualTo("축하합니다!"); - assertThat(((DrawWinModalResponseDto) actualResponse).getWinModal().getSubtitle()).isEqualTo("아이패드에 당첨됐어요!"); - assertThat(((DrawWinModalResponseDto) actualResponse).getWinModal().getImg()).isEqualTo("https://d1wv99asbppzjv.cloudfront.net/main-page/draw_reward_image_1.svg"); - assertThat(((DrawWinModalResponseDto) actualResponse).getWinModal().getDescription()).isEqualTo("이벤트 경품 수령을 위해 등록된 전화번호로 영업일 기준 3~5일 내 개별 안내가 진행될 예정입니다.\n" + - "이벤트 당첨 이후 개인정보 제공을 거부하거나 개별 안내를 거부하는 경우, 당첨이 취소될 수 있습니다."); - } - - @Test - @DisplayName("추첨 2등 응답 반환") - void participateDrawEventSecond() { - // given - Integer userId = 6; - - ShareInfo shareInfo = ShareInfo.builder() - .userId(userId) - .invitedNum(3) - .remainDrawCount(2) - .build(); - - when(shareInfoRepository.findShareInfoByUserId(userId)).thenReturn(Optional.ofNullable(shareInfo)); - - when(drawRedisUtil.getRankingIfWinner(userId)).thenReturn(0); - - when(drawSettingManager.getWinnerNum1()).thenReturn(1); - when(drawSettingManager.getWinnerNum2()).thenReturn(10); - when(drawSettingManager.getWinnerNum3()).thenReturn(100); - - when(drawUtil.isDrawWin()).thenReturn(true); - when(drawUtil.getRanking()).thenReturn(2); - - when(drawRedisUtil.isWinner(userId, 2, 10)).thenReturn(true); - - ArrayList images = new ArrayList<>(); - images.add("up"); - images.add("up"); - images.add("up"); - - WinModal winModal = WinModal.builder() - .title("축하합니다!") - .subtitle("현대백화점 쿠폰 10만원퀀에 당첨됐어요!") - .img("https://d1wv99asbppzjv.cloudfront.net/main-page/draw_reward_image_2.svg") - .description("이벤트 경품 수령을 위해 등록된 전화번호로 영업일 기준 3~5일 내 개별 안내가 진행될 예정입니다.\n" + - "이벤트 당첨 이후 개인정보 제공을 거부하거나 개별 안내를 거부하는 경우, 당첨이 취소될 수 있습니다.") - .build(); - - DrawWinModalResponseDto drawWinModalResponseDto = DrawWinModalResponseDto.builder() - .isDrawWin(true) - .images(images) - .winModal(winModal) - .build(); - - when(drawResponseGenerateUtil.generateDrawWinnerResponse(2)).thenReturn(drawWinModalResponseDto); - - // when - DrawModalResponseDto actualResponse = drawService.participateDrawEvent(userId); - - // then - assertThat(actualResponse).isNotNull(); - assertThat(actualResponse.isDrawWin()).isEqualTo(true); - assertThat(actualResponse.getImages().get(0)).isEqualTo("up"); - assertThat(actualResponse.getImages().get(1)).isEqualTo("up"); - assertThat(actualResponse.getImages().get(2)).isEqualTo("up"); - assertThat(((DrawWinModalResponseDto) actualResponse).getWinModal().getTitle()).isEqualTo("축하합니다!"); - assertThat(((DrawWinModalResponseDto) actualResponse).getWinModal().getSubtitle()).isEqualTo("현대백화점 쿠폰 10만원퀀에 당첨됐어요!"); - assertThat(((DrawWinModalResponseDto) actualResponse).getWinModal().getImg()).isEqualTo("https://d1wv99asbppzjv.cloudfront.net/main-page/draw_reward_image_2.svg"); - assertThat(((DrawWinModalResponseDto) actualResponse).getWinModal().getDescription()).isEqualTo("이벤트 경품 수령을 위해 등록된 전화번호로 영업일 기준 3~5일 내 개별 안내가 진행될 예정입니다.\n" + - "이벤트 당첨 이후 개인정보 제공을 거부하거나 개별 안내를 거부하는 경우, 당첨이 취소될 수 있습니다."); - } - - @Test - @DisplayName("추첨 3등 응답 반환") - void participateDrawEventThird() { - // given - Integer userId = 6; - - ShareInfo shareInfo = ShareInfo.builder() - .userId(userId) - .invitedNum(3) - .remainDrawCount(2) - .build(); - - when(shareInfoRepository.findShareInfoByUserId(userId)).thenReturn(Optional.ofNullable(shareInfo)); - - when(drawRedisUtil.getRankingIfWinner(userId)).thenReturn(0); - - when(drawSettingManager.getWinnerNum1()).thenReturn(1); - when(drawSettingManager.getWinnerNum2()).thenReturn(10); - when(drawSettingManager.getWinnerNum3()).thenReturn(100); - - when(drawUtil.isDrawWin()).thenReturn(true); - when(drawUtil.getRanking()).thenReturn(3); - - when(drawRedisUtil.isWinner(userId, 3, 100)).thenReturn(true); - - ArrayList images = new ArrayList<>(); - images.add("up"); - images.add("up"); - images.add("up"); - - WinModal winModal = WinModal.builder() - .title("축하합니다!") - .subtitle("현대백화점 쿠폰 1만원퀀에 당첨됐어요!") - .img("https://d1wv99asbppzjv.cloudfront.net/main-page/draw_reward_image_3.svg") - .description("이벤트 경품 수령을 위해 등록된 전화번호로 영업일 기준 3~5일 내 개별 안내가 진행될 예정입니다.\n" + - "이벤트 당첨 이후 개인정보 제공을 거부하거나 개별 안내를 거부하는 경우, 당첨이 취소될 수 있습니다.") - .build(); - - DrawWinModalResponseDto drawWinModalResponseDto = DrawWinModalResponseDto.builder() - .isDrawWin(true) - .images(images) - .winModal(winModal) - .build(); - - when(drawResponseGenerateUtil.generateDrawWinnerResponse(3)).thenReturn(drawWinModalResponseDto); - - // when - DrawModalResponseDto actualResponse = drawService.participateDrawEvent(userId); - - // then - assertThat(actualResponse).isNotNull(); - assertThat(actualResponse.isDrawWin()).isEqualTo(true); - assertThat(actualResponse.getImages().get(0)).isEqualTo("up"); - assertThat(actualResponse.getImages().get(1)).isEqualTo("up"); - assertThat(actualResponse.getImages().get(2)).isEqualTo("up"); - assertThat(((DrawWinModalResponseDto) actualResponse).getWinModal().getTitle()).isEqualTo("축하합니다!"); - assertThat(((DrawWinModalResponseDto) actualResponse).getWinModal().getSubtitle()).isEqualTo("현대백화점 쿠폰 1만원퀀에 당첨됐어요!"); - assertThat(((DrawWinModalResponseDto) actualResponse).getWinModal().getImg()).isEqualTo("https://d1wv99asbppzjv.cloudfront.net/main-page/draw_reward_image_3.svg"); - assertThat(((DrawWinModalResponseDto) actualResponse).getWinModal().getDescription()).isEqualTo("이벤트 경품 수령을 위해 등록된 전화번호로 영업일 기준 3~5일 내 개별 안내가 진행될 예정입니다.\n" + - "이벤트 당첨 이후 개인정보 제공을 거부하거나 개별 안내를 거부하는 경우, 당첨이 취소될 수 있습니다."); - } - - @Test - @DisplayName("로직상 당첨이어도 레디스에 자리가 없는 경우 실패 응답 반환") - void participateDrawEventWithoutSeat() { - // given - Integer userId = 6; - - ShareInfo shareInfo = ShareInfo.builder() - .userId(userId) - .invitedNum(3) - .remainDrawCount(2) - .build(); - - when(shareInfoRepository.findShareInfoByUserId(userId)).thenReturn(Optional.ofNullable(shareInfo)); - - when(drawRedisUtil.getRankingIfWinner(userId)).thenReturn(0); - - when(drawSettingManager.getWinnerNum1()).thenReturn(1); - when(drawSettingManager.getWinnerNum2()).thenReturn(10); - when(drawSettingManager.getWinnerNum3()).thenReturn(100); - - when(drawUtil.isDrawWin()).thenReturn(true); - when(drawUtil.getRanking()).thenReturn(1); - - when(drawRedisUtil.isWinner(userId, 1, 1)).thenReturn(false); - - ArrayList images = new ArrayList<>(); - images.add("up"); - images.add("up"); - images.add("down"); - - DrawLoseModalResponseDto expectedResponse = DrawLoseModalResponseDto.builder() - .isDrawWin(false) - .images(images) - .shareUrl("https://softeer.shop/share/of8w") - .build(); - - when(drawResponseGenerateUtil.generateDrawLoserResponse(userId)).thenReturn(expectedResponse); - - // when - DrawModalResponseDto actualResponse = drawService.participateDrawEvent(userId); - - // then - assertThat(actualResponse).isNotNull(); - assertThat(actualResponse.isDrawWin()).isEqualTo(false); - assertThat(actualResponse.getImages().get(0)).isEqualTo("up"); - assertThat(actualResponse.getImages().get(1)).isEqualTo("up"); - assertThat(actualResponse.getImages().get(2)).isEqualTo("down"); - assertThat(((DrawLoseModalResponseDto) actualResponse).getShareUrl()).isEqualTo("https://softeer.shop/share/of8w"); - } - - @BeforeEach - @DisplayName("getDrawHistory를 위한 초기화") - void setUpGetDrawHistory() { - lenient().when(drawService.getDrawHistory(6)).thenReturn(DrawHistoryLoserResponseDto.builder() - .isDrawWin(false) - .shareUrl("https://softeer.shop/share/of8w") - .build()); - } - - @Test - @DisplayName("getDrawHistory - 사용자가 당첨자인 경우") - void testGetDrawHistory_WhenUserIsWinner() { - // Given - Integer userId = 1; - List drawList; - int redisRank = 1; // Mock된 redis 순위 (1, 2, 3 중 하나로 설정) - List drawHistoryList; - - drawList = Arrays.asList( - Draw.builder().rank(2).winningDate(LocalDate.of(2022, 1, 1)).build(), - Draw.builder().rank(3).winningDate(LocalDate.of(2022, 2, 1)).build() - ); - - drawHistoryList = Arrays.asList( - DrawHistoryDto.builder().drawRank(2).winningDate(LocalDate.of(2022, 1, 1)).image("url1").build(), - DrawHistoryDto.builder().drawRank(3).winningDate(LocalDate.of(2022, 2, 1)).image("url2").build(), - DrawHistoryDto.builder().drawRank(redisRank).winningDate(LocalDate.now()).image("url3").build() - ); - - when(drawRedisUtil.getRankingIfWinner(userId)).thenReturn(redisRank); - when(drawRepository.findAllByUserIdOrderByWinningDateAsc(userId)).thenReturn(drawList); - when(drawResponseGenerateUtil.getImageUrl(anyInt())).thenReturn("url1", "url2", "url3"); - when(drawResponseGenerateUtil.generateDrawHistoryWinnerResponse(anyList())).thenReturn( - DrawHistoryWinnerResponseDto.builder() - .isDrawWin(true) - .historyList(drawHistoryList) - .build() - ); - - // When - DrawHistoryResponseDto response = drawService.getDrawHistory(userId); - - // Then - assertThat((DrawHistoryWinnerResponseDto) response).isNotNull(); - assertThat(((DrawHistoryWinnerResponseDto) response).getHistoryList()).hasSize(3); - - assertThat(((DrawHistoryWinnerResponseDto) response).getHistoryList().get(0).getDrawRank()).isEqualTo(2); - assertThat(((DrawHistoryWinnerResponseDto) response).getHistoryList().get(1).getDrawRank()).isEqualTo(3); - assertThat(((DrawHistoryWinnerResponseDto) response).getHistoryList().get(2).getDrawRank()).isEqualTo(redisRank); - } - - @Test - @DisplayName("당첨자가 아닐 경우 당첨 내역 조회 시 낙첨 결과") - void getDrawHistoryLoser() { - // given - Integer userId = 6; - - when(drawRedisUtil.getRankingIfWinner(userId)).thenReturn(0); - when(drawRepository.findAllByUserIdOrderByWinningDateAsc(userId)).thenReturn(new ArrayList<>()); - - DrawHistoryLoserResponseDto expectedResponse = DrawHistoryLoserResponseDto.builder() - .isDrawWin(false) - .shareUrl("https://softeer.shop/share/of8w") - .build(); - - // when - DrawHistoryResponseDto actualResponse = drawService.getDrawHistory(userId); - - // then - assertThat(actualResponse).isNotEqualTo(null); - assertThat(actualResponse.isDrawWin()).isEqualTo(false); - assertThat(((DrawHistoryLoserResponseDto) actualResponse).getShareUrl()).isEqualTo(expectedResponse.getShareUrl()); - } -} \ No newline at end of file diff --git a/src/test/java/com/softeer/backend/fo_domain/fcfs/service/FcfsServiceTest.java b/src/test/java/com/softeer/backend/fo_domain/fcfs/service/FcfsServiceTest.java deleted file mode 100644 index 04e69d97..00000000 --- a/src/test/java/com/softeer/backend/fo_domain/fcfs/service/FcfsServiceTest.java +++ /dev/null @@ -1,247 +0,0 @@ -package com.softeer.backend.fo_domain.fcfs.service; - -import com.softeer.backend.fo_domain.draw.service.DrawSettingManager; -import com.softeer.backend.fo_domain.fcfs.domain.FcfsSetting; -import com.softeer.backend.fo_domain.fcfs.dto.FcfsPageResponseDto; -import com.softeer.backend.fo_domain.fcfs.dto.FcfsRequestDto; -import com.softeer.backend.fo_domain.fcfs.dto.FcfsSettingDto; -import com.softeer.backend.fo_domain.fcfs.dto.QuizDto; -import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsFailResult; -import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsResultResponseDto; -import com.softeer.backend.fo_domain.fcfs.dto.result.FcfsSuccessResult; -import com.softeer.backend.fo_domain.fcfs.exception.FcfsException; -import com.softeer.backend.global.common.code.status.ErrorStatus; -import com.softeer.backend.global.staticresources.constant.StaticTextName; -import com.softeer.backend.global.staticresources.util.StaticResourceUtil; -import com.softeer.backend.global.util.FcfsRedisUtil; -import com.softeer.backend.global.util.RandomCodeUtil; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -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.beans.factory.ObjectProvider; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class FcfsServiceTest { - - @Mock - private StaticResourceUtil staticResourceUtil; - - @Mock - private QuizManager quizManager; - - @Mock - private FcfsSettingManager fcfsSettingManager; - - @Mock - private DrawSettingManager drawSettingManager; - - @Mock - private FcfsRedisUtil fcfsRedisUtil; - - @Mock - private RandomCodeUtil randomCodeUtil; - - @Mock - private ObjectProvider fcfsServiceProvider; - - private FcfsService mockFcfsService; - - @InjectMocks - private FcfsService fcfsService; - - private FcfsRequestDto fcfsRequestDto; - private String correctAnswer = "correct"; - private String wrongAnswer = "wrong"; - private int userId = 1; - private int round = 1; - - @BeforeEach - void setUp() { - fcfsRequestDto = new FcfsRequestDto(); - - mockFcfsService = mock(FcfsService.class); - - - } - - @Test - @DisplayName("선착순 페이지에 필요한 정보를 반환한다.") - void testGetFcfsPage() { - // Given - QuizDto quizDto = QuizDto.builder() - .answerWord("correct") - .answerSentence("Answer sentence") - .startIndex(1) - .endIndex(10) - .build(); - - Map textContentMap = new HashMap<>(); - textContentMap.put(StaticTextName.FCFS_QUIZ_DESCRIPTION.name(), "Description: %d"); - - when(staticResourceUtil.format(anyString(), anyInt())).thenAnswer(invocation -> { - String template = invocation.getArgument(0); - Integer winnerNum = invocation.getArgument(1); - return String.format(template, winnerNum); - }); - - when(quizManager.getQuiz(round)).thenReturn(quizDto); - when(staticResourceUtil.getTextContentMap()).thenReturn(textContentMap); - when(fcfsSettingManager.getFcfsWinnerNum()).thenReturn(100); - - // When - FcfsPageResponseDto response = fcfsService.getFcfsPage(round); - - // Then - assertThat(response).isNotNull(); - assertThat(response.getAnswerWord()).isEqualTo("correct"); - assertThat(response.getAnswerSentence()).isEqualTo("Answer sentence"); - assertThat(response.getStartIndex()).isEqualTo(1); - assertThat(response.getEndIndex()).isEqualTo(10); - assertThat(response.getQuizDescription()).isEqualTo("Description: 100"); - } - - @Test - @DisplayName("선착순 이벤트 처리 성공 시 성공 응답을 반환한다.") - void testHandleFcfsEvent_Success() { - // Given - fcfsRequestDto.setAnswer(correctAnswer); - when(fcfsServiceProvider.getObject()).thenReturn(mockFcfsService); - when(quizManager.getQuiz(round)).thenReturn(new QuizDto("hint", correctAnswer, "Answer sentence", 1, 10)); - when(fcfsSettingManager.isFcfsClosed()).thenReturn(false); - - FcfsResultResponseDto successResponse = FcfsResultResponseDto.builder() - .fcfsWinner(true) - .build(); - when(mockFcfsService.saveFcfsWinners(userId, round)).thenReturn(successResponse); - - // When - FcfsResultResponseDto response = fcfsService.handleFcfsEvent(userId, round, fcfsRequestDto); - - // Then - assertThat(response).isNotNull(); - assertThat(response.isFcfsWinner()).isTrue(); - } - - @Test - @DisplayName("선착순 이벤트 처리 시 퀴즈 정답이 틀리면 예외가 발생한다.") - void testHandleFcfsEvent_WrongAnswer() { - // Given - fcfsRequestDto.setAnswer(wrongAnswer); - when(quizManager.getQuiz(round)).thenReturn(new QuizDto("hint", correctAnswer, "Answer sentence", 1, 10)); - - // When / Then - assertThatThrownBy(() -> fcfsService.handleFcfsEvent(userId, round, fcfsRequestDto)) - .isInstanceOf(FcfsException.class) - .extracting(ex -> ((FcfsException) ex).getCode()) - .isEqualTo(ErrorStatus._BAD_REQUEST); - } - - @Test - @DisplayName("선착순 이벤트가 마감되었을 때 선착순 실패 응답을 반환한다.") - void testHandleFcfsEvent_Closed() { - // Given - fcfsRequestDto.setAnswer(correctAnswer); - when(quizManager.getQuiz(round)).thenReturn(new QuizDto("hint", correctAnswer, "Answer sentence", 1, 10)); - when(fcfsSettingManager.isFcfsClosed()).thenReturn(true); - when(fcfsServiceProvider.getObject()).thenReturn(mockFcfsService); - - // When - FcfsResultResponseDto response = fcfsService.handleFcfsEvent(userId, round, fcfsRequestDto); - - // Then - assertThat(response).isNotNull(); - assertThat(response.isFcfsWinner()).isFalse(); - } - - @Test - @DisplayName("선착순 당첨자 등록 성공 시 성공 응답을 반환한다.") - void testSaveFcfsWinners_Success() { - // Given - when(fcfsRedisUtil.getIntegerSetSize(anyString())).thenReturn(0L); - when(fcfsSettingManager.getFcfsWinnerNum()).thenReturn(100); - when(fcfsRedisUtil.isValueInIntegerSet(anyString(), anyInt())).thenReturn(false); - when(randomCodeUtil.generateRandomCode(5)).thenReturn("ABCDE"); - when(fcfsServiceProvider.getObject()).thenReturn(mockFcfsService); - - FcfsSettingDto mockSettingDto = new FcfsSettingDto(); - when(fcfsSettingManager.getFcfsSettingByRound(1)).thenReturn(mockSettingDto); - - FcfsSuccessResult successResult = FcfsSuccessResult.builder() - .fcfsCode("ABCDE") - .build(); - - when(mockFcfsService.getFcfsSuccessResult(any(FcfsSettingDto.class))) - .thenReturn(successResult); - - // When - FcfsResultResponseDto response = fcfsService.saveFcfsWinners(userId, round); - - // Then - assertThat(response).isNotNull(); - assertThat(response.isFcfsWinner()).isTrue(); - assertThat(response.getFcfsResult()).isNotNull(); - assertThat(((FcfsSuccessResult) (response.getFcfsResult())).getFcfsCode()).isEqualTo("AABCDE"); - } - - @Test - @DisplayName("선착순 당첨자 등록 실패 시 실패 응답을 반환한다.") - void testSaveFcfsWinners_Failure() { - // Given - when(fcfsRedisUtil.getIntegerSetSize(anyString())).thenReturn(100L); // 이미 모든 당첨자가 존재함 - when(fcfsSettingManager.getFcfsWinnerNum()).thenReturn(100); - when(fcfsServiceProvider.getObject()).thenReturn(mockFcfsService); - - // When - FcfsResultResponseDto response = fcfsService.saveFcfsWinners(userId, round); - - // Then - assertThat(response).isNotNull(); - assertThat(response.isFcfsWinner()).isFalse(); - } - - @Test - @DisplayName("선착순 성공 결과 모달 정보를 반환한다.") - void testGetFcfsResult_Success() { - // Given - Map textContentMap = new HashMap<>(); - Map s3ContentMap = new HashMap<>(); - FcfsSettingDto fcfsSettingDto = FcfsSettingDto.builder() - .round(1) - .startTime(LocalDateTime.now()) - .endTime(LocalDateTime.now()) - .winnerNum(10) - .build(); - when(fcfsServiceProvider.getObject()).thenReturn(mockFcfsService); - when(fcfsSettingManager.getFcfsSettingByRound(1)).thenReturn(fcfsSettingDto); - - FcfsSuccessResult successResult = FcfsSuccessResult.builder() - .fcfsCode("ABCDE") - .build(); - - when(mockFcfsService.getFcfsSuccessResult(any(FcfsSettingDto.class))) - .thenReturn(successResult); - - - // When - FcfsResultResponseDto response = fcfsService.getFcfsResult(true, false, "A12345"); - - // Then - assertThat(response).isNotNull(); - assertThat(response.isFcfsWinner()).isTrue(); - } -} diff --git a/src/test/java/com/softeer/backend/fo_domain/mainpage/service/MainPageServiceTest.java b/src/test/java/com/softeer/backend/fo_domain/mainpage/service/MainPageServiceTest.java deleted file mode 100644 index ce4fd3e1..00000000 --- a/src/test/java/com/softeer/backend/fo_domain/mainpage/service/MainPageServiceTest.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.softeer.backend.fo_domain.mainpage.service; - -import com.softeer.backend.fo_domain.draw.repository.DrawRepository; -import com.softeer.backend.fo_domain.draw.service.DrawSettingManager; -import com.softeer.backend.fo_domain.fcfs.dto.FcfsSettingDto; -import com.softeer.backend.fo_domain.fcfs.service.FcfsSettingManager; -import com.softeer.backend.fo_domain.fcfs.service.QuizManager; -import com.softeer.backend.fo_domain.mainpage.dto.MainPageCarResponseDto; -import com.softeer.backend.fo_domain.mainpage.dto.MainPageEventInfoResponseDto; -import com.softeer.backend.fo_domain.mainpage.dto.MainPageEventStaticResponseDto; -import com.softeer.backend.global.common.constant.RedisKeyPrefix; -import com.softeer.backend.global.staticresources.constant.S3FileName; -import com.softeer.backend.global.staticresources.constant.StaticTextName; -import com.softeer.backend.global.staticresources.util.StaticResourceUtil; -import com.softeer.backend.global.util.EventLockRedisUtil; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -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.beans.factory.ObjectProvider; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -public class MainPageServiceTest { - @InjectMocks - private MainPageService mainPageService; - - @Mock - private EventLockRedisUtil eventLockRedisUtil; - - @Mock - private FcfsSettingManager fcfsSettingManager; - - @Mock - private DrawSettingManager drawSettingManager; - - @Mock - private QuizManager quizManager; - - @Mock - private DrawRepository drawRepository; - - @Mock - private StaticResourceUtil staticResourceUtil; - - @Test - @DisplayName("메인 페이지의 정적 정보를 올바르게 반환한다.") - void testGetEventPageStatic() { - // Given - Map textContentMap = new HashMap<>(); - textContentMap.put(StaticTextName.FCFS_TITLE.name(), "FCFS 제목"); - textContentMap.put(StaticTextName.FCFS_CONTENT.name(), "FCFS 내용"); - textContentMap.put(StaticTextName.DRAW_TITLE.name(), "추첨 제목"); - textContentMap.put(StaticTextName.DRAW_CONTENT.name(), "추첨 내용"); - textContentMap.put(StaticTextName.EVENT_TITLE.name(), "이벤트 제목"); - textContentMap.put(StaticTextName.EVENT_DESCRIPTION.name(), "이벤트 설명"); - - Map s3ContentMap = new HashMap<>(); - s3ContentMap.put(S3FileName.FCFS_REWARD_IMAGE_1.name(), "fcfs_reward_image_1.jpg"); - s3ContentMap.put(S3FileName.FCFS_REWARD_IMAGE_2.name(), "fcfs_reward_image_2.jpg"); - s3ContentMap.put(S3FileName.DRAW_REWARD_IMAGE_1.name(), "draw_reward_image_1.jpg"); - s3ContentMap.put(S3FileName.DRAW_REWARD_IMAGE_2.name(), "draw_reward_image_2.jpg"); - s3ContentMap.put(S3FileName.DRAW_REWARD_IMAGE_3.name(), "draw_reward_image_3.jpg"); - - when(staticResourceUtil.getTextContentMap()).thenReturn(textContentMap); - when(staticResourceUtil.getS3ContentMap()).thenReturn(s3ContentMap); - - // When - MainPageEventStaticResponseDto response = mainPageService.getEventPageStatic(); - - // Then - assertThat(response).isNotNull(); - assertThat(response.getEventTitle()).isEqualTo("이벤트 제목"); - assertThat(response.getEventDescription()).isEqualTo("이벤트 설명"); - - List eventInfoList = response.getEventInfoList(); - assertThat(eventInfoList).hasSize(2); - - MainPageEventStaticResponseDto.EventInfo fcfsInfo = eventInfoList.get(0); - assertThat(fcfsInfo.getTitle()).isEqualTo("FCFS 제목"); - assertThat(fcfsInfo.getContent()).isEqualTo("FCFS 내용"); - assertThat(fcfsInfo.getRewardImage1()).isEqualTo("fcfs_reward_image_1.jpg"); - assertThat(fcfsInfo.getRewardImage2()).isEqualTo("fcfs_reward_image_2.jpg"); - - MainPageEventStaticResponseDto.EventInfo drawInfo = eventInfoList.get(1); - assertThat(drawInfo.getTitle()).isEqualTo("추첨 제목"); - assertThat(drawInfo.getContent()).isEqualTo("추첨 내용"); - assertThat(drawInfo.getRewardImage1()).isEqualTo("draw_reward_image_1.jpg"); - assertThat(drawInfo.getRewardImage2()).isEqualTo("draw_reward_image_2.jpg"); - assertThat(drawInfo.getRewardImage3()).isEqualTo("draw_reward_image_3.jpg"); - } - - @Test - @DisplayName("메인 페이지의 이벤트 정보를 올바르게 반환한다.") - void testGetEventPageInfo() { - // Given - Map textContentMap = new HashMap<>(); - - when(staticResourceUtil.getTextContentMap()).thenReturn(textContentMap); - - FcfsSettingDto firstFcfsSetting = new FcfsSettingDto(); - firstFcfsSetting.setStartTime(LocalDateTime.of(2024, 8, 21, 10, 0)); - firstFcfsSetting.setWinnerNum(5); - - FcfsSettingDto secondFcfsSetting = new FcfsSettingDto(); - secondFcfsSetting.setStartTime(LocalDateTime.of(2024, 8, 22, 11, 0)); - - when(fcfsSettingManager.getFcfsSettingByRound(1)).thenReturn(firstFcfsSetting); - when(fcfsSettingManager.getFcfsSettingByRound(2)).thenReturn(secondFcfsSetting); - - when(drawSettingManager.getWinnerNum1()).thenReturn(10); - when(drawSettingManager.getWinnerNum2()).thenReturn(20); - when(drawSettingManager.getWinnerNum3()).thenReturn(30); - when(drawRepository.count()).thenReturn(15L); - when(drawSettingManager.getStartDate()).thenReturn(LocalDate.of(2024, 8, 1)); - when(drawSettingManager.getEndDate()).thenReturn(LocalDate.of(2024, 8, 31)); - when(quizManager.getHint()).thenReturn("퀴즈 힌트"); - when(fcfsSettingManager.getNowOrNextFcfsTime(any())).thenReturn(LocalDateTime.of(2024, 8, 22, 11, 0)); - when(drawSettingManager.getStartTime()).thenReturn(LocalTime.of(22, 11, 0)); - when(drawSettingManager.getEndTime()).thenReturn(LocalTime.of(22, 11, 0)); - - // When - MainPageEventInfoResponseDto response = mainPageService.getEventPageInfo(); - - // Then - assertThat(response).isNotNull(); - assertThat(response.getStartDate()).isEqualTo("2024.08.01"); - assertThat(response.getEndDate()).isEqualTo("2024.08.31"); - assertThat(response.getFcfsHint()).isEqualTo("퀴즈 힌트"); - assertThat(response.getFcfsStartTime()).isEqualTo(LocalDateTime.of(2024, 8, 22, 11, 0)); - } - - @Test - @DisplayName("메인 페이지의 자동차 정보를 올바르게 반환한다.") - void testGetCarPage() { - // Given - Map textContentMap = new HashMap<>(); - textContentMap.put(StaticTextName.MAIN_TITLE.name(), "자동차 메인 제목"); - textContentMap.put(StaticTextName.MAIN_SUBTITLE.name(), "자동차 메인 부제목"); - textContentMap.put(StaticTextName.INTERIOR_TITLE.name(), "인테리어 제목"); - textContentMap.put(StaticTextName.INTERIOR_SUBTITLE.name(), "인테리어 부제목"); - textContentMap.put(StaticTextName.PERFORMANCE_TITLE.name(), "성능 제목"); - textContentMap.put(StaticTextName.PERFORMANCE_SUBTITLE.name(), "성능 부제목"); - textContentMap.put(StaticTextName.CHARGING_TITLE.name(), "충전 제목"); - textContentMap.put(StaticTextName.CHARGING_SUBTITLE.name(), "충전 부제목"); - textContentMap.put(StaticTextName.SAFE_TITLE.name(), "안전 제목"); - textContentMap.put(StaticTextName.SAFE_SUBTITLE.name(), "안전 부제목"); - - Map s3ContentMap = new HashMap<>(); - s3ContentMap.put(S3FileName.IONIQ_VIDEO.name(), "ioniq_video.mp4"); - s3ContentMap.put(S3FileName.MAIN_BACKGROUND_IMAGE.name(), "main_background.jpg"); - s3ContentMap.put(S3FileName.INTERIOR_THUMBNAIL_IMAGE.name(), "interior_thumbnail.jpg"); - s3ContentMap.put(S3FileName.INTERIOR_BACKGROUND_IMAGE.name(), "interior_background.jpg"); - s3ContentMap.put(S3FileName.PERFORMANCE_THUMBNAIL_IMAGE.name(), "performance_thumbnail.jpg"); - s3ContentMap.put(S3FileName.PERFORMANCE_BACKGROUND_IMAGE.name(), "performance_background.jpg"); - s3ContentMap.put(S3FileName.CHARGING_THUMBNAIL_IMAGE.name(), "charging_thumbnail.jpg"); - s3ContentMap.put(S3FileName.CHARGING_BACKGROUND_IMAGE.name(), "charging_background.jpg"); - s3ContentMap.put(S3FileName.SAFE_THUMBNAIL_IMAGE.name(), "safe_thumbnail.jpg"); - s3ContentMap.put(S3FileName.SAFE_BACKGROUND_IMAGE.name(), "safe_background.jpg"); - - when(staticResourceUtil.getTextContentMap()).thenReturn(textContentMap); - when(staticResourceUtil.getS3ContentMap()).thenReturn(s3ContentMap); - - // When - MainPageCarResponseDto response = mainPageService.getCarPage(); - - // Then - assertThat(response).isNotNull(); - assertThat(response.getCarInfoList()).hasSize(5); - - MainPageCarResponseDto.CarInfo mainCarInfo = response.getCarInfoList().get(0); - assertThat(mainCarInfo.getTitle()).isEqualTo("자동차 메인 제목"); - assertThat(mainCarInfo.getSubTitle()).isEqualTo("자동차 메인 부제목"); - assertThat(mainCarInfo.getImgUrl()).isEqualTo("ioniq_video.mp4"); - assertThat(mainCarInfo.getBackgroundImgUrl()).isEqualTo("main_background.jpg"); - } -} diff --git a/src/test/java/com/softeer/backend/fo_domain/share/service/ShareUrlInfoServiceTest.java b/src/test/java/com/softeer/backend/fo_domain/share/service/ShareUrlInfoServiceTest.java deleted file mode 100644 index 597417de..00000000 --- a/src/test/java/com/softeer/backend/fo_domain/share/service/ShareUrlInfoServiceTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.softeer.backend.fo_domain.share.service; - -import com.softeer.backend.fo_domain.share.dto.ShareUrlInfoResponseDto; -import com.softeer.backend.fo_domain.share.exception.ShareInfoException; -import com.softeer.backend.fo_domain.share.repository.ShareUrlInfoRepository; -import org.junit.jupiter.api.DisplayName; -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.transaction.annotation.Transactional; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.when; - -@Transactional -@ExtendWith(MockitoExtension.class) -class ShareUrlInfoServiceTest { - public static final String NON_USER_SHARE_URL = "https://softeer.site"; - public static final String BASE_URL = "https://softeer.site/share/"; - private static final Integer VALID_USER_ID = 6; - private static final String VALID_SHARE_CODE = "of8w"; - - @Mock - private ShareUrlInfoRepository shareUrlInfoRepository; - - @InjectMocks - private ShareUrlInfoService shareUrlInfoService; - - @Test - @DisplayName("로그인하지 않은 사용자 (userId가 null)인 경우") - void testGetShortenShareUrl_forNonLoggedInUser() { - ShareUrlInfoResponseDto response = shareUrlInfoService.getShortenShareUrl(null); - - assertThat(response).isNotNull(); - assertThat(response.getShareUrl()).isEqualTo(NON_USER_SHARE_URL); - } - - @Test - @DisplayName("로그인한 사용자, 유효한 userId와 shareCode가 존재하는 경우") - void testGetShortenShareUrl_forLoggedInUser_validShareCode() { - // given - when(shareUrlInfoRepository.findShareUrlByUserId(VALID_USER_ID)) - .thenReturn(Optional.of(VALID_SHARE_CODE)); - - // when - ShareUrlInfoResponseDto response = shareUrlInfoService.getShortenShareUrl(VALID_USER_ID); - - // then - assertThat(response).isNotNull(); - assertThat(response.getShareUrl()).isEqualTo(BASE_URL + VALID_SHARE_CODE); - } - - @Test - @DisplayName("로그인한 사용자, 유효한 userId에 해당하는 shareCode가 없는 경우") - void testGetShortenShareUrl_forLoggedInUser_shareCodeNotFound() { - // when - when(shareUrlInfoRepository.findShareUrlByUserId(VALID_USER_ID)) - .thenReturn(Optional.empty()); - - // then - assertThatThrownBy(() -> shareUrlInfoService.getShortenShareUrl(VALID_USER_ID)) - .isInstanceOf(ShareInfoException.class); - } -} \ No newline at end of file diff --git a/src/test/java/com/softeer/backend/fo_domain/user/service/LoginServiceTest.java b/src/test/java/com/softeer/backend/fo_domain/user/service/LoginServiceTest.java deleted file mode 100644 index 23252019..00000000 --- a/src/test/java/com/softeer/backend/fo_domain/user/service/LoginServiceTest.java +++ /dev/null @@ -1,180 +0,0 @@ -package com.softeer.backend.fo_domain.user.service; - -import com.softeer.backend.fo_domain.draw.domain.DrawParticipationInfo; -import com.softeer.backend.fo_domain.draw.repository.DrawParticipationInfoRepository; -import com.softeer.backend.fo_domain.share.domain.ShareInfo; -import com.softeer.backend.fo_domain.share.domain.ShareUrlInfo; -import com.softeer.backend.fo_domain.share.exception.ShareUrlInfoException; -import com.softeer.backend.fo_domain.share.repository.ShareInfoRepository; -import com.softeer.backend.fo_domain.share.repository.ShareUrlInfoRepository; -import com.softeer.backend.fo_domain.user.domain.User; -import com.softeer.backend.fo_domain.user.dto.LoginRequestDto; -import com.softeer.backend.fo_domain.user.exception.UserException; -import com.softeer.backend.fo_domain.user.repository.UserRepository; -import com.softeer.backend.global.common.code.status.ErrorStatus; -import com.softeer.backend.global.common.dto.JwtClaimsDto; -import com.softeer.backend.global.common.dto.JwtTokenResponseDto; -import com.softeer.backend.global.util.JwtUtil; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -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 java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -public class LoginServiceTest { - @Mock - private UserRepository userRepository; - - @Mock - private ShareInfoRepository shareInfoRepository; - - @Mock - private ShareUrlInfoRepository shareUrlInfoRepository; - - @Mock - private DrawParticipationInfoRepository drawParticipationInfoRepository; - - @Mock - private JwtUtil jwtUtil; - - @InjectMocks - private LoginService loginService; - - private LoginRequestDto loginRequestDto; - private final String shareCode = "ABCD"; - private final String phoneNumber = "01012345678"; - - @BeforeEach - void setUp() { - loginRequestDto = LoginRequestDto.builder() - .phoneNumber(phoneNumber) - .name("TestUser") - .hasCodeVerified(true) - .privacyConsent(true) - .marketingConsent(true) - .build(); - - } - - @Test - @DisplayName("인증되지 않은 코드로 로그인 시도 시 예외 발생") - void testHandleLogin_UnverifiedCode() { - // given - loginRequestDto.setHasCodeVerified(false); - - // when / then - assertThatThrownBy(() -> loginService.handleLogin(loginRequestDto, shareCode)) - .isInstanceOf(UserException.class) - .extracting(ex -> ((UserException) ex).getCode()) - .isEqualTo(ErrorStatus._AUTH_CODE_NOT_VERIFIED); - } - - @Test - @DisplayName("전화번호가 DB에 없는 경우 새로운 User 등록 및 관련 정보 생성") - void testHandleLogin_NewUserRegistration() { - // given - lenient().when(shareUrlInfoRepository.findUserIdByShareUrl(anyString())).thenReturn(Optional.of(1)); - when(userRepository.existsByPhoneNumber(phoneNumber)).thenReturn(false); - when(userRepository.save(any(User.class))).thenAnswer(invocation -> { - User user = invocation.getArgument(0); - user.setId(1); // 임의의 ID 설정 - return user; - }); - - when(jwtUtil.createServiceToken(any(JwtClaimsDto.class))) - .thenReturn(JwtTokenResponseDto.builder() - .accessToken("accessToken") - .refreshToken("refreshToken") - .build()); - - // when - JwtTokenResponseDto response = loginService.handleLogin(loginRequestDto, shareCode); - - // then - verify(userRepository, times(1)).save(any(User.class)); - verify(drawParticipationInfoRepository, times(1)).save(any(DrawParticipationInfo.class)); - verify(shareInfoRepository, times(1)).save(any(ShareInfo.class)); - verify(shareUrlInfoRepository, times(1)).save(any(ShareUrlInfo.class)); - - assertThat(response).isNotNull(); - assertThat(response.getAccessToken()).isEqualTo("accessToken"); - assertThat(response.getRefreshToken()).isEqualTo("refreshToken"); - } - - @Test - @DisplayName("전화번호가 DB에 있는 경우 기존 User 객체 조회") - void testHandleLogin_ExistingUser() { - // given - User existingUser = User.builder() - .id(1) - .name("TestUser") - .phoneNumber(phoneNumber) - .privacyConsent(true) - .marketingConsent(true) - .build(); - - when(userRepository.existsByPhoneNumber(phoneNumber)).thenReturn(true); - when(userRepository.findByPhoneNumber(phoneNumber)).thenReturn(existingUser); - - when(jwtUtil.createServiceToken(any(JwtClaimsDto.class))) - .thenReturn(JwtTokenResponseDto.builder() - .accessToken("accessToken") - .refreshToken("refreshToken") - .build()); - - // when - JwtTokenResponseDto response = loginService.handleLogin(loginRequestDto, shareCode); - - // then - verify(userRepository, times(1)).findByPhoneNumber(phoneNumber); - verify(userRepository, never()).save(any(User.class)); // 새로운 User는 저장되지 않음 - - assertThat(response).isNotNull(); - assertThat(response.getAccessToken()).isEqualTo("accessToken"); - assertThat(response.getRefreshToken()).isEqualTo("refreshToken"); - } - - @Test - @DisplayName("공유 URL을 통해 로그인한 경우 공유자에게 추첨 기회 추가") - void testHandleLogin_SharedUrl() { - // given - User newUser = User.builder() - .id(1) - .name("TestUser") - .phoneNumber(phoneNumber) - .privacyConsent(true) - .marketingConsent(true) - .build(); - - when(userRepository.existsByPhoneNumber(phoneNumber)).thenReturn(false); - when(userRepository.save(any(User.class))).thenReturn(newUser); - when(shareUrlInfoRepository.findUserIdByShareUrl(shareCode)).thenReturn(Optional.of(2)); - - when(jwtUtil.createServiceToken(any(JwtClaimsDto.class))) - .thenReturn(JwtTokenResponseDto.builder() - .accessToken("accessToken") - .refreshToken("refreshToken") - .build()); - - // when - JwtTokenResponseDto response = loginService.handleLogin(loginRequestDto, shareCode); - - // then - verify(shareInfoRepository, times(1)).increaseInvitedNumAndRemainDrawCount(2); - verify(shareUrlInfoRepository, times(1)).findUserIdByShareUrl(shareCode); - - assertThat(response).isNotNull(); - assertThat(response.getAccessToken()).isEqualTo("accessToken"); - assertThat(response.getRefreshToken()).isEqualTo("refreshToken"); - } -} diff --git a/src/test/java/com/softeer/backend/fo_domain/user/service/VerificationServiceTest.java b/src/test/java/com/softeer/backend/fo_domain/user/service/VerificationServiceTest.java deleted file mode 100644 index 4ca85627..00000000 --- a/src/test/java/com/softeer/backend/fo_domain/user/service/VerificationServiceTest.java +++ /dev/null @@ -1,179 +0,0 @@ -package com.softeer.backend.fo_domain.user.service; - -import com.softeer.backend.fo_domain.user.constatnt.RedisVerificationPrefix; -import com.softeer.backend.fo_domain.user.constatnt.VerificationProperty; -import com.softeer.backend.fo_domain.user.dto.verification.VerificationCodeResponseDto; -import com.softeer.backend.fo_domain.user.exception.UserException; -import com.softeer.backend.fo_domain.user.properties.SmsProperties; -import com.softeer.backend.global.common.code.status.ErrorStatus; -import com.softeer.backend.global.util.RandomCodeUtil; -import com.softeer.backend.global.util.StringRedisUtil; -import net.nurigo.sdk.message.request.SingleMessageSendingRequest; -import net.nurigo.sdk.message.service.DefaultMessageService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -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 java.time.LocalDateTime; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowableOfType; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -public class VerificationServiceTest { - - @Mock - private StringRedisUtil stringRedisUtil; - - @Mock - private RandomCodeUtil randomCodeUtil; - - @Mock - private SmsProperties smsProperties; - - @Mock - private DefaultMessageService messageService; - - @InjectMocks - private VerificationService verificationService; - - private static final String PHONE_NUMBER = "01012345678"; - private static final String VERIFICATION_CODE = "123456"; - private static final long TIME_LIMIT = 300L; - - @Test - @DisplayName("처음 인증 코드 발급 시 Redis에 발급 횟수 저장 및 코드 발송") - void testSendVerificationCode_FirstTime() { - // Given - given(smsProperties.getSenderNumber()).willReturn("01000000000"); - given(randomCodeUtil.generateRandomCode(anyInt())).willReturn(VERIFICATION_CODE); - given(stringRedisUtil.hasKey(anyString())).willReturn(false); - - // When - VerificationCodeResponseDto response = verificationService.sendVerificationCode(PHONE_NUMBER); - - // Then - verify(stringRedisUtil, times(1)).setDataExpireAt(anyString(), eq("1"), any(LocalDateTime.class)); - verify(stringRedisUtil, times(1)).deleteData(anyString()); - verify(messageService, times(1)).sendOne(any(SingleMessageSendingRequest.class)); - verify(stringRedisUtil, times(1)).setDataExpire(anyString(), eq(VERIFICATION_CODE), eq(TIME_LIMIT)); - - assertThat(response).isNotNull(); - assertThat(response.getTimeLimit()).isEqualTo(TIME_LIMIT); - } - - @Test - @DisplayName("발급 횟수 초과 시 UserException 예외 발생") - void testSendVerificationCode_ExceedIssueLimit() { - // Given - given(stringRedisUtil.hasKey(anyString())).willReturn(true); - given(stringRedisUtil.incrementData(anyString())).willReturn((long) (VerificationProperty.CODE_ISSUE_ATTEMPTS.getValue() + 1)); - - // When & Then - assertThrows(UserException.class, () -> verificationService.sendVerificationCode(PHONE_NUMBER)); - - verify(stringRedisUtil, never()).setDataExpireAt(anyString(), anyString(), any(LocalDateTime.class)); - verify(stringRedisUtil, never()).deleteData(anyString()); - verify(messageService, never()).sendOne(any(SingleMessageSendingRequest.class)); - verify(stringRedisUtil, never()).setDataExpire(anyString(), anyString(), anyLong()); - } - - @Test - @DisplayName("발급 횟수를 초과하지 않은 경우 정상적으로 인증 코드 발송") - void testSendVerificationCode_NotExceedIssueLimit() { - // Given - given(smsProperties.getSenderNumber()).willReturn("01000000000"); - given(randomCodeUtil.generateRandomCode(anyInt())).willReturn(VERIFICATION_CODE); - given(stringRedisUtil.hasKey(anyString())).willReturn(true); - given(stringRedisUtil.incrementData(anyString())).willReturn(2L); - - // When - VerificationCodeResponseDto response = verificationService.sendVerificationCode(PHONE_NUMBER); - - // Then - verify(stringRedisUtil, never()).setDataExpireAt(anyString(), anyString(), any(LocalDateTime.class)); - verify(stringRedisUtil, times(1)).deleteData(anyString()); - verify(messageService, times(1)).sendOne(any(SingleMessageSendingRequest.class)); - verify(stringRedisUtil, times(1)).setDataExpire(anyString(), eq(VERIFICATION_CODE), eq(TIME_LIMIT)); - - assertThat(response).isNotNull(); - assertThat(response.getTimeLimit()).isEqualTo(TIME_LIMIT); - } - - @Test - @DisplayName("인증 코드가 일치하고 인증에 성공하면 Redis에서 관련 데이터 삭제") - void testConfirmVerificationCode_Success() { - // given - when(stringRedisUtil.getData(RedisVerificationPrefix.VERIFICATION_CODE.getPrefix() + PHONE_NUMBER)) - .thenReturn(VERIFICATION_CODE); - - // when - verificationService.confirmVerificationCode(PHONE_NUMBER, VERIFICATION_CODE); - - // then - verify(stringRedisUtil).deleteData(RedisVerificationPrefix.VERIFICATION_ISSUE_COUNT.getPrefix() + PHONE_NUMBER); - verify(stringRedisUtil).deleteData(RedisVerificationPrefix.VERIFICATION_ATTEMPTS.getPrefix() + PHONE_NUMBER); - verify(stringRedisUtil).deleteData(RedisVerificationPrefix.VERIFICATION_CODE.getPrefix() + PHONE_NUMBER); - } - - @Test - @DisplayName("인증 코드가 만료되었을 때 UserException 발생") - void testConfirmVerificationCode_CodeExpired() { - // given - when(stringRedisUtil.getData(RedisVerificationPrefix.VERIFICATION_CODE.getPrefix() + PHONE_NUMBER)) - .thenReturn(null); - - // when / then - UserException exception = catchThrowableOfType( - () -> verificationService.confirmVerificationCode(PHONE_NUMBER, VERIFICATION_CODE), - UserException.class - ); - - assertThat(exception).isNotNull(); - assertThat(exception.getCode()).isEqualTo(ErrorStatus._AUTH_CODE_NOT_EXIST); - } - - @Test - @DisplayName("인증 코드가 일치하지 않을 때 UserException 발생") - void testConfirmVerificationCode_CodeNotMatch() { - // given - when(stringRedisUtil.getData(RedisVerificationPrefix.VERIFICATION_CODE.getPrefix() + PHONE_NUMBER)) - .thenReturn("654321"); - - // when / then - UserException exception = catchThrowableOfType( - () -> verificationService.confirmVerificationCode(PHONE_NUMBER, VERIFICATION_CODE), - UserException.class - ); - - assertThat(exception).isNotNull(); - assertThat(exception.getCode()).isEqualTo(ErrorStatus._AUTH_CODE_NOT_MATCH); - } - - @Test - @DisplayName("인증 시도 횟수를 초과하면 UserException 발생") - void testConfirmVerificationCode_ExceedAttemptsLimit() { - // given - when(stringRedisUtil.hasKey(RedisVerificationPrefix.VERIFICATION_ATTEMPTS.getPrefix() + PHONE_NUMBER)) - .thenReturn(true); - when(stringRedisUtil.incrementData(RedisVerificationPrefix.VERIFICATION_ATTEMPTS.getPrefix() + PHONE_NUMBER)) - .thenReturn((long) (VerificationProperty.MAX_ATTEMPTS.getValue() + 1)); - - // when / then - UserException exception = catchThrowableOfType( - () -> verificationService.confirmVerificationCode(PHONE_NUMBER, VERIFICATION_CODE), - UserException.class - ); - - assertThat(exception).isNotNull(); - assertThat(exception.getCode()).isEqualTo(ErrorStatus._AUTH_CODE_ATTEMPTS_EXCEEDED); - } -}