diff --git a/src/main/java/com/example/spot/api/code/status/ErrorStatus.java b/src/main/java/com/example/spot/api/code/status/ErrorStatus.java index 37862f98..b23b7a74 100644 --- a/src/main/java/com/example/spot/api/code/status/ErrorStatus.java +++ b/src/main/java/com/example/spot/api/code/status/ErrorStatus.java @@ -90,7 +90,7 @@ public enum ErrorStatus implements BaseErrorCode { _STUDY_POST_COMMENT_ALREADY_DISLIKED(HttpStatus.BAD_REQUEST, "POST4011", "이미 싫어요 한 댓글입니다."), _STUDY_LIKED_COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "POST4012", "좋아요를 누르지 않은 게시글의 좋아요를 취소할 수 없습니다."), _STUDY_DISLIKED_COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "POST4013", "싫어요를 누르지 않은 게시글의 싫어요를 취소할 수 없습니다."), - _STUDY_POST_DELETION_INVALID(HttpStatus.FORBIDDEN, "POST4014", "게시글 작성자만 삭제 가능합니다."), + _STUDY_POST_DELETION_INVALID(HttpStatus.FORBIDDEN, "POST4014", "게시글 작성자 및 스터디장만 삭제 가능합니다."), _STUDY_POST_NULL(HttpStatus.BAD_REQUEST, "POST4015", "게시글 아이디가 입력되지 않았습니다."), _STUDY_POST_COMMENT_NULL(HttpStatus.BAD_REQUEST, "POST4016", "댓글 아이디가 입력되지 않았습니다."), _STUDY_POST_COMMENT_REACTIOM_ID_NULL(HttpStatus.BAD_REQUEST, "POST4017", "댓글 반응 아이디가 입력되지 않았습니다."), diff --git a/src/main/java/com/example/spot/domain/mapping/StudyLikedComment.java b/src/main/java/com/example/spot/domain/mapping/StudyLikedComment.java index d87d7640..a9171ff0 100644 --- a/src/main/java/com/example/spot/domain/mapping/StudyLikedComment.java +++ b/src/main/java/com/example/spot/domain/mapping/StudyLikedComment.java @@ -10,8 +10,10 @@ @Entity @Getter +@Builder @DynamicUpdate @DynamicInsert +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) public class StudyLikedComment extends BaseEntity { @@ -31,13 +33,4 @@ public class StudyLikedComment extends BaseEntity { @JoinColumn(name = "member_id", nullable = false) private Member member; -/* ----------------------------- 생성자 ------------------------------------- */ - - @Builder - public StudyLikedComment(StudyPostComment studyPostComment, Member member, Boolean isLiked) { - this.studyPostComment = studyPostComment; - this.member = member; - this.isLiked = isLiked; - } - } diff --git a/src/main/java/com/example/spot/domain/mapping/StudyLikedPost.java b/src/main/java/com/example/spot/domain/mapping/StudyLikedPost.java index 28644d82..7ff36c46 100644 --- a/src/main/java/com/example/spot/domain/mapping/StudyLikedPost.java +++ b/src/main/java/com/example/spot/domain/mapping/StudyLikedPost.java @@ -16,8 +16,10 @@ @Entity @Getter +@Builder @DynamicUpdate @DynamicInsert +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) public class StudyLikedPost extends BaseEntity { @@ -35,11 +37,4 @@ public class StudyLikedPost extends BaseEntity { @JoinColumn(name = "member_id", nullable = false) private Member member; -/* ----------------------------- 생성자 ------------------------------------- */ - - @Builder - public StudyLikedPost(Member member, StudyPost studyPost) { - this.member = member; - this.studyPost = studyPost; - } } diff --git a/src/main/java/com/example/spot/domain/study/Study.java b/src/main/java/com/example/spot/domain/study/Study.java index 59399c10..cf5f61aa 100644 --- a/src/main/java/com/example/spot/domain/study/Study.java +++ b/src/main/java/com/example/spot/domain/study/Study.java @@ -28,8 +28,8 @@ @Entity @Getter -@DynamicUpdate -@DynamicInsert +@Builder +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Study extends BaseEntity { @@ -62,7 +62,7 @@ public class Study extends BaseEntity { @Column(nullable = false) private Boolean isOnline; - @Column(nullable = false, columnDefinition = "BIGINT DEFAULT 0") + @Column(nullable = false, columnDefinition = "INTEGER DEFAULT 0") private Integer heartCount; @Column(nullable = false) @@ -85,72 +85,46 @@ public class Study extends BaseEntity { @Column(nullable = false) private Long maxPeople; + @Builder.Default @OneToMany(mappedBy = "study", cascade = CascadeType.ALL) private List schedules = new ArrayList<>(); + @Builder.Default @OneToMany(mappedBy = "study", cascade = CascadeType.ALL) private List posts = new ArrayList<>(); + @Builder.Default @OneToMany(mappedBy = "study", cascade = CascadeType.ALL) private List votes = new ArrayList<>(); + @Builder.Default @OneToMany(mappedBy = "study", cascade = CascadeType.ALL) private List studyThemes = new ArrayList<>(); + @Builder.Default @OneToMany(mappedBy = "study", cascade = CascadeType.ALL) private List memberStudies = new ArrayList<>(); + @Builder.Default @OneToMany(mappedBy = "study", cascade = CascadeType.ALL) private List regionStudies = new ArrayList<>(); + @Builder.Default @OneToMany(mappedBy = "study", cascade = CascadeType.ALL) private List preferredStudies = new ArrayList<>(); + @Builder.Default @OneToMany(mappedBy = "study", cascade = CascadeType.ALL) private List studyPosts = new ArrayList<>(); + @Builder.Default @OneToMany(mappedBy = "study", cascade = CascadeType.ALL) private List toDoLists = new ArrayList<>(); + @Builder.Default @OneToMany(mappedBy = "study", cascade = CascadeType.ALL) private List notifications = new ArrayList<>(); - -/* ----------------------------- 생성자 ------------------------------------- */ - - @Builder - public Study(Gender gender, Integer minAge, Integer maxAge, Integer fee, - String profileImage, boolean hasFee, - Boolean isOnline, String goal, String introduction, - String title, Long maxPeople) { - this.gender = gender; - this.minAge = minAge; - this.maxAge = maxAge; - this.fee = fee; - this.profileImage = profileImage; - this.studyState = StudyState.RECRUITING; - this.isOnline = isOnline; - this.heartCount = 0; - this.hasFee = hasFee; - this.goal = goal; - this.introduction = introduction; - this.title = title; - this.status = Status.ON; - this.hitNum = 0L; - this.maxPeople = maxPeople; - this.schedules = new ArrayList<>(); - this.posts = new ArrayList<>(); - this.votes = new ArrayList<>(); - this.studyThemes = new ArrayList<>(); - this.preferredStudies = new ArrayList<>(); - this.memberStudies = new ArrayList<>(); - this.regionStudies = new ArrayList<>(); - this.studyPosts = new ArrayList<>(); - this.toDoLists = new ArrayList<>(); - this.notifications = new ArrayList<>(); - - } - /* ----------------------------- 연관관계 메소드 ------------------------------------- */ public void addMemberStudy(MemberStudy memberStudy) { diff --git a/src/main/java/com/example/spot/domain/study/StudyPost.java b/src/main/java/com/example/spot/domain/study/StudyPost.java index f7bba4f7..b58d5397 100644 --- a/src/main/java/com/example/spot/domain/study/StudyPost.java +++ b/src/main/java/com/example/spot/domain/study/StudyPost.java @@ -12,13 +12,11 @@ import java.util.List; import lombok.*; -import org.hibernate.annotations.DynamicInsert; -import org.hibernate.annotations.DynamicUpdate; @Entity @Getter -@DynamicUpdate -@DynamicInsert +@Builder +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) public class StudyPost extends BaseEntity { @@ -50,47 +48,35 @@ public class StudyPost extends BaseEntity { @Column(nullable = false) private String title; - @Column(nullable = false) + @Column(nullable = false, columnDefinition = "text") private String content; - @Column(nullable = false) + @Column(columnDefinition = "INTEGER DEFAULT 0") private Integer likeNum; - @Column(nullable = false) + @Column(columnDefinition = "INTEGER DEFAULT 0") private Integer hitNum; @Setter - @Column(nullable = false) + @Column(columnDefinition = "INTEGER DEFAULT 0") private Integer commentNum; + @Builder.Default @OneToMany(mappedBy = "studyPost", cascade = CascadeType.ALL) - private List images; + private List images = new ArrayList<>(); + @Builder.Default @OneToMany(mappedBy = "studyPost", cascade = CascadeType.ALL) - private List comments; + private List comments = new ArrayList<>(); + @Builder.Default @OneToMany(mappedBy = "studyPost", cascade = CascadeType.ALL) - private List likedPosts; + private List likedPosts = new ArrayList<>(); + @Builder.Default @OneToMany(mappedBy = "studyPost", cascade = CascadeType.ALL) - private List studyPostReports; - -/* ----------------------------- 생성자 ------------------------------------- */ - - @Builder - public StudyPost(Boolean isAnnouncement, Theme theme, String title, String content) { - this.isAnnouncement = isAnnouncement; - this.theme = theme; - this.title = title; - this.content = content; - this.likeNum = 0; - this.hitNum = 0; - this.commentNum = 0; - this.images = new ArrayList<>(); - this.comments = new ArrayList<>(); - this.likedPosts = new ArrayList<>(); - this.studyPostReports = new ArrayList<>(); - } + private List studyPostReports = new ArrayList<>(); + /* ----------------------------- 연관관계 메소드 ------------------------------------- */ public void addImage(StudyPostImage image) { diff --git a/src/main/java/com/example/spot/domain/study/StudyPostComment.java b/src/main/java/com/example/spot/domain/study/StudyPostComment.java index 6083cd80..9e397391 100644 --- a/src/main/java/com/example/spot/domain/study/StudyPostComment.java +++ b/src/main/java/com/example/spot/domain/study/StudyPostComment.java @@ -14,8 +14,8 @@ @Entity @Getter -@DynamicUpdate -@DynamicInsert +@Builder +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) public class StudyPostComment extends BaseEntity { @@ -36,10 +36,10 @@ public class StudyPostComment extends BaseEntity { @Column(nullable = false) private String content; - @Column(nullable = false) + @Column(columnDefinition = "INTEGER DEFAULT 0") private Integer likeCount; - @Column(nullable = false) + @Column(columnDefinition = "INTEGER DEFAULT 0") private Integer dislikeCount; @Column(nullable = false, columnDefinition = "BIT DEFAULT 0") @@ -56,29 +56,13 @@ public class StudyPostComment extends BaseEntity { @JoinColumn(name = "parent_comment_id") private StudyPostComment parentComment; + @Builder.Default @OneToMany(mappedBy = "parentComment", cascade = CascadeType.ALL) - private List childrenComment; + private List childrenComment = new ArrayList<>(); + @Builder.Default @OneToMany(mappedBy = "studyPostComment", cascade = CascadeType.ALL) - private List likedComments; - -/* ----------------------------- 생성자 ------------------------------------- */ - - @Builder - public StudyPostComment(StudyPost studyPost, Member member, String content, - Boolean isAnonymous, Integer anonymousNum, StudyPostComment parentComment) { - this.studyPost = studyPost; - this.member = member; - this.content = content; - this.likeCount = 0; - this.dislikeCount = 0; - this.isAnonymous = isAnonymous; - this.anonymousNum = anonymousNum; - this.isDeleted = Boolean.FALSE; - this.parentComment = parentComment; - this.childrenComment = new ArrayList<>(); - this.likedComments = new ArrayList<>(); - } + private List likedComments = new ArrayList<>(); /* ----------------------------- 연관관계 메소드 ------------------------------------- */ diff --git a/src/main/java/com/example/spot/service/study/StudyCommandServiceImpl.java b/src/main/java/com/example/spot/service/study/StudyCommandServiceImpl.java index 4f71aa0e..7a6d52a3 100644 --- a/src/main/java/com/example/spot/service/study/StudyCommandServiceImpl.java +++ b/src/main/java/com/example/spot/service/study/StudyCommandServiceImpl.java @@ -7,6 +7,7 @@ import com.example.spot.domain.Region; import com.example.spot.domain.Theme; import com.example.spot.domain.enums.ApplicationStatus; +import com.example.spot.domain.enums.Status; import com.example.spot.domain.enums.StudyLikeStatus; import com.example.spot.domain.enums.StudyState; import com.example.spot.domain.mapping.MemberStudy; @@ -121,13 +122,17 @@ public StudyRegisterResponseDTO.RegisterDTO registerStudy(StudyRegisterRequestDT .gender(studyRegisterRequestDTO.getGender()) .minAge(studyRegisterRequestDTO.getMinAge()) .maxAge(studyRegisterRequestDTO.getMaxAge()) + .hasFee(studyRegisterRequestDTO.isHasFee()) .fee(studyRegisterRequestDTO.getFee()) .profileImage(studyRegisterRequestDTO.getProfileImage()) + .studyState(StudyState.RECRUITING) .isOnline(studyRegisterRequestDTO.getIsOnline()) - .hasFee(studyRegisterRequestDTO.isHasFee()) + .heartCount(0) .goal(studyRegisterRequestDTO.getGoal()) .introduction(studyRegisterRequestDTO.getIntroduction()) .title(studyRegisterRequestDTO.getTitle()) + .status(Status.ON) + .hitNum(0L) .maxPeople(studyRegisterRequestDTO.getMaxPeople()) .build(); diff --git a/src/main/java/com/example/spot/service/studypost/StudyPostCommandServiceImpl.java b/src/main/java/com/example/spot/service/studypost/StudyPostCommandServiceImpl.java index 3e4fbb9d..503682e8 100644 --- a/src/main/java/com/example/spot/service/studypost/StudyPostCommandServiceImpl.java +++ b/src/main/java/com/example/spot/service/studypost/StudyPostCommandServiceImpl.java @@ -93,6 +93,9 @@ public StudyPostResDTO.PostPreviewDTO createPost(Long studyId, StudyPostRequestD .theme(postRequestDTO.getTheme()) .title(postRequestDTO.getTitle()) .content(postRequestDTO.getContent()) + .likeNum(0) + .hitNum(0) + .commentNum(0) .build(); // 공지면 announcedAt 설정 @@ -173,19 +176,28 @@ public StudyPostResDTO.PostPreviewDTO deletePost(Long studyId, Long postId) { studyPostRepository.findByIdAndStudyId(postId, studyId) .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_POST_NOT_FOUND)); - // 로그인 회원이 게시글 작성자인지 확인 - studyPostRepository.findByIdAndMemberId(postId, memberId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_POST_DELETION_INVALID)); + // 로그인 회원이 게시글 작성자거나 owner인지 확인 + Long ownerId = studyPost.getStudy().getMemberStudies().stream() + .filter(MemberStudy::getIsOwned) + .map(memberStudy -> memberStudy.getMember().getId()) + .findFirst() + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_OWNER_NOT_FOUND)); - //=== Feature ===// - studyPostImageRepository.deleteAllByStudyPostId(postId); - studyPostCommentRepository.deleteAllByStudyPostId(postId); - studyLikedPostRepository.deleteAllByStudyPostId(postId); - studyPostReportRepository.deleteAllByStudyPostId(postId); + if (studyPost.getMember().getId().equals(memberId) || + memberId.equals(ownerId)) { + + studyPostImageRepository.deleteAllByStudyPostId(postId); + studyPostCommentRepository.deleteAllByStudyPostId(postId); + studyLikedPostRepository.deleteAllByStudyPostId(postId); + studyPostReportRepository.deleteAllByStudyPostId(postId); + + member.deleteStudyPost(studyPost); + study.deleteStudyPost(studyPost); + studyPostRepository.delete(studyPost); - member.deleteStudyPost(studyPost); - study.deleteStudyPost(studyPost); - studyPostRepository.delete(studyPost); + } else { + throw new StudyHandler(ErrorStatus._STUDY_POST_DELETION_INVALID); + } return StudyPostResDTO.PostPreviewDTO.toDTO(studyPost); } @@ -320,8 +332,11 @@ public StudyPostCommentResponseDTO.CommentDTO createComment(Long studyId, Long p .studyPost(studyPost) .member(member) .content(commentRequestDTO.getContent()) + .likeCount(0) + .dislikeCount(0) .isAnonymous(commentRequestDTO.getIsAnonymous()) .parentComment(null) + .isDeleted(false) .anonymousNum(anonymousNum) .build(); @@ -376,9 +391,12 @@ public StudyPostCommentResponseDTO.CommentDTO createReply(Long studyId, Long pos .studyPost(studyPost) .member(member) .content(commentRequestDTO.getContent()) + .likeCount(0) + .dislikeCount(0) .isAnonymous(commentRequestDTO.getIsAnonymous()) .anonymousNum(anonymousNum) .parentComment(parentComment) + .isDeleted(false) .build(); studyPostCommentRepository.save(studyPostComment); diff --git a/src/main/java/com/example/spot/service/studypost/StudyPostQueryServiceImpl.java b/src/main/java/com/example/spot/service/studypost/StudyPostQueryServiceImpl.java index 7b30e46a..0ee04353 100644 --- a/src/main/java/com/example/spot/service/studypost/StudyPostQueryServiceImpl.java +++ b/src/main/java/com/example/spot/service/studypost/StudyPostQueryServiceImpl.java @@ -62,8 +62,10 @@ public StudyPostResDTO.PostListDTO getAllPosts(PageRequest pageRequest, Long stu Study study = studyRepository.findById(studyId) .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - //=== Feature ===// + memberStudyRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, ApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + //=== Feature ===// List studyPosts; if (themeQuery == null) { // query가 없는 경우 diff --git a/src/main/java/com/example/spot/web/dto/memberstudy/request/StudyPostCommentRequestDTO.java b/src/main/java/com/example/spot/web/dto/memberstudy/request/StudyPostCommentRequestDTO.java index d8505e3e..95ce9c9c 100644 --- a/src/main/java/com/example/spot/web/dto/memberstudy/request/StudyPostCommentRequestDTO.java +++ b/src/main/java/com/example/spot/web/dto/memberstudy/request/StudyPostCommentRequestDTO.java @@ -2,6 +2,7 @@ import com.example.spot.validation.annotation.TextLength; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -9,6 +10,7 @@ public class StudyPostCommentRequestDTO { @Getter + @Builder @NoArgsConstructor @AllArgsConstructor public static class CommentDTO { diff --git a/src/main/java/com/example/spot/web/dto/memberstudy/request/StudyPostRequestDTO.java b/src/main/java/com/example/spot/web/dto/memberstudy/request/StudyPostRequestDTO.java index aca1527a..a3716640 100644 --- a/src/main/java/com/example/spot/web/dto/memberstudy/request/StudyPostRequestDTO.java +++ b/src/main/java/com/example/spot/web/dto/memberstudy/request/StudyPostRequestDTO.java @@ -15,6 +15,7 @@ public class StudyPostRequestDTO { @Getter @Setter + @Builder @Schema(name = "StudyPostDTO") @AllArgsConstructor public static class PostDTO { @@ -28,7 +29,7 @@ public static class PostDTO { private Theme theme; @NotNull - @TextLength(min = 1, max = 255) + @TextLength(min = 1, max = 50) @Schema(description = "제목", example = "title") private String title; diff --git a/src/main/java/com/example/spot/web/dto/memberstudy/response/StudyPostCommentResponseDTO.java b/src/main/java/com/example/spot/web/dto/memberstudy/response/StudyPostCommentResponseDTO.java index 908811f6..3255e105 100644 --- a/src/main/java/com/example/spot/web/dto/memberstudy/response/StudyPostCommentResponseDTO.java +++ b/src/main/java/com/example/spot/web/dto/memberstudy/response/StudyPostCommentResponseDTO.java @@ -39,7 +39,7 @@ public static CommentDTO toDTO(StudyPostComment comment, String name, String def @Getter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) @Builder(access = AccessLevel.PRIVATE) - private static class MemberInfoDTO { + public static class MemberInfoDTO { private final Long memberId; private final String name; diff --git a/src/test/java/com/example/spot/service/memberstudy/MemberStudyQueryServiceTest.java b/src/test/java/com/example/spot/service/memberstudy/MemberStudyQueryServiceTest.java index 01c7db33..ddb714ab 100644 --- a/src/test/java/com/example/spot/service/memberstudy/MemberStudyQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/memberstudy/MemberStudyQueryServiceTest.java @@ -77,6 +77,7 @@ public class MemberStudyQueryServiceTest { private static MemberStudy studyMember; private static MemberStudy apply; private static ToDoList toDoList; + @BeforeEach void setup(){ member = Member.builder() @@ -122,7 +123,13 @@ void setup(){ long studyId = 1L; String title = "공지"; String content = "공지입니다."; - StudyPost studyPost = new StudyPost(true, Theme.WELCOME, title, content); + StudyPost studyPost = StudyPost.builder() + .title(title) + .content(content) + .theme(Theme.WELCOME) + .isAnnouncement(true) + .build(); + MemberStudy memberStudy = MemberStudy.builder() .introduction(title).study(study).member(member).isOwned(true).status(ApplicationStatus.APPROVED).build(); diff --git a/src/test/java/com/example/spot/service/study/StudyCommandServiceTest.java b/src/test/java/com/example/spot/service/study/StudyCommandServiceTest.java index 0c018c3f..d294a45c 100644 --- a/src/test/java/com/example/spot/service/study/StudyCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/study/StudyCommandServiceTest.java @@ -299,6 +299,7 @@ private static void initStudy() { .profileImage("a.jpg") .hasFee(true) .isOnline(true) + .studyState(StudyState.RECRUITING) .goal("SQLD") .introduction("SQLD 자격증 스터디") .title("SQLD Master") diff --git a/src/test/java/com/example/spot/service/study/StudyQueryServiceTest.java b/src/test/java/com/example/spot/service/study/StudyQueryServiceTest.java index 60929b8b..8944ac97 100644 --- a/src/test/java/com/example/spot/service/study/StudyQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/study/StudyQueryServiceTest.java @@ -16,12 +16,7 @@ import com.example.spot.domain.Member; import com.example.spot.domain.Region; import com.example.spot.domain.Theme; -import com.example.spot.domain.enums.ApplicationStatus; -import com.example.spot.domain.enums.Gender; -import com.example.spot.domain.enums.StudyLikeStatus; -import com.example.spot.domain.enums.StudySortBy; -import com.example.spot.domain.enums.StudyState; -import com.example.spot.domain.enums.ThemeType; +import com.example.spot.domain.enums.*; import com.example.spot.domain.mapping.MemberStudy; import com.example.spot.domain.mapping.MemberTheme; import com.example.spot.domain.mapping.PreferredRegion; @@ -1807,6 +1802,7 @@ private static Member getMember() { } private static void initStudy() { study1 = Study.builder() + .id(1L) .gender(Gender.MALE) .minAge(18) .maxAge(35) @@ -1814,6 +1810,10 @@ private static void initStudy() { .profileImage("profile1.jpg") .hasFee(true) .isOnline(true) + .studyState(StudyState.RECRUITING) + .heartCount(0) + .status(Status.ON) + .hitNum(0L) .goal("Learn English") .introduction("This is an English study group") .title("English Study Group") @@ -1828,6 +1828,10 @@ private static void initStudy() { .profileImage("profile2.jpg") .hasFee(true) .isOnline(false) + .studyState(StudyState.RECRUITING) + .heartCount(0) + .status(Status.ON) + .hitNum(0L) .goal("Win a competition") .introduction("This is a competition study group") .title("Competition Study Group") @@ -1841,6 +1845,10 @@ private static void initStudy() { .profileImage("profile1.jpg") .hasFee(true) .isOnline(true) + .studyState(StudyState.RECRUITING) + .heartCount(0) + .status(Status.ON) + .hitNum(0L) .goal("Learn Korean") .introduction("This is an Korean study group") .title("Korean Study Group") diff --git a/src/test/java/com/example/spot/service/studypost/StudyPostCommandServiceTest.java b/src/test/java/com/example/spot/service/studypost/StudyPostCommandServiceTest.java new file mode 100644 index 00000000..5577640c --- /dev/null +++ b/src/test/java/com/example/spot/service/studypost/StudyPostCommandServiceTest.java @@ -0,0 +1,1229 @@ +package com.example.spot.service.studypost; + +import com.example.spot.api.exception.handler.StudyHandler; +import com.example.spot.domain.Member; +import com.example.spot.domain.Notification; +import com.example.spot.domain.enums.ApplicationStatus; +import com.example.spot.domain.enums.Gender; +import com.example.spot.domain.enums.Theme; +import com.example.spot.domain.mapping.MemberStudy; +import com.example.spot.domain.mapping.StudyLikedComment; +import com.example.spot.domain.mapping.StudyLikedPost; +import com.example.spot.domain.study.Study; +import com.example.spot.domain.study.StudyPost; +import com.example.spot.domain.study.StudyPostComment; +import com.example.spot.repository.*; +import com.example.spot.web.dto.memberstudy.request.StudyPostCommentRequestDTO; +import com.example.spot.web.dto.memberstudy.request.StudyPostRequestDTO; +import com.example.spot.web.dto.memberstudy.response.StudyPostCommentResponseDTO; +import com.example.spot.web.dto.memberstudy.response.StudyPostResDTO; +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.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class StudyPostCommandServiceTest { + + @Mock + private MemberRepository memberRepository; + + @Mock + private StudyRepository studyRepository; + @Mock + private MemberStudyRepository memberStudyRepository; + + @Mock + private StudyPostRepository studyPostRepository; + @Mock + private StudyLikedPostRepository studyLikedPostRepository; + @Mock + private StudyPostImageRepository studyPostImageRepository; + @Mock + private StudyPostReportRepository studyPostReportRepository; + + @Mock + private StudyPostCommentRepository studyPostCommentRepository; + @Mock + private StudyLikedCommentRepository studyLikedCommentRepository; + + @Mock + private NotificationRepository notificationRepository; + + @InjectMocks + private StudyPostCommandServiceImpl studyPostCommandService; + + private static Study study; + private static Member member1; + private static Member member2; + private static Member owner; + private static MemberStudy member1Study; + private static MemberStudy ownerStudy; + + private static StudyPost studyPost1; + private static StudyPost studyPost2; + private static StudyPost studyPost3; + private static StudyLikedPost studyLikedPost; + private static StudyPostComment studyPost1Comment1; + private static StudyPostComment studyPost1Comment2; + private static StudyLikedComment studyLikedComment; + private static StudyLikedComment studyDislikedComment; + + @BeforeEach + void setUp() { + initMember(); + initStudy(); + initMemberStudy(); + initStudyPost(); + initStudyLikedPost(); + initStudyPostComment(); + initStudyLikedComment(); + + // Member + when(memberRepository.findById(1L)).thenReturn(Optional.of(member1)); + when(memberRepository.findById(2L)).thenReturn(Optional.of(member2)); + when(memberRepository.findById(3L)).thenReturn(Optional.of(owner)); + + // Study + when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); + + // MemberStudy + when(memberStudyRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, ApplicationStatus.APPROVED)) + .thenReturn(Optional.of(member1Study)); + when(memberStudyRepository.findByMemberIdAndStudyIdAndStatus(2L, 1L, ApplicationStatus.APPROVED)) + .thenReturn(Optional.empty()); + when(memberStudyRepository.findByMemberIdAndStudyIdAndStatus(3L, 1L, ApplicationStatus.APPROVED)) + .thenReturn(Optional.of(ownerStudy)); + + // StudyPost + when(studyPostRepository.findById(1L)).thenReturn(Optional.of(studyPost1)); + when(studyPostRepository.findById(2L)).thenReturn(Optional.of(studyPost2)); + when(studyPostRepository.findById(3L)).thenReturn(Optional.of(studyPost3)); + + when(studyLikedPostRepository.findByMemberIdAndStudyPostId(3L, 1L)) + .thenReturn(Optional.of(studyLikedPost)); + when(studyLikedPostRepository.existsByMemberIdAndStudyPostId(3L, 1L)) + .thenReturn(true); + + // Comment + when(studyPostCommentRepository.findAllByStudyPostId(1L)) + .thenReturn(List.of(studyPost1Comment1, studyPost1Comment2)); + when(studyPostCommentRepository.findById(1L)).thenReturn(Optional.of(studyPost1Comment1)); + when(studyPostCommentRepository.findById(2L)).thenReturn(Optional.of(studyPost1Comment2)); + + } + +/*-------------------------------------------------------- 게시글 작성 ------------------------------------------------------------------------*/ + + @Test + @DisplayName("스터디 게시글 작성 - 공지 게시글 (성공)") + void createPost_Announcement_Success() { + + // given + Long memberId = 3L; + Long studyId = 1L; + + StudyPostRequestDTO.PostDTO postPreviewDTO = StudyPostRequestDTO.PostDTO.builder() + .isAnnouncement(true) + .theme(Theme.INFO_SHARING) + .title("공지") + .content("내용") + .build(); + + getAuthentication(memberId); + + when(memberStudyRepository.findByMemberIdAndStudyIdAndIsOwned(memberId, studyId, true)) + .thenReturn(Optional.of(ownerStudy)); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost2); + when(notificationRepository.save(any(Notification.class))).thenReturn(null); + when(memberStudyRepository.findAllByStudyIdAndStatus(studyId, ApplicationStatus.APPROVED)) + .thenReturn(List.of(member1Study, ownerStudy)); + + // when + StudyPostResDTO.PostPreviewDTO result = studyPostCommandService.createPost(studyId, postPreviewDTO); + + // then + assertNotNull(result); + assertThat(result.getTitle()).isEqualTo("공지"); + verify(studyPostRepository, times(1)).save(any(StudyPost.class)); + } + + @Test + @DisplayName("스터디 게시글 작성 - 일반 게시글 (성공)") + void createPost_Common_Success() { + + // given + Long memberId = 1L; + Long studyId = 1L; + + StudyPostRequestDTO.PostDTO postPreviewDTO = StudyPostRequestDTO.PostDTO.builder() + .isAnnouncement(false) + .theme(Theme.FREE_TALK) + .title("잡담") + .content("내용") + .build(); + + getAuthentication(memberId); + + when(memberStudyRepository.findByMemberIdAndStudyIdAndIsOwned(memberId, studyId, true)) + .thenReturn(Optional.of(member1Study)); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + when(notificationRepository.save(any(Notification.class))).thenReturn(null); + when(memberStudyRepository.findAllByStudyIdAndStatus(studyId, ApplicationStatus.APPROVED)) + .thenReturn(List.of(member1Study, ownerStudy)); + + // when + StudyPostResDTO.PostPreviewDTO result = studyPostCommandService.createPost(studyId, postPreviewDTO); + + // then + assertNotNull(result); + assertThat(result.getTitle()).isEqualTo("잡담"); + verify(studyPostRepository, times(1)).save(any(StudyPost.class)); + } + + @Test + @DisplayName("스터디 게시글 작성 - 스터디 회원이 아닌 경우 (실패)") + void createPost_NotStudyMember_Fail() { + + // given + Long memberId = 2L; + Long studyId = 1L; + + StudyPostRequestDTO.PostDTO postPreviewDTO = StudyPostRequestDTO.PostDTO.builder() + .isAnnouncement(true) + .theme(Theme.INFO_SHARING) + .title("공지") + .content("내용") + .build(); + + getAuthentication(memberId); + + when(memberStudyRepository.findByMemberIdAndStudyIdAndIsOwned(memberId, studyId, true)) + .thenReturn(Optional.of(ownerStudy)); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost2); + when(notificationRepository.save(any(Notification.class))).thenReturn(null); + when(memberStudyRepository.findAllByStudyIdAndStatus(studyId, ApplicationStatus.APPROVED)) + .thenReturn(List.of(member1Study, ownerStudy)); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostCommandService.createPost(studyId, postPreviewDTO)); + } + + @Test + @DisplayName("스터디 게시글 작성 - 스터디장이 아닌 회원이 공지 게시글을 작성하는 경우 (실패)") + void createPost_MemberAnnounced_Fail() { + + // given + Long memberId = 2L; + Long studyId = 1L; + + StudyPostRequestDTO.PostDTO postPreviewDTO = StudyPostRequestDTO.PostDTO.builder() + .isAnnouncement(true) + .theme(Theme.INFO_SHARING) + .title("공지") + .content("내용") + .build(); + + getAuthentication(memberId); + + when(memberStudyRepository.findByMemberIdAndStudyIdAndIsOwned(memberId, studyId, true)) + .thenReturn(Optional.of(member1Study)); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost2); + when(notificationRepository.save(any(Notification.class))).thenReturn(null); + when(memberStudyRepository.findAllByStudyIdAndStatus(studyId, ApplicationStatus.APPROVED)) + .thenReturn(List.of(member1Study, ownerStudy)); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostCommandService.createPost(studyId, postPreviewDTO)); + } + + @Test + @DisplayName("스터디 게시글 작성 - 제목이 50자를 초과하는 경우 (실패)") + void createPost_TitleOverflow_Fail() { + + // given + Long memberId = 1L; + Long studyId = 1L; + + StudyPostRequestDTO.PostDTO postPreviewDTO = StudyPostRequestDTO.PostDTO.builder() + .isAnnouncement(true) + .theme(Theme.INFO_SHARING) + .title("50자가 넘어가는 제목 " + + "50자가 넘어가는 제목 " + + "50자가 넘어가는 제목 " + + "50자가 넘어가는 제목 " + + "50자가 넘어가는 제목 ") + .content("내용") + .build(); + + getAuthentication(memberId); + + when(memberStudyRepository.findByMemberIdAndStudyIdAndIsOwned(memberId, studyId, true)) + .thenReturn(Optional.of(ownerStudy)); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost2); + when(notificationRepository.save(any(Notification.class))).thenReturn(null); + when(memberStudyRepository.findAllByStudyIdAndStatus(studyId, ApplicationStatus.APPROVED)) + .thenReturn(List.of(member1Study, ownerStudy)); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostCommandService.createPost(studyId, postPreviewDTO)); + } + + +/*-------------------------------------------------------- 게시글 삭제 ------------------------------------------------------------------------*/ + + @Test + @DisplayName("스터디 게시글 삭제 - (성공)") + void deletePost_Success() { + + // given + Long memberId = 1L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyPostRepository.findByIdAndMemberId(postId, memberId)) + .thenReturn(Optional.of(studyPost1)); + + // when + StudyPostResDTO.PostPreviewDTO result = studyPostCommandService.deletePost(studyId, postId); + + // then + assertNotNull(result); + assertThat(result.getPostId()).isEqualTo(1L); + assertThat(result.getTitle()).isEqualTo("잡담"); + verify(studyPostRepository, times(1)).delete(any(StudyPost.class)); + verify(studyLikedPostRepository, times(1)).deleteAllByStudyPostId(postId); + } + + @Test + @DisplayName("스터디 게시글 삭제 - 이미 삭제된 게시글인 경우 (실패)") + void deletePost_AlreadyDeleted_Fail() { + + // given + Long memberId = 1L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.empty()); + when(studyPostRepository.findByIdAndMemberId(postId, memberId)) + .thenReturn(Optional.empty()); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostCommandService.deletePost(studyId, postId)); + } + + @Test + @DisplayName("스터디 게시글 삭제 - 작성자 본인이나 스터디장이 아닌 경우 (실패)") + void deletePost_NotAvailableMember_Fail() { + + // given + Long memberId = 2L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyPostRepository.findByIdAndMemberId(postId, memberId)) + .thenReturn(Optional.of(studyPost1)); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostCommandService.deletePost(studyId, postId)); + } + +/*-------------------------------------------------------- 게시글 좋아요 ------------------------------------------------------------------------*/ + + @Test + @DisplayName("스터디 게시글 좋아요 - (성공)") + void likePost_Success() { + + // given + Long memberId = 3L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyLikedPostRepository.findByMemberIdAndStudyPostId(memberId, postId)) + .thenReturn(Optional.empty()); + when(studyLikedPostRepository.save(any(StudyLikedPost.class))).thenReturn(studyLikedPost); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + + // when + StudyPostResDTO.PostLikeNumDTO result = studyPostCommandService.likePost(studyId, postId); + + // then + assertNotNull(result); + assertThat(result.getPostId()).isEqualTo(1L); + assertThat(result.getTitle()).isEqualTo("잡담"); + assertThat(result.getLikeNum()).isEqualTo(2); + verify(studyLikedPostRepository, times(1)).save(any(StudyLikedPost.class)); + } + + @Test + @DisplayName("스터디 게시글 좋아요 - 스터디 회원이 아닌 경우 (실패)") + void likePost_NotStudyMember_Fail() { + + // given + Long memberId = 2L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyLikedPostRepository.findByMemberIdAndStudyPostId(memberId, postId)) + .thenReturn(Optional.empty()); + when(studyLikedPostRepository.save(any(StudyLikedPost.class))).thenReturn(studyLikedPost); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostCommandService.likePost(studyId, postId)); + } + @Test + @DisplayName("스터디 게시글 좋아요 - 이미 좋아요를 누른 경우 (실패)") + void likePost_AlreadyLiked_Fail() { + + // given + Long memberId = 3L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyLikedPostRepository.findByMemberIdAndStudyPostId(memberId, postId)) + .thenReturn(Optional.of(studyLikedPost)); + when(studyLikedPostRepository.save(any(StudyLikedPost.class))).thenReturn(studyLikedPost); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostCommandService.likePost(studyId, postId)); + } + + +/*-------------------------------------------------------- 게시글 좋아요 취소 ------------------------------------------------------------------------*/ + + @Test + @DisplayName("스터디 게시글 좋아요 취소 - (성공)") + void cancelPostLike_Success() { + + // given + Long memberId = 3L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyLikedPostRepository.findByMemberIdAndStudyPostId(memberId, postId)) + .thenReturn(Optional.of(studyLikedPost)); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + + // when + StudyPostResDTO.PostLikeNumDTO result = studyPostCommandService.cancelPostLike(studyId, postId); + + // then + assertNotNull(result); + assertThat(result.getPostId()).isEqualTo(1L); + assertThat(result.getTitle()).isEqualTo("잡담"); + assertThat(result.getLikeNum()).isEqualTo(0); + } + + @Test + @DisplayName("스터디 게시글 좋아요 취소 - 스터디 회원이 아닌 경우 (실패)") + void cancelPostLike_NotStudyMember_Fail() { + + // given + Long memberId = 2L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyLikedPostRepository.findByMemberIdAndStudyPostId(memberId, postId)) + .thenReturn(Optional.of(studyLikedPost)); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostCommandService.cancelPostLike(studyId, postId)); + } + + @Test + @DisplayName("스터디 게시글 좋아요 취소 - 좋아요를 누르지 않은 게시글인 경우 (실패)") + void cancelPostLike_NotLiked_Fail() { + + // given + Long memberId = 3L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyLikedPostRepository.findByMemberIdAndStudyPostId(memberId, postId)) + .thenReturn(Optional.empty()); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostCommandService.cancelPostLike(studyId, postId)); + } + + +/*-------------------------------------------------------- 댓글 작성 ------------------------------------------------------------------------*/ + + @Test + @DisplayName("스터디 게시글 댓글 작성 - 익명 댓글 (성공)") + void createComment_Anonymous_Success() { + + // given + Long memberId = 1L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + .content("댓글") + .isAnonymous(true) + .build(); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyPostCommentRepository.save(any(StudyPostComment.class))).thenReturn(studyPost1Comment1); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + when(studyPostCommentRepository.findAllByStudyPostId(postId)).thenReturn(List.of()); + when(studyPostCommentRepository.findAllByMemberIdAndStudyPostId(memberId, postId)).thenReturn(List.of()); + + // when + StudyPostCommentResponseDTO.CommentDTO result = studyPostCommandService.createComment(studyId, postId, commentDTO); + + // then + assertNotNull(result); + assertThat(result.getMember().getMemberId()).isEqualTo(1L); + assertThat(result.getMember().getName()).isEqualTo("익명1"); + assertThat(result.getContent()).isEqualTo("댓글"); + } + + @Test + @DisplayName("스터디 게시글 댓글 작성 - 실명 댓글 (성공)") + void createComment_Name_Success() { + + // given + Long memberId = 1L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + .content("댓글") + .isAnonymous(false) + .build(); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyPostCommentRepository.save(any(StudyPostComment.class))).thenReturn(studyPost1Comment1); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + when(studyPostCommentRepository.findAllByStudyPostId(postId)).thenReturn(List.of()); + when(studyPostCommentRepository.findAllByMemberIdAndStudyPostId(memberId, postId)).thenReturn(List.of()); + + // when + StudyPostCommentResponseDTO.CommentDTO result = studyPostCommandService.createComment(studyId, postId, commentDTO); + + // then + assertNotNull(result); + assertThat(result.getMember().getMemberId()).isEqualTo(1L); + assertThat(result.getMember().getName()).isEqualTo("회원1"); + assertThat(result.getContent()).isEqualTo("댓글"); + } + + @Test + @DisplayName("스터디 게시글 댓글 작성 - 스터디 회원이 아닌 경우 (실패)") + void createComment_NotStudyMember_Fail() { + + // given + Long memberId = 2L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + .content("댓글") + .isAnonymous(false) + .build(); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyPostCommentRepository.save(any(StudyPostComment.class))).thenReturn(studyPost1Comment1); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + when(studyPostCommentRepository.findAllByStudyPostId(postId)).thenReturn(List.of()); + when(studyPostCommentRepository.findAllByMemberIdAndStudyPostId(memberId, postId)).thenReturn(List.of()); + + // when + assertThrows(StudyHandler.class, () -> studyPostCommandService.createComment(studyId, postId, commentDTO)); + } + + + +/*-------------------------------------------------------- 답글 작성 ------------------------------------------------------------------------*/ + + @Test + @DisplayName("스터디 게시글 답글 작성 - 익명 댓글 (성공)") + void createReply_Anonymous_Success() { + + // given + Long memberId = 3L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 1L; + + getAuthentication(memberId); + + StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + .content("답글") + .isAnonymous(true) + .build(); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyPostCommentRepository.save(any(StudyPostComment.class))).thenReturn(studyPost1Comment1); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + when(studyPostCommentRepository.findAllByStudyPostId(postId)) + .thenReturn(List.of(studyPost1Comment1)); + when(studyPostCommentRepository.findAllByMemberIdAndStudyPostId(memberId, postId)) + .thenReturn(List.of()); + + // when + StudyPostCommentResponseDTO.CommentDTO result = studyPostCommandService + .createReply(studyId, postId, commentId, commentDTO); + + //then + assertNotNull(result); + assertThat(result.getMember().getMemberId()).isEqualTo(3L); + assertThat(result.getMember().getName()).isEqualTo("익명2"); + assertThat(result.getContent()).isEqualTo("답글"); + } + + @Test + @DisplayName("스터디 게시글 답글 작성 - 실명 댓글 (성공)") + void createReply_Name_Success() { + + // given + Long memberId = 3L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 1L; + + getAuthentication(memberId); + + StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + .content("답글") + .isAnonymous(false) + .build(); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyPostCommentRepository.save(any(StudyPostComment.class))).thenReturn(studyPost1Comment1); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + when(studyPostCommentRepository.findAllByStudyPostId(postId)) + .thenReturn(List.of(studyPost1Comment1)); + when(studyPostCommentRepository.findAllByMemberIdAndStudyPostId(memberId, postId)) + .thenReturn(List.of()); + + // when + StudyPostCommentResponseDTO.CommentDTO result = studyPostCommandService + .createReply(studyId, postId, commentId, commentDTO); + + //then + assertNotNull(result); + assertThat(result.getMember().getMemberId()).isEqualTo(3L); + assertThat(result.getMember().getName()).isEqualTo("회원3"); + assertThat(result.getContent()).isEqualTo("답글"); + } + + @Test + @DisplayName("스터디 게시글 답글 작성 - 스터디 회원이 아닌 경우 (실패)") + void createReply_NotStudyMember_Fail() { + + // given + Long memberId = 2L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 1L; + + getAuthentication(memberId); + + StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + .content("답글") + .isAnonymous(false) + .build(); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyPostCommentRepository.save(any(StudyPostComment.class))).thenReturn(studyPost1Comment1); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + when(studyPostCommentRepository.findAllByStudyPostId(postId)) + .thenReturn(List.of(studyPost1Comment1)); + when(studyPostCommentRepository.findAllByMemberIdAndStudyPostId(memberId, postId)) + .thenReturn(List.of()); + + // when + assertThrows(StudyHandler.class, () -> studyPostCommandService.createReply(studyId, postId, commentId, commentDTO)); + } + + @Test + @DisplayName("스터디 게시글 답글 작성 - 상위 댓글이 존재하지 않는 경우 (실패)") + void createReply_ParentCommentNotExist_Fail() { + + // given + Long memberId = 1L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + .content("답글") + .isAnonymous(false) + .build(); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyPostCommentRepository.save(any(StudyPostComment.class))).thenReturn(studyPost1Comment1); + when(studyPostRepository.save(any(StudyPost.class))).thenReturn(studyPost1); + when(studyPostCommentRepository.findAllByStudyPostId(postId)) + .thenReturn(List.of(studyPost1Comment1, studyPost1Comment2)); + when(studyPostCommentRepository.findAllByMemberIdAndStudyPostId(memberId, postId)) + .thenReturn(List.of(studyPost1Comment1)); + + // when + assertThrows(StudyHandler.class, () -> studyPostCommandService.createReply(studyId, postId, null, commentDTO)); + } + + +/*-------------------------------------------------------- 댓글 삭제 ------------------------------------------------------------------------*/ + + // @Test + // @DisplayName("스터디 게시글 댓글 삭제") + // void deleteComment() { + // } + +/*-------------------------------------------------------- 댓글 좋아요 ------------------------------------------------------------------------*/ + + @Test + @DisplayName("스터디 게시글 댓글 좋아요 - (성공)") + void likeComment_Success() { + + // given + Long memberId = 1L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 1L; + + getAuthentication(memberId); + + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, commentId, true)) + .thenReturn(Optional.empty()); + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, commentId, false)) + .thenReturn(Optional.empty()); + when(studyLikedCommentRepository.save(any(StudyLikedComment.class))).thenReturn(studyLikedComment); + + // when + StudyPostCommentResponseDTO.CommentPreviewDTO result = studyPostCommandService.likeComment(studyId, postId, commentId); + + // then + assertNotNull(result); + assertThat(result.getCommentId()).isEqualTo(1L); + assertThat(result.getLikeCount()).isEqualTo(1L); + assertThat(result.getDislikeCount()).isEqualTo(0L); + } + + @Test + @DisplayName("스터디 게시글 댓글 좋아요 - 스터디 회원이 아닌 경우 (실패)") + void likeComment_NotStudyMember_Fail() { + + // given + Long memberId = 2L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 1L; + + getAuthentication(memberId); + + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, commentId, true)) + .thenReturn(Optional.empty()); + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, commentId, false)) + .thenReturn(Optional.empty()); + when(studyLikedCommentRepository.save(any(StudyLikedComment.class))).thenReturn(studyLikedComment); + + // when + assertThrows(StudyHandler.class, () -> studyPostCommandService.likeComment(studyId, postId, commentId)); + } + + @Test + @DisplayName("스터디 게시글 댓글 좋아요 - 이미 좋아요를 누른 경우 (실패)") + void likeComment_AlreadyLiked_Fail() { + + // given + Long memberId = 1L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 2L; + + getAuthentication(memberId); + + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, commentId, true)) + .thenReturn(Optional.of(studyLikedComment)); + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, commentId, false)) + .thenReturn(Optional.empty()); + when(studyLikedCommentRepository.save(any(StudyLikedComment.class))).thenReturn(studyLikedComment); + + // when + assertThrows(StudyHandler.class, () -> studyPostCommandService.likeComment(studyId, postId, commentId)); + } + + +/*-------------------------------------------------------- 댓글 싫어요 ------------------------------------------------------------------------*/ + + @Test + @DisplayName("스터디 게시글 댓글 싫어요 - (성공)") + void dislikeComment_Success() { + + // given + Long memberId = 1L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 1L; + + getAuthentication(memberId); + + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, commentId, true)) + .thenReturn(Optional.empty()); + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, commentId, false)) + .thenReturn(Optional.empty()); + when(studyLikedCommentRepository.save(any(StudyLikedComment.class))).thenReturn(studyLikedComment); + + // when + StudyPostCommentResponseDTO.CommentPreviewDTO result = studyPostCommandService.dislikeComment(studyId, postId, commentId); + + // then + assertNotNull(result); + assertThat(result.getCommentId()).isEqualTo(1L); + assertThat(result.getLikeCount()).isEqualTo(1L); + assertThat(result.getDislikeCount()).isEqualTo(0L); + } + + @Test + @DisplayName("스터디 게시글 댓글 싫어요 - 스터디 회원인 아닌 경우(실패)") + void dislikeComment_NotStudyMember_Fail() { + + // given + Long memberId = 2L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 1L; + + getAuthentication(memberId); + + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, commentId, true)) + .thenReturn(Optional.empty()); + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, commentId, false)) + .thenReturn(Optional.empty()); + when(studyLikedCommentRepository.save(any(StudyLikedComment.class))).thenReturn(studyLikedComment); + + // when + assertThrows(StudyHandler.class, () -> studyPostCommandService.dislikeComment(studyId, postId, commentId)); + + } + + @Test + @DisplayName("스터디 게시글 댓글 싫어요 - 이미 싫어요를 누른 경우(실패)") + void dislikeComment_AlreadyDisliked_Fail() { + + // given + Long memberId = 3L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 2L; + + getAuthentication(memberId); + + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, commentId, true)) + .thenReturn(Optional.empty()); + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, commentId, false)) + .thenReturn(Optional.of(studyLikedComment)); + when(studyLikedCommentRepository.save(any(StudyLikedComment.class))).thenReturn(studyLikedComment); + + // when + assertThrows(StudyHandler.class, () -> studyPostCommandService.dislikeComment(studyId, postId, commentId)); + } + +/*-------------------------------------------------------- 댓글 좋아요 취소 ------------------------------------------------------------------------*/ + + @Test + @DisplayName("스터디 게시글 댓글 좋아요 취소 - (성공)") + void cancelCommentLike_Success() { + + // given + Long memberId = 1L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 2L; + + getAuthentication(memberId); + + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, studyPost1Comment2.getId(), true)) + .thenReturn(Optional.of(studyLikedComment)); + when(studyPostCommentRepository.save(any(StudyPostComment.class))).thenReturn(studyPost1Comment2); + + // when + StudyPostCommentResponseDTO.CommentPreviewDTO result = studyPostCommandService + .cancelCommentLike(studyId, postId, commentId); + + // then + assertNotNull(result); + assertThat(result.getCommentId()).isEqualTo(2L); + assertThat(result.getLikeCount()).isEqualTo(0L); + assertThat(result.getDislikeCount()).isEqualTo(1L); + } + + @Test + @DisplayName("스터디 게시글 댓글 좋아요 취소 - 스터디 회원이 아닌 경우 (실패)") + void cancelCommentLike_NotStudyMember_Fail() { + + // given + Long memberId = 2L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 2L; + + getAuthentication(memberId); + + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, studyPost1Comment2.getId(), true)) + .thenReturn(Optional.of(studyLikedComment)); + when(studyPostCommentRepository.save(any(StudyPostComment.class))).thenReturn(studyPost1Comment2); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostCommandService.cancelCommentLike(studyId, postId, commentId)); + } + + @Test + @DisplayName("스터디 게시글 댓글 좋아요 취소 - 좋아요를 누른 댓글이 아닌 경우 (실패)") + void cancelCommentLike_NotLiked_Fail() { + + // given + Long memberId = 3L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 2L; + + getAuthentication(memberId); + + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, studyPost1Comment2.getId(), true)) + .thenReturn(Optional.empty()); + when(studyPostCommentRepository.save(any(StudyPostComment.class))).thenReturn(studyPost1Comment2); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostCommandService.cancelCommentLike(studyId, postId, commentId)); + } + +/*-------------------------------------------------------- 댓글 싫어요 취소 ------------------------------------------------------------------------*/ + + @Test + @DisplayName("스터디 게시글 댓글 싫어요 취소 - (성공)") + void cancelCommentDislike() { + + // given + Long memberId = 3L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 2L; + + getAuthentication(memberId); + + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, studyPost1Comment2.getId(), false)) + .thenReturn(Optional.of(studyDislikedComment)); + when(studyPostCommentRepository.save(any(StudyPostComment.class))).thenReturn(studyPost1Comment2); + + // when + StudyPostCommentResponseDTO.CommentPreviewDTO result = studyPostCommandService + .cancelCommentDislike(studyId, postId, commentId); + + // then + assertNotNull(result); + assertThat(result.getCommentId()).isEqualTo(2L); + assertThat(result.getLikeCount()).isEqualTo(1L); + assertThat(result.getDislikeCount()).isEqualTo(0L); + } + + @Test + @DisplayName("스터디 게시글 댓글 싫어요 취소 - 스터디 회원이 아닌 경우 (실패)") + void cancelCommentDislike_NotStudyMember_Fail() { + + // given + Long memberId = 2L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 2L; + + getAuthentication(memberId); + + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, studyPost1Comment2.getId(), false)) + .thenReturn(Optional.of(studyDislikedComment)); + when(studyPostCommentRepository.save(any(StudyPostComment.class))).thenReturn(studyPost1Comment2); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostCommandService.cancelCommentDislike(studyId, postId, commentId)); + } + + @Test + @DisplayName("스터디 게시글 댓글 싫어요 취소 - 싫어요를 누른 댓글이 아닌 경우 (실패)") + void cancelCommentDislike_NotDisliked_Fail() { + + // given + Long memberId = 1L; + Long studyId = 1L; + Long postId = 1L; + Long commentId = 2L; + + getAuthentication(memberId); + + when(studyLikedCommentRepository.findByMemberIdAndStudyPostCommentIdAndIsLiked(memberId, studyPost1Comment2.getId(), false)) + .thenReturn(Optional.empty()); + when(studyPostCommentRepository.save(any(StudyPostComment.class))).thenReturn(studyPost1Comment2); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostCommandService.cancelCommentDislike(studyId, postId, commentId)); + } + + +/*-------------------------------------------------------- Utils ------------------------------------------------------------------------*/ + + private static void getAuthentication(Long memberId) { + String idString = String.valueOf(memberId); + Authentication authentication = new UsernamePasswordAuthenticationToken(idString, null, Collections.emptyList()); + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(authentication); + SecurityContextHolder.setContext(securityContext); + } + + private static void initMember() { + member1 = Member.builder() + .id(1L) + .name("회원1") + .studyPostList(new ArrayList<>()) + .studyLikedPostList(new ArrayList<>()) + .studyPostCommentList(new ArrayList<>()) + .studyLikedCommentList(new ArrayList<>()) + .build(); + member2 = Member.builder() + .id(2L) + .name("회원2") + .studyPostList(new ArrayList<>()) + .studyLikedPostList(new ArrayList<>()) + .studyPostCommentList(new ArrayList<>()) + .studyLikedCommentList(new ArrayList<>()) + .build(); + owner = Member.builder() + .id(3L) + .name("회원3") + .studyPostList(new ArrayList<>()) + .studyLikedPostList(new ArrayList<>()) + .studyPostCommentList(new ArrayList<>()) + .studyLikedCommentList(new ArrayList<>()) + .build(); + } + + private static void initStudy() { + study = Study.builder() + .id(1L) + .gender(Gender.MALE) + .minAge(20) + .maxAge(29) + .fee(10000) + .profileImage("a.jpg") + .hasFee(true) + .isOnline(true) + .goal("SQLD") + .introduction("SQLD 자격증 스터디") + .title("SQLD Master") + .maxPeople(10L) + .build(); + } + + private static void initMemberStudy() { + ownerStudy = MemberStudy.builder() + .id(1L) + .status(ApplicationStatus.APPROVED) + .isOwned(true) + .introduction("Hi") + .member(owner) + .study(study) + .build(); + owner.addMemberStudy(ownerStudy); + study.addMemberStudy(ownerStudy); + + member1Study = MemberStudy.builder() + .id(2L) + .status(ApplicationStatus.APPROVED) + .isOwned(false) + .introduction("Hi") + .member(member1) + .study(study) + .build(); + member1.addMemberStudy(member1Study); + study.addMemberStudy(member1Study); + } + + private static void initStudyPost() { + studyPost1 = StudyPost.builder() + .id(1L) + .member(member1) + .study(study) + .isAnnouncement(false) + .theme(Theme.FREE_TALK) + .title("잡담") + .content("내용") + .hitNum(0) + .likeNum(0) + .commentNum(0) + .build(); + member1.addStudyPost(studyPost1); + study.addStudyPost(studyPost1); + + studyPost2 = StudyPost.builder() + .id(2L) + .member(owner) + .study(study) + .isAnnouncement(true) + .theme(Theme.INFO_SHARING) + .title("공지") + .content("내용") + .hitNum(0) + .likeNum(0) + .commentNum(0) + .build(); + owner.addStudyPost(studyPost2); + study.addStudyPost(studyPost2); + + studyPost3 = StudyPost.builder() + .id(3L) + .member(owner) + .study(study) + .isAnnouncement(false) + .theme(Theme.FREE_TALK) + .title("테스트") + .content("내용") + .hitNum(0) + .likeNum(0) + .commentNum(0) + .build(); + owner.addStudyPost(studyPost3); + study.addStudyPost(studyPost3); + + for (int i=0; i<10; i++) { + studyPost1.plusHitNum(); + } + } + + private static void initStudyLikedPost() { + studyLikedPost = StudyLikedPost.builder() + .id(1L) + .studyPost(studyPost1) + .member(owner) + .build(); + studyPost1.addLikedPost(studyLikedPost); + studyPost1.plusLikeNum(); + owner.addStudyLikedPost(studyLikedPost); + } + + private static void initStudyPostComment() { + studyPost1Comment1 = StudyPostComment.builder() + .id(1L) + .studyPost(studyPost1) + .member(member1) + .content("댓글") + .likeCount(0) + .dislikeCount(0) + .isAnonymous(true) + .anonymousNum(1) + .isDeleted(false) + .parentComment(null) + .build(); + studyPost1.addComment(studyPost1Comment1); + + studyPost1Comment2 = StudyPostComment.builder() + .id(2L) + .studyPost(studyPost1) + .member(owner) + .content("답글") + .likeCount(0) + .dislikeCount(0) + .isAnonymous(false) + .isDeleted(false) + .parentComment(studyPost1Comment1) + .build(); + studyPost1Comment1.addChildrenComment(studyPost1Comment2); + studyPost1.addComment(studyPost1Comment2); + } + + private static void initStudyLikedComment() { + studyLikedComment = StudyLikedComment.builder() + .id(1L) + .isLiked(true) + .studyPostComment(studyPost1Comment2) + .member(member1) + .build(); + studyPost1Comment2.addLikedComment(studyLikedComment); + studyPost1Comment2.plusLikeCount(); + member1.addStudyLikedComment(studyLikedComment); + + studyDislikedComment = StudyLikedComment.builder() + .id(2L) + .isLiked(false) + .studyPostComment(studyPost1Comment2) + .member(owner) + .build(); + studyPost1Comment2.addLikedComment(studyDislikedComment); + studyPost1Comment2.plusDislikeCount(); + owner.addStudyLikedComment(studyDislikedComment); + } +} \ No newline at end of file diff --git a/src/test/java/com/example/spot/service/studypost/StudyPostQueryServiceTest.java b/src/test/java/com/example/spot/service/studypost/StudyPostQueryServiceTest.java new file mode 100644 index 00000000..09fe76e1 --- /dev/null +++ b/src/test/java/com/example/spot/service/studypost/StudyPostQueryServiceTest.java @@ -0,0 +1,600 @@ +package com.example.spot.service.studypost; + +import com.example.spot.api.exception.handler.StudyHandler; +import com.example.spot.domain.Member; +import com.example.spot.domain.enums.ApplicationStatus; +import com.example.spot.domain.enums.Gender; +import com.example.spot.domain.enums.Theme; +import com.example.spot.domain.enums.ThemeQuery; +import com.example.spot.domain.mapping.MemberStudy; +import com.example.spot.domain.mapping.StudyLikedComment; +import com.example.spot.domain.mapping.StudyLikedPost; +import com.example.spot.domain.study.Study; +import com.example.spot.domain.study.StudyPost; +import com.example.spot.domain.study.StudyPostComment; +import com.example.spot.repository.*; +import com.example.spot.web.dto.memberstudy.response.StudyPostCommentResponseDTO; +import com.example.spot.web.dto.memberstudy.response.StudyPostResDTO; +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.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.data.domain.PageRequest; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class StudyPostQueryServiceTest { + + @Mock + private MemberRepository memberRepository; + + @Mock + private StudyRepository studyRepository; + @Mock + private MemberStudyRepository memberStudyRepository; + + @Mock + private StudyPostRepository studyPostRepository; + @Mock + private StudyLikedPostRepository studyLikedPostRepository; + + @Mock + private StudyPostCommentRepository studyPostCommentRepository; + + @InjectMocks + private StudyPostQueryServiceImpl studyPostQueryService; + + private static PageRequest pageRequest; + + private static Study study; + private static Member member1; + private static Member member2; + private static Member owner; + private static MemberStudy member1Study; + private static MemberStudy ownerStudy; + + private static StudyPost studyPost1; + private static StudyPost studyPost2; + private static StudyPost studyPost3; + private static StudyLikedPost studyLikedPost; + private static StudyPostComment studyPost1Comment1; + private static StudyPostComment studyPost1Comment2; + private static StudyLikedComment studyLikedComment; + private static StudyLikedComment studyDislikedComment; + + @BeforeEach + void setUp() { + initMember(); + initStudy(); + initMemberStudy(); + initStudyPost(); + initStudyLikedPost(); + initStudyPostComment(); + initStudyLikedComment(); + + // Member + when(memberRepository.findById(1L)).thenReturn(Optional.of(member1)); + when(memberRepository.findById(2L)).thenReturn(Optional.of(member2)); + when(memberRepository.findById(3L)).thenReturn(Optional.of(owner)); + + // Study + when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); + + // MemberStudy + when(memberStudyRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, ApplicationStatus.APPROVED)) + .thenReturn(Optional.of(member1Study)); + when(memberStudyRepository.findByMemberIdAndStudyIdAndStatus(2L, 1L, ApplicationStatus.APPROVED)) + .thenReturn(Optional.empty()); + when(memberStudyRepository.findByMemberIdAndStudyIdAndStatus(3L, 1L, ApplicationStatus.APPROVED)) + .thenReturn(Optional.of(ownerStudy)); + + // StudyPost + when(studyPostRepository.findById(1L)).thenReturn(Optional.of(studyPost1)); + when(studyPostRepository.findById(2L)).thenReturn(Optional.of(studyPost2)); + when(studyPostRepository.findById(3L)).thenReturn(Optional.of(studyPost3)); + + when(studyLikedPostRepository.findByMemberIdAndStudyPostId(3L, 1L)) + .thenReturn(Optional.of(studyLikedPost)); + when(studyLikedPostRepository.existsByMemberIdAndStudyPostId(3L, 1L)) + .thenReturn(true); + + // Comment + when(studyPostCommentRepository.findAllByStudyPostId(1L)) + .thenReturn(List.of(studyPost1Comment1, studyPost1Comment2)); + + } + +/*-------------------------------------------------------- 게시글 목록 조회 ------------------------------------------------------------------------*/ + + @Test + @DisplayName("스터디 게시글 목록 조회 - 전체 게시글 조회 (성공)") + void getAllPosts_All_Success() { + + // given + Long studyId = 1L; + Long memberId = 1L; + + getAuthentication(memberId); + + pageRequest = PageRequest.of(0, 10); + when(studyPostRepository.findAllByStudyId(studyId, pageRequest)) + .thenReturn(List.of(studyPost1, studyPost2, studyPost3)); + when(studyPostRepository.findAllByStudyIdAndIsAnnouncement(studyId, true, pageRequest)) + .thenReturn(List.of(studyPost2)); + when(studyPostRepository.findAllByStudyIdAndTheme(studyId, Theme.FREE_TALK, pageRequest)) + .thenReturn(List.of(studyPost1, studyPost3)); + + // when + StudyPostResDTO.PostListDTO result = studyPostQueryService.getAllPosts(pageRequest, studyId, null); + + // then + assertNotNull(result); + assertThat(result.getPosts()).isNotEmpty(); + assertThat(result.getPosts()).size().isLessThanOrEqualTo(10); + assertThat(result.getPosts()).size().isEqualTo(3); + assertThat(result.getStudyId()).isEqualTo(studyId); + } + + @Test + @DisplayName("스터디 게시글 목록 조회 - 테마별 조회 (성공)") + void getAllPosts_Theme_Success() { + + // given + Long studyId = 1L; + Long memberId = 1L; + + getAuthentication(memberId); + + pageRequest = PageRequest.of(0, 10); + when(studyPostRepository.findAllByStudyId(studyId, pageRequest)) + .thenReturn(List.of(studyPost1, studyPost2, studyPost3)); + when(studyPostRepository.findAllByStudyIdAndIsAnnouncement(studyId, true, pageRequest)) + .thenReturn(List.of(studyPost2)); + when(studyPostRepository.findAllByStudyIdAndTheme(studyId, Theme.FREE_TALK, pageRequest)) + .thenReturn(List.of(studyPost1, studyPost3)); + + // when + StudyPostResDTO.PostListDTO result = studyPostQueryService.getAllPosts(pageRequest, studyId, ThemeQuery.FREE_TALK); + + // then + assertNotNull(result); + assertThat(result.getPosts()).isNotEmpty(); + assertThat(result.getPosts()).size().isLessThanOrEqualTo(10); + assertThat(result.getPosts()).size().isEqualTo(2); + assertThat(result.getStudyId()).isEqualTo(studyId); + } + + @Test + @DisplayName("스터디 게시글 목록 조회 - 공지 조회 (성공)") + void getAllPosts_Announcements_Success() { + + // given + Long studyId = 1L; + Long memberId = 1L; + + getAuthentication(memberId); + + pageRequest = PageRequest.of(0, 10); + when(studyPostRepository.findAllByStudyId(studyId, pageRequest)) + .thenReturn(List.of(studyPost1, studyPost2, studyPost3)); + when(studyPostRepository.findAllByStudyIdAndIsAnnouncement(studyId, true, pageRequest)) + .thenReturn(List.of(studyPost2)); + when(studyPostRepository.findAllByStudyIdAndTheme(studyId, Theme.FREE_TALK, pageRequest)) + .thenReturn(List.of(studyPost1, studyPost3)); + + // when + StudyPostResDTO.PostListDTO result = studyPostQueryService.getAllPosts(pageRequest, studyId, ThemeQuery.ANNOUNCEMENT); + + // then + assertNotNull(result); + assertThat(result.getPosts()).isNotEmpty(); + assertThat(result.getPosts()).size().isLessThanOrEqualTo(10); + assertThat(result.getPosts()).size().isEqualTo(1); + assertThat(result.getStudyId()).isEqualTo(studyId); + } + + @Test + @DisplayName("스터디 게시글 목록 조회 - 스터디 회원이 아닌 경우(실패)") + void getAllPosts_NotStudyMember_Fail() { + + // given + Long studyId = 1L; + Long memberId = 2L; + + getAuthentication(memberId); + + pageRequest = PageRequest.of(0, 10); + when(studyPostRepository.findAllByStudyId(studyId, pageRequest)) + .thenReturn(List.of(studyPost1, studyPost2, studyPost3)); + when(studyPostRepository.findAllByStudyIdAndIsAnnouncement(studyId, true, pageRequest)) + .thenReturn(List.of(studyPost2)); + when(studyPostRepository.findAllByStudyIdAndTheme(studyId, Theme.FREE_TALK, pageRequest)) + .thenReturn(List.of(studyPost1, studyPost3)); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostQueryService.getAllPosts(pageRequest, studyId, null)); + } + + @Test + @DisplayName("스터디 게시글 목록 조회 - 존재하는 카테고리가 아닌 경우(실패)") + void getAllPosts_NotCategorized_Fail() { + + // given + Long studyId = 1L; + Long memberId = 1L; + + getAuthentication(memberId); + + pageRequest = PageRequest.of(0, 10); + when(studyPostRepository.findAllByStudyId(studyId, pageRequest)) + .thenReturn(List.of(studyPost1, studyPost2, studyPost3)); + when(studyPostRepository.findAllByStudyIdAndIsAnnouncement(studyId, true, pageRequest)) + .thenReturn(List.of(studyPost2)); + when(studyPostRepository.findAllByStudyIdAndTheme(studyId, Theme.FREE_TALK, pageRequest)) + .thenReturn(List.of(studyPost1, studyPost3)); + + // when & then + assertThrows(IllegalArgumentException.class, () -> studyPostQueryService.getAllPosts(pageRequest, studyId, ThemeQuery.valueOf("Nothing"))); + } + + +/*-------------------------------------------------------- 게시글 조회 ------------------------------------------------------------------------*/ + + @Test + @DisplayName("스터디 게시글 단건 조회 - (성공)") + void getPost_Success() { + + // given + Long studyId = 1L; + Long memberId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyPostRepository.save(studyPost1)).thenReturn(studyPost1); + when(memberRepository.save(member1)).thenReturn(member1); + when(studyRepository.save(study)).thenReturn(study); + when(studyPostCommentRepository.findAllByStudyPostId(postId)) + .thenReturn(List.of(studyPost1Comment1, studyPost1Comment2)); + when(studyLikedPostRepository.existsByMemberIdAndStudyPostId(memberId, postId)) + .thenReturn(false); + + // when + StudyPostResDTO.PostDetailDTO result = studyPostQueryService.getPost(studyId, postId); + + // then + assertNotNull(result); + assertThat(result.getPostId()).isEqualTo(1L); + assertThat(result.getHitNum()).isEqualTo(11); + assertThat(result.getTitle()).isEqualTo("잡담"); + assertThat(result.getCommentNum()).isEqualTo(2); + assertThat(result.getIsLiked()).isEqualTo(false); + + } + + @Test + @DisplayName("스터디 게시글 단건 조회 - 스터디 회원이 아닌 경우(실패)") + void getPost_NotStudyMember_Fail() { + + // given + Long studyId = 1L; + Long memberId = 2L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyPostRepository.save(studyPost1)).thenReturn(studyPost1); + when(memberRepository.save(member1)).thenReturn(member1); + when(studyRepository.save(study)).thenReturn(study); + when(studyPostCommentRepository.findAllByStudyPostId(postId)) + .thenReturn(List.of(studyPost1Comment1, studyPost1Comment2)); + when(studyLikedPostRepository.existsByMemberIdAndStudyPostId(memberId, postId)) + .thenReturn(false); + + // when & then + assertThrows(StudyHandler.class, () ->studyPostQueryService.getPost(studyId, postId)); + } + + @Test + @DisplayName("스터디 게시글 단건 조회 - 스터디 게시글이 아닌 경우(실패)") + void getPost_NotStudyPost_Fail() { + + // given + Long studyId = 1L; + Long memberId = 2L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.empty()); + when(studyPostCommentRepository.findAllByStudyPostId(postId)) + .thenReturn(List.of()); + + // when & then + assertThrows(StudyHandler.class, () ->studyPostQueryService.getPost(studyId, postId)); + } + +/*-------------------------------------------------------- 댓글 목록 조회 ------------------------------------------------------------------------*/ + + @Test + @DisplayName("스터디 게시글 댓글 목록 조회 - (성공)") + void getAllComments_Success() { + + // given + Long memberId = 1L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyPostCommentRepository.findAllByStudyPostId(postId)) + .thenReturn(List.of(studyPost1Comment1, studyPost1Comment2)); + + // when + StudyPostCommentResponseDTO.CommentReplyListDTO result = studyPostQueryService.getAllComments(studyId, postId); + + // then + assertNotNull(result); + assertThat(result.getPostId()).isEqualTo(1L); + assertThat(result.getComments()).size().isEqualTo(1); + result.getComments() + .forEach(comment -> { + assertThat(comment.getCommentId()).isEqualTo(1L); // 댓글 1개 + assertThat(comment.getApplies()).size().isEqualTo(1L); // 답글 1개 + }); + } + + @Test + @DisplayName("스터디 게시글 댓글 목록 조회 - 스터디 회원이 아닌 경우(실패)") + void getAllComments_NotStudyMember_Fail() { + + // given + Long memberId = 2L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.of(studyPost1)); + when(studyPostCommentRepository.findAllByStudyPostId(postId)) + .thenReturn(List.of(studyPost1Comment1, studyPost1Comment2)); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostQueryService.getAllComments(studyId, postId)); + } + + @Test + @DisplayName("스터디 게시글 댓글 목록 조회 - 스터디 게시글이 아닌 경우(실패)") + void getAllComments_NotStudyPost_Fail() { + + // given + Long memberId = 1L; + Long studyId = 1L; + Long postId = 1L; + + getAuthentication(memberId); + + when(studyPostRepository.findByIdAndStudyId(postId, studyId)) + .thenReturn(Optional.empty()); + when(studyPostCommentRepository.findAllByStudyPostId(postId)) + .thenReturn(List.of()); + + // when & then + assertThrows(StudyHandler.class, () -> studyPostQueryService.getAllComments(studyId, postId)); + } + + +/*-------------------------------------------------------- Utils ------------------------------------------------------------------------*/ + + private static void getAuthentication(Long memberId) { + String idString = String.valueOf(memberId); + Authentication authentication = new UsernamePasswordAuthenticationToken(idString, null, Collections.emptyList()); + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(authentication); + SecurityContextHolder.setContext(securityContext); + } + + private static void initMember() { + member1 = Member.builder() + .id(1L) + .studyPostList(new ArrayList<>()) + .studyLikedPostList(new ArrayList<>()) + .studyPostCommentList(new ArrayList<>()) + .studyLikedCommentList(new ArrayList<>()) + .build(); + member2 = Member.builder() + .id(2L) + .studyPostList(new ArrayList<>()) + .studyLikedPostList(new ArrayList<>()) + .studyPostCommentList(new ArrayList<>()) + .studyLikedCommentList(new ArrayList<>()) + .build(); + owner = Member.builder() + .id(3L) + .studyPostList(new ArrayList<>()) + .studyLikedPostList(new ArrayList<>()) + .studyPostCommentList(new ArrayList<>()) + .studyLikedCommentList(new ArrayList<>()) + .build(); + } + + private static void initStudy() { + study = Study.builder() + .id(1L) + .gender(Gender.MALE) + .minAge(20) + .maxAge(29) + .fee(10000) + .profileImage("a.jpg") + .hasFee(true) + .isOnline(true) + .goal("SQLD") + .introduction("SQLD 자격증 스터디") + .title("SQLD Master") + .maxPeople(10L) + .build(); + } + + private static void initMemberStudy() { + ownerStudy = MemberStudy.builder() + .id(1L) + .status(ApplicationStatus.APPROVED) + .isOwned(true) + .introduction("Hi") + .member(owner) + .study(study) + .build(); + owner.addMemberStudy(ownerStudy); + study.addMemberStudy(ownerStudy); + + member1Study = MemberStudy.builder() + .id(2L) + .status(ApplicationStatus.APPROVED) + .isOwned(false) + .introduction("Hi") + .member(member1) + .study(study) + .build(); + member1.addMemberStudy(member1Study); + study.addMemberStudy(member1Study); + } + + private static void initStudyPost() { + studyPost1 = StudyPost.builder() + .id(1L) + .member(member1) + .study(study) + .isAnnouncement(false) + .theme(Theme.FREE_TALK) + .title("잡담") + .content("내용") + .hitNum(0) + .likeNum(0) + .commentNum(0) + .build(); + member1.addStudyPost(studyPost1); + study.addStudyPost(studyPost1); + + studyPost2 = StudyPost.builder() + .id(2L) + .member(owner) + .study(study) + .isAnnouncement(true) + .theme(Theme.INFO_SHARING) + .title("공지") + .content("내용") + .hitNum(0) + .likeNum(0) + .commentNum(0) + .build(); + owner.addStudyPost(studyPost2); + study.addStudyPost(studyPost2); + + studyPost3 = StudyPost.builder() + .id(3L) + .member(owner) + .study(study) + .isAnnouncement(false) + .theme(Theme.FREE_TALK) + .title("테스트") + .content("내용") + .hitNum(0) + .likeNum(0) + .commentNum(0) + .build(); + owner.addStudyPost(studyPost3); + study.addStudyPost(studyPost3); + + for (int i=0; i<10; i++) { + studyPost1.plusHitNum(); + } + } + + private static void initStudyLikedPost() { + studyLikedPost = StudyLikedPost.builder() + .id(1L) + .studyPost(studyPost1) + .member(owner) + .build(); + studyPost1.addLikedPost(studyLikedPost); + studyPost1.plusLikeNum(); + owner.addStudyLikedPost(studyLikedPost); + } + + private static void initStudyPostComment() { + studyPost1Comment1 = StudyPostComment.builder() + .id(1L) + .studyPost(studyPost1) + .member(member1) + .content("댓글") + .likeCount(0) + .dislikeCount(0) + .isAnonymous(true) + .isDeleted(false) + .parentComment(null) + .build(); + studyPost1Comment2 = StudyPostComment.builder() + .id(2L) + .studyPost(studyPost1) + .member(owner) + .content("답글") + .likeCount(0) + .dislikeCount(0) + .isAnonymous(false) + .isDeleted(false) + .parentComment(studyPost1Comment1) + .build(); + studyPost1Comment1.addChildrenComment(studyPost1Comment2); + + studyPost1.addComment(studyPost1Comment1); + studyPost1.addComment(studyPost1Comment2); + } + + private static void initStudyLikedComment() { + studyLikedComment = StudyLikedComment.builder() + .id(1L) + .isLiked(true) + .studyPostComment(studyPost1Comment2) + .member(member1) + .build(); + studyPost1Comment2.addLikedComment(studyLikedComment); + studyPost1Comment2.plusLikeCount(); + member1.addStudyLikedComment(studyLikedComment); + + studyDislikedComment = StudyLikedComment.builder() + .id(2L) + .isLiked(false) + .studyPostComment(studyPost1Comment2) + .member(owner) + .build(); + studyPost1Comment2.addLikedComment(studyDislikedComment); + studyPost1Comment2.plusDislikeCount(); + owner.addStudyLikedComment(studyDislikedComment); + } +} \ No newline at end of file