diff --git a/src/main/kotlin/com/weeth/domain/board/application/mapper/PostMapper.kt b/src/main/kotlin/com/weeth/domain/board/application/mapper/PostMapper.kt index 62a63ffe..8a6e1f75 100644 --- a/src/main/kotlin/com/weeth/domain/board/application/mapper/PostMapper.kt +++ b/src/main/kotlin/com/weeth/domain/board/application/mapper/PostMapper.kt @@ -26,7 +26,6 @@ class PostMapper( fun toDetailResponse( post: Post, - authorMember: ClubMember, comments: List, files: List, isLiked: Boolean, @@ -34,7 +33,7 @@ class PostMapper( id = post.id, boardId = post.board.id, boardName = post.board.name, - author = UserInfo.of(post.user, authorMember.memberRole, resolveProfileImage(authorMember)), + author = UserInfo.of(post.clubMember.user, post.clubMember.memberRole, resolveProfileImage(post.clubMember)), title = post.title, content = post.content, time = post.modifiedAt, @@ -46,13 +45,12 @@ class PostMapper( fun toListResponse( post: Post, - authorMember: ClubMember, hasFile: Boolean, now: LocalDateTime, isLiked: Boolean, ) = PostListResponse( id = post.id, - author = UserInfo.of(post.user, authorMember.memberRole, resolveProfileImage(authorMember)), + author = UserInfo.of(post.clubMember.user, post.clubMember.memberRole, resolveProfileImage(post.clubMember)), boardId = post.board.id, boardName = post.board.name, title = post.title, diff --git a/src/main/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCase.kt b/src/main/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCase.kt index 7868d60d..6dce1ddc 100644 --- a/src/main/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCase.kt +++ b/src/main/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCase.kt @@ -20,7 +20,6 @@ import com.weeth.domain.file.application.mapper.FileMapper import com.weeth.domain.file.domain.enums.FileOwnerType import com.weeth.domain.file.domain.repository.FileReader import com.weeth.domain.file.domain.repository.FileRepository -import com.weeth.domain.user.domain.repository.UserReader import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -28,7 +27,6 @@ import org.springframework.transaction.annotation.Transactional class ManagePostUseCase( private val postRepository: PostRepository, private val boardRepository: BoardRepository, - private val userReader: UserReader, private val clubMemberPolicy: ClubMemberPolicy, private val clubMemberCardinalReader: ClubMemberCardinalReader, private val fileRepository: FileRepository, @@ -44,7 +42,6 @@ class ManagePostUseCase( userId: Long, ): PostSaveResponse { val member = clubMemberPolicy.getActiveMember(clubId, userId) - val user = userReader.getById(userId) val board = findBoardInClub(boardId, clubId) validateWritePermission(board, member) @@ -57,7 +54,7 @@ class ManagePostUseCase( Post.create( title = request.title, content = request.content, - user = user, + clubMember = member, board = board, cardinalNumber = currentCardinalNumber, ) @@ -75,7 +72,6 @@ class ManagePostUseCase( userId: Long, ): PostSaveResponse { val member = clubMemberPolicy.getActiveMember(clubId, userId) - val user = userReader.getById(userId) val post = findPost(postId) if (post.board.club.id != clubId) throw PostNotFoundException() validateOwner(post, userId) @@ -97,7 +93,6 @@ class ManagePostUseCase( userId: Long, ) { val member = clubMemberPolicy.getActiveMember(clubId, userId) - val user = userReader.getById(userId) val post = findPost(postId) if (post.board.club.id != clubId) throw PostNotFoundException() validateOwner(post, userId) diff --git a/src/main/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryService.kt b/src/main/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryService.kt index 2f868ca9..e5b26f58 100644 --- a/src/main/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryService.kt +++ b/src/main/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryService.kt @@ -10,9 +10,7 @@ import com.weeth.domain.board.application.mapper.PostMapper import com.weeth.domain.board.domain.repository.BoardRepository import com.weeth.domain.board.domain.repository.PostLikeRepository import com.weeth.domain.board.domain.repository.PostRepository -import com.weeth.domain.club.domain.entity.ClubMember import com.weeth.domain.club.domain.enums.MemberRole -import com.weeth.domain.club.domain.repository.ClubMemberReader import com.weeth.domain.club.domain.service.ClubMemberPolicy import com.weeth.domain.comment.application.usecase.query.GetCommentQueryService import com.weeth.domain.comment.domain.repository.CommentReader @@ -34,7 +32,6 @@ class GetPostQueryService( private val boardRepository: BoardRepository, private val postLikeRepository: PostLikeRepository, private val clubMemberPolicy: ClubMemberPolicy, - private val clubMemberReader: ClubMemberReader, private val commentReader: CommentReader, private val getCommentQueryService: GetCommentQueryService, private val fileReader: FileReader, @@ -60,14 +57,10 @@ class GetPostQueryService( val files = fileReader.findAll(FileOwnerType.POST, post.id).map(fileMapper::toFileResponse) val comments = commentReader.findAllByPostId(post.id) - val commentAuthorIds = comments.map { it.user.id }.distinct() - val allAuthorIds = (commentAuthorIds + post.user.id).distinct() - val memberMap = buildMemberMap(clubId, allAuthorIds) - - val commentTree = getCommentQueryService.toCommentTreeResponses(comments, memberMap) + val commentTree = getCommentQueryService.toCommentTreeResponses(comments) val isLiked = postLikeRepository.existsByPostAndUserIdAndIsActiveTrue(post, userId) - return postMapper.toDetailResponse(post, memberMap.getValue(post.user.id), commentTree, files, isLiked) + return postMapper.toDetailResponse(post, commentTree, files, isLiked) } fun findAllPosts( @@ -93,7 +86,6 @@ class GetPostQueryService( val posts = postRepository.findAllActiveByBoardIds(accessibleBoardIds, pageable) val postIds = posts.content.map { it.id } - val memberMap = buildMemberMap(clubId, posts.content.map { it.user.id }.distinct()) val fileExistsByPostId = buildFileExistsMap(postIds) val likedPostIds = postLikeRepository.findLikedPostIds(postIds, userId) val now = LocalDateTime.now() @@ -101,7 +93,6 @@ class GetPostQueryService( return posts.map { post -> postMapper.toListResponse( post, - memberMap.getValue(post.user.id), fileExistsByPostId[post.id] == true, now, post.id in likedPostIds, @@ -125,14 +116,12 @@ class GetPostQueryService( val postIds = posts.content.map { it.id } val fileExistsByPostId = buildFileExistsMap(postIds) - val memberMap = buildMemberMap(clubId, posts.content.map { it.user.id }.distinct()) val likedPostIds = postLikeRepository.findLikedPostIds(postIds, userId) val now = LocalDateTime.now() return posts.map { post -> postMapper.toListResponse( post, - memberMap.getValue(post.user.id), fileExistsByPostId[post.id] == true, now, post.id in likedPostIds, @@ -160,14 +149,12 @@ class GetPostQueryService( val postIds = posts.content.map { it.id } val fileExistsByPostId = buildFileExistsMap(postIds) - val memberMap = buildMemberMap(clubId, posts.content.map { it.user.id }.distinct()) val likedPostIds = postLikeRepository.findLikedPostIds(postIds, userId) val now = LocalDateTime.now() return posts.map { post -> postMapper.toListResponse( post, - memberMap.getValue(post.user.id), fileExistsByPostId[post.id] == true, now, post.id in likedPostIds, @@ -175,17 +162,6 @@ class GetPostQueryService( } } - /** - * Post, Comment 조회 시 작성자 정보를 매핑하기 위한 헬퍼 메서드 - */ - private fun buildMemberMap( - clubId: Long, - userIds: List, - ): Map { - if (userIds.isEmpty()) return emptyMap() - return clubMemberReader.findAllByClubIdAndUserIds(clubId, userIds).associateBy { it.user.id } - } - private fun validatePage( pageNumber: Int, pageSize: Int, diff --git a/src/main/kotlin/com/weeth/domain/board/domain/entity/Post.kt b/src/main/kotlin/com/weeth/domain/board/domain/entity/Post.kt index 32d886f8..7e949f69 100644 --- a/src/main/kotlin/com/weeth/domain/board/domain/entity/Post.kt +++ b/src/main/kotlin/com/weeth/domain/board/domain/entity/Post.kt @@ -1,6 +1,6 @@ package com.weeth.domain.board.domain.entity -import com.weeth.domain.user.domain.entity.User +import com.weeth.domain.club.domain.entity.ClubMember import com.weeth.global.common.entity.BaseEntity import jakarta.persistence.Column import jakarta.persistence.Entity @@ -17,7 +17,7 @@ import jakarta.persistence.Table class Post( title: String, content: String, - user: User, + clubMember: ClubMember, board: Board, cardinalNumber: Int? = null, ) : BaseEntity() { @@ -35,8 +35,8 @@ class Post( private set @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "user_id", nullable = false) - var user: User = user + @JoinColumn(name = "club_member_id", nullable = false) + var clubMember: ClubMember = clubMember private set @ManyToOne(fetch = FetchType.LAZY, optional = false) @@ -78,7 +78,7 @@ class Post( likeCount-- } - fun isOwnedBy(userId: Long): Boolean = user.id == userId + fun isOwnedBy(userId: Long): Boolean = clubMember.user.id == userId fun belongsToClub(clubId: Long): Boolean = board.club.id == clubId && !board.isDeleted @@ -108,7 +108,7 @@ class Post( fun create( title: String, content: String, - user: User, + clubMember: ClubMember, board: Board, cardinalNumber: Int? = null, ): Post { @@ -117,7 +117,7 @@ class Post( return Post( title = title, content = content, - user = user, + clubMember = clubMember, board = board, cardinalNumber = cardinalNumber, ) diff --git a/src/main/kotlin/com/weeth/domain/board/domain/repository/PostRepository.kt b/src/main/kotlin/com/weeth/domain/board/domain/repository/PostRepository.kt index b2aef8ff..d15a994f 100644 --- a/src/main/kotlin/com/weeth/domain/board/domain/repository/PostRepository.kt +++ b/src/main/kotlin/com/weeth/domain/board/domain/repository/PostRepository.kt @@ -19,7 +19,7 @@ import java.time.LocalDateTime interface PostRepository : JpaRepository, PostReader { - @EntityGraph(attributePaths = ["user", "board"]) + @EntityGraph(attributePaths = ["clubMember", "clubMember.user", "board"]) @Query( """ SELECT p @@ -35,7 +35,7 @@ interface PostRepository : pageable: Pageable, ): Slice - @EntityGraph(attributePaths = ["user", "board"]) + @EntityGraph(attributePaths = ["clubMember", "clubMember.user", "board"]) @Query( """ SELECT p @@ -52,6 +52,7 @@ interface PostRepository : fun findByIdAndIsDeletedFalse(id: Long): Post? + @EntityGraph(attributePaths = ["clubMember", "clubMember.user", "board"]) @Query( """ SELECT p @@ -65,6 +66,7 @@ interface PostRepository : @Param("id") id: Long, ): Post? + @EntityGraph(attributePaths = ["board", "board.club"]) @Lock(LockModeType.PESSIMISTIC_WRITE) @QueryHints(QueryHint(name = "jakarta.persistence.lock.timeout", value = "2000")) @Query( @@ -80,7 +82,7 @@ interface PostRepository : @Param("id") id: Long, ): Post? - @EntityGraph(attributePaths = ["user", "board"]) + @EntityGraph(attributePaths = ["clubMember", "clubMember.user", "board"]) @Query( """ SELECT p @@ -106,7 +108,7 @@ interface PostRepository : pageable: Pageable, ): Slice = findAllActiveByBoardIds(boardIds, pageable) - @EntityGraph(attributePaths = ["user"]) + @EntityGraph(attributePaths = ["clubMember", "clubMember.user"]) @Query( """ SELECT p @@ -122,7 +124,7 @@ interface PostRepository : pageable: Pageable, ): Slice - @EntityGraph(attributePaths = ["user"]) + @EntityGraph(attributePaths = ["clubMember", "clubMember.user"]) @Query( """ SELECT p @@ -138,7 +140,7 @@ interface PostRepository : pageable: Pageable, ): Slice - @EntityGraph(attributePaths = ["user"]) + @EntityGraph(attributePaths = ["clubMember", "clubMember.user"]) @Query( """ SELECT p @@ -156,7 +158,7 @@ interface PostRepository : pageable: Pageable, ): Slice - @EntityGraph(attributePaths = ["user"]) + @EntityGraph(attributePaths = ["clubMember", "clubMember.user"]) @Query( """ SELECT p diff --git a/src/main/kotlin/com/weeth/domain/comment/application/mapper/CommentMapper.kt b/src/main/kotlin/com/weeth/domain/comment/application/mapper/CommentMapper.kt index 006b5b0d..0a167ecf 100644 --- a/src/main/kotlin/com/weeth/domain/comment/application/mapper/CommentMapper.kt +++ b/src/main/kotlin/com/weeth/domain/comment/application/mapper/CommentMapper.kt @@ -1,6 +1,5 @@ package com.weeth.domain.comment.application.mapper -import com.weeth.domain.club.domain.entity.ClubMember import com.weeth.domain.comment.application.dto.response.CommentResponse import com.weeth.domain.comment.domain.entity.Comment import com.weeth.domain.file.application.dto.response.FileResponse @@ -14,7 +13,6 @@ class CommentMapper( ) { fun toCommentDto( comment: Comment, - authorMember: ClubMember, children: List, fileUrls: List, ): CommentResponse = @@ -22,9 +20,9 @@ class CommentMapper( id = comment.id, author = UserInfo.of( - comment.user, - authorMember.memberRole, - authorMember.profileImageStorageKey?.let { fileAccessUrlPort.resolve(it) }, + comment.clubMember.user, + comment.clubMember.memberRole, + comment.clubMember.profileImageStorageKey?.let { fileAccessUrlPort.resolve(it) }, ), content = comment.content, time = comment.modifiedAt, diff --git a/src/main/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCase.kt b/src/main/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCase.kt index cb6e2444..cbc2501d 100644 --- a/src/main/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCase.kt +++ b/src/main/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCase.kt @@ -3,6 +3,7 @@ package com.weeth.domain.comment.application.usecase.command import com.weeth.domain.board.application.exception.PostNotFoundException import com.weeth.domain.board.domain.entity.Post import com.weeth.domain.board.domain.repository.PostRepository +import com.weeth.domain.club.domain.service.ClubMemberPolicy import com.weeth.domain.comment.application.dto.request.CommentSaveRequest import com.weeth.domain.comment.application.dto.request.CommentUpdateRequest import com.weeth.domain.comment.application.exception.CommentAlreadyDeletedException @@ -15,7 +16,6 @@ import com.weeth.domain.file.application.mapper.FileMapper import com.weeth.domain.file.domain.enums.FileOwnerType import com.weeth.domain.file.domain.repository.FileReader import com.weeth.domain.file.domain.repository.FileRepository -import com.weeth.domain.user.domain.repository.UserReader import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -23,7 +23,7 @@ import org.springframework.transaction.annotation.Transactional class ManageCommentUseCase( private val commentRepository: CommentRepository, private val postRepository: PostRepository, // 타 도메인 이므로 Reader 사용 검토 - private val userReader: UserReader, + private val clubMemberPolicy: ClubMemberPolicy, private val fileReader: FileReader, private val fileRepository: FileRepository, private val fileMapper: FileMapper, @@ -34,8 +34,9 @@ class ManageCommentUseCase( postId: Long, userId: Long, ) { - val user = userReader.getById(userId) val post = findPostWithLock(postId) + val clubId = post.board.club.id + val clubMember = clubMemberPolicy.getActiveMember(clubId, userId) val parent = dto.parentCommentId?.let { parentId -> commentRepository.findByIdAndPostId(parentId, postId) ?: throw CommentNotFoundException() @@ -45,7 +46,7 @@ class ManageCommentUseCase( Comment.createForPost( content = dto.content, post = post, - user = user, + clubMember = clubMember, parent = parent, ) val savedComment = commentRepository.save(comment) diff --git a/src/main/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryService.kt b/src/main/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryService.kt index 54e9592a..1c698b91 100644 --- a/src/main/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryService.kt +++ b/src/main/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryService.kt @@ -1,6 +1,5 @@ package com.weeth.domain.comment.application.usecase.query -import com.weeth.domain.club.domain.entity.ClubMember import com.weeth.domain.comment.application.dto.response.CommentResponse import com.weeth.domain.comment.application.mapper.CommentMapper import com.weeth.domain.comment.domain.entity.Comment @@ -21,10 +20,7 @@ class GetCommentQueryService( /** * Comment 리스트를 받아 자식, 부모 관계 트리를 형성하는 메서드 */ - fun toCommentTreeResponses( - comments: List, - memberMap: Map, - ): List { + fun toCommentTreeResponses(comments: List): List { if (comments.isEmpty()) { return emptyList() } @@ -42,18 +38,17 @@ class GetCommentQueryService( return comments .filter { it.parent == null } - .map { mapToCommentResponse(it, childrenByParentId, filesByCommentId, memberMap) } + .map { mapToCommentResponse(it, childrenByParentId, filesByCommentId) } } private fun mapToCommentResponse( comment: Comment, childrenByParentId: Map>, filesByCommentId: Map>, - memberMap: Map, ): CommentResponse { val children = childrenByParentId[comment.id] - ?.map { mapToCommentResponse(it, childrenByParentId, filesByCommentId, memberMap) } + ?.map { mapToCommentResponse(it, childrenByParentId, filesByCommentId) } ?: emptyList() val files = @@ -61,7 +56,6 @@ class GetCommentQueryService( ?.map(fileMapper::toFileResponse) ?: emptyList() - val authorMember = memberMap.getValue(comment.user.id) - return commentMapper.toCommentDto(comment, authorMember, children, files) + return commentMapper.toCommentDto(comment, children, files) } } diff --git a/src/main/kotlin/com/weeth/domain/comment/domain/entity/Comment.kt b/src/main/kotlin/com/weeth/domain/comment/domain/entity/Comment.kt index f070b906..2dbcef75 100644 --- a/src/main/kotlin/com/weeth/domain/comment/domain/entity/Comment.kt +++ b/src/main/kotlin/com/weeth/domain/comment/domain/entity/Comment.kt @@ -1,8 +1,8 @@ package com.weeth.domain.comment.domain.entity import com.weeth.domain.board.domain.entity.Post +import com.weeth.domain.club.domain.entity.ClubMember import com.weeth.domain.comment.domain.vo.CommentContent -import com.weeth.domain.user.domain.entity.User import com.weeth.global.common.entity.BaseEntity import jakarta.persistence.CascadeType import jakarta.persistence.Column @@ -31,8 +31,8 @@ class Comment( @JoinColumn(name = "post_id") val post: Post, @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - val user: User, + @JoinColumn(name = "club_member_id") + val clubMember: ClubMember, @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_id") val parent: Comment? = null, @@ -50,7 +50,7 @@ class Comment( content = CommentContent.from(newContent).value } - fun isOwnedBy(userId: Long): Boolean = user.id == userId + fun isOwnedBy(userId: Long): Boolean = clubMember.user.id == userId companion object { private const val DELETED_CONTENT = "삭제된 댓글입니다." @@ -58,7 +58,7 @@ class Comment( fun createForPost( content: String, post: Post, - user: User, + clubMember: ClubMember, parent: Comment?, ): Comment { require(parent == null || parent.post.id == post.id) { @@ -67,7 +67,7 @@ class Comment( return Comment( content = CommentContent.from(content).value, post = post, - user = user, + clubMember = clubMember, parent = parent, ) } diff --git a/src/main/kotlin/com/weeth/domain/comment/domain/repository/CommentRepository.kt b/src/main/kotlin/com/weeth/domain/comment/domain/repository/CommentRepository.kt index 3728d37d..b511b037 100644 --- a/src/main/kotlin/com/weeth/domain/comment/domain/repository/CommentRepository.kt +++ b/src/main/kotlin/com/weeth/domain/comment/domain/repository/CommentRepository.kt @@ -1,13 +1,23 @@ package com.weeth.domain.comment.domain.repository import com.weeth.domain.comment.domain.entity.Comment +import org.springframework.data.jpa.repository.EntityGraph import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param interface CommentRepository : JpaRepository, CommentReader { + @EntityGraph(attributePaths = ["clubMember", "clubMember.user"]) fun findByIdAndPostId( id: Long, postId: Long, ): Comment? + + @EntityGraph(attributePaths = ["clubMember", "clubMember.user"]) + @Query("SELECT c FROM Comment c WHERE c.post.id = :postId") + override fun findAllByPostId( + @Param("postId") postId: Long, + ): List } diff --git a/src/main/kotlin/com/weeth/domain/dashboard/application/mapper/DashboardMapper.kt b/src/main/kotlin/com/weeth/domain/dashboard/application/mapper/DashboardMapper.kt index 97731d9a..73a35942 100644 --- a/src/main/kotlin/com/weeth/domain/dashboard/application/mapper/DashboardMapper.kt +++ b/src/main/kotlin/com/weeth/domain/dashboard/application/mapper/DashboardMapper.kt @@ -99,12 +99,11 @@ class DashboardMapper( fun toPostResponse( post: Post, - authorMember: ClubMember, files: List, now: LocalDateTime, ) = DashboardPostResponse( id = post.id, - author = UserInfo.of(post.user, authorMember.memberRole, resolveProfileImage(authorMember)), + author = UserInfo.of(post.clubMember.user, post.clubMember.memberRole, resolveProfileImage(post.clubMember)), title = post.title, content = post.content, time = post.createdAt, diff --git a/src/main/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryService.kt b/src/main/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryService.kt index c6e56108..7cac5e13 100644 --- a/src/main/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryService.kt +++ b/src/main/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryService.kt @@ -3,7 +3,6 @@ package com.weeth.domain.dashboard.application.usecase.query import com.weeth.domain.board.domain.enums.BoardType import com.weeth.domain.board.domain.repository.BoardReader import com.weeth.domain.board.domain.repository.PostReader -import com.weeth.domain.club.domain.entity.ClubMember import com.weeth.domain.club.domain.repository.ClubMemberReader import com.weeth.domain.club.domain.repository.ClubReader import com.weeth.domain.club.domain.service.ClubMemberPolicy @@ -90,13 +89,10 @@ class GetDashboardQueryService( val now = LocalDateTime.now() val postIds = posts.content.map { it.id } val filesByPostId = fileReader.findAll(FileOwnerType.POST, postIds).groupBy { it.ownerId } - val authorIds = posts.content.map { it.user.id }.distinct() - val memberMap = buildMemberMap(clubId, authorIds) return posts.map { post -> dashboardMapper.toPostResponse( post = post, - authorMember = memberMap.getValue(post.user.id), files = filesByPostId[post.id] ?: emptyList(), now = now, ) @@ -131,14 +127,6 @@ class GetDashboardQueryService( return dashboardMapper.toScheduleResponses(events, sessions) } - private fun buildMemberMap( - clubId: Long, - userIds: List, - ): Map { - if (userIds.isEmpty()) return emptyMap() - return clubMemberReader.findAllByClubIdAndUserIds(clubId, userIds).associateBy { it.user.id } - } - fun getUnreadNotice( clubId: Long, userId: Long, diff --git a/src/test/kotlin/com/weeth/domain/board/application/mapper/PostMapperTest.kt b/src/test/kotlin/com/weeth/domain/board/application/mapper/PostMapperTest.kt index 2ea39cfc..52f28963 100644 --- a/src/test/kotlin/com/weeth/domain/board/application/mapper/PostMapperTest.kt +++ b/src/test/kotlin/com/weeth/domain/board/application/mapper/PostMapperTest.kt @@ -34,11 +34,12 @@ class PostMapperTest : every { authorMember.memberRole } returns MemberRole.USER every { authorMember.profileImageStorageKey } returns null + every { authorMember.user } returns user every { post.id } returns 1L every { post.title } returns "제목" every { post.content } returns "내용" - every { post.user } returns user + every { post.clubMember } returns authorMember every { post.board } returns board every { post.commentCount } returns 2 every { post.likeCount } returns 0 @@ -47,7 +48,7 @@ class PostMapperTest : describe("toListResponse") { it("24시간 이내 생성된 게시글은 isNew=true") { - val response = mapper.toListResponse(post, authorMember, hasFile = true, now = now, isLiked = false) + val response = mapper.toListResponse(post, hasFile = true, now = now, isLiked = false) response.id shouldBe 1L response.hasFile shouldBe true @@ -81,7 +82,7 @@ class PostMapperTest : ), ) - val response = mapper.toDetailResponse(post, authorMember, comments, files, isLiked = false) + val response = mapper.toDetailResponse(post, comments, files, isLiked = false) response.id shouldBe 1L response.commentCount shouldBe 2 diff --git a/src/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.kt b/src/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.kt index ebe3410d..6fa3167e 100644 --- a/src/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.kt +++ b/src/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.kt @@ -20,16 +20,13 @@ import com.weeth.domain.club.domain.enums.MemberRole import com.weeth.domain.club.domain.repository.ClubMemberCardinalReader import com.weeth.domain.club.domain.service.ClubMemberPolicy import com.weeth.domain.club.fixture.ClubMemberCardinalTestFixture +import com.weeth.domain.club.fixture.ClubMemberTestFixture import com.weeth.domain.file.application.dto.request.FileSaveRequest import com.weeth.domain.file.application.mapper.FileMapper import com.weeth.domain.file.domain.entity.File import com.weeth.domain.file.domain.enums.FileOwnerType import com.weeth.domain.file.domain.repository.FileReader import com.weeth.domain.file.domain.repository.FileRepository -import com.weeth.domain.user.domain.entity.User -import com.weeth.domain.user.domain.enums.Status -import com.weeth.domain.user.domain.repository.UserReader -import com.weeth.domain.user.domain.vo.Email import com.weeth.domain.user.fixture.UserTestFixture import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.DescribeSpec @@ -40,13 +37,11 @@ import io.mockk.just import io.mockk.mockk import io.mockk.runs import io.mockk.verify -import org.springframework.test.util.ReflectionTestUtils class ManagePostUseCaseTest : DescribeSpec({ val postRepository = mockk() val boardRepository = mockk() - val userReader = mockk() val clubMemberPolicy = mockk(relaxed = true) val clubMemberCardinalReader = mockk() val fileRepository = mockk() @@ -58,7 +53,6 @@ class ManagePostUseCaseTest : ManagePostUseCase( postRepository, boardRepository, - userReader, clubMemberPolicy, clubMemberCardinalReader, fileRepository, @@ -80,20 +74,10 @@ class ManagePostUseCaseTest : ownerId = ownerId, ) - fun createUser(id: Long = 1L): User = - User( - name = "적순", - email = Email.from("test1@test.com"), - status = Status.ACTIVE, - ).apply { - ReflectionTestUtils.setField(this, "id", id) - } - beforeTest { clearMocks( postRepository, boardRepository, - userReader, clubMemberPolicy, clubMemberCardinalReader, fileRepository, @@ -108,17 +92,13 @@ class ManagePostUseCaseTest : every { postMapper.toSaveResponse(any()) } returns PostSaveResponse(1L) every { fileRepository.delete(any()) } just runs every { clubMemberCardinalReader.findLatestCardinalByClubMember(any()) } returns null - // update/delete 공통 기본값: 작성자 - every { userReader.getById(any()) } returns UserTestFixture.createActiveUser1(1L) } describe("save") { it("일반 게시판에서 게시글을 저장한다") { - val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create(name = "일반", type = BoardType.GENERAL) val request = CreatePostRequest(title = "제목", content = "내용") - every { userReader.getById(1L) } returns user every { boardRepository.findByIdAndClubIdAndIsDeletedFalse(10L, 1L) } returns board val result = useCase.save(1L, 10L, request, 1L) @@ -128,7 +108,6 @@ class ManagePostUseCaseTest : } it("ADMIN 전용 게시판에 일반 사용자가 작성하면 예외를 던진다") { - val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create( name = "공지", @@ -137,7 +116,6 @@ class ManagePostUseCaseTest : ) val request = CreatePostRequest(title = "제목", content = "내용") - every { userReader.getById(1L) } returns user every { boardRepository.findByIdAndClubIdAndIsDeletedFalse(20L, 1L) } returns board shouldThrow { @@ -148,7 +126,6 @@ class ManagePostUseCaseTest : } it("비공개 게시판에 일반 사용자가 작성하면 예외를 던진다") { - val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create( name = "비공개", @@ -157,7 +134,6 @@ class ManagePostUseCaseTest : ) val request = CreatePostRequest(title = "제목", content = "내용") - every { userReader.getById(1L) } returns user every { boardRepository.findByIdAndClubIdAndIsDeletedFalse(21L, 1L) } returns board shouldThrow { @@ -168,16 +144,11 @@ class ManagePostUseCaseTest : } it("사용자의 최신 기수가 존재하면 게시글에 자동 반영된다") { - val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create(name = "일반", type = BoardType.GENERAL) - val cardinal = - CardinalTestFixture.createCardinalInProgress( - cardinalNumber = 6, - ) + val cardinal = CardinalTestFixture.createCardinalInProgress(cardinalNumber = 6) val clubMemberCardinal = ClubMemberCardinalTestFixture.create(cardinal = cardinal) val request = CreatePostRequest(title = "게시글", content = "내용") - every { userReader.getById(1L) } returns user every { boardRepository.findByIdAndClubIdAndIsDeletedFalse(11L, 1L) } returns board every { clubMemberCardinalReader.findLatestCardinalByClubMember(any()) } returns clubMemberCardinal @@ -189,11 +160,9 @@ class ManagePostUseCaseTest : } it("사용자의 기수 정보가 없으면 cardinalNumber가 null로 저장된다") { - val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create(name = "일반", type = BoardType.GENERAL) val request = CreatePostRequest(title = "게시글", content = "내용") - every { userReader.getById(1L) } returns user every { boardRepository.findByIdAndClubIdAndIsDeletedFalse(11L, 1L) } returns board useCase.save(1L, 11L, request, 1L) @@ -204,10 +173,8 @@ class ManagePostUseCaseTest : } it("존재하지 않는 boardId면 예외를 던진다") { - val user = UserTestFixture.createActiveUser1(1L) val request = CreatePostRequest(title = "제목", content = "내용") - every { userReader.getById(1L) } returns user every { boardRepository.findByIdAndClubIdAndIsDeletedFalse(999L, 1L) } returns null shouldThrow { @@ -218,13 +185,12 @@ class ManagePostUseCaseTest : describe("update") { it("files가 null이면 기존 파일을 유지한다") { - val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create(name = "일반", type = BoardType.GENERAL) val clubId = board.club.id - val post = Post.create("제목", "내용", user, board) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = UserTestFixture.createActiveUser1(1L)) + val post = Post.create("제목", "내용", ownerMember, board) val request = UpdatePostRequest(title = "수정", content = "수정") - every { userReader.getById(1L) } returns user every { postRepository.findActivePostById(1L) } returns post useCase.update(clubId, 1L, request, 1L) @@ -234,10 +200,10 @@ class ManagePostUseCaseTest : } it("files가 있으면 기존 파일을 soft delete 후 교체한다") { - val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create(name = "일반", type = BoardType.GENERAL) val clubId = board.club.id - val post = PostTestFixture.create(title = "제목", content = "내용", user = user, board = board) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = UserTestFixture.createActiveUser1(1L)) + val post = PostTestFixture.create(title = "제목", content = "내용", clubMember = ownerMember, board = board) val oldFile = createUploadedPostFile("old.png") val newFiles = listOf(createUploadedPostFile("new.png")) val request = @@ -255,7 +221,6 @@ class ManagePostUseCaseTest : ), ) - every { userReader.getById(1L) } returns user every { postRepository.findActivePostById(1L) } returns post every { fileReader.findAll(FileOwnerType.POST, any(), any()) } returns listOf(oldFile) every { fileMapper.toFileList(request.files, FileOwnerType.POST, any()) } returns newFiles @@ -270,13 +235,12 @@ class ManagePostUseCaseTest : } it("title이 null이면 기존 제목을 유지한다") { - val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create(name = "일반", type = BoardType.GENERAL) val clubId = board.club.id - val post = Post.create("원래 제목", "원래 내용", user, board) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = UserTestFixture.createActiveUser1(1L)) + val post = Post.create("원래 제목", "원래 내용", ownerMember, board) val request = UpdatePostRequest(content = "수정된 내용") - every { userReader.getById(1L) } returns user every { postRepository.findActivePostById(1L) } returns post useCase.update(clubId, 1L, request, 1L) @@ -286,13 +250,12 @@ class ManagePostUseCaseTest : } it("content가 null이면 기존 내용을 유지한다") { - val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create(name = "일반", type = BoardType.GENERAL) val clubId = board.club.id - val post = Post.create("원래 제목", "원래 내용", user, board) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = UserTestFixture.createActiveUser1(1L)) + val post = Post.create("원래 제목", "원래 내용", ownerMember, board) val request = UpdatePostRequest(title = "수정된 제목") - every { userReader.getById(1L) } returns user every { postRepository.findActivePostById(1L) } returns post useCase.update(clubId, 1L, request, 1L) @@ -310,7 +273,6 @@ class ManagePostUseCaseTest : } it("게시판이 ADMIN 전용으로 바뀐 후 일반 사용자가 수정하면 예외를 던진다") { - val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create( name = "공지", @@ -318,9 +280,9 @@ class ManagePostUseCaseTest : config = BoardConfig(writePermission = MemberRole.ADMIN), ) val clubId = board.club.id - val post = PostTestFixture.create(title = "제목", content = "내용", user = user, board = board) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = UserTestFixture.createActiveUser1(1L)) + val post = PostTestFixture.create(title = "제목", content = "내용", clubMember = ownerMember, board = board) - every { userReader.getById(1L) } returns user every { postRepository.findActivePostById(1L) } returns post shouldThrow { @@ -329,7 +291,6 @@ class ManagePostUseCaseTest : } it("게시판이 비공개로 바뀐 후 일반 사용자가 수정하면 예외를 던진다") { - val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create( name = "비공개", @@ -337,9 +298,9 @@ class ManagePostUseCaseTest : config = BoardConfig(isPrivate = true), ) val clubId = board.club.id - val post = PostTestFixture.create(title = "제목", content = "내용", user = user, board = board) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = UserTestFixture.createActiveUser1(1L)) + val post = PostTestFixture.create(title = "제목", content = "내용", clubMember = ownerMember, board = board) - every { userReader.getById(1L) } returns user every { postRepository.findActivePostById(1L) } returns post shouldThrow { @@ -350,13 +311,12 @@ class ManagePostUseCaseTest : describe("delete") { it("삭제 시 첨부 파일과 게시글을 soft delete한다") { - val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create(name = "일반", type = BoardType.GENERAL) val clubId = board.club.id - val post = PostTestFixture.create(title = "제목", content = "내용", user = user, board = board) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = UserTestFixture.createActiveUser1(1L)) + val post = PostTestFixture.create(title = "제목", content = "내용", clubMember = ownerMember, board = board) val oldFile = createUploadedPostFile("old.png") - every { userReader.getById(1L) } returns user every { postRepository.findActivePostById(1L) } returns post every { fileReader.findAll(FileOwnerType.POST, any(), any()) } returns listOf(oldFile) @@ -376,7 +336,6 @@ class ManagePostUseCaseTest : } it("게시판이 ADMIN 전용으로 바뀐 후 일반 사용자가 삭제하면 예외를 던진다") { - val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create( name = "공지", @@ -384,9 +343,9 @@ class ManagePostUseCaseTest : config = BoardConfig(writePermission = MemberRole.ADMIN), ) val clubId = board.club.id - val post = PostTestFixture.create(title = "제목", content = "내용", user = user, board = board) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = UserTestFixture.createActiveUser1(1L)) + val post = PostTestFixture.create(title = "제목", content = "내용", clubMember = ownerMember, board = board) - every { userReader.getById(1L) } returns user every { postRepository.findActivePostById(1L) } returns post shouldThrow { @@ -397,14 +356,12 @@ class ManagePostUseCaseTest : describe("owner validation") { it("작성자가 아니면 수정 시 예외를 던진다") { - val owner = UserTestFixture.createActiveUser1(1L) - val otherUser = UserTestFixture.createActiveUser1(2L) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = UserTestFixture.createActiveUser1(1L)) val board = BoardTestFixture.create(name = "일반", type = BoardType.GENERAL) val clubId = board.club.id - val post = PostTestFixture.create(title = "제목", content = "내용", user = owner, board = board) + val post = PostTestFixture.create(title = "제목", content = "내용", clubMember = ownerMember, board = board) val request = UpdatePostRequest(title = "수정", content = "수정") - every { userReader.getById(2L) } returns otherUser every { postRepository.findActivePostById(1L) } returns post shouldThrow { diff --git a/src/test/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryServiceTest.kt b/src/test/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryServiceTest.kt index efcb5479..e8de86c2 100644 --- a/src/test/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryServiceTest.kt +++ b/src/test/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryServiceTest.kt @@ -13,7 +13,6 @@ import com.weeth.domain.board.domain.repository.PostRepository import com.weeth.domain.board.fixture.BoardTestFixture import com.weeth.domain.board.fixture.PostTestFixture import com.weeth.domain.club.domain.enums.MemberRole -import com.weeth.domain.club.domain.repository.ClubMemberReader import com.weeth.domain.club.domain.service.ClubMemberPolicy import com.weeth.domain.club.fixture.ClubMemberTestFixture import com.weeth.domain.comment.application.dto.response.CommentResponse @@ -44,7 +43,6 @@ class GetPostQueryServiceTest : val boardRepository = mockk() val postLikeRepository = mockk() val clubMemberPolicy = mockk(relaxed = true) - val clubMemberReader = mockk() val commentReader = mockk() val getCommentQueryService = mockk() val fileReader = mockk() @@ -57,7 +55,6 @@ class GetPostQueryServiceTest : boardRepository, postLikeRepository, clubMemberPolicy, - clubMemberReader, commentReader, getCommentQueryService, fileReader, @@ -74,7 +71,6 @@ class GetPostQueryServiceTest : boardRepository, postLikeRepository, clubMemberPolicy, - clubMemberReader, commentReader, getCommentQueryService, fileReader, @@ -99,13 +95,7 @@ class GetPostQueryServiceTest : val board = BoardTestFixture.create(name = "일반", type = BoardType.GENERAL) val actualClubId = board.club.id val member = ClubMemberTestFixture.createActiveMember(club = board.club, user = user) - val post = - PostTestFixture.create( - title = "제목", - content = "내용", - user = user, - board = board, - ) + val post = PostTestFixture.create(title = "제목", content = "내용", clubMember = member, board = board) val comments = listOf(mockk()) val fileResponses = listOf( @@ -148,11 +138,10 @@ class GetPostQueryServiceTest : every { clubMemberPolicy.getActiveMember(actualClubId, userId) } returns member every { postRepository.findByIdAndIsDeletedFalse(1L) } returns post every { commentReader.findAllByPostId(any()) } returns emptyList() - every { clubMemberReader.findAllByClubIdAndUserIds(actualClubId, any()) } returns listOf(member) - every { getCommentQueryService.toCommentTreeResponses(any(), any()) } returns comments + every { getCommentQueryService.toCommentTreeResponses(any()) } returns comments every { fileReader.findAll(FileOwnerType.POST, any(), any()) } returns files every { postLikeRepository.existsByPostAndUserIdAndIsActiveTrue(post, userId) } returns false - every { postMapper.toDetailResponse(post, member, comments, fileResponses, false) } returns detail + every { postMapper.toDetailResponse(post, comments, fileResponses, false) } returns detail every { fileMapper.toFileResponse(files.first()) } returns fileResponses.first() val result = queryService.findPost(actualClubId, userId, 1L) @@ -172,7 +161,7 @@ class GetPostQueryServiceTest : PostTestFixture.create( title = "제목", content = "내용", - user = user, + clubMember = member, board = privateBoard, ) @@ -188,17 +177,15 @@ class GetPostQueryServiceTest : val user = UserTestFixture.createActiveUser1(1L) val deletedBoard = BoardTestFixture - .create( - name = "삭제", - type = BoardType.GENERAL, - ).also { it.markDeleted() } + .create(name = "삭제", type = BoardType.GENERAL) + .also { it.markDeleted() } val actualClubId = deletedBoard.club.id val member = ClubMemberTestFixture.createActiveMember(club = deletedBoard.club, user = user) val post = PostTestFixture.create( title = "제목", content = "내용", - user = user, + clubMember = member, board = deletedBoard, ) @@ -270,7 +257,7 @@ class GetPostQueryServiceTest : val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create(name = "일반", type = BoardType.GENERAL) val member = ClubMemberTestFixture.createActiveMember(club = board.club, user = user) - val post = PostTestFixture.create(title = "제목", content = "내용", user = user, board = board) + val post = PostTestFixture.create(title = "제목", content = "내용", clubMember = member, board = board) val pageable = PageRequest.of(0, 10) val postSlice = SliceImpl(listOf(post), pageable, false) val response = @@ -293,9 +280,8 @@ class GetPostQueryServiceTest : listOf(board) every { postRepository.findAllActiveByBoardIds(any(), any()) } returns postSlice every { fileReader.findAll(FileOwnerType.POST, any>(), any()) } returns emptyList() - every { clubMemberReader.findAllByClubIdAndUserIds(clubId, any()) } returns listOf(member) every { postLikeRepository.findLikedPostIds(any(), any()) } returns emptySet() - every { postMapper.toListResponse(any(), any(), any(), any(), any()) } returns response + every { postMapper.toListResponse(any(), any(), any(), any()) } returns response val result = queryService.findAllPosts(clubId, userId, 0, 10) @@ -321,14 +307,8 @@ class GetPostQueryServiceTest : it("목록 조회 시 mapper를 통해 응답으로 변환한다") { val user = UserTestFixture.createActiveUser1(1L) val board = BoardTestFixture.create(name = "일반", type = BoardType.GENERAL) - val member = ClubMemberTestFixture.createActiveMember(user = user) - val post = - PostTestFixture.create( - title = "제목", - content = "내용", - user = user, - board = board, - ) + val member = ClubMemberTestFixture.createActiveMember(club = board.club, user = user) + val post = PostTestFixture.create(title = "제목", content = "내용", clubMember = member, board = board) val pageable = PageRequest.of(0, 10) val postSlice = SliceImpl(listOf(post), pageable, false) val response = @@ -350,9 +330,8 @@ class GetPostQueryServiceTest : every { boardRepository.findByIdAndClubIdAndIsDeletedFalse(1L, clubId) } returns board every { postRepository.findAllActiveByBoardId(1L, any()) } returns postSlice every { fileReader.findAll(FileOwnerType.POST, any>(), any()) } returns emptyList() - every { clubMemberReader.findAllByClubIdAndUserIds(clubId, any()) } returns listOf(member) every { postLikeRepository.findLikedPostIds(any(), any()) } returns emptySet() - every { postMapper.toListResponse(any(), any(), any(), any(), any()) } returns response + every { postMapper.toListResponse(any(), any(), any(), any()) } returns response val result = queryService.findPosts(clubId, userId, 1L, 0, 10) diff --git a/src/test/kotlin/com/weeth/domain/board/fixture/PostTestFixture.kt b/src/test/kotlin/com/weeth/domain/board/fixture/PostTestFixture.kt index bb64a142..0b8cd245 100644 --- a/src/test/kotlin/com/weeth/domain/board/fixture/PostTestFixture.kt +++ b/src/test/kotlin/com/weeth/domain/board/fixture/PostTestFixture.kt @@ -2,14 +2,14 @@ package com.weeth.domain.board.fixture import com.weeth.domain.board.domain.entity.Board import com.weeth.domain.board.domain.entity.Post -import com.weeth.domain.user.domain.entity.User -import com.weeth.domain.user.fixture.UserTestFixture +import com.weeth.domain.club.domain.entity.ClubMember +import com.weeth.domain.club.fixture.ClubMemberTestFixture object PostTestFixture { fun create( title: String = "게시글", content: String = "내용", - user: User = UserTestFixture.createActiveUser1(1L), + clubMember: ClubMember = ClubMemberTestFixture.createActiveMember(), board: Board = BoardTestFixture.create(), cardinalNumber: Int? = null, initialLikeCount: Int = 0, @@ -17,7 +17,7 @@ object PostTestFixture { Post( title = title, content = content, - user = user, + clubMember = clubMember, board = board, cardinalNumber = cardinalNumber, ).also { post -> diff --git a/src/test/kotlin/com/weeth/domain/comment/application/usecase/command/CommentConcurrencyTest.kt b/src/test/kotlin/com/weeth/domain/comment/application/usecase/command/CommentConcurrencyTest.kt index 1ff692bb..c6605053 100644 --- a/src/test/kotlin/com/weeth/domain/comment/application/usecase/command/CommentConcurrencyTest.kt +++ b/src/test/kotlin/com/weeth/domain/comment/application/usecase/command/CommentConcurrencyTest.kt @@ -7,6 +7,8 @@ import com.weeth.domain.board.domain.entity.Post import com.weeth.domain.board.domain.enums.BoardType import com.weeth.domain.board.domain.repository.BoardRepository import com.weeth.domain.board.domain.repository.PostRepository +import com.weeth.domain.club.domain.entity.ClubMember +import com.weeth.domain.club.domain.repository.ClubMemberRepository import com.weeth.domain.club.domain.repository.ClubRepository import com.weeth.domain.club.fixture.ClubTestFixture import com.weeth.domain.comment.application.dto.request.CommentSaveRequest @@ -46,6 +48,7 @@ class CommentConcurrencyTest( private val userRepository: UserRepository, private val commentRepository: CommentRepository, private val clubRepository: ClubRepository, + private val clubMemberRepository: ClubMemberRepository, private val entityManager: EntityManager, private val atomicCommentCountCommand: AtomicCommentCountCommand, ) : DescribeSpec({ @@ -102,11 +105,13 @@ class CommentConcurrencyTest( type = BoardType.GENERAL, ), ) + val clubMember = ClubMember.create(club = club, user = user).also { it.accept() } + clubMemberRepository.save(clubMember) return postRepository.save( Post( title = title, content = "내용", - user = user, + clubMember = clubMember, board = board, ), ) @@ -119,6 +124,11 @@ class CommentConcurrencyTest( val runId = UUID.randomUUID().toString().take(8) val users = createUsers(threadCount, runId) val post = createPost("동시성 테스트 게시글-$runId", users.first(), runId) + // 나머지 사용자들도 같은 클럽의 ClubMember로 등록 (ACTIVE 상태로 저장) + users.drop(1).forEach { commenter -> + val member = ClubMember.create(club = post.board.club, user = commenter).also { it.accept() } + clubMemberRepository.save(member) + } val executor = Executors.newFixedThreadPool(threadCount) val latch = CountDownLatch(threadCount) val successCount = AtomicInteger(0) @@ -206,6 +216,7 @@ class CommentConcurrencyTest( commentRepository.deleteAllInBatch() postRepository.deleteAllInBatch() boardRepository.deleteAllInBatch() + clubMemberRepository.deleteAllInBatch() clubRepository.deleteAllInBatch() userRepository.deleteAllInBatch() } @@ -298,8 +309,9 @@ class AtomicCommentCountCommand( repeat(maxRetries) { attempt -> try { transactionTemplate.executeWithoutResult { - val user = entityManager.getReference(User::class.java, userId) val post = entityManager.getReference(Post::class.java, postId) + // 벤치마크 전용: commentCount 동시성 측정이 목적이므로 userId 대신 post 작성자의 ClubMember를 재사용 + val clubMember = post.clubMember val parent = dto.parentCommentId?.let { parentId -> commentRepository.findByIdAndPostId(parentId, postId) @@ -310,7 +322,7 @@ class AtomicCommentCountCommand( Comment.createForPost( content = dto.content, post = post, - user = user, + clubMember = clubMember, parent = parent, ), ) diff --git a/src/test/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCaseTest.kt b/src/test/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCaseTest.kt index 38446825..d3cb81b5 100644 --- a/src/test/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCaseTest.kt +++ b/src/test/kotlin/com/weeth/domain/comment/application/usecase/command/ManageCommentUseCaseTest.kt @@ -2,6 +2,8 @@ package com.weeth.domain.comment.application.usecase.command import com.weeth.domain.board.domain.repository.PostRepository import com.weeth.domain.board.fixture.PostTestFixture +import com.weeth.domain.club.domain.service.ClubMemberPolicy +import com.weeth.domain.club.fixture.ClubMemberTestFixture import com.weeth.domain.comment.application.dto.request.CommentSaveRequest import com.weeth.domain.comment.application.dto.request.CommentUpdateRequest import com.weeth.domain.comment.application.exception.CommentAlreadyDeletedException @@ -9,6 +11,7 @@ import com.weeth.domain.comment.application.exception.CommentNotFoundException import com.weeth.domain.comment.application.exception.CommentNotOwnedException import com.weeth.domain.comment.domain.entity.Comment import com.weeth.domain.comment.domain.repository.CommentRepository +import com.weeth.domain.comment.fixture.CommentTestFixture import com.weeth.domain.file.application.dto.request.FileSaveRequest import com.weeth.domain.file.application.mapper.FileMapper import com.weeth.domain.file.domain.entity.File @@ -16,7 +19,6 @@ import com.weeth.domain.file.domain.enums.FileOwnerType import com.weeth.domain.file.domain.enums.FileStatus import com.weeth.domain.file.domain.repository.FileReader import com.weeth.domain.file.domain.repository.FileRepository -import com.weeth.domain.user.domain.repository.UserReader import com.weeth.domain.user.fixture.UserTestFixture import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.DescribeSpec @@ -32,7 +34,7 @@ class ManageCommentUseCaseTest : DescribeSpec({ val commentRepository = mockk(relaxUnitFun = true) val postRepository = mockk() - val userReader = mockk() + val clubMemberPolicy = mockk(relaxed = true) val fileReader = mockk() val fileRepository = mockk(relaxed = true) val fileMapper = mockk() @@ -41,14 +43,14 @@ class ManageCommentUseCaseTest : ManageCommentUseCase( commentRepository, postRepository, - userReader, + clubMemberPolicy, fileReader, fileRepository, fileMapper, ) beforeTest { - clearMocks(commentRepository, postRepository, userReader, fileReader, fileRepository, fileMapper) + clearMocks(commentRepository, postRepository, clubMemberPolicy, fileReader, fileRepository, fileMapper) every { fileMapper.toFileList(any(), FileOwnerType.COMMENT, any()) } returns emptyList() every { commentRepository.save(any()) } answers { firstArg() } every { fileReader.findAll(FileOwnerType.COMMENT, any(), any()) } returns emptyList() @@ -57,11 +59,9 @@ class ManageCommentUseCaseTest : describe("savePostComment") { it("최상위 댓글 저장 시 댓글 수가 증가한다") { - val user = UserTestFixture.createActiveUser1(1L) - val post = PostTestFixture.create(user = user) + val post = PostTestFixture.create() val dto = CommentSaveRequest(parentCommentId = null, content = "최상위 댓글", files = null) - every { userReader.getById(1L) } returns user every { postRepository.findByIdWithLock(10L) } returns post useCase.savePostComment(dto, postId = 10L, userId = 1L) @@ -72,11 +72,9 @@ class ManageCommentUseCaseTest : } it("부모 댓글이 존재하지 않으면 예외를 던진다") { - val user = UserTestFixture.createActiveUser1(1L) - val post = PostTestFixture.create(user = user) + val post = PostTestFixture.create() val dto = CommentSaveRequest(parentCommentId = 999L, content = "대댓글", files = null) - every { userReader.getById(1L) } returns user every { postRepository.findByIdWithLock(10L) } returns post every { commentRepository.findByIdAndPostId(999L, 10L) } returns null @@ -89,8 +87,9 @@ class ManageCommentUseCaseTest : describe("updatePostComment") { it("작성자가 아니면 예외를 던진다") { val owner = UserTestFixture.createActiveUser1(1L) - val post = PostTestFixture.create(user = owner) - val comment = Comment(id = 200L, content = "old", post = post, user = owner) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = owner) + val post = PostTestFixture.create() + val comment = CommentTestFixture.createPostComment(id = 200L, post = post, clubMember = ownerMember) val dto = CommentUpdateRequest(content = "new", files = null) every { commentRepository.findByIdAndPostId(200L, 10L) } returns comment @@ -102,8 +101,9 @@ class ManageCommentUseCaseTest : it("files가 있으면 기존 파일은 삭제되고 새 파일이 저장된다") { val owner = UserTestFixture.createActiveUser1(1L) - val post = PostTestFixture.create(user = owner) - val comment = Comment(id = 202L, content = "old", post = post, user = owner) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = owner) + val post = PostTestFixture.create() + val comment = CommentTestFixture.createPostComment(id = 202L, post = post, clubMember = ownerMember) val dto = CommentUpdateRequest( content = "new content", @@ -151,11 +151,12 @@ class ManageCommentUseCaseTest : describe("deletePostComment") { it("리프 댓글 삭제 시 hard delete 되고 댓글 수가 감소한다") { val owner = UserTestFixture.createActiveUser1(1L) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = owner) val post = - PostTestFixture.create(user = owner, title = "title").also { + PostTestFixture.create(title = "title").also { it.increaseCommentCount() } - val comment = Comment(id = 310L, content = "leaf", post = post, user = owner) + val comment = CommentTestFixture.createPostComment(id = 310L, post = post, clubMember = ownerMember) every { postRepository.findByIdWithLock(10L) } returns post every { commentRepository.findByIdAndPostId(310L, 10L) } returns comment @@ -168,14 +169,21 @@ class ManageCommentUseCaseTest : it("자식이 있는 댓글 삭제 시 soft delete 된다") { val owner = UserTestFixture.createActiveUser1(1L) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = owner) val post = - PostTestFixture.create(user = owner).also { + PostTestFixture.create().also { it.increaseCommentCount() it.increaseCommentCount() } - val comment = Comment(id = 300L, content = "target", post = post, user = owner) - val child = Comment(id = 301L, content = "child", post = post, user = owner, parent = comment) + val comment = CommentTestFixture.createPostComment(id = 300L, post = post, clubMember = ownerMember) + val child = + CommentTestFixture.createPostComment( + id = 301L, + post = post, + clubMember = ownerMember, + parent = comment, + ) comment.children.add(child) every { postRepository.findByIdWithLock(10L) } returns post @@ -191,8 +199,15 @@ class ManageCommentUseCaseTest : it("이미 삭제된 댓글은 삭제할 수 없다") { val owner = UserTestFixture.createActiveUser1(1L) - val post = PostTestFixture.create(user = owner) - val comment = Comment(id = 320L, content = "삭제된 댓글입니다.", post = post, user = owner, isDeleted = true) + val ownerMember = ClubMemberTestFixture.createActiveMember(user = owner) + val post = PostTestFixture.create() + val comment = + CommentTestFixture.createPostComment( + id = 320L, + post = post, + clubMember = ownerMember, + isDeleted = true, + ) every { postRepository.findByIdWithLock(10L) } returns post every { commentRepository.findByIdAndPostId(320L, 10L) } returns comment diff --git a/src/test/kotlin/com/weeth/domain/comment/application/usecase/query/CommentQueryPerformanceTest.kt b/src/test/kotlin/com/weeth/domain/comment/application/usecase/query/CommentQueryPerformanceTest.kt index f2f4caa1..45b75ace 100644 --- a/src/test/kotlin/com/weeth/domain/comment/application/usecase/query/CommentQueryPerformanceTest.kt +++ b/src/test/kotlin/com/weeth/domain/comment/application/usecase/query/CommentQueryPerformanceTest.kt @@ -72,14 +72,14 @@ class CommentQueryPerformanceTest( } fun createPost( - user: User, + clubMember: ClubMember, board: Board, ): Post = postRepository.save( Post( title = "query-performance", content = "measure comment query performance", - user = user, + clubMember = clubMember, board = board, cardinalNumber = 4, ), @@ -87,7 +87,6 @@ class CommentQueryPerformanceTest( data class SetupResult( val commentIds: List, - val memberMap: Map, ) fun setupData( @@ -97,13 +96,9 @@ class CommentQueryPerformanceTest( ): SetupResult { val user = createUser() val board = createBoard() - val post = createPost(user, board) - val clubMember = - clubMemberRepository - .save( - ClubMember.create(club = board.club, user = user), - ).apply { accept() } - val memberMap = mapOf(user.id to clubMember) + val clubMember = ClubMember.create(club = board.club, user = user).also { it.accept() } + clubMemberRepository.save(clubMember) + val post = createPost(clubMember, board) val commentIds = mutableListOf() repeat(rootCount) { rootIdx -> @@ -112,7 +107,7 @@ class CommentQueryPerformanceTest( Comment.createForPost( content = "root-$rootIdx", post = post, - user = user, + clubMember = clubMember, parent = null, ), ) @@ -123,7 +118,7 @@ class CommentQueryPerformanceTest( Comment.createForPost( content = "child-$rootIdx-$childIdx", post = post, - user = user, + clubMember = clubMember, parent = root, ), ) @@ -146,7 +141,7 @@ class CommentQueryPerformanceTest( } } - return SetupResult(commentIds, memberMap) + return SetupResult(commentIds) } describe("comment file query performance") { @@ -156,12 +151,11 @@ class CommentQueryPerformanceTest( childrenPerRoot: Int, filesPerComment: Int, ) { - val (_, memberMap) = - setupData( - rootCount = rootCount, - childrenPerRoot = childrenPerRoot, - filesPerComment = filesPerComment, - ) + setupData( + rootCount = rootCount, + childrenPerRoot = childrenPerRoot, + filesPerComment = filesPerComment, + ) val fileAccessUrlPort = object : FileAccessUrlPort { @@ -178,7 +172,7 @@ class CommentQueryPerformanceTest( val legacy = QueryCountUtil.count(entityManager) { val comments = commentRepository.findAll().sortedBy { it.id } - val tree = legacyService.toCommentTreeResponses(comments, memberMap) + val tree = legacyService.toCommentTreeResponses(comments) tree.size shouldBe rootCount } @@ -187,7 +181,7 @@ class CommentQueryPerformanceTest( val improved = QueryCountUtil.count(entityManager) { val comments = commentRepository.findAll().sortedBy { it.id } - val tree = improvedService.toCommentTreeResponses(comments, memberMap) + val tree = improvedService.toCommentTreeResponses(comments) tree.size shouldBe rootCount } @@ -211,10 +205,7 @@ private class LegacyCommentQueryService( private val fileMapper: FileMapper, private val commentMapper: CommentMapper, ) { - fun toCommentTreeResponses( - comments: List, - memberMap: Map, - ): List { + fun toCommentTreeResponses(comments: List): List { if (comments.isEmpty()) { return emptyList() } @@ -226,17 +217,16 @@ private class LegacyCommentQueryService( return comments .filter { it.parent == null } - .map { mapToCommentResponse(it, childrenByParentId, memberMap) } + .map { mapToCommentResponse(it, childrenByParentId) } } private fun mapToCommentResponse( comment: Comment, childrenByParentId: Map>, - memberMap: Map, ): CommentResponse { val children = childrenByParentId[comment.id] - ?.map { mapToCommentResponse(it, childrenByParentId, memberMap) } + ?.map { mapToCommentResponse(it, childrenByParentId) } ?: emptyList() val files = @@ -244,7 +234,6 @@ private class LegacyCommentQueryService( .findAll(FileOwnerType.COMMENT, comment.id) .map(fileMapper::toFileResponse) - val authorMember = memberMap.getValue(comment.user.id) - return commentMapper.toCommentDto(comment, authorMember, children, files) + return commentMapper.toCommentDto(comment, children, files) } } diff --git a/src/test/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryServiceTest.kt b/src/test/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryServiceTest.kt index 85dd0fc6..c2abfcc7 100644 --- a/src/test/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryServiceTest.kt +++ b/src/test/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryServiceTest.kt @@ -27,9 +27,8 @@ class GetCommentQueryServiceTest : val service = GetCommentQueryService(fileReader, fileMapper, commentMapper) val user = UserTestFixture.createActiveUser1(1L) - val post = PostTestFixture.create(user = user) val member = ClubMemberTestFixture.createActiveMember(user = user) - val memberMap = mapOf(user.id to member) + val post = PostTestFixture.create(clubMember = member) beforeTest { clearMocks(fileReader, fileMapper, commentMapper) @@ -49,7 +48,7 @@ class GetCommentQueryServiceTest : describe("toCommentTreeResponses") { it("빈 리스트면 빈 리스트를 반환하고 파일 조회를 하지 않는다") { - val result = service.toCommentTreeResponses(emptyList(), memberMap) + val result = service.toCommentTreeResponses(emptyList()) result shouldBe emptyList() verify(exactly = 0) { fileReader.findAll(any(), any(), any()) } @@ -57,13 +56,13 @@ class GetCommentQueryServiceTest : } it("최상위 댓글만 있을 때 파일 조회를 1회 수행한다") { - val comment = CommentTestFixture.createPostComment(id = 1L, post = post, user = user) + val comment = CommentTestFixture.createPostComment(id = 1L, post = post, clubMember = member) val response = stubResponse(1L) every { fileReader.findAll(FileOwnerType.COMMENT, listOf(1L), any()) } returns emptyList() - every { commentMapper.toCommentDto(comment, member, emptyList(), emptyList()) } returns response + every { commentMapper.toCommentDto(comment, emptyList(), emptyList()) } returns response - val result = service.toCommentTreeResponses(listOf(comment), memberMap) + val result = service.toCommentTreeResponses(listOf(comment)) result.size shouldBe 1 result[0].id shouldBe 1L @@ -71,18 +70,22 @@ class GetCommentQueryServiceTest : } it("부모-자식 구조를 트리로 조립한다") { - val parent = CommentTestFixture.createPostComment(id = 10L, post = post, user = user) - val child = CommentTestFixture.createPostComment(id = 11L, post = post, user = user, parent = parent) + val parent = CommentTestFixture.createPostComment(id = 10L, post = post, clubMember = member) + val child = + CommentTestFixture.createPostComment( + id = 11L, + post = post, + clubMember = member, + parent = parent, + ) val childResponse = stubResponse(11L) val parentResponse = stubResponse(10L, children = listOf(childResponse)) every { fileReader.findAll(FileOwnerType.COMMENT, listOf(10L, 11L), any()) } returns emptyList() - every { commentMapper.toCommentDto(child, member, emptyList(), emptyList()) } returns childResponse - every { - commentMapper.toCommentDto(parent, member, listOf(childResponse), emptyList()) - } returns parentResponse + every { commentMapper.toCommentDto(child, emptyList(), emptyList()) } returns childResponse + every { commentMapper.toCommentDto(parent, listOf(childResponse), emptyList()) } returns parentResponse - val result = service.toCommentTreeResponses(listOf(parent, child), memberMap) + val result = service.toCommentTreeResponses(listOf(parent, child)) result.size shouldBe 1 result[0].id shouldBe 10L diff --git a/src/test/kotlin/com/weeth/domain/comment/domain/entity/CommentEntityTest.kt b/src/test/kotlin/com/weeth/domain/comment/domain/entity/CommentEntityTest.kt index d7fecad9..1223fa8c 100644 --- a/src/test/kotlin/com/weeth/domain/comment/domain/entity/CommentEntityTest.kt +++ b/src/test/kotlin/com/weeth/domain/comment/domain/entity/CommentEntityTest.kt @@ -1,30 +1,30 @@ package com.weeth.domain.comment.domain.entity import com.weeth.domain.board.fixture.PostTestFixture +import com.weeth.domain.club.fixture.ClubMemberTestFixture import com.weeth.domain.comment.fixture.CommentTestFixture -import com.weeth.domain.user.fixture.UserTestFixture import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe class CommentEntityTest : DescribeSpec({ - val user = UserTestFixture.createActiveUser1(1L) - val post = PostTestFixture.create(title = "title") + val member = ClubMemberTestFixture.createActiveMember() + val post = PostTestFixture.create(title = "title", clubMember = member) describe("createForPost") { it("부모 없이 최상위 댓글을 생성한다") { - val comment = Comment.createForPost(content = "내용", post = post, user = user, parent = null) + val comment = Comment.createForPost(content = "내용", post = post, clubMember = member, parent = null) comment.content shouldBe "내용" comment.post shouldBe post - comment.user shouldBe user + comment.clubMember shouldBe member comment.parent shouldBe null } } describe("markAsDeleted") { it("isDeleted를 true로 바꾸고 내용을 대체 문구로 변경한다") { - val comment = CommentTestFixture.createPostComment(post = post, user = user) + val comment = CommentTestFixture.createPostComment(post = post, clubMember = member) comment.markAsDeleted() diff --git a/src/test/kotlin/com/weeth/domain/comment/fixture/CommentTestFixture.kt b/src/test/kotlin/com/weeth/domain/comment/fixture/CommentTestFixture.kt index fc6481fb..0b7b1d81 100644 --- a/src/test/kotlin/com/weeth/domain/comment/fixture/CommentTestFixture.kt +++ b/src/test/kotlin/com/weeth/domain/comment/fixture/CommentTestFixture.kt @@ -1,22 +1,23 @@ package com.weeth.domain.comment.fixture import com.weeth.domain.board.domain.entity.Post +import com.weeth.domain.club.domain.entity.ClubMember +import com.weeth.domain.club.fixture.ClubMemberTestFixture import com.weeth.domain.comment.domain.entity.Comment -import com.weeth.domain.user.domain.entity.User object CommentTestFixture { fun createPostComment( id: Long = 1L, content: String = "테스트 댓글", post: Post, - user: User, + clubMember: ClubMember = ClubMemberTestFixture.createActiveMember(), parent: Comment? = null, isDeleted: Boolean = false, ) = Comment( id = id, content = content, post = post, - user = user, + clubMember = clubMember, parent = parent, isDeleted = isDeleted, ) diff --git a/src/test/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryServiceTest.kt b/src/test/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryServiceTest.kt index 9b530e28..5d457a7c 100644 --- a/src/test/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryServiceTest.kt +++ b/src/test/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryServiceTest.kt @@ -174,7 +174,7 @@ class GetDashboardQueryServiceTest : context("멤버인 경우") { it("공지 제외한 접근 가능한 게시판의 최신 게시글을 반환한다") { val board = BoardTestFixture.create(id = 10L, type = BoardType.GENERAL) - val post = PostTestFixture.create(board = board, user = user) + val post = PostTestFixture.create(board = board, clubMember = memberWithUser) val pageable = PageRequest.of(0, 10) val slice = SliceImpl(listOf(post), pageable, false) @@ -182,7 +182,6 @@ class GetDashboardQueryServiceTest : every { boardReader.findAllActiveByClubId(clubId) } returns listOf(board) every { postReader.findRecentByBoardIds(listOf(board.id), any()) } returns slice every { fileReader.findAll(FileOwnerType.POST, any>()) } returns emptyList() - every { clubMemberReader.findAllByClubIdAndUserIds(clubId, any()) } returns listOf(memberWithUser) val result = queryService.getRecentPosts(clubId, userId, 0, 10) @@ -201,7 +200,7 @@ class GetDashboardQueryServiceTest : it("일반 멤버에게는 비공개 게시판 글이 포함되지 않는다") { val publicBoard = BoardTestFixture.create(id = 10L, type = BoardType.GENERAL) - val post = PostTestFixture.create(board = publicBoard, user = user) + val post = PostTestFixture.create(board = publicBoard, clubMember = memberWithUser) val pageable = PageRequest.of(0, 10) val slice = SliceImpl(listOf(post), pageable, false) @@ -209,7 +208,6 @@ class GetDashboardQueryServiceTest : every { boardReader.findAllActiveByClubId(clubId) } returns listOf(publicBoard, privateBoard) every { postReader.findRecentByBoardIds(listOf(publicBoard.id), any()) } returns slice every { fileReader.findAll(FileOwnerType.POST, any>()) } returns emptyList() - every { clubMemberReader.findAllByClubIdAndUserIds(clubId, any()) } returns listOf(memberWithUser) val result = queryService.getRecentPosts(clubId, userId, 0, 10) @@ -223,7 +221,7 @@ class GetDashboardQueryServiceTest : user = user, memberRole = MemberRole.ADMIN, ) - val post = PostTestFixture.create(board = privateBoard, user = user) + val post = PostTestFixture.create(board = privateBoard, clubMember = adminMember) val pageable = PageRequest.of(0, 10) val slice = SliceImpl(listOf(post), pageable, false) @@ -231,7 +229,6 @@ class GetDashboardQueryServiceTest : every { boardReader.findAllActiveByClubId(clubId) } returns listOf(privateBoard) every { postReader.findRecentByBoardIds(listOf(privateBoard.id), any()) } returns slice every { fileReader.findAll(FileOwnerType.POST, any>()) } returns emptyList() - every { clubMemberReader.findAllByClubIdAndUserIds(clubId, any()) } returns listOf(adminMember) val result = queryService.getRecentPosts(clubId, userId, 0, 10)