diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 60e5b4cd..1711a21b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,6 +3,8 @@ name: Sarang Backend CI/CD on: push: branches: [ main ] + pull_request: + branches: [ main ] jobs: ci: diff --git a/src/test/kotlin/gomushin/backend/schedule/domain/facade/UpsertAndDeleteCommentFacadeTest.kt b/src/test/kotlin/gomushin/backend/schedule/domain/facade/UpsertAndDeleteCommentFacadeTest.kt index 14334823..6267b495 100644 --- a/src/test/kotlin/gomushin/backend/schedule/domain/facade/UpsertAndDeleteCommentFacadeTest.kt +++ b/src/test/kotlin/gomushin/backend/schedule/domain/facade/UpsertAndDeleteCommentFacadeTest.kt @@ -1,6 +1,9 @@ package gomushin.backend.schedule.domain.facade import gomushin.backend.core.CustomUserDetails +import gomushin.backend.core.common.support.SpringContextHolder +import gomushin.backend.core.configuration.env.AppEnv +import gomushin.backend.core.infrastructure.exception.BadRequestException import gomushin.backend.member.domain.entity.Member import gomushin.backend.member.domain.service.MemberService import gomushin.backend.schedule.domain.entity.Comment @@ -9,90 +12,182 @@ import gomushin.backend.schedule.domain.service.CommentService import gomushin.backend.schedule.domain.service.LetterService import gomushin.backend.schedule.dto.request.UpsertCommentRequest import gomushin.backend.schedule.facade.UpsertAndDeleteCommentFacade +import io.mockk.every +import io.mockk.impl.annotations.InjectMockKs +import io.mockk.impl.annotations.MockK +import io.mockk.junit5.MockKExtension +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.verify +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Mockito.* -import org.mockito.junit.jupiter.MockitoExtension import kotlin.test.Test +import kotlin.test.assertEquals -@ExtendWith(MockitoExtension::class) +@ExtendWith(MockKExtension::class) class UpsertAndDeleteCommentFacadeTest { - @Mock + @MockK private lateinit var commentService: CommentService - @Mock + @MockK private lateinit var letterService: LetterService - @Mock + @MockK private lateinit var memberService: MemberService - @InjectMocks + @InjectMockKs private lateinit var upsertAndDeleteCommentFacade: UpsertAndDeleteCommentFacade + private val customUserDetails = mockk() + + @BeforeEach + fun setUp() { + mockkObject(SpringContextHolder) + val mockAppEnv = mockk() + every { SpringContextHolder.getBean(AppEnv::class.java) } returns mockAppEnv + every { mockAppEnv.getId() } returns "test" + } + + init { + every { customUserDetails.getId() } returns 1L + } + @DisplayName("댓글 생성 또는 수정 성공") @Test fun upsert_success() { // given - val customUserDetails = mock(CustomUserDetails::class.java) val letterId = 1L + val letter = mockk() val memberId = 1L - val upsertCommentRequest = UpsertCommentRequest( - commentId = null, - content = "댓글 내용" + val member = mockk() + val commentId = 1L + val upsertCommentRequest = mockk() + + every { upsertCommentRequest.commentId } returns commentId + every { memberService.getById(memberId) } returns member + every { letterService.getById(letterId) } returns letter + every { letter.id } returns letterId + every { member.id } returns memberId + every { member.nickname } returns "작성자" + every { + commentService.upsert( + id = eq(commentId), + letterId = eq(letterId), + authorId = eq(memberId), + nickname = eq("작성자"), + upsertCommentRequest = eq(upsertCommentRequest) + ) + } returns Unit + + // when + upsertAndDeleteCommentFacade.upsert( + customUserDetails, + letterId, + upsertCommentRequest ) - val mockMember = mock(Member::class.java).apply { - `when`(id).thenReturn(memberId) - `when`(nickname).thenReturn("테스트유저") - } - val mockLetter = mock(Letter::class.java).apply { - `when`(id).thenReturn(letterId) + // then + verify(exactly = 1) { + memberService.getById(memberId) + letterService.getById(letterId) + commentService.upsert( + id = eq(commentId), + letterId = eq(letterId), + authorId = eq(memberId), + nickname = eq("작성자"), + upsertCommentRequest = eq(upsertCommentRequest) + ) } + } - `when`(customUserDetails.getId()).thenReturn(memberId) - `when`(memberService.getById(memberId)).thenReturn(mockMember) - `when`(letterService.getById(letterId)).thenReturn(mockLetter) + @DisplayName("delete 성공") + @Test + fun delete() { + // given + val letterId = 1L + val commentId = 1L + val member = mockk() + val comment = mockk() + every { memberService.getById(1L) } returns member + every { commentService.getById(commentId) } returns comment + every { member.id } returns 1L + every { comment.authorId } returns 1L + every { comment.letterId } returns letterId + every { commentService.delete(commentId) } returns Unit // when - upsertAndDeleteCommentFacade.upsert(customUserDetails, letterId, upsertCommentRequest) + upsertAndDeleteCommentFacade.delete( + customUserDetails, + letterId, + commentId + ) // then - verify(commentService, times(1)).upsert( - id = upsertCommentRequest.commentId, - letterId = mockLetter.id, - authorId = mockMember.id, - nickname = "테스트유저", - upsertCommentRequest = upsertCommentRequest - ) + verify(exactly = 1) { + memberService.getById(1L) + commentService.getById(commentId) + commentService.delete(commentId) + } } - @DisplayName("댓글 삭제 성공") + @DisplayName("delete 실패 - 작성자가 아닌 경우") @Test - fun delete_success() { + fun delete_fail_not_author() { // given - val customUserDetails = mock(CustomUserDetails::class.java) val letterId = 1L val commentId = 1L - val mockMember = mock(Member::class.java) - val mockComment = mock(Comment::class.java) + val member = mockk() + val comment = mockk() + every { memberService.getById(1L) } returns member + every { commentService.getById(commentId) } returns comment + every { member.id } returns 2L + every { comment.authorId } returns 1L + every { comment.letterId } returns letterId - // when - `when`(customUserDetails.getId()).thenReturn(1L) - `when`(memberService.getById(customUserDetails.getId())).thenReturn(mockMember) - `when`(commentService.getById(commentId)).thenReturn(mockComment) - `when`(mockMember.id).thenReturn(1L) - `when`(mockComment.authorId).thenReturn(1L) - `when`(mockComment.letterId).thenReturn(letterId) - `when`(mockComment.letterId).thenReturn(letterId) + // when & then + val exception = assertThrows { + upsertAndDeleteCommentFacade.delete( + customUserDetails, + letterId, + commentId + ) + } - upsertAndDeleteCommentFacade.delete(customUserDetails, letterId, commentId) + val message = exception.error.element.message.resolved - // then - verify(memberService, times(1)).getById(customUserDetails.getId()) - verify(commentService, times(1)).getById(commentId) - verify(commentService, times(1)).delete(commentId) + assertEquals("댓글은 작성자만 삭제하거나 업데이트 할 수 있어요.", message) } + + @DisplayName("delete 실패 - letterId가 일치하지 않는 경우") + @Test + fun delete_fail_invalid_letter() { + // given + val letterId = 1L + val commentId = 1L + val member = mockk() + val comment = mockk() + every { memberService.getById(1L) } returns member + every { commentService.getById(commentId) } returns comment + every { member.id } returns 1L + every { comment.authorId } returns 1L + every { comment.letterId } returns 2L + + // when & then + val exception = assertThrows { + upsertAndDeleteCommentFacade.delete( + customUserDetails, + letterId, + commentId + ) + } + + val message = exception.error.element.message.resolved + + assertEquals("편지에 해당하는 댓글이 아니에요.", message) + } + + } diff --git a/src/test/kotlin/gomushin/backend/schedule/domain/service/CommentServiceTest.kt b/src/test/kotlin/gomushin/backend/schedule/domain/service/CommentServiceTest.kt index b10d7b86..0adffc84 100644 --- a/src/test/kotlin/gomushin/backend/schedule/domain/service/CommentServiceTest.kt +++ b/src/test/kotlin/gomushin/backend/schedule/domain/service/CommentServiceTest.kt @@ -1,74 +1,245 @@ package gomushin.backend.schedule.domain.service +import gomushin.backend.core.common.support.SpringContextHolder +import gomushin.backend.core.configuration.env.AppEnv +import gomushin.backend.core.infrastructure.exception.BadRequestException import gomushin.backend.schedule.domain.entity.Comment +import gomushin.backend.schedule.domain.entity.Letter import gomushin.backend.schedule.domain.repository.CommentRepository import gomushin.backend.schedule.dto.request.UpsertCommentRequest +import io.mockk.every +import io.mockk.impl.annotations.InjectMockKs +import io.mockk.impl.annotations.MockK +import io.mockk.junit5.MockKExtension +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.verify +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Mockito.* -import org.mockito.junit.jupiter.MockitoExtension -import java.util.* +import org.springframework.data.repository.findByIdOrNull import kotlin.test.Test +import kotlin.test.assertEquals -@ExtendWith(MockitoExtension::class) +@ExtendWith(MockKExtension::class) class CommentServiceTest { - @Mock + @MockK lateinit var commentRepository: CommentRepository - @InjectMocks - lateinit var commentService: CommentService - - @Nested - inner class Upsert { - - @DisplayName("업데이트 성공 - 댓글이 존재할 때") - @Test - fun upload_success() { - // given - val id = 1L - val letterId = 1L - val authorId = 1L - val nickname = "닉네임" - val upsertCommentRequest = UpsertCommentRequest( - commentId = 1L, - content = "내용" - ) - val mockComment = mock(Comment::class.java).apply { - `when`(this.authorId).thenReturn(authorId) - } + @InjectMockKs + private lateinit var commentService: CommentService + + @BeforeEach + fun setUp() { + mockkObject(SpringContextHolder) + val mockAppEnv = mockk() + every { SpringContextHolder.getBean(AppEnv::class.java) } returns mockAppEnv + every { mockAppEnv.getId() } returns "test" + } + + @DisplayName("업로드 성공 - 댓글이 존재하지 않을 때") + @Test + fun upload_success() { + // given + val id = null + val letterId = 1L + val authorId = 1L + val nickname = "작성자" + val upsertCommentRequest = mockk() + every { upsertCommentRequest.content } returns "댓글 내용" + every { commentRepository.save(any()) } returns mockk() + + // when + commentService.upsert( + id = id, + letterId = letterId, + authorId = authorId, + nickname = nickname, + upsertCommentRequest = upsertCommentRequest + ) + + // then + verify { commentRepository.save(any()) } + } + + @DisplayName("업데이트 성공 - 댓글이 존재할 때") + @Test + fun update_success() { + // given + val id = 1L + val comment = mockk(relaxUnitFun = true) { + every { this@mockk.id } returns id + every { authorId } returns 1L + every { content } returns "기존 내용" + } + every { commentRepository.findByIdOrNull(id) } returns comment + every { commentService.getById(id) } returns comment - // when - `when`(commentRepository.findById(id)).thenReturn(Optional.of(mockComment)) - commentService.upsert(id, letterId, authorId, nickname, upsertCommentRequest) + // when + commentService.upsert( + id = id, + letterId = 1L, + authorId = 1L, + nickname = "작성자", + upsertCommentRequest = mockk { every { content } returns "새 내용" } + ) - // then - verify(mockComment).content = upsertCommentRequest.content + // then + verify { comment.content = "새 내용" } + } + + @DisplayName("업데이트 실패 - 작성자가 일치하지 않을 때") + @Test + fun update_failed_authorId_not_match() { + // given + val id = 1L + val comment = mockk(relaxUnitFun = true) { + every { this@mockk.id } returns id + every { authorId } returns 1L + every { content } returns "기존 내용" } + every { commentRepository.findByIdOrNull(id) } returns comment + every { commentService.getById(id) } returns comment - @DisplayName("댓글 생성 성공") - @Test - fun insert_success() { - // given - val letterId = 1L - val authorId = 1L - val nickname = "닉네임" - val upsertCommentRequest = UpsertCommentRequest( - commentId = null, - content = "내용" + // when + val exception = assertThrows { + commentService.upsert( + id = id, + letterId = 1L, + authorId = 2L, + nickname = "작성자", + upsertCommentRequest = mockk { every { content } returns "새 내용" } ) - val mockComment = mock(Comment::class.java) + } + val errorMessage = exception.error.element.message.resolved + + // then + assertEquals("댓글은 작성자만 삭제하거나 업데이트 할 수 있어요.", errorMessage) + + } + + @DisplayName("성공 - findByIdOrNull 반환이 Null이 아닌 경우") + @Test + fun findByIdOrNull_success() { + // given + val id = 1L + val mockComment = mockk() + every { commentRepository.findByIdOrNull(id) } returns mockComment + + // when + commentService.findById(id) + + // then + verify { commentRepository.findByIdOrNull(id) } + } + + @DisplayName("성공 - findByIdOrNull 반환이 Null인 경우") + @Test + fun findByIdOrNull_notFound() { + // given + val id = 1L + every { commentRepository.findByIdOrNull(id) } returns null + + // when & then + commentService.findById(id) + + verify { commentRepository.findByIdOrNull(id) } + + } - // when - `when`(commentRepository.save(any())).thenReturn(mockComment) - commentService.upsert(null, letterId, authorId, nickname, upsertCommentRequest) + @DisplayName("성공 - getById") + @Test + fun getById_success() { + // given + val id = 1L + val mockComment = mockk() + every { commentRepository.findByIdOrNull(id) } returns mockComment - // then - verify(commentRepository, times(1)).save(org.mockito.kotlin.any()) + // when + commentService.getById(id) + + // then + verify { commentRepository.findByIdOrNull(id) } + } + + @DisplayName("실패 - getById - 존재하지 않는 id로 조회 시 BadRequestException 발생") + @Test + fun getById_notFound() { + // given + val id = 1L + every { commentRepository.findByIdOrNull(id) } returns null + + // when & then + val exception = assertThrows { + commentService.getById(id) } + val errorMessage = exception.error.element.message.resolved + + assertEquals("댓글을 찾을 수 없어요.", errorMessage) + } + + @DisplayName("성공 - findAllByLetter") + @Test + fun findAllByLetter_success() { + // given + val letterId = 1L + val mockLetter = mockk() + every { mockLetter.id } returns letterId + val mockCommentList = listOf(mockk()) + + every { commentRepository.findAllByLetterId(letterId) } returns mockCommentList + + // when + commentService.findAllByLetter(mockLetter) + + // then + verify { commentRepository.findAllByLetterId(letterId) } + } + + @DisplayName("성공 - delete") + @Test + fun delete_success() { + // given + val id = 1L + + every { commentRepository.deleteById(id) } returns Unit + + // when + commentService.delete(id) + + // then + verify { commentRepository.deleteById(id) } + } + + @DisplayName("성공 - deleteAllByMemberId") + @Test + fun deleteAllByMemberId_success() { + // given + val memberId = 1L + + every { commentRepository.deleteAllByAuthorId(memberId) } returns Unit + + // when + commentService.deleteAllByMemberId(memberId) + + // then + verify { commentRepository.deleteAllByAuthorId(memberId) } + } + + @DisplayName("성공 - deleteAllByLetterId") + @Test + fun deleteAllByLetterId_success() { + // given + val letterId = 1L + + every { commentRepository.deleteAllByLetterId(letterId) } returns Unit + + // when + commentService.deleteAllByLetterId(letterId) + // then + verify { commentRepository.deleteAllByLetterId(letterId) } } }