From 385e34c48ecf24cd7f5bf58cf9e94cd7cd2597a4 Mon Sep 17 00:00:00 2001 From: Moojun Date: Sun, 8 Oct 2023 06:22:11 +0900 Subject: [PATCH 1/2] =?UTF-8?q?#105=20-=20feat:=20=EB=A9=98=ED=86=A0?= =?UTF-8?q?=EC=9D=98=20=EC=83=81=EC=84=B8=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/dao/QaListRepository.java | 14 +++- .../following/api/FollowingController.java | 21 ++++-- .../application/FollowingService.java | 69 ++++++++++++++----- .../domain/following/dto/FollowingQaDto.java | 21 ++++++ ...ingUserInfo.java => FollowingUserDto.java} | 6 +- .../following/dto/FollowingUserInfoDto.java | 42 +++++++++++ .../response/FollowingMentorInfoResponse.java | 25 +++++++ .../dto/response/FollowingMentorResponse.java | 6 +- .../menjil/global/exception/SuccessCode.java | 4 +- 9 files changed, 174 insertions(+), 34 deletions(-) create mode 100644 src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingQaDto.java rename src/main/java/seoultech/capstone/menjil/domain/following/dto/{FollowingUserInfo.java => FollowingUserDto.java} (79%) create mode 100644 src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingUserInfoDto.java create mode 100644 src/main/java/seoultech/capstone/menjil/domain/following/dto/response/FollowingMentorInfoResponse.java diff --git a/src/main/java/seoultech/capstone/menjil/domain/chat/dao/QaListRepository.java b/src/main/java/seoultech/capstone/menjil/domain/chat/dao/QaListRepository.java index a8dc964..6d752c0 100644 --- a/src/main/java/seoultech/capstone/menjil/domain/chat/dao/QaListRepository.java +++ b/src/main/java/seoultech/capstone/menjil/domain/chat/dao/QaListRepository.java @@ -11,9 +11,21 @@ @Repository public interface QaListRepository extends MongoRepository { - @Query(value = "{ 'mentor_nickname' : ?0, 'answer' : { '$ne' : null } }", fields = "{ 'question_summary' : 1 }") + /* + 이 메서드에서는, question_summary field 외에 다른 필드는 필요하지 않으므로 + 쿼리성능 향상을 위해, fields 에서 question_summary 값만 가져오도록 설정하였다. + But in MongoDb, the _id field is included by default even if you don't explicitly specify it. + This is just the standard behavior of MongoDB + */ + @Query(value = "{ 'mentor_nickname' : ?0, 'answer' : { '$ne' : null } }", + fields = "{ 'question_summary' : 1 }") List findAnsweredQuestionsByMentor(String mentorNickname, Pageable pageable); + @Query(value = "{'answer' : { '$ne' : null } }", + fields = "{ 'question_origin' : 1, 'question_summary' : 1, " + + "'answer' : 1, 'answer_time': 1 }") + List findQaListsByMentorNicknameOrderByAnswerTimeAsc(String mentorNickname); + Long countByMentorNicknameAndAnswerIsNotNull(String mentorNickname); } diff --git a/src/main/java/seoultech/capstone/menjil/domain/following/api/FollowingController.java b/src/main/java/seoultech/capstone/menjil/domain/following/api/FollowingController.java index 1c5cdd2..1b38c9a 100644 --- a/src/main/java/seoultech/capstone/menjil/domain/following/api/FollowingController.java +++ b/src/main/java/seoultech/capstone/menjil/domain/following/api/FollowingController.java @@ -8,11 +8,9 @@ import org.springframework.data.web.PageableDefault; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import seoultech.capstone.menjil.domain.following.application.FollowingService; +import seoultech.capstone.menjil.domain.following.dto.response.FollowingMentorInfoResponse; import seoultech.capstone.menjil.domain.following.dto.response.FollowingMentorResponse; import seoultech.capstone.menjil.global.common.dto.ApiResponse; import seoultech.capstone.menjil.global.exception.SuccessCode; @@ -27,10 +25,19 @@ public class FollowingController { private final int PAGE_SIZE = 9; @GetMapping() - public ResponseEntity>> getFollowMentorsOfUser(@RequestParam("nickname") String nickname, @PageableDefault(size = PAGE_SIZE, sort = {"createdDate"}, + public ResponseEntity>> getAllFollowMentors(@RequestParam("nickname") String nickname, @PageableDefault(size = PAGE_SIZE, sort = {"createdDate"}, direction = Sort.Direction.ASC) Pageable pageable) { return ResponseEntity.status(HttpStatus.OK) - .body(ApiResponse.success(SuccessCode.GET_FOLLOW_MENTOR_LIST_AVAILABLE, - followingService.getFollowMentorsOfUser(nickname, pageable))); + .body(ApiResponse.success(SuccessCode.GET_ALL_FOLLOW_MENTOR_SUCCESS, + followingService.getAllFollowMentors(nickname, pageable))); + } + + @GetMapping("/info") + public ResponseEntity> getFollowMentorInfo( + @RequestParam("nickname") String nickname, + @RequestParam("followNickname") String followNickname) { + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(SuccessCode.GET_FOLLOW_MENTOR_INFO_SUCCESS, + followingService.getFollowMentorInfo(nickname, followNickname))); } } diff --git a/src/main/java/seoultech/capstone/menjil/domain/following/application/FollowingService.java b/src/main/java/seoultech/capstone/menjil/domain/following/application/FollowingService.java index ad084cf..5d5b937 100644 --- a/src/main/java/seoultech/capstone/menjil/domain/following/application/FollowingService.java +++ b/src/main/java/seoultech/capstone/menjil/domain/following/application/FollowingService.java @@ -8,23 +8,25 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import seoultech.capstone.menjil.domain.auth.dao.UserRepository; import seoultech.capstone.menjil.domain.auth.domain.User; import seoultech.capstone.menjil.domain.chat.dao.QaListRepository; import seoultech.capstone.menjil.domain.chat.domain.QaList; import seoultech.capstone.menjil.domain.follow.dao.FollowRepository; import seoultech.capstone.menjil.domain.follow.domain.Follow; -import seoultech.capstone.menjil.domain.following.dto.FollowingUserInfo; +import seoultech.capstone.menjil.domain.following.dto.FollowingQaDto; +import seoultech.capstone.menjil.domain.following.dto.FollowingUserDto; +import seoultech.capstone.menjil.domain.following.dto.FollowingUserInfoDto; +import seoultech.capstone.menjil.domain.following.dto.response.FollowingMentorInfoResponse; import seoultech.capstone.menjil.domain.following.dto.response.FollowingMentorResponse; import seoultech.capstone.menjil.global.exception.CustomException; import seoultech.capstone.menjil.global.exception.ErrorCode; import seoultech.capstone.menjil.global.handler.AwsS3Handler; import java.time.Duration; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -import java.util.stream.Stream; @Slf4j @RequiredArgsConstructor @@ -41,7 +43,8 @@ public class FollowingService { @Value("${cloud.aws.s3.bucket}") private String BUCKET_NAME; - public Page getFollowMentorsOfUser(String nickname, Pageable pageable) { + @Transactional + public Page getAllFollowMentors(String nickname, Pageable pageable) { // get follows Page page = followRepository.findFollowsByUserNickname(nickname, pageable); @@ -51,39 +54,69 @@ public Page getFollowMentorsOfUser(String nickname, Pag // 1. get follows User user = userRepository.findUserByNickname(followNickname) .orElseThrow(() -> new CustomException(ErrorCode.SERVER_ERROR)); - FollowingUserInfo followingUserInfo = FollowingUserInfo.fromUserEntity(user); + FollowingUserDto followingUserDto = FollowingUserDto.fromUserEntity(user); // set AWS S3 presigned url - followingUserInfo.setImgUrl(String.valueOf(awsS3Handler.generatePresignedUrl( + followingUserDto.setImgUrl(String.valueOf(awsS3Handler.generatePresignedUrl( BUCKET_NAME, user.getImgUrl(), Duration.ofDays(AWS_URL_DURATION)))); // 2. get last answered messages List lastAnsweredMessages = getLastAnsweredMessages(followNickname); // 3. get followers count + // TODO: user와 follow를 join하면 쿼리를 두 번 날리지 않아도 될 것으로 생각됨. 추후 JPA 공부한 뒤 적용해볼 것 Long followersCount = followRepository.countByFollowNickname(followNickname); // 4. get answers count Long answersCount = qaListRepository.countByMentorNicknameAndAnswerIsNotNull(followNickname); - return FollowingMentorResponse.of(followingUserInfo, lastAnsweredMessages, followersCount, answersCount); + return FollowingMentorResponse.of(followingUserDto, lastAnsweredMessages, followersCount, answersCount); }); return followMentorInfoResponse; } - private List getLastAnsweredMessages(String mentorNickname) { - Pageable pageable = PageRequest.of(0, 2, - Sort.by(Sort.Direction.DESC, "question_time", "id")); // Get only the first 2 documents and sort them by 'question_time' and 'id' in descending order + @Transactional + public FollowingMentorInfoResponse getFollowMentorInfo(String nickname, String followNickname) { + User user = userRepository.findUserByNickname(followNickname) + .orElseThrow(() -> new CustomException(ErrorCode.SERVER_ERROR)); + + // 1. 사용자 정보 + FollowingUserInfoDto followingUserInfoDto = FollowingUserInfoDto.fromUserEntity(user); + followingUserInfoDto.setImgUrl(String.valueOf(awsS3Handler.generatePresignedUrl( + BUCKET_NAME, user.getImgUrl(), Duration.ofDays(AWS_URL_DURATION)))); + + // 2. 작성 답변 개수 + Long answersCount = qaListRepository.countByMentorNicknameAndAnswerIsNotNull(followNickname); + + // 3. 작성 질문/답변 목록 리스트 + List followingQaDtos = qaListRepository.findQaListsByMentorNicknameOrderByAnswerTimeAsc(followNickname) + .stream() + .map(q -> new FollowingQaDto(q.getQuestionOrigin(), q.getQuestionSummary(), + q.getAnswer(), q.getAnswerTime())) + .collect(Collectors.toList()); + + // TODO: 4. 추천 답변 개수 + // TODO: 5. 멘토링 후기 + + return FollowingMentorInfoResponse.of(followingUserInfoDto, answersCount, followingQaDtos); + } + + protected List getLastAnsweredMessages(String mentorNickname) { + int page = 0; + int size = 2; + // Get only the first 2 documents and sort them by 'question_time' and 'id' in descending order + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "question_time", "id")); List qaLists = qaListRepository.findAnsweredQuestionsByMentor(mentorNickname, pageable); - if (qaLists.isEmpty()) { - return new ArrayList<>(); - } else if (qaLists.size() == 1) { - return Stream.of(qaLists.get(0)) - .map(QaList::getQuestionSummary) - .collect(Collectors.toList()); - } - return List.of(qaLists.get(0).getQuestionSummary(), qaLists.get(1).getQuestionSummary()); + /* + At first, I checked if qaLists is empty(), or size == 1, or else. But + The stream() and map() method calls will produce an empty list if qaLists is empty, + and will otherwise produce a list of question summaries, + making the explicit checks for qaLists.isEmpty() and qaLists.size() == 1 unnecessary. + */ + return qaLists.stream() + .map(QaList::getQuestionSummary) + .collect(Collectors.toList()); } } diff --git a/src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingQaDto.java b/src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingQaDto.java new file mode 100644 index 0000000..a62d8da --- /dev/null +++ b/src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingQaDto.java @@ -0,0 +1,21 @@ +package seoultech.capstone.menjil.domain.following.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class FollowingQaDto { + + private String questionOrigin; + private String questionSummary; + private String answer; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + private LocalDateTime answerTime; +} diff --git a/src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingUserInfo.java b/src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingUserDto.java similarity index 79% rename from src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingUserInfo.java rename to src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingUserDto.java index c805885..a4249de 100644 --- a/src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingUserInfo.java +++ b/src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingUserDto.java @@ -8,7 +8,7 @@ @Getter @NoArgsConstructor @AllArgsConstructor -public class FollowingUserInfo { +public class FollowingUserDto { private String nickname; private String company; // 재직 중인 회사 @@ -22,8 +22,8 @@ public void setImgUrl(String imgUrl) { this.imgUrl = imgUrl; } - public static FollowingUserInfo fromUserEntity(User user) { - return new FollowingUserInfo(user.getNickname(), user.getCompany(), user.getField(), + public static FollowingUserDto fromUserEntity(User user) { + return new FollowingUserDto(user.getNickname(), user.getCompany(), user.getField(), user.getTechStack(), user.getSchool(), user.getMajor(), user.getImgUrl()); } diff --git a/src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingUserInfoDto.java b/src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingUserInfoDto.java new file mode 100644 index 0000000..01f7e69 --- /dev/null +++ b/src/main/java/seoultech/capstone/menjil/domain/following/dto/FollowingUserInfoDto.java @@ -0,0 +1,42 @@ +package seoultech.capstone.menjil.domain.following.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import seoultech.capstone.menjil.domain.auth.domain.User; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class FollowingUserInfoDto { + + private String nickname; + private String company; // 재직 중인 회사 + private String field; // 관심 분야 + private String school; + private String major; // 본전공 + private String subMajor; // 복수전공 + + private String minor; // 부전공 + private String techStack; // 기술 스택 + private String imgUrl; + + private String career; + private String certificate; + private String awards; + private String activity; + + + public void setImgUrl(String imgUrl) { + this.imgUrl = imgUrl; + } + + public static FollowingUserInfoDto fromUserEntity(User user) { + return new FollowingUserInfoDto(user.getNickname(), user.getCompany(), user.getField(), + user.getSchool(), user.getMajor(), user.getSubMajor(), user.getMinor(), + user.getTechStack(), user.getImgUrl(), + user.getOptionInfo().getCareer(), user.getOptionInfo().getCertificate(), + user.getOptionInfo().getAwards(), user.getOptionInfo().getActivity() + ); + } +} diff --git a/src/main/java/seoultech/capstone/menjil/domain/following/dto/response/FollowingMentorInfoResponse.java b/src/main/java/seoultech/capstone/menjil/domain/following/dto/response/FollowingMentorInfoResponse.java new file mode 100644 index 0000000..627d9d3 --- /dev/null +++ b/src/main/java/seoultech/capstone/menjil/domain/following/dto/response/FollowingMentorInfoResponse.java @@ -0,0 +1,25 @@ +package seoultech.capstone.menjil.domain.following.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import seoultech.capstone.menjil.domain.following.dto.FollowingQaDto; +import seoultech.capstone.menjil.domain.following.dto.FollowingUserInfoDto; + +import java.util.List; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class FollowingMentorInfoResponse { + + private FollowingUserInfoDto followingUserInfoDto; + private Long answersCount; + private List answers; + + public static FollowingMentorInfoResponse of(FollowingUserInfoDto userInfo, + Long answersCount, List answers) { + return new FollowingMentorInfoResponse(userInfo, answersCount, answers); + } +} + diff --git a/src/main/java/seoultech/capstone/menjil/domain/following/dto/response/FollowingMentorResponse.java b/src/main/java/seoultech/capstone/menjil/domain/following/dto/response/FollowingMentorResponse.java index 9b1dbff..ae17ae9 100644 --- a/src/main/java/seoultech/capstone/menjil/domain/following/dto/response/FollowingMentorResponse.java +++ b/src/main/java/seoultech/capstone/menjil/domain/following/dto/response/FollowingMentorResponse.java @@ -3,7 +3,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; -import seoultech.capstone.menjil.domain.following.dto.FollowingUserInfo; +import seoultech.capstone.menjil.domain.following.dto.FollowingUserDto; import java.util.List; @@ -12,12 +12,12 @@ @AllArgsConstructor public class FollowingMentorResponse { - private FollowingUserInfo followingUserInfo; + private FollowingUserDto followingUserDto; private List lastAnsweredMessages; // 가장 최근에 답변한 질문(최대 2개) private Long followersCount; private Long answersCount; - public static FollowingMentorResponse of(FollowingUserInfo userInfo, List lastAnsweredMessages, + public static FollowingMentorResponse of(FollowingUserDto userInfo, List lastAnsweredMessages, Long followersCount, Long answersCount) { return new FollowingMentorResponse(userInfo, lastAnsweredMessages, followersCount, answersCount); } diff --git a/src/main/java/seoultech/capstone/menjil/global/exception/SuccessCode.java b/src/main/java/seoultech/capstone/menjil/global/exception/SuccessCode.java index de1c021..bb1783c 100644 --- a/src/main/java/seoultech/capstone/menjil/global/exception/SuccessCode.java +++ b/src/main/java/seoultech/capstone/menjil/global/exception/SuccessCode.java @@ -34,8 +34,8 @@ public enum SuccessCode { FOLLOW_CHECK_SUCCESS(HttpStatus.OK.value(), "팔로우 조회에 성공하셨습니다"), // following - GET_FOLLOW_MENTOR_LIST_AVAILABLE(HttpStatus.OK.value(), "팔로우 멘토 리스트를 불러오는데 성공하였습니다"), - + GET_ALL_FOLLOW_MENTOR_SUCCESS(HttpStatus.OK.value(), "팔로우 멘토 리스트를 불러오는데 성공하였습니다"), + GET_FOLLOW_MENTOR_INFO_SUCCESS(HttpStatus.OK.value(), "팔로우 멘토 정보를 불러오는데 성공하였습니다"), /** * 201 CREATED From d4a6ab80a537a21d10de6a3cf54a10ef67ed4fa0 Mon Sep 17 00:00:00 2001 From: Moojun Date: Sun, 8 Oct 2023 07:29:32 +0900 Subject: [PATCH 2/2] =?UTF-8?q?#105=20-=20test:=20=EB=A9=98=ED=86=A0?= =?UTF-8?q?=EC=9D=98=20=EC=83=81=EC=84=B8=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/dao/QaListRepository.java | 5 +- .../application/FollowingService.java | 3 +- .../application/FollowingServiceTest.java | 275 ++++++++++++++++++ 3 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 src/test/java/seoultech/capstone/menjil/domain/following/application/FollowingServiceTest.java diff --git a/src/main/java/seoultech/capstone/menjil/domain/chat/dao/QaListRepository.java b/src/main/java/seoultech/capstone/menjil/domain/chat/dao/QaListRepository.java index 6d752c0..9ee5247 100644 --- a/src/main/java/seoultech/capstone/menjil/domain/chat/dao/QaListRepository.java +++ b/src/main/java/seoultech/capstone/menjil/domain/chat/dao/QaListRepository.java @@ -1,6 +1,7 @@ package seoultech.capstone.menjil.domain.chat.dao; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; @@ -21,10 +22,10 @@ public interface QaListRepository extends MongoRepository { fields = "{ 'question_summary' : 1 }") List findAnsweredQuestionsByMentor(String mentorNickname, Pageable pageable); - @Query(value = "{'answer' : { '$ne' : null } }", + @Query(value = "{'mentor_nickname' : ?0, 'answer' : { '$ne' : null } }", fields = "{ 'question_origin' : 1, 'question_summary' : 1, " + "'answer' : 1, 'answer_time': 1 }") - List findQaListsByMentorNicknameOrderByAnswerTimeAsc(String mentorNickname); + List findQuestionAndAnswerWithMentorNickname(String mentorNickname, Sort sort); Long countByMentorNicknameAndAnswerIsNotNull(String mentorNickname); diff --git a/src/main/java/seoultech/capstone/menjil/domain/following/application/FollowingService.java b/src/main/java/seoultech/capstone/menjil/domain/following/application/FollowingService.java index 5d5b937..6789eab 100644 --- a/src/main/java/seoultech/capstone/menjil/domain/following/application/FollowingService.java +++ b/src/main/java/seoultech/capstone/menjil/domain/following/application/FollowingService.java @@ -90,7 +90,8 @@ public FollowingMentorInfoResponse getFollowMentorInfo(String nickname, String f Long answersCount = qaListRepository.countByMentorNicknameAndAnswerIsNotNull(followNickname); // 3. 작성 질문/답변 목록 리스트 - List followingQaDtos = qaListRepository.findQaListsByMentorNicknameOrderByAnswerTimeAsc(followNickname) + Sort sort = Sort.by(Sort.Order.asc("answer_time")); + List followingQaDtos = qaListRepository.findQuestionAndAnswerWithMentorNickname(followNickname, sort) .stream() .map(q -> new FollowingQaDto(q.getQuestionOrigin(), q.getQuestionSummary(), q.getAnswer(), q.getAnswerTime())) diff --git a/src/test/java/seoultech/capstone/menjil/domain/following/application/FollowingServiceTest.java b/src/test/java/seoultech/capstone/menjil/domain/following/application/FollowingServiceTest.java new file mode 100644 index 0000000..37a6d9b --- /dev/null +++ b/src/test/java/seoultech/capstone/menjil/domain/following/application/FollowingServiceTest.java @@ -0,0 +1,275 @@ +package seoultech.capstone.menjil.domain.following.application; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; +import seoultech.capstone.menjil.domain.auth.dao.UserRepository; +import seoultech.capstone.menjil.domain.auth.domain.OptionInfo; +import seoultech.capstone.menjil.domain.auth.domain.User; +import seoultech.capstone.menjil.domain.auth.domain.UserRole; +import seoultech.capstone.menjil.domain.chat.dao.QaListRepository; +import seoultech.capstone.menjil.domain.chat.domain.QaList; +import seoultech.capstone.menjil.domain.follow.dao.FollowRepository; +import seoultech.capstone.menjil.domain.follow.domain.Follow; +import seoultech.capstone.menjil.domain.following.dto.FollowingQaDto; +import seoultech.capstone.menjil.domain.following.dto.response.FollowingMentorInfoResponse; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +class FollowingServiceTest { + + @Autowired + private FollowingService followingService; + + @Autowired + private UserRepository userRepository; + + @Autowired + private FollowRepository followRepository; + + @Autowired + private QaListRepository qaListRepository; + + private final String TEST_MENTEE_NICKNAME = "test_mentee_1"; + private final String TEST_MENTOR_NICKNAME_1 = "test_mentor_1"; + private final int qaNumWithAnswers = 11; + + @BeforeEach + void setUp() { + // Save Mentee and Mentors + User mentee = createTestUser("google_123123", "mentee@mentee.com", TEST_MENTEE_NICKNAME, + UserRole.MENTEE); + userRepository.save(mentee); + + int mentorNum = 30; + List users = IntStream.rangeClosed(1, mentorNum) + .mapToObj(i -> { + String id = "google_" + i; + String email = "mentor" + i + "@mentor.com"; + String nickname = "test_mentor_" + i; + return createTestUser(id, email, nickname, UserRole.MENTOR); + }) + .collect(Collectors.toList()); + userRepository.saveAll(users); + + // Save Follows + LocalDateTime now = LocalDateTime.now(); + int followNum = 6; + List follows = IntStream.rangeClosed(1, followNum) + .mapToObj(i -> { + String menteeNickname = TEST_MENTEE_NICKNAME; + String mentorNickname = "test_mentor_" + i; + return Follow.of(menteeNickname, mentorNickname, now.plusMinutes(i)); + }) + .collect(Collectors.toList()); + followRepository.saveAll(follows); + + // Save QaList with answer is not null + // mentor is only TEST_MENTOR_NICKNAME_1 + List qaLists = IntStream.rangeClosed(1, qaNumWithAnswers) + .mapToObj(i -> createTestQaListAndAnswerIsNotNull(TEST_MENTOR_NICKNAME_1, i)) + .collect(Collectors.toList()); + qaListRepository.saveAll(qaLists); + + // Save QaList with answer is null + // mentor is only TEST_MENTOR_NICKNAME_1 + int qaNumWithAnswerIsNull = 8; + List qaListsWithAnswerIsNull = IntStream.rangeClosed(1, qaNumWithAnswerIsNull) + .mapToObj(i -> createTestQaListAndAnswerIsNull(TEST_MENTOR_NICKNAME_1, i)) + .collect(Collectors.toList()); + qaListRepository.saveAll(qaListsWithAnswerIsNull); + } + + @AfterEach + void tearDown() { + // delete mongodb manually + qaListRepository.deleteAll(); + } + + /** + * getAllFollowMentors + */ + @Test + void getAllFollowMentors() { + } + + /** + * getFollowMentorInfo + */ + @Test + @DisplayName("case 1: 멘토가 팔로우 되어 있고, 질문답변 데이터도 존재하는 경우") + void getFollowMentorInfo() { + // given + + // when + FollowingMentorInfoResponse followMentorInfo = followingService.getFollowMentorInfo(TEST_MENTEE_NICKNAME, TEST_MENTOR_NICKNAME_1); + + // then + assertThat(followMentorInfo).isNotNull(); + assertThat(followMentorInfo.getFollowingUserInfoDto() + .getMajor()).isEqualTo("컴퓨터공학과"); + assertThat(followMentorInfo.getFollowingUserInfoDto() + .getCareer()).isNull(); + assertThat(followMentorInfo.getAnswersCount()).isEqualTo(qaNumWithAnswers); + assertThat(followMentorInfo.getAnswers().size()).isEqualTo(qaNumWithAnswers); + + // check if answer_time Order by ASC + List qaDtos = followMentorInfo.getAnswers(); + boolean areMessagesInOrder = IntStream.range(0, qaDtos.size() - 1) + .allMatch(i -> { + LocalDateTime currentTime = qaDtos.get(i).getAnswerTime(); + LocalDateTime nextTime = qaDtos.get(i + 1).getAnswerTime(); + System.out.println("currentTime.isBefore(nextTime) = " + currentTime.isBefore(nextTime)); + return currentTime.isBefore(nextTime); + }); + assertThat(areMessagesInOrder).isTrue(); + + /*for (int i = 0; i < qaDtos.size() - 1; i++) { + LocalDateTime currentTime = qaDtos.get(i).getAnswerTime(); + LocalDateTime nextTime = qaDtos.get(i + 1).getAnswerTime(); + + // Assert that the current message's time is before the next message's time + assertThat(currentTime.isBefore(nextTime)).isTrue(); + }*/ + } + + @Test + @DisplayName("case 2: 멘토가 팔로우는 되어 있으나, 질문답변 데이터가 존재하지 않는 경우") + void getFollowMentorInfo_QaData_is_Empty() { + // given + // test_mentor_2의 경우 팔로우는 되어 있으나, 질문답변 정보는 없다. + String mentor2 = "test_mentor_2"; + + // when + FollowingMentorInfoResponse followMentorInfo = followingService.getFollowMentorInfo(TEST_MENTEE_NICKNAME, mentor2); + + // then + assertThat(followMentorInfo).isNotNull(); + assertThat(followMentorInfo.getFollowingUserInfoDto() + .getMajor()).isEqualTo("컴퓨터공학과"); + assertThat(followMentorInfo.getFollowingUserInfoDto() + .getCareer()).isNull(); + + // here is different with case 1 + assertThat(followMentorInfo.getAnswersCount()).isZero(); + assertThat(followMentorInfo.getAnswers().size()).isZero(); + } + + + /** + * getLastAnsweredMessages + */ + @Test + @DisplayName("case 1: 멘토의 질문 답변 데이터가 0개인 경우(존재하지 않는 경우)") + void getLastAnsweredMessages_returns_empty_List() { + // given + String id = "google_1234123124"; + String email = "mentor2@mentor.com"; + String nickname = "mentor_test_33"; + User mentor1 = createTestUser(id, email, nickname, UserRole.MENTOR); + + // when + List lastAnsweredMessages = followingService.getLastAnsweredMessages(mentor1.getNickname()); + + // then + assertThat(lastAnsweredMessages).isEmpty(); + } + + @Test + @DisplayName("case 2: 멘토의 질문 답변 데이터가 1개인 경우") + void getLastAnsweredMessages_size_is_one() { + // given + String id = "google_1234123124"; + String email = "mentor2@mentor.com"; + String nickname = "mentor_test_33"; + User mentor1 = createTestUser(id, email, nickname, UserRole.MENTOR); + + int qaNum = 1; + List qaLists = IntStream.rangeClosed(1, qaNum) + .mapToObj(i -> createTestQaListAndAnswerIsNotNull(mentor1.getNickname(), i)) + .collect(Collectors.toList()); + qaListRepository.saveAll(qaLists); + + // when + List lastAnsweredMessages = followingService.getLastAnsweredMessages(mentor1.getNickname()); + + // then + assertThat(lastAnsweredMessages.size()).isEqualTo(1); + } + + @Test + @DisplayName("case 3: 멘토의 질문 답변 데이터가 2개 이상인 경우") + void getLastAnsweredMessages_size_is_more_than_two() { + // given + String id = "google_1234123124"; + String email = "mentor2@mentor.com"; + String nickname = "mentor_test_33"; + User mentor1 = createTestUser(id, email, nickname, UserRole.MENTOR); + + int qaNum = 5; + List qaLists = IntStream.rangeClosed(1, qaNum) + .mapToObj(i -> createTestQaListAndAnswerIsNotNull(mentor1.getNickname(), i)) + .collect(Collectors.toList()); + qaListRepository.saveAll(qaLists); + + // when + List lastAnsweredMessages = followingService.getLastAnsweredMessages(mentor1.getNickname()); + + // then + assertThat(lastAnsweredMessages.size()).isEqualTo(2); + } + + private User createTestUser(String id, String email, String nickname, UserRole role) { + return User.builder() + .id(id).email(email).provider("google").nickname(nickname) + .role(role).birthYear(2000).birthMonth(3) + .school("서울과학기술대학교").score(3).scoreRange("중반") + .graduateDate(2021).graduateMonth(3) + .major("컴퓨터공학과").subMajor("심리학과") + .minor(null).field("백엔드").techStack("AWS") + .optionInfo(new OptionInfo(null, null, null, null)) + .imgUrl("default/profile.png") // set img url + .build(); + } + + private QaList createTestQaListAndAnswerIsNotNull(String mentorNickname, int index) { + LocalDateTime now = LocalDateTime.now(); + return QaList.builder() + .menteeNickname(TEST_MENTEE_NICKNAME) + .mentorNickname(mentorNickname) + .questionOrigin("origin message_" + index) + .questionSummary("summary message_" + index) + .questionSummaryEn("summary eng message_" + index) + .questionTime(now.minusHours(index)) + .answer("answer message_" + index) + .answerTime(now.plusMinutes(index)) + .build(); + } + + private QaList createTestQaListAndAnswerIsNull(String mentorNickname, int index) { + LocalDateTime now = LocalDateTime.now(); + return QaList.builder() + .menteeNickname(TEST_MENTEE_NICKNAME) + .mentorNickname(mentorNickname) + .questionOrigin("origin message_" + index) + .questionSummary("summary message_" + index) + .questionSummaryEn("summary eng message_" + index) + .questionTime(now.minusHours(index)) + .answer(null) // here is null + .answerTime(null) // here is null + .build(); + } +} \ No newline at end of file